Repository: karelia/ConnectionKit Branch: v2.x-beta Commit: 2d7235aa7e13 Files: 292 Total size: 2.7 MB Directory structure: gitextract_rey4k_n1/ ├── .appledoc.plist ├── .gitignore ├── .gitmodules ├── AuthTester/ │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── AuthTester-Info.plist │ ├── AuthTester-Prefix.pch │ ├── en.lproj/ │ │ ├── Credits.rtf │ │ ├── InfoPlist.strings │ │ └── MainMenu.xib │ └── main.m ├── Connection-Info.plist ├── Connection.svxSite ├── Connection.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ └── contents.xcworkspacedata │ └── xcshareddata/ │ └── xcschemes/ │ └── ConnectionKit.xcscheme ├── ConnectionKit/ │ ├── CK2Authentication.h │ ├── CK2Authentication.m │ ├── CK2BrowserPreviewController.h │ ├── CK2BrowserPreviewController.m │ ├── CK2BrowserPreviewView.h │ ├── CK2BrowserPreviewView.m │ ├── CK2CURLBasedProtocol.h │ ├── CK2CURLBasedProtocol.m │ ├── CK2CurlTransferStackManager.h │ ├── CK2CurlTransferStackManager.m │ ├── CK2FTPProtocol.h │ ├── CK2FTPProtocol.m │ ├── CK2FileCell.h │ ├── CK2FileCell.m │ ├── CK2FileManager.h │ ├── CK2FileManager.m │ ├── CK2FileManagerWithTestSupport.h │ ├── CK2FileManagerWithTestSupport.m │ ├── CK2FileOperation.h │ ├── CK2FileOperation.m │ ├── CK2FileOperationWithTestSupport.h │ ├── CK2FileOperationWithTestSupport.m │ ├── CK2FileProtocol.h │ ├── CK2FileProtocol.m │ ├── CK2FileSizeFormatter.h │ ├── CK2FileSizeFormatter.m │ ├── CK2IconItemView.h │ ├── CK2IconItemView.m │ ├── CK2IconView.h │ ├── CK2IconView.m │ ├── CK2IconViewItem.h │ ├── CK2IconViewItem.m │ ├── CK2NewFolderWindowController.h │ ├── CK2NewFolderWindowController.m │ ├── CK2OpenPanel.h │ ├── CK2OpenPanel.m │ ├── CK2OpenPanelColumnViewController.h │ ├── CK2OpenPanelColumnViewController.m │ ├── CK2OpenPanelController.h │ ├── CK2OpenPanelController.m │ ├── CK2OpenPanelIconViewController.h │ ├── CK2OpenPanelIconViewController.m │ ├── CK2OpenPanelListViewController.h │ ├── CK2OpenPanelListViewController.m │ ├── CK2OpenPanelViewController.h │ ├── CK2OpenPanelViewController.m │ ├── CK2PathControl.h │ ├── CK2PathControl.m │ ├── CK2PathFieldWindowController.h │ ├── CK2PathFieldWindowController.m │ ├── CK2Protocol.h │ ├── CK2Protocol.m │ ├── CK2SFTPProtocol.h │ ├── CK2SFTPProtocol.m │ ├── CK2WebDAVProtocol.h │ ├── CK2WebDAVProtocol.m │ ├── CKConnectionProtocol.h │ ├── CKS3Connection.h │ ├── CKS3Connection.m │ ├── CKTransferProgressCell.h │ ├── CKTransferProgressCell.m │ ├── CKTransferRecord.h │ ├── CKTransferRecord.m │ ├── CKUploader.h │ ├── CKUploader.m │ ├── ConnectionKit.h │ ├── ConnectionTest.h │ ├── ConnectionTest.m │ ├── Connection_Prefix.pch │ ├── NSImage+CK2OpenPanel.h │ ├── NSImage+CK2OpenPanel.m │ ├── NSURL+CK2OpenPanel.h │ ├── NSURL+CK2OpenPanel.m │ ├── en.lproj/ │ │ ├── CK2FilePreview.xib │ │ ├── CK2NewFolderWindow.xib │ │ ├── CK2OpenPanel.xib │ │ └── CK2PathFieldWindow.xib │ └── main.m ├── ConnectionKit 2.graffle ├── ConnectionKit.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── ConnectionKit.xccheckout ├── ConnectionKitUI/ │ ├── ConnectionKitUI-Info.plist │ ├── ConnectionKitUI-Prefix.pch │ ├── ConnectionKitUI.h │ └── en.lproj/ │ └── InfoPlist.strings ├── Documentation/ │ └── Standard Errors.md ├── Example/ │ ├── Connection-Info.plist │ ├── Connection.svxSite │ ├── ConnectionApp.xcodeproj/ │ │ └── project.pbxproj │ ├── ConnectionTest.h │ ├── ConnectionTest.m │ ├── Connection_Prefix.pch │ ├── Controller.h │ ├── Controller.m │ ├── DropletController.h │ ├── DropletController.m │ ├── DropletHelper-Info.plist │ ├── DropletIcon.icns │ ├── DropletLauncher.m │ ├── DropletMain.m │ ├── DropletOutlineView.h │ ├── DropletOutlineView.m │ ├── DropletPanel.h │ ├── DropletPanel.m │ ├── FileTransfer.h │ ├── FileTransfer.m │ ├── Framework Debug.xcconfig │ ├── Framework Release.xcconfig │ ├── Framework-Info.plist │ ├── InputDialog.h │ ├── InputDialog.m │ ├── KTLog Viewer-Info.plist │ ├── KTLogController.h │ ├── KTLogController.m │ ├── LeopardFolder.tiff │ ├── Logs.icns │ ├── ONBSSLContext.h │ ├── ONBSSLContext.m │ ├── ONBSSLIdentity.h │ ├── ONBSSLIdentity.m │ ├── PermissionsController.h │ ├── PermissionsController.m │ ├── ProgressCell.h │ ├── ProgressCell.m │ ├── README │ ├── ReleaseNotes.txt │ ├── Stop.tiff │ ├── UKKQueue_Symbols │ ├── channel.c │ ├── comp.c │ ├── connect.tiff │ ├── console.tiff │ ├── crypt.c │ ├── da.lproj/ │ │ ├── DropletLauncher.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── keyedobjects.nib │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ ├── de.lproj/ │ │ ├── DropletLauncher.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── keyedobjects.nib │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ ├── delete.tiff │ ├── download.tif │ ├── en.lproj/ │ │ ├── Droplet.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── keyedobjects.nib │ │ ├── DropletLauncher.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── keyedobjects.nib │ │ ├── InfoPlist.strings │ │ ├── InputDialog.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── keyedobjects.nib │ │ ├── KTLogViewer.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── keyedobjects.nib │ │ ├── Localizable.strings │ │ ├── MainMenu.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ ├── keyedobjects.nib │ │ │ └── objects.nib │ │ └── Permissions.nib/ │ │ ├── classes.nib │ │ ├── info.nib │ │ └── keyedobjects.nib │ ├── fr.lproj/ │ │ ├── DropletLauncher.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── keyedobjects.nib │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ ├── home.tiff │ ├── hostkey.c │ ├── icon.icns │ ├── it.lproj/ │ │ ├── DropletLauncher.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── keyedobjects.nib │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ ├── ja.lproj/ │ │ ├── DropletLauncher.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── keyedobjects.nib │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ ├── kex.c │ ├── ktlogviewer_main.m │ ├── libssh2.h │ ├── libssh2_config.h │ ├── libssh2_priv.h │ ├── libssh2_publickey.h │ ├── libssh2_sftp.h │ ├── lock.tiff │ ├── mac.c │ ├── main.m │ ├── misc.c │ ├── openssl.c │ ├── openssl.h │ ├── packet.c │ ├── pem.c │ ├── publickey.c │ ├── redo.tif │ ├── scp.c │ ├── session.c │ ├── sftp.c │ ├── symlink_file.tif │ ├── symlink_folder.tif │ ├── upload.tif │ ├── userauth.c │ ├── version.plist │ ├── zh_CN.lproj/ │ │ ├── DropletLauncher.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── keyedobjects.nib │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ └── zh_TW.lproj/ │ ├── DropletLauncher.nib/ │ │ ├── classes.nib │ │ ├── info.nib │ │ └── keyedobjects.nib │ ├── InfoPlist.strings │ └── Localizable.strings ├── Framework Debug.xcconfig ├── Framework Release.xcconfig ├── ProfilingTester/ │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── ProfilingTester-Info.plist │ ├── ProfilingTester-Prefix.pch │ ├── en.lproj/ │ │ ├── Credits.rtf │ │ ├── InfoPlist.strings │ │ └── MainMenu.xib │ └── main.m ├── README.md ├── Resources/ │ ├── Framework-Info.plist │ ├── bookmark.tif │ ├── da.lproj/ │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ ├── de.lproj/ │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ ├── en.lproj/ │ │ └── Localizable.strings │ ├── es.lproj/ │ │ └── Localizable.strings │ ├── fr.lproj/ │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ ├── it.lproj/ │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ ├── ja.lproj/ │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ ├── pt_BR.lproj/ │ │ └── Localizable.strings │ ├── zh_CN.lproj/ │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ └── zh_TW.lproj/ │ ├── InfoPlist.strings │ └── Localizable.strings ├── UKQueue/ │ └── UKKQueue_Symbols ├── UnitTests/ │ ├── BaseCKProtocolTests.h │ ├── BaseCKProtocolTests.m │ ├── BaseCKTests.h │ ├── BaseCKTests.m │ ├── CKUploaderTests.m │ ├── ErrorTests.m │ ├── FTPAuthenticationTests.m │ ├── FTPTests.m │ ├── FileTests.m │ ├── PathTests.m │ ├── README.md │ ├── SFTPTests.m │ ├── URLAppendingTests.m │ ├── URLCanonicalizationTests.m │ ├── URLDirectoryTests.m │ ├── URLTests.m │ ├── URLs.testdata │ ├── UnitTest-Info.plist │ ├── UnitTests_Prefix.pch │ ├── WebDAVTests.m │ ├── test.sh │ ├── use-ftp-server.sh │ ├── use-mockserver.sh │ ├── use-sftp-server.sh │ └── use-webdav-server.sh └── version.plist ================================================ FILE CONTENTS ================================================ ================================================ FILE: .appledoc.plist ================================================ --include Documentation --ignore CurlHandle DAVKit AuthTester Example ProfilingTester UKQueue .git .gitmodules .gitignore --index-desc README.md --logformat xcode --exit-threshold 2 --explicit-crossref --project-company Karelia Systems --company-id com.karelia --keep-undocumented-members --keep-undocumented-objects --merge-categories --keep-merged-sections --prefix-merged-sections ================================================ FILE: .gitignore ================================================ # Mac OS X *.DS_Store # Xcode build profile *.pbxuser *.mode1v3 *.mode2v3 *.perspectivev3 *.xcuserstate xcuserdata/ ================================================ FILE: .gitmodules ================================================ [submodule "DAVKit"] path = DAVKit url = https://github.com/karelia/DAVKit.git [submodule "CurlHandle"] path = CurlHandle url = https://github.com/karelia/CurlHandle.git ================================================ FILE: AuthTester/AppDelegate.h ================================================ // // AppDelegate.h // AuthTester // // Created by Mike on 01/02/2013. // // #import @interface AppDelegate : NSObject @property (assign) IBOutlet NSWindow *window; @end ================================================ FILE: AuthTester/AppDelegate.m ================================================ // // AppDelegate.m // AuthTester // // Created by Mike on 01/02/2013. // // #import "AppDelegate.h" @implementation AppDelegate - (void)dealloc { [super dealloc]; } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://tv.eurosport.com/"]] delegate:self]; } - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; { NSLog(@"challenge error: %@", challenge.error); [[challenge sender] performDefaultHandlingForAuthenticationChallenge:challenge]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error; { [NSApp presentError:error modalForWindow:self.window delegate:nil didPresentSelector:NULL contextInfo:NULL]; } @end ================================================ FILE: AuthTester/AuthTester-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: AuthTester/AuthTester-Prefix.pch ================================================ // // Prefix header for all source files of the 'AuthTester' target in the 'AuthTester' project // #ifdef __OBJC__ #import #endif ================================================ FILE: AuthTester/en.lproj/Credits.rtf ================================================ {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} {\colortbl;\red255\green255\blue255;} \paperw9840\paperh8400 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural \f0\b\fs24 \cf0 Engineering: \b0 \ Some people\ \ \b Human Interface Design: \b0 \ Some other people\ \ \b Testing: \b0 \ Hopefully not nobody\ \ \b Documentation: \b0 \ Whoever\ \ \b With special thanks to: \b0 \ Mom\ } ================================================ FILE: AuthTester/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: AuthTester/en.lproj/MainMenu.xib ================================================ 1080 11D50 2457 1138.32 568.00 com.apple.InterfaceBuilder.CocoaPlugin 2457 NSWindowTemplate NSView NSMenu NSMenuItem NSCustomObject com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion NSApplication FirstResponder NSApplication AMainMenu AuthTester 1048576 2147483647 NSImage NSMenuCheckmark NSImage NSMenuMixedState submenuAction: AuthTester About AuthTester 2147483647 YES YES 1048576 2147483647 Preferences… , 1048576 2147483647 YES YES 1048576 2147483647 Services 1048576 2147483647 submenuAction: Services _NSServicesMenu YES YES 1048576 2147483647 Hide AuthTester h 1048576 2147483647 Hide Others h 1572864 2147483647 Show All 1048576 2147483647 YES YES 1048576 2147483647 Quit AuthTester q 1048576 2147483647 _NSAppleMenu File 1048576 2147483647 submenuAction: File New n 1048576 2147483647 Open… o 1048576 2147483647 Open Recent 1048576 2147483647 submenuAction: Open Recent Clear Menu 1048576 2147483647 _NSRecentDocumentsMenu YES YES 1048576 2147483647 Close w 1048576 2147483647 Save… s 1048576 2147483647 Revert to Saved 2147483647 YES YES 1048576 2147483647 Page Setup... P 1179648 2147483647 Print… p 1048576 2147483647 Edit 1048576 2147483647 submenuAction: Edit Undo z 1048576 2147483647 Redo Z 1179648 2147483647 YES YES 1048576 2147483647 Cut x 1048576 2147483647 Copy c 1048576 2147483647 Paste v 1048576 2147483647 Paste and Match Style V 1572864 2147483647 Delete 1048576 2147483647 Select All a 1048576 2147483647 YES YES 1048576 2147483647 Find 1048576 2147483647 submenuAction: Find Find… f 1048576 2147483647 1 Find and Replace… f 1572864 2147483647 12 Find Next g 1048576 2147483647 2 Find Previous G 1179648 2147483647 3 Use Selection for Find e 1048576 2147483647 7 Jump to Selection j 1048576 2147483647 Spelling and Grammar 1048576 2147483647 submenuAction: Spelling and Grammar Show Spelling and Grammar : 1048576 2147483647 Check Document Now ; 1048576 2147483647 YES YES 2147483647 Check Spelling While Typing 1048576 2147483647 Check Grammar With Spelling 1048576 2147483647 Correct Spelling Automatically 2147483647 Substitutions 1048576 2147483647 submenuAction: Substitutions Show Substitutions 2147483647 YES YES 2147483647 Smart Copy/Paste f 1048576 2147483647 1 Smart Quotes g 1048576 2147483647 2 Smart Dashes 2147483647 Smart Links G 1179648 2147483647 3 Text Replacement 2147483647 Transformations 2147483647 submenuAction: Transformations Make Upper Case 2147483647 Make Lower Case 2147483647 Capitalize 2147483647 Speech 1048576 2147483647 submenuAction: Speech Start Speaking 1048576 2147483647 Stop Speaking 1048576 2147483647 Format 2147483647 submenuAction: Format Font 2147483647 submenuAction: Font Show Fonts t 1048576 2147483647 Bold b 1048576 2147483647 2 Italic i 1048576 2147483647 1 Underline u 1048576 2147483647 YES YES 2147483647 Bigger + 1048576 2147483647 3 Smaller - 1048576 2147483647 4 YES YES 2147483647 Kern 2147483647 submenuAction: Kern Use Default 2147483647 Use None 2147483647 Tighten 2147483647 Loosen 2147483647 Ligatures 2147483647 submenuAction: Ligatures Use Default 2147483647 Use None 2147483647 Use All 2147483647 Baseline 2147483647 submenuAction: Baseline Use Default 2147483647 Superscript 2147483647 Subscript 2147483647 Raise 2147483647 Lower 2147483647 YES YES 2147483647 Show Colors C 1048576 2147483647 YES YES 2147483647 Copy Style c 1572864 2147483647 Paste Style v 1572864 2147483647 _NSFontMenu Text 2147483647 submenuAction: Text Align Left { 1048576 2147483647 Center | 1048576 2147483647 Justify 2147483647 Align Right } 1048576 2147483647 YES YES 2147483647 Writing Direction 2147483647 submenuAction: Writing Direction YES Paragraph 2147483647 CURlZmF1bHQ 2147483647 CUxlZnQgdG8gUmlnaHQ 2147483647 CVJpZ2h0IHRvIExlZnQ 2147483647 YES YES 2147483647 YES Selection 2147483647 CURlZmF1bHQ 2147483647 CUxlZnQgdG8gUmlnaHQ 2147483647 CVJpZ2h0IHRvIExlZnQ 2147483647 YES YES 2147483647 Show Ruler 2147483647 Copy Ruler c 1310720 2147483647 Paste Ruler v 1310720 2147483647 View 1048576 2147483647 submenuAction: View Show Toolbar t 1572864 2147483647 Customize Toolbar… 1048576 2147483647 Window 1048576 2147483647 submenuAction: Window Minimize m 1048576 2147483647 Zoom 1048576 2147483647 YES YES 1048576 2147483647 Bring All to Front 1048576 2147483647 _NSWindowsMenu Help 2147483647 submenuAction: Help AuthTester Help ? 1048576 2147483647 _NSHelpMenu _NSMainMenu 15 2 {{335, 390}, {480, 360}} 1954021376 AuthTester NSWindow 256 {480, 360} {{0, 0}, {2560, 1418}} {10000000000000, 10000000000000} YES AppDelegate NSFontManager terminate: 449 orderFrontStandardAboutPanel: 142 delegate 495 performMiniaturize: 37 arrangeInFront: 39 print: 86 runPageLayout: 87 clearRecentDocuments: 127 performClose: 193 toggleContinuousSpellChecking: 222 undo: 223 copy: 224 checkSpelling: 225 paste: 226 stopSpeaking: 227 cut: 228 showGuessPanel: 230 redo: 231 selectAll: 232 startSpeaking: 233 delete: 235 performZoom: 240 performFindPanelAction: 241 centerSelectionInVisibleArea: 245 toggleGrammarChecking: 347 toggleSmartInsertDelete: 355 toggleAutomaticQuoteSubstitution: 356 toggleAutomaticLinkDetection: 357 saveDocument: 362 revertDocumentToSaved: 364 runToolbarCustomizationPalette: 365 toggleToolbarShown: 366 hide: 367 hideOtherApplications: 368 unhideAllApplications: 370 newDocument: 373 openDocument: 374 raiseBaseline: 426 lowerBaseline: 427 copyFont: 428 subscript: 429 superscript: 430 tightenKerning: 431 underline: 432 orderFrontColorPanel: 433 useAllLigatures: 434 loosenKerning: 435 pasteFont: 436 unscript: 437 useStandardKerning: 438 useStandardLigatures: 439 turnOffLigatures: 440 turnOffKerning: 441 toggleAutomaticSpellingCorrection: 456 orderFrontSubstitutionsPanel: 458 toggleAutomaticDashSubstitution: 461 toggleAutomaticTextReplacement: 463 uppercaseWord: 464 capitalizeWord: 467 lowercaseWord: 468 pasteAsPlainText: 486 performFindPanelAction: 487 performFindPanelAction: 488 performFindPanelAction: 489 showHelp: 493 alignCenter: 518 pasteRuler: 519 toggleRuler: 520 alignRight: 521 copyRuler: 522 alignJustified: 523 alignLeft: 524 makeBaseWritingDirectionNatural: 525 makeBaseWritingDirectionLeftToRight: 526 makeBaseWritingDirectionRightToLeft: 527 makeTextWritingDirectionNatural: 528 makeTextWritingDirectionLeftToRight: 529 makeTextWritingDirectionRightToLeft: 530 performFindPanelAction: 535 addFontTrait: 421 addFontTrait: 422 modifyFont: 423 orderFrontFontPanel: 424 modifyFont: 425 window 532 0 -2 File's Owner -1 First Responder -3 Application 29 19 56 217 83 81 75 78 72 82 124 77 73 79 112 74 125 126 205 202 198 207 214 199 203 197 206 215 218 216 200 219 201 204 220 213 210 221 208 209 57 58 134 150 136 144 129 143 236 131 149 145 130 24 92 5 239 23 295 296 297 298 211 212 195 196 346 348 349 350 351 354 371 372 375 376 377 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 450 451 452 453 454 457 459 460 462 465 466 485 490 491 492 494 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 534 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{380, 496}, {480, 360}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin 535 ABCardController NSObject id id id id id id id addCardViewField: id copy: id cut: id doDelete: id find: id paste: id saveChanges: id ABCardView NSButton NSManagedObjectContext NSSearchField NSTextField NSWindow mCardView ABCardView mEditButton NSButton mManagedObjectContext NSManagedObjectContext mSearchField NSSearchField mStatusTextField NSTextField mWindow NSWindow IBProjectSource ./Classes/ABCardController.h ABCardView NSView id id commitAndSave: id statusImageClicked: id NSObjectController NSImageView NSView ABNameFrameView NSView NSImage ABImageView mBindingsController NSObjectController mBuddyStatusImage NSImageView mHeaderView NSView mNameView ABNameFrameView mNextKeyView NSView mUserImage NSImage mUserImageView ABImageView IBProjectSource ./Classes/ABCardView.h ABImageView NSImageView id id id id copy: id cut: id delete: id paste: id IBProjectSource ./Classes/ABImageView.h DVTBorderedView DVTLayoutView_ML contentView NSView contentView contentView NSView IBProjectSource ./Classes/DVTBorderedView.h DVTDelayedMenuButton NSButton IBProjectSource ./Classes/DVTDelayedMenuButton.h DVTGradientImageButton NSButton IBProjectSource ./Classes/DVTGradientImageButton.h DVTImageAndTextCell NSTextFieldCell IBProjectSource ./Classes/DVTImageAndTextCell.h DVTImageAndTextColumn NSTableColumn IBProjectSource ./Classes/DVTImageAndTextColumn.h DVTLayoutView_ML NSView IBProjectSource ./Classes/DVTLayoutView_ML.h DVTOutlineView NSOutlineView IBProjectSource ./Classes/DVTOutlineView.h DVTSplitView NSSplitView IBProjectSource ./Classes/DVTSplitView.h DVTStackView_ML DVTLayoutView_ML IBProjectSource ./Classes/DVTStackView_ML.h DVTTableView NSTableView IBProjectSource ./Classes/DVTTableView.h DVTViewController NSViewController IBProjectSource ./Classes/DVTViewController.h HFController NSObject selectAll: id selectAll: selectAll: id IBProjectSource ./Classes/HFController.h HFRepresenterTextView NSView selectAll: id selectAll: selectAll: id IBProjectSource ./Classes/HFRepresenterTextView.h IBEditor NSObject id id id id id changeFont: id performCopy: id performCut: id selectAll: id sizeSelectionToFit: id IBProjectSource ./Classes/IBEditor.h IDECapsuleListView DVTStackView_ML dataSource id dataSource dataSource id IBProjectSource ./Classes/IDECapsuleListView.h IDEDMArrayController NSArrayController IBProjectSource ./Classes/IDEDMArrayController.h IDEDMEditor IDEEditor DVTBorderedView NSView IDEDMEditorSourceListController DVTSplitView bottomToolbarBorderView DVTBorderedView sourceListSplitViewPane NSView sourceListViewController IDEDMEditorSourceListController splitView DVTSplitView IBProjectSource ./Classes/IDEDMEditor.h IDEDMEditorController IDEViewController IBProjectSource ./Classes/IDEDMEditorController.h IDEDMEditorSourceListController IDEDMEditorController DVTBorderedView IDEDMEditor DVTImageAndTextColumn DVTOutlineView NSTreeController borderedView DVTBorderedView parentEditor IDEDMEditor primaryColumn DVTImageAndTextColumn sourceListOutlineView DVTOutlineView sourceListTreeController NSTreeController IBProjectSource ./Classes/IDEDMEditorSourceListController.h IDEDMHighlightImageAndTextCell DVTImageAndTextCell IBProjectSource ./Classes/IDEDMHighlightImageAndTextCell.h IDEDataModelBrowserEditor IDEDMEditorController IDEDataModelPropertiesTableController IDECapsuleListView NSArrayController IDEDataModelPropertiesTableController IDEDataModelEntityContentsEditor IDEDataModelPropertiesTableController attributesTableViewController IDEDataModelPropertiesTableController capsuleView IDECapsuleListView entityArrayController NSArrayController fetchedPropertiesTableViewController IDEDataModelPropertiesTableController parentEditor IDEDataModelEntityContentsEditor relationshipsTableViewController IDEDataModelPropertiesTableController IBProjectSource ./Classes/IDEDataModelBrowserEditor.h IDEDataModelConfigurationEditor IDEDMEditorController IDECapsuleListView IDEDataModelEditor IDEDataModelConfigurationTableController capsuleListView IDECapsuleListView parentEditor IDEDataModelEditor tableController IDEDataModelConfigurationTableController IBProjectSource ./Classes/IDEDataModelConfigurationEditor.h IDEDataModelConfigurationTableController IDEDMEditorController NSArrayController NSArrayController IDEDataModelConfigurationEditor XDTableView configurationsArrayController NSArrayController entitiesArrayController NSArrayController parentEditor IDEDataModelConfigurationEditor tableView XDTableView IBProjectSource ./Classes/IDEDataModelConfigurationTableController.h IDEDataModelDiagramEditor IDEDMEditorController XDDiagramView IDEDataModelEntityContentsEditor diagramView XDDiagramView parentEditor IDEDataModelEntityContentsEditor IBProjectSource ./Classes/IDEDataModelDiagramEditor.h IDEDataModelEditor IDEDMEditor DVTDelayedMenuButton DVTDelayedMenuButton NSSegmentedControl IDEDataModelConfigurationEditor IDEDataModelEntityContentsEditor IDEDataModelFetchRequestEditor NSSegmentedControl NSTabView addEntityButton DVTDelayedMenuButton addPropertyButton DVTDelayedMenuButton browserDiagramSegmentControl NSSegmentedControl configurationViewController IDEDataModelConfigurationEditor entityContentsViewController IDEDataModelEntityContentsEditor fetchRequestViewController IDEDataModelFetchRequestEditor hierarchySegmentControl NSSegmentedControl tabView NSTabView IBProjectSource ./Classes/IDEDataModelEditor.h IDEDataModelEntityContentsEditor IDEDMEditorController IDEDataModelBrowserEditor IDEDataModelDiagramEditor IDEDataModelEditor NSTabView browserViewController IDEDataModelBrowserEditor diagramViewController IDEDataModelDiagramEditor parentEditor IDEDataModelEditor tabView NSTabView IBProjectSource ./Classes/IDEDataModelEntityContentsEditor.h IDEDataModelFetchRequestEditor IDEDMEditorController NSArrayController IDEDataModelEditor IDECapsuleListView entityController NSArrayController parentEditor IDEDataModelEditor tableView IDECapsuleListView IBProjectSource ./Classes/IDEDataModelFetchRequestEditor.h IDEDataModelPropertiesTableController IDEDMEditorController IDEDMArrayController NSTableColumn NSArrayController IDEDataModelBrowserEditor IDEDMHighlightImageAndTextCell XDTableView arrayController IDEDMArrayController entitiesColumn NSTableColumn entityArrayController NSArrayController parentEditor IDEDataModelBrowserEditor propertyNameAndImageCell IDEDMHighlightImageAndTextCell tableView XDTableView IBProjectSource ./Classes/IDEDataModelPropertiesTableController.h IDEDocDownloadsTableViewController NSObject NSButtonCell DVTTableView IDEDocViewingPrefPaneController _downloadButtonCell NSButtonCell _tableView DVTTableView prefPaneController IDEDocViewingPrefPaneController IBProjectSource ./Classes/IDEDocDownloadsTableViewController.h IDEDocSetOutlineView NSOutlineView IBProjectSource ./Classes/IDEDocSetOutlineView.h IDEDocSetOutlineViewController NSObject id id id id id getDocSetAction: id showProblemInfoForUpdate: id subscribeToPublisherAction: id unsubscribeFromPublisher: id updateDocSetAction: id docSetOutlineView IDEDocSetOutlineView docSetOutlineView docSetOutlineView IDEDocSetOutlineView IBProjectSource ./Classes/IDEDocSetOutlineViewController.h IDEDocViewingPrefPaneController IDEViewController id id id id id id id id id id id addSubscription: id checkForAndInstallUpdatesNow: id deleteDocSet: id downloadAction: id minimumFontSizeComboBoxAction: id minimumFontSizeEnabledAction: id showHelp: id showSubscriptionSheet: id subscriptionCancelAction: id toggleAutoCheckForAndInstallUpdates: id toggleDocSetInfo: id DVTGradientImageButton DVTGradientImageButton DVTGradientImageButton NSSplitView NSView NSView DVTBorderedView DVTBorderedView NSButton NSTextView IDEDocSetOutlineViewController IDEDocDownloadsTableViewController NSComboBox NSTextField NSButton NSTextField NSWindow NSButton _addButton DVTGradientImageButton _deleteButton DVTGradientImageButton _showInfoAreaButton DVTGradientImageButton _splitView NSSplitView _splitViewDocSetInfoSubview NSView _splitViewDocSetsListSubview NSView borderedViewAroundSplitView DVTBorderedView borderedViewBelowTable DVTBorderedView checkAndInstallNowButton NSButton docSetInfoTextView NSTextView docSetOutlineViewController IDEDocSetOutlineViewController downloadsTableViewController IDEDocDownloadsTableViewController minimumFontSizeControl NSComboBox noUpdatesAvailableMessage NSTextField showInfoButton NSButton subscriptionTextField NSTextField subscriptionWindow NSWindow validateAddSubscriptionButton NSButton IBProjectSource ./Classes/IDEDocViewingPrefPaneController.h IDEEditor IDEViewController IBProjectSource ./Classes/IDEEditor.h IDEViewController DVTViewController IBProjectSource ./Classes/IDEViewController.h IKImageView id id id id copy: id crop: id cut: id paste: id IBProjectSource ./Classes/IKImageView.h NSDocument id id id id id id printDocument: id revertDocumentToSaved: id runPageLayout: id saveDocument: id saveDocumentAs: id saveDocumentTo: id IBProjectSource ./Classes/NSDocument.h NSResponder _insertFindPattern: id _insertFindPattern: _insertFindPattern: id IBProjectSource ./Classes/NSResponder.h QLPreviewBubble NSObject id id hide: id show: id parentWindow NSWindow parentWindow parentWindow NSWindow IBProjectSource ./Classes/QLPreviewBubble.h QTMovieView id id id id id showAll: id showCustomButton: id toggleLoops: id zoomIn: id zoomOut: id IBProjectSource ./Classes/QTMovieView.h WebView id id id id reloadFromOrigin: id resetPageZoom: id zoomPageIn: id zoomPageOut: id IBProjectSource ./Classes/WebView.h XDDiagramView NSView id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id _graphLayouterMenuItemAction: id _zoomPopUpButtonAction: id alignBottomEdges: id alignCentersHorizontallyInContainer: id alignCentersVerticallyInContainer: id alignHorizontalCenters: id alignLeftEdges: id alignRightEdges: id alignTopEdges: id alignVerticalCenters: id bringToFront: id collapseAllCompartments: id copy: id cut: id delete: id deleteBackward: id deleteForward: id deselectAll: id diagramZoomIn: id diagramZoomOut: id expandAllCompartments: id flipHorizontally: id flipVertically: id layoutGraphicsConcentrically: id layoutGraphicsHierarchically: id lock: id makeSameHeight: id makeSameWidth: id moveDown: id moveDownAndModifySelection: id moveLeft: id moveLeftAndModifySelection: id moveRight: id moveRightAndModifySelection: id moveUp: id moveUpAndModifySelection: id paste: id rollDownAllCompartments: id rollUpAllCompartments: id selectAll: id sendToBack: id sizeToFit: id toggleGridShown: id toggleHiddenGraphicsShown: id togglePageBreaksShown: id toggleRuler: id toggleSnapsToGrid: id unlock: id _diagramController IDEDataModelDiagramEditor _diagramController _diagramController IDEDataModelDiagramEditor IBProjectSource ./Classes/XDDiagramView.h XDTableView NSTableView showAllTableColumns: id showAllTableColumns: showAllTableColumns: id IBProjectSource ./Classes/XDTableView.h AppDelegate NSObject id id applicationShouldTerminate: id applicationWillFinishLaunching: id IBProjectSource ./Classes/AppDelegate.h 0 IBCocoaFramework YES 3 {11, 11} {10, 3} YES ================================================ FILE: AuthTester/main.m ================================================ // // main.m // AuthTester // // Created by Mike on 01/02/2013. // // #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **)argv); } ================================================ FILE: Connection-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.yourcompany.Connection CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType FMWK CFBundleSignature ???? CFBundleVersion 1.0 ================================================ FILE: Connection.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 220526BA165E8C9D00A2BBC9 /* FTPAuthenticationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 220526B9165E8C9D00A2BBC9 /* FTPAuthenticationTests.m */; }; 220526F8165E9DE400A2BBC9 /* DAVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27448C371458100D00EB086F /* DAVKit.framework */; }; 22052702165EA23800A2BBC9 /* CURLHandle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 220526E8165E96AA00A2BBC9 /* CURLHandle.framework */; }; 22052706165EA62500A2BBC9 /* DAVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27448C371458100D00EB086F /* DAVKit.framework */; }; 220C11961715C5E20086F199 /* ErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 220C11951715C5E10086F199 /* ErrorTests.m */; }; 22407D54166F9A6100E1EAD4 /* BaseCKProtocolTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22F6D0E7165A8A2200443CC9 /* BaseCKProtocolTests.m */; }; 2246AF6516B99987001D39D9 /* KMSSendStringCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 2246AF5916B99986001D39D9 /* KMSSendStringCommand.m */; }; 2246AF6616B99987001D39D9 /* KMSSendDataCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 2246AF5A16B99986001D39D9 /* KMSSendDataCommand.m */; }; 2246AF6716B99987001D39D9 /* KMSSendServerDataCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 2246AF5C16B99986001D39D9 /* KMSSendServerDataCommand.m */; }; 2246AF6816B99987001D39D9 /* KMSPauseCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 2246AF5E16B99986001D39D9 /* KMSPauseCommand.m */; }; 2246AF6916B99987001D39D9 /* KMSCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 2246AF5F16B99986001D39D9 /* KMSCommand.m */; }; 2246AF6A16B99987001D39D9 /* KMSCloseCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 2246AF6016B99986001D39D9 /* KMSCloseCommand.m */; }; 224AB37D166E500E0066B1C6 /* KMSConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 224AB372166E500E0066B1C6 /* KMSConnection.m */; }; 224AB37E166E500E0066B1C6 /* KMSListener.m in Sources */ = {isa = PBXBuildFile; fileRef = 224AB374166E500E0066B1C6 /* KMSListener.m */; }; 224AB37F166E500E0066B1C6 /* KMSRegExResponder.m in Sources */ = {isa = PBXBuildFile; fileRef = 224AB376166E500E0066B1C6 /* KMSRegExResponder.m */; }; 224AB380166E500E0066B1C6 /* KMSResponder.m in Sources */ = {isa = PBXBuildFile; fileRef = 224AB378166E500E0066B1C6 /* KMSResponder.m */; }; 224AB381166E500E0066B1C6 /* KMSResponseCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 224AB37A166E500E0066B1C6 /* KMSResponseCollection.m */; }; 224AB382166E500E0066B1C6 /* KMSServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 224AB37C166E500E0066B1C6 /* KMSServer.m */; }; 224AB387166E501C0066B1C6 /* KMSCollectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 224AB386166E501C0066B1C6 /* KMSCollectionTests.m */; }; 224AB38A166E52680066B1C6 /* KMSTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 224AB389166E52680066B1C6 /* KMSTestCase.m */; }; 224AB38C166E587F0066B1C6 /* KMSManualTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 224AB38B166E587F0066B1C6 /* KMSManualTests.m */; }; 225FCA3816B046F800A9F5AE /* CKUploaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 225FCA3716B046F800A9F5AE /* CKUploaderTests.m */; }; 22662EE4165D1EE4005FCC4A /* BaseCKTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22662EE3165D1EE3005FCC4A /* BaseCKTests.m */; }; 22662EF3165D2EEC005FCC4A /* ftp.json in Resources */ = {isa = PBXBuildFile; fileRef = 22662EF1165D2EEC005FCC4A /* ftp.json */; }; 22662EF4165D2EEC005FCC4A /* webdav.json in Resources */ = {isa = PBXBuildFile; fileRef = 22662EF2165D2EEC005FCC4A /* webdav.json */; }; 2288CD76165A99FC00F34E24 /* CK2WebDAVProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 2288CD74165A98E300F34E24 /* CK2WebDAVProtocol.m */; }; 228E180C1700AD5600ACDE94 /* CURLHandle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 220526E8165E96AA00A2BBC9 /* CURLHandle.framework */; }; 2298E4AB17442272005A4160 /* FileTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2298E4AA17442272005A4160 /* FileTests.m */; }; 22AC1C141742980000AB09E1 /* FTPTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22AC1C111742980000AB09E1 /* FTPTests.m */; }; 22AC1C151742980000AB09E1 /* SFTPTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22AC1C121742980000AB09E1 /* SFTPTests.m */; }; 22AC1C161742980000AB09E1 /* WebDAVTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22AC1C131742980000AB09E1 /* WebDAVTests.m */; }; 22AC1C1917429F8200AB09E1 /* URLAppendingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22AC1C1817429F8100AB09E1 /* URLAppendingTests.m */; }; 22AC1C1B17429FAA00AB09E1 /* URLDirectoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22AC1C1A17429FAA00AB09E1 /* URLDirectoryTests.m */; }; 22C9CF991703327A004610FE /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27F3372A16BC1FB100E70511 /* Cocoa.framework */; }; 22C9CF9F1703327A004610FE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 22C9CF9D1703327A004610FE /* InfoPlist.strings */; }; 22C9CFA11703327A004610FE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 22C9CFA01703327A004610FE /* main.m */; }; 22C9CFA51703327A004610FE /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 22C9CFA31703327A004610FE /* Credits.rtf */; }; 22C9CFA81703327A004610FE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 22C9CFA71703327A004610FE /* AppDelegate.m */; }; 22C9CFAB1703327A004610FE /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 22C9CFA91703327A004610FE /* MainMenu.xib */; }; 22C9CFBA170333A2004610FE /* ConnectionKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD12609F702BE00172CDD /* ConnectionKit.framework */; }; 22CC56F91509048E00F94154 /* PathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22CC56F81509048E00F94154 /* PathTests.m */; }; 22CC578B1509068600F94154 /* ConnectionKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD12609F702BE00172CDD /* ConnectionKit.framework */; }; 22E67F1B171311C5001ECE34 /* ConnectionKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 79CFD12609F702BE00172CDD /* ConnectionKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 22F6D112165A8A2200443CC9 /* URLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22F6D102165A8A2200443CC9 /* URLTests.m */; }; 22FEB6691680818800BB778B /* KMSTranscriptEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = 22FEB6671680818800BB778B /* KMSTranscriptEntry.m */; }; 271059521671334500E20511 /* DAVKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 27448C371458100D00EB086F /* DAVKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 27105953167143D800E20511 /* CURLHandle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 220526E8165E96AA00A2BBC9 /* CURLHandle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 27431CA01630381D00F6FB58 /* CK2FileProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 27431C9E1630381D00F6FB58 /* CK2FileProtocol.h */; }; 27431CA11630381D00F6FB58 /* CK2FileProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 27431C9F1630381D00F6FB58 /* CK2FileProtocol.m */; }; 2743E8091622E47600019979 /* CK2FileManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 2743E8071622E47600019979 /* CK2FileManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2743E80A1622E47600019979 /* CK2FileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2743E8081622E47600019979 /* CK2FileManager.m */; }; 278CFE1316BADE030018A14B /* URLCanonicalizationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 278CFE1216BADE030018A14B /* URLCanonicalizationTests.m */; }; 278D8B79167FF35D00622468 /* CK2Authentication.h in Headers */ = {isa = PBXBuildFile; fileRef = 278D8B77167FF35D00622468 /* CK2Authentication.h */; settings = {ATTRIBUTES = (Public, ); }; }; 278D8B7A167FF35D00622468 /* CK2Authentication.m in Sources */ = {isa = PBXBuildFile; fileRef = 278D8B78167FF35D00622468 /* CK2Authentication.m */; }; 2790A8291627636E000C9D9F /* CK2Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 2790A8271627636E000C9D9F /* CK2Protocol.h */; }; 2790A82A1627636E000C9D9F /* CK2Protocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 2790A8281627636E000C9D9F /* CK2Protocol.m */; }; 2790A94816278F1D000C9D9F /* CK2FTPProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 2790A94616278F1D000C9D9F /* CK2FTPProtocol.h */; }; 2790A94916278F1D000C9D9F /* CK2FTPProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 2790A94716278F1D000C9D9F /* CK2FTPProtocol.m */; }; 279117EC178B5C64006BF857 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27F3372A16BC1FB100E70511 /* Cocoa.framework */; }; 279117F2178B5C64006BF857 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 279117F0178B5C64006BF857 /* InfoPlist.strings */; }; 279117FD178B5C64006BF857 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 279117FC178B5C64006BF857 /* XCTest.framework */; }; 279117FE178B5C64006BF857 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27F3372A16BC1FB100E70511 /* Cocoa.framework */; }; 27911801178B5C64006BF857 /* ConnectionKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 279117EB178B5C64006BF857 /* ConnectionKitUI.framework */; }; 27911810178B5D33006BF857 /* CK2OpenPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592A116A4960B0020FFE9 /* CK2OpenPanel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27911811178B5D3E006BF857 /* CK2OpenPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592A216A4960B0020FFE9 /* CK2OpenPanel.m */; }; 27911812178B5D4B006BF857 /* CK2OpenPanelController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592A416A4960B0020FFE9 /* CK2OpenPanelController.m */; }; 27911813178B5D4F006BF857 /* CK2OpenPanelController.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592A316A4960B0020FFE9 /* CK2OpenPanelController.h */; }; 27911814178B5D59006BF857 /* CK2OpenPanelViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592B016A4960B0020FFE9 /* CK2OpenPanelViewController.h */; }; 27911815178B5D59006BF857 /* CK2OpenPanelViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592B116A4960B0020FFE9 /* CK2OpenPanelViewController.m */; }; 27911816178B5D59006BF857 /* CK2OpenPanelIconViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592B216A4960B0020FFE9 /* CK2OpenPanelIconViewController.h */; }; 27911817178B5D59006BF857 /* CK2OpenPanelIconViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592B316A4960B0020FFE9 /* CK2OpenPanelIconViewController.m */; }; 27911818178B5D59006BF857 /* CK2OpenPanelListViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592B416A4960B0020FFE9 /* CK2OpenPanelListViewController.h */; }; 27911819178B5D59006BF857 /* CK2OpenPanelListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592B516A4960B0020FFE9 /* CK2OpenPanelListViewController.m */; }; 2791181A178B5D59006BF857 /* CK2OpenPanelColumnViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592B616A4960B0020FFE9 /* CK2OpenPanelColumnViewController.h */; }; 2791181B178B5D59006BF857 /* CK2OpenPanelColumnViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592B716A4960B0020FFE9 /* CK2OpenPanelColumnViewController.m */; }; 2791181C178B5D59006BF857 /* CK2NewFolderWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = 324D22D116AF490A001CFDE6 /* CK2NewFolderWindowController.h */; }; 2791181D178B5D59006BF857 /* CK2NewFolderWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 324D22D216AF490A001CFDE6 /* CK2NewFolderWindowController.m */; }; 2791181E178B5D59006BF857 /* CK2FileCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592A516A4960B0020FFE9 /* CK2FileCell.h */; }; 2791181F178B5D59006BF857 /* CK2FileCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592A616A4960B0020FFE9 /* CK2FileCell.m */; }; 27911820178B5D59006BF857 /* CK2PathControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592A916A4960B0020FFE9 /* CK2PathControl.h */; }; 27911821178B5D59006BF857 /* CK2PathControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592AA16A4960B0020FFE9 /* CK2PathControl.m */; }; 27911822178B5D59006BF857 /* CK2PathFieldWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3274F1CB170888430049DDE5 /* CK2PathFieldWindowController.h */; }; 27911823178B5D59006BF857 /* CK2PathFieldWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3274F1CC170888430049DDE5 /* CK2PathFieldWindowController.m */; }; 27911824178B5D59006BF857 /* CK2IconViewItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592AB16A4960B0020FFE9 /* CK2IconViewItem.h */; }; 27911825178B5D59006BF857 /* CK2IconViewItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592AC16A4960B0020FFE9 /* CK2IconViewItem.m */; }; 27911826178B5D59006BF857 /* CK2IconItemView.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592AD16A4960B0020FFE9 /* CK2IconItemView.h */; }; 27911827178B5D59006BF857 /* CK2IconItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592AE16A4960B0020FFE9 /* CK2IconItemView.m */; }; 27911828178B5D59006BF857 /* CK2IconView.h in Headers */ = {isa = PBXBuildFile; fileRef = 324D22C716AE1762001CFDE6 /* CK2IconView.h */; }; 27911829178B5D59006BF857 /* CK2IconView.m in Sources */ = {isa = PBXBuildFile; fileRef = 324D22C816AE1762001CFDE6 /* CK2IconView.m */; }; 2791182A178B5D59006BF857 /* CK2BrowserPreviewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 324D22BB16AE173F001CFDE6 /* CK2BrowserPreviewController.h */; }; 2791182B178B5D59006BF857 /* CK2BrowserPreviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 324D22BC16AE173F001CFDE6 /* CK2BrowserPreviewController.m */; }; 2791182C178B5D59006BF857 /* CK2BrowserPreviewView.h in Headers */ = {isa = PBXBuildFile; fileRef = 324D22BD16AE1740001CFDE6 /* CK2BrowserPreviewView.h */; }; 2791182D178B5D59006BF857 /* CK2BrowserPreviewView.m in Sources */ = {isa = PBXBuildFile; fileRef = 324D22BE16AE1740001CFDE6 /* CK2BrowserPreviewView.m */; }; 2791182E178B5D59006BF857 /* CK2FileSizeFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 324D22C316AE1754001CFDE6 /* CK2FileSizeFormatter.h */; }; 2791182F178B5D59006BF857 /* CK2FileSizeFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 324D22C416AE1754001CFDE6 /* CK2FileSizeFormatter.m */; }; 27911832178B5D59006BF857 /* NSURL+CK2OpenPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B592A716A4960B0020FFE9 /* NSURL+CK2OpenPanel.h */; }; 27911833178B5D59006BF857 /* NSURL+CK2OpenPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B592A816A4960B0020FFE9 /* NSURL+CK2OpenPanel.m */; }; 27911834178B5D59006BF857 /* NSImage+CK2OpenPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B3EA7C16B58FFA003D4836 /* NSImage+CK2OpenPanel.h */; }; 27911835178B5D59006BF857 /* NSImage+CK2OpenPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B3EA7D16B58FFB003D4836 /* NSImage+CK2OpenPanel.m */; }; 27911836178B5D79006BF857 /* CK2OpenPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = 323B7349170E253900219F9A /* CK2OpenPanel.xib */; }; 27911837178B5D79006BF857 /* CK2FilePreview.xib in Resources */ = {isa = PBXBuildFile; fileRef = 323B7356170E254800219F9A /* CK2FilePreview.xib */; }; 27911838178B5D79006BF857 /* CK2NewFolderWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 323B7359170E255200219F9A /* CK2NewFolderWindow.xib */; }; 27911839178B5D79006BF857 /* CK2PathFieldWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 323B735C170E256000219F9A /* CK2PathFieldWindow.xib */; }; 2791183A178B5E12006BF857 /* ConnectionKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD12609F702BE00172CDD /* ConnectionKit.framework */; }; 2791188D178B6782006BF857 /* ConnectionKitUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 279117F4178B5C64006BF857 /* ConnectionKitUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27993E8416FCB30D008DC1B0 /* CK2FileOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 27993E8216FCB30D008DC1B0 /* CK2FileOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27993E8516FCB30D008DC1B0 /* CK2FileOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 27993E8316FCB30D008DC1B0 /* CK2FileOperation.m */; }; 27999BAB170B4B9800A54BEE /* CK2FileOperationWithTestSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 22C9CFEE1703AD4D004610FE /* CK2FileOperationWithTestSupport.m */; }; 27999BB7170B4BA200A54BEE /* CK2FileManagerWithTestSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 228E180E1700AEA300ACDE94 /* CK2FileManagerWithTestSupport.m */; }; 27A2072B1671634800D8284D /* CK2CURLBasedProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 27A207291671634800D8284D /* CK2CURLBasedProtocol.h */; }; 27A2072C1671634800D8284D /* CK2CURLBasedProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A2072A1671634800D8284D /* CK2CURLBasedProtocol.m */; }; 27ADC5771AC0CD7D0085C7F7 /* CK2CurlTransferStackManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 27ADC5751AC0CD7D0085C7F7 /* CK2CurlTransferStackManager.h */; }; 27ADC5781AC0CD7D0085C7F7 /* CK2CurlTransferStackManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 27ADC5761AC0CD7D0085C7F7 /* CK2CurlTransferStackManager.m */; }; 27CFEC7218E73526007158A4 /* URLs.testdata in Resources */ = {isa = PBXBuildFile; fileRef = 27CFEC7118E73526007158A4 /* URLs.testdata */; }; 27D03B421471787000FEA588 /* CKUploader.h in Headers */ = {isa = PBXBuildFile; fileRef = 27D03B401471787000FEA588 /* CKUploader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27D03B431471787000FEA588 /* CKUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D03B411471787000FEA588 /* CKUploader.m */; }; 27F3372B16BC1FB100E70511 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27F3372A16BC1FB100E70511 /* Cocoa.framework */; }; 27F3373116BC1FB100E70511 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 27F3372F16BC1FB100E70511 /* InfoPlist.strings */; }; 27F3373316BC1FB100E70511 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F3373216BC1FB100E70511 /* main.m */; }; 27F3373716BC1FB100E70511 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 27F3373516BC1FB100E70511 /* Credits.rtf */; }; 27F3373A16BC1FB100E70511 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F3373916BC1FB100E70511 /* AppDelegate.m */; }; 27F3373D16BC1FB100E70511 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27F3373B16BC1FB100E70511 /* MainMenu.xib */; }; 27F394F5162C162900944F43 /* CK2SFTPProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 27F394F3162C162900944F43 /* CK2SFTPProtocol.h */; }; 27F394F6162C162900944F43 /* CK2SFTPProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F394F4162C162900944F43 /* CK2SFTPProtocol.m */; }; 791E83050B0EDAC90060E5FC /* error.png in Resources */ = {isa = PBXBuildFile; fileRef = 791E83030B0EDAC90060E5FC /* error.png */; }; 791E83060B0EDAC90060E5FC /* finished.png in Resources */ = {isa = PBXBuildFile; fileRef = 791E83040B0EDAC90060E5FC /* finished.png */; }; 796DB30109F8BB1D0065897B /* SecurityInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 796DB2F609F8BB1D0065897B /* SecurityInterface.framework */; }; 7978FC150B117D7C0048168B /* bookmark.tif in Resources */ = {isa = PBXBuildFile; fileRef = 7978FC140B117D7C0048168B /* bookmark.tif */; }; 797CC8C709F861770063FF9B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 797CC8C509F861770063FF9B /* Localizable.strings */; }; 798313C90B0D67E000F5078E /* CKTransferProgressCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 798313C70B0D67E000F5078E /* CKTransferProgressCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; 798313CA0B0D67E000F5078E /* CKTransferProgressCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 798313C80B0D67E000F5078E /* CKTransferProgressCell.m */; }; 7983DF8E0B0C0FAC00F5078E /* CKTransferRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = 7983DF8C0B0C0FAC00F5078E /* CKTransferRecord.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7983DF8F0B0C0FAC00F5078E /* CKTransferRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = 7983DF8D0B0C0FAC00F5078E /* CKTransferRecord.m */; }; 79CFD89509F704F400172CDD /* ConnectionKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CFD89409F704F400172CDD /* ConnectionKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 79CFD92F09F7080B00172CDD /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD92D09F7080B00172CDD /* libz.dylib */; }; 79CFD93709F7084000172CDD /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD93609F7084000172CDD /* Security.framework */; }; 79F9533D09FDC3A80041E345 /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79F9532B09FDC3A80041E345 /* ApplicationServices.framework */; }; 79FB807209F74185006E7D11 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79FB807109F74185006E7D11 /* Carbon.framework */; }; ADEE5E18169C84DF006188C5 /* KMSState.h in Headers */ = {isa = PBXBuildFile; fileRef = ADEE5E17169C84DF006188C5 /* KMSState.h */; }; CEB6FA0B13A696B200C8059F /* libsasl2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CEB6FA0A13A696B200C8059F /* libsasl2.dylib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 220526E7165E96AA00A2BBC9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 27BFFEDA15027F4100EFA319 /* CURLHandle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 8DC2EF5B0486A6940098B216; remoteInfo = CURLHandle; }; 220526F0165E97B400A2BBC9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 27448C2414580F7500EB086F /* DAVKit.xcodeproj */; proxyType = 2; remoteGlobalIDString = 8DC2EF5B0486A6940098B216; remoteInfo = DAVKit; }; 220526F2165E97B400A2BBC9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 27448C2414580F7500EB086F /* DAVKit.xcodeproj */; proxyType = 2; remoteGlobalIDString = C9FF77EC121C6F51001250C7; remoteInfo = Tests; }; 220526FC165EA20600A2BBC9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 27BFFEDA15027F4100EFA319 /* CURLHandle.xcodeproj */; proxyType = 1; remoteGlobalIDString = 8DC2EF4F0486A6940098B216; remoteInfo = CURLHandle; }; 220AD7AD1509021A00748655 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; proxyType = 1; remoteGlobalIDString = 79CFD12509F702BE00172CDD; remoteInfo = Framework; }; 22182129170209D300584589 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 27BFFEDA15027F4100EFA319 /* CURLHandle.xcodeproj */; proxyType = 1; remoteGlobalIDString = 223FD091160B523700BE1C80; remoteInfo = CURLHandleTests; }; 22AC1C0D174297C500AB09E1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 27448C2414580F7500EB086F /* DAVKit.xcodeproj */; proxyType = 2; remoteGlobalIDString = 5FEEF26016CE830C002C1CBF; remoteInfo = "DAVKit iOS"; }; 22AC1C0F174297C500AB09E1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 27448C2414580F7500EB086F /* DAVKit.xcodeproj */; proxyType = 2; remoteGlobalIDString = 221F8BA817259BFF004E7B9D; remoteInfo = "Tests iOS"; }; 22C9CFFA1703AD4D004610FE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 27BFFEDA15027F4100EFA319 /* CURLHandle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 22C9CFBF17035A0A004610FE; remoteInfo = crashtest; }; 279117E0178B5A86006BF857 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 27BFFEDA15027F4100EFA319 /* CURLHandle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 223FD092160B523700BE1C80; remoteInfo = CURLHandleTests; }; 279117FF178B5C64006BF857 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; proxyType = 1; remoteGlobalIDString = 279117EA178B5C64006BF857; remoteInfo = ConnectionKitUI; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 22E67F1C171312B6001ECE34 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 7; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 22FA97F715090855001728B8 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 22E67F1B171311C5001ECE34 /* ConnectionKit.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; 27282EDA167131150057CCF7 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 271059521671334500E20511 /* DAVKit.framework in CopyFiles */, 27105953167143D800E20511 /* CURLHandle.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 09D6601E09FD37990000BA00 /* UnitTest-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "UnitTest-Info.plist"; sourceTree = ""; }; 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 220526B9165E8C9D00A2BBC9 /* FTPAuthenticationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTPAuthenticationTests.m; sourceTree = ""; }; 220526F4165E989700A2BBC9 /* Connection_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Connection_Prefix.pch; path = ConnectionKit/Connection_Prefix.pch; sourceTree = ""; }; 220526F6165E98B400A2BBC9 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ConnectionKit/main.m; sourceTree = ""; }; 220AD7981509019B00748655 /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 220C11951715C5E10086F199 /* ErrorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ErrorTests.m; sourceTree = ""; }; 22407D5A166FA12500E1EAD4 /* libssl.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssl.dylib; path = CurlHandle/SFTP/libssl.dylib; sourceTree = ""; }; 22407D5D166FA17600E1EAD4 /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = CurlHandle/SFTP/libcrypto.dylib; sourceTree = ""; }; 2246AF5916B99986001D39D9 /* KMSSendStringCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSSendStringCommand.m; sourceTree = ""; }; 2246AF5A16B99986001D39D9 /* KMSSendDataCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSSendDataCommand.m; sourceTree = ""; }; 2246AF5B16B99986001D39D9 /* KMSSendStringCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSSendStringCommand.h; sourceTree = ""; }; 2246AF5C16B99986001D39D9 /* KMSSendServerDataCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSSendServerDataCommand.m; sourceTree = ""; }; 2246AF5D16B99986001D39D9 /* KMSCloseCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSCloseCommand.h; sourceTree = ""; }; 2246AF5E16B99986001D39D9 /* KMSPauseCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSPauseCommand.m; sourceTree = ""; }; 2246AF5F16B99986001D39D9 /* KMSCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSCommand.m; sourceTree = ""; }; 2246AF6016B99986001D39D9 /* KMSCloseCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSCloseCommand.m; sourceTree = ""; }; 2246AF6116B99986001D39D9 /* KMSCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSCommand.h; sourceTree = ""; }; 2246AF6216B99986001D39D9 /* KMSPauseCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSPauseCommand.h; sourceTree = ""; }; 2246AF6316B99986001D39D9 /* KMSSendDataCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSSendDataCommand.h; sourceTree = ""; }; 2246AF6416B99986001D39D9 /* KMSSendServerDataCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSSendServerDataCommand.h; sourceTree = ""; }; 2248933B165D48CF006C4C7F /* UnitTests_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnitTests_Prefix.pch; sourceTree = ""; }; 224AB371166E500E0066B1C6 /* KMSConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSConnection.h; sourceTree = ""; }; 224AB372166E500E0066B1C6 /* KMSConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSConnection.m; sourceTree = ""; }; 224AB373166E500E0066B1C6 /* KMSListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSListener.h; sourceTree = ""; }; 224AB374166E500E0066B1C6 /* KMSListener.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSListener.m; sourceTree = ""; }; 224AB375166E500E0066B1C6 /* KMSRegExResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSRegExResponder.h; sourceTree = ""; }; 224AB376166E500E0066B1C6 /* KMSRegExResponder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSRegExResponder.m; sourceTree = ""; }; 224AB377166E500E0066B1C6 /* KMSResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSResponder.h; sourceTree = ""; }; 224AB378166E500E0066B1C6 /* KMSResponder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSResponder.m; sourceTree = ""; }; 224AB379166E500E0066B1C6 /* KMSResponseCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSResponseCollection.h; sourceTree = ""; }; 224AB37A166E500E0066B1C6 /* KMSResponseCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSResponseCollection.m; sourceTree = ""; }; 224AB37B166E500E0066B1C6 /* KMSServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSServer.h; sourceTree = ""; }; 224AB37C166E500E0066B1C6 /* KMSServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSServer.m; sourceTree = ""; }; 224AB386166E501C0066B1C6 /* KMSCollectionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSCollectionTests.m; sourceTree = ""; }; 224AB388166E52680066B1C6 /* KMSTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSTestCase.h; sourceTree = ""; }; 224AB389166E52680066B1C6 /* KMSTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSTestCase.m; sourceTree = ""; }; 224AB38B166E587F0066B1C6 /* KMSManualTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSManualTests.m; sourceTree = ""; }; 225FCA3716B046F800A9F5AE /* CKUploaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKUploaderTests.m; sourceTree = ""; }; 2264399116FCB702001FDC20 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = ""; wrapsLines = 1; }; 22662EE2165D1EE3005FCC4A /* BaseCKTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BaseCKTests.h; sourceTree = ""; }; 22662EE3165D1EE3005FCC4A /* BaseCKTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseCKTests.m; sourceTree = ""; }; 22662EF1165D2EEC005FCC4A /* ftp.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ftp.json; sourceTree = ""; }; 22662EF2165D2EEC005FCC4A /* webdav.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = webdav.json; sourceTree = ""; }; 227113BE168388FD00280005 /* http.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = http.json; sourceTree = ""; }; 2280F4B1171826D100BBEF22 /* Documentation */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Documentation; sourceTree = ""; }; 2280F4B41718276E00BBEF22 /* .appledoc.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = .appledoc.plist; sourceTree = ""; }; 2288CD73165A98E300F34E24 /* CK2WebDAVProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2WebDAVProtocol.h; sourceTree = ""; }; 2288CD74165A98E300F34E24 /* CK2WebDAVProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2WebDAVProtocol.m; sourceTree = ""; }; 228E180D1700AEA200ACDE94 /* CK2FileManagerWithTestSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2FileManagerWithTestSupport.h; sourceTree = ""; }; 228E180E1700AEA300ACDE94 /* CK2FileManagerWithTestSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2FileManagerWithTestSupport.m; sourceTree = ""; }; 2298E4AA17442272005A4160 /* FileTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileTests.m; sourceTree = ""; }; 22AC1BFE174297C500AB09E1 /* BaseCKProtocolTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BaseCKProtocolTests.h; sourceTree = ""; }; 22AC1C111742980000AB09E1 /* FTPTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTPTests.m; sourceTree = ""; }; 22AC1C121742980000AB09E1 /* SFTPTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SFTPTests.m; sourceTree = ""; }; 22AC1C131742980000AB09E1 /* WebDAVTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebDAVTests.m; sourceTree = ""; }; 22AC1C1817429F8100AB09E1 /* URLAppendingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = URLAppendingTests.m; sourceTree = ""; }; 22AC1C1A17429FAA00AB09E1 /* URLDirectoryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = URLDirectoryTests.m; sourceTree = ""; }; 22C7A9E316FCC8C300C88317 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = ""; wrapsLines = 1; }; 22C9CF981703327A004610FE /* ProfilingTester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProfilingTester.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22C9CF9C1703327A004610FE /* ProfilingTester-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ProfilingTester-Info.plist"; sourceTree = ""; }; 22C9CF9E1703327A004610FE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 22C9CFA01703327A004610FE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 22C9CFA21703327A004610FE /* ProfilingTester-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ProfilingTester-Prefix.pch"; sourceTree = ""; }; 22C9CFA41703327A004610FE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 22C9CFA61703327A004610FE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 22C9CFA71703327A004610FE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 22C9CFAA1703327A004610FE /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 22C9CFED1703AD4C004610FE /* CK2FileOperationWithTestSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2FileOperationWithTestSupport.h; sourceTree = ""; }; 22C9CFEE1703AD4D004610FE /* CK2FileOperationWithTestSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2FileOperationWithTestSupport.m; sourceTree = ""; }; 22CC56F81509048E00F94154 /* PathTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PathTests.m; sourceTree = ""; }; 22F6D0E7165A8A2200443CC9 /* BaseCKProtocolTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseCKProtocolTests.m; sourceTree = ""; }; 22F6D102165A8A2200443CC9 /* URLTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = URLTests.m; sourceTree = ""; }; 22FEB6671680818800BB778B /* KMSTranscriptEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSTranscriptEntry.m; sourceTree = ""; }; 22FEB6681680818800BB778B /* KMSTranscriptEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSTranscriptEntry.h; sourceTree = ""; }; 2702E4671459D0F50085BBC4 /* libssh2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssh2.dylib; path = CurlHandle/SFTP/libssh2.dylib; sourceTree = ""; }; 27431C9E1630381D00F6FB58 /* CK2FileProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2FileProtocol.h; sourceTree = ""; }; 27431C9F1630381D00F6FB58 /* CK2FileProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2FileProtocol.m; sourceTree = ""; }; 2743E8071622E47600019979 /* CK2FileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2FileManager.h; sourceTree = ""; }; 2743E8081622E47600019979 /* CK2FileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2FileManager.m; sourceTree = ""; }; 27448C2414580F7500EB086F /* DAVKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DAVKit.xcodeproj; path = ../DAVKit/DAVKit.xcodeproj; sourceTree = ""; }; 27448C371458100D00EB086F /* DAVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = DAVKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 278CFE1216BADE030018A14B /* URLCanonicalizationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = URLCanonicalizationTests.m; sourceTree = ""; }; 278D8B77167FF35D00622468 /* CK2Authentication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2Authentication.h; sourceTree = ""; }; 278D8B78167FF35D00622468 /* CK2Authentication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2Authentication.m; sourceTree = ""; }; 2790A8271627636E000C9D9F /* CK2Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2Protocol.h; sourceTree = ""; }; 2790A8281627636E000C9D9F /* CK2Protocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2Protocol.m; sourceTree = ""; }; 2790A94616278F1D000C9D9F /* CK2FTPProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2FTPProtocol.h; sourceTree = ""; }; 2790A94716278F1D000C9D9F /* CK2FTPProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2FTPProtocol.m; sourceTree = ""; }; 279117EB178B5C64006BF857 /* ConnectionKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ConnectionKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 279117EF178B5C64006BF857 /* ConnectionKitUI-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ConnectionKitUI-Info.plist"; sourceTree = ""; }; 279117F1178B5C64006BF857 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 279117F3178B5C64006BF857 /* ConnectionKitUI-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ConnectionKitUI-Prefix.pch"; sourceTree = ""; }; 279117F4178B5C64006BF857 /* ConnectionKitUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ConnectionKitUI.h; path = ConnectionKitUI/ConnectionKitUI.h; sourceTree = SOURCE_ROOT; }; 279117FB178B5C64006BF857 /* ConnectionKitUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ConnectionKitUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 279117FC178B5C64006BF857 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 27993E8216FCB30D008DC1B0 /* CK2FileOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2FileOperation.h; sourceTree = ""; }; 27993E8316FCB30D008DC1B0 /* CK2FileOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2FileOperation.m; sourceTree = ""; }; 27A207291671634800D8284D /* CK2CURLBasedProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2CURLBasedProtocol.h; sourceTree = ""; }; 27A2072A1671634800D8284D /* CK2CURLBasedProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2CURLBasedProtocol.m; sourceTree = ""; }; 27ADC5751AC0CD7D0085C7F7 /* CK2CurlTransferStackManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2CurlTransferStackManager.h; sourceTree = ""; }; 27ADC5761AC0CD7D0085C7F7 /* CK2CurlTransferStackManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2CurlTransferStackManager.m; sourceTree = ""; }; 27BFFEDA15027F4100EFA319 /* CURLHandle.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CURLHandle.xcodeproj; path = ../CurlHandle/CURLHandleSource/CURLHandle.xcodeproj; sourceTree = ""; }; 27CFEC7118E73526007158A4 /* URLs.testdata */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = URLs.testdata; sourceTree = ""; }; 27D03B401471787000FEA588 /* CKUploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKUploader.h; path = ConnectionKit/CKUploader.h; sourceTree = ""; }; 27D03B411471787000FEA588 /* CKUploader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CKUploader.m; path = ConnectionKit/CKUploader.m; sourceTree = ""; }; 27F3372816BC1FB100E70511 /* AuthTester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AuthTester.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27F3372A16BC1FB100E70511 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 27F3372E16BC1FB100E70511 /* AuthTester-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AuthTester-Info.plist"; sourceTree = ""; }; 27F3373016BC1FB100E70511 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 27F3373216BC1FB100E70511 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 27F3373416BC1FB100E70511 /* AuthTester-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AuthTester-Prefix.pch"; sourceTree = ""; }; 27F3373616BC1FB100E70511 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 27F3373816BC1FB100E70511 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 27F3373916BC1FB100E70511 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 27F3373C16BC1FB100E70511 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 27F394F3162C162900944F43 /* CK2SFTPProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2SFTPProtocol.h; sourceTree = ""; }; 27F394F4162C162900944F43 /* CK2SFTPProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2SFTPProtocol.m; sourceTree = ""; }; 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 323B7348170E253900219F9A /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/CK2OpenPanel.xib; sourceTree = ""; }; 323B7355170E254800219F9A /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/CK2FilePreview.xib; sourceTree = ""; }; 323B7358170E255200219F9A /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/CK2NewFolderWindow.xib; sourceTree = ""; }; 323B735B170E256000219F9A /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/CK2PathFieldWindow.xib; sourceTree = ""; }; 324D22BB16AE173F001CFDE6 /* CK2BrowserPreviewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2BrowserPreviewController.h; sourceTree = ""; }; 324D22BC16AE173F001CFDE6 /* CK2BrowserPreviewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2BrowserPreviewController.m; sourceTree = ""; }; 324D22BD16AE1740001CFDE6 /* CK2BrowserPreviewView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2BrowserPreviewView.h; sourceTree = ""; }; 324D22BE16AE1740001CFDE6 /* CK2BrowserPreviewView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2BrowserPreviewView.m; sourceTree = ""; }; 324D22C316AE1754001CFDE6 /* CK2FileSizeFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2FileSizeFormatter.h; sourceTree = ""; }; 324D22C416AE1754001CFDE6 /* CK2FileSizeFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2FileSizeFormatter.m; sourceTree = ""; }; 324D22C716AE1762001CFDE6 /* CK2IconView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2IconView.h; sourceTree = ""; }; 324D22C816AE1762001CFDE6 /* CK2IconView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2IconView.m; sourceTree = ""; }; 324D22D116AF490A001CFDE6 /* CK2NewFolderWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2NewFolderWindowController.h; sourceTree = ""; }; 324D22D216AF490A001CFDE6 /* CK2NewFolderWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2NewFolderWindowController.m; sourceTree = ""; }; 3274F1CB170888430049DDE5 /* CK2PathFieldWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2PathFieldWindowController.h; sourceTree = ""; }; 3274F1CC170888430049DDE5 /* CK2PathFieldWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2PathFieldWindowController.m; sourceTree = ""; }; 32B3EA7C16B58FFA003D4836 /* NSImage+CK2OpenPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSImage+CK2OpenPanel.h"; sourceTree = ""; }; 32B3EA7D16B58FFB003D4836 /* NSImage+CK2OpenPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSImage+CK2OpenPanel.m"; sourceTree = ""; }; 32B592A116A4960B0020FFE9 /* CK2OpenPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2OpenPanel.h; sourceTree = ""; }; 32B592A216A4960B0020FFE9 /* CK2OpenPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2OpenPanel.m; sourceTree = ""; }; 32B592A316A4960B0020FFE9 /* CK2OpenPanelController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2OpenPanelController.h; sourceTree = ""; }; 32B592A416A4960B0020FFE9 /* CK2OpenPanelController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2OpenPanelController.m; sourceTree = ""; }; 32B592A516A4960B0020FFE9 /* CK2FileCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2FileCell.h; sourceTree = ""; }; 32B592A616A4960B0020FFE9 /* CK2FileCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2FileCell.m; sourceTree = ""; }; 32B592A716A4960B0020FFE9 /* NSURL+CK2OpenPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+CK2OpenPanel.h"; sourceTree = ""; }; 32B592A816A4960B0020FFE9 /* NSURL+CK2OpenPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+CK2OpenPanel.m"; sourceTree = ""; }; 32B592A916A4960B0020FFE9 /* CK2PathControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2PathControl.h; sourceTree = ""; }; 32B592AA16A4960B0020FFE9 /* CK2PathControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2PathControl.m; sourceTree = ""; }; 32B592AB16A4960B0020FFE9 /* CK2IconViewItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2IconViewItem.h; sourceTree = ""; }; 32B592AC16A4960B0020FFE9 /* CK2IconViewItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2IconViewItem.m; sourceTree = ""; }; 32B592AD16A4960B0020FFE9 /* CK2IconItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2IconItemView.h; sourceTree = ""; }; 32B592AE16A4960B0020FFE9 /* CK2IconItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2IconItemView.m; sourceTree = ""; }; 32B592B016A4960B0020FFE9 /* CK2OpenPanelViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2OpenPanelViewController.h; sourceTree = ""; }; 32B592B116A4960B0020FFE9 /* CK2OpenPanelViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2OpenPanelViewController.m; sourceTree = ""; }; 32B592B216A4960B0020FFE9 /* CK2OpenPanelIconViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2OpenPanelIconViewController.h; sourceTree = ""; }; 32B592B316A4960B0020FFE9 /* CK2OpenPanelIconViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2OpenPanelIconViewController.m; sourceTree = ""; }; 32B592B416A4960B0020FFE9 /* CK2OpenPanelListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2OpenPanelListViewController.h; sourceTree = ""; }; 32B592B516A4960B0020FFE9 /* CK2OpenPanelListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2OpenPanelListViewController.m; sourceTree = ""; }; 32B592B616A4960B0020FFE9 /* CK2OpenPanelColumnViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2OpenPanelColumnViewController.h; sourceTree = ""; }; 32B592B716A4960B0020FFE9 /* CK2OpenPanelColumnViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2OpenPanelColumnViewController.m; sourceTree = ""; }; 3C2D8EC10A63FF31008FE1B0 /* zh_TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = zh_TW; path = zh_TW.lproj/Localizable.strings; sourceTree = ""; }; 3C2D8EC20A63FF3B008FE1B0 /* fr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 3C2D8EC30A63FF41008FE1B0 /* da */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; 791E83030B0EDAC90060E5FC /* error.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = error.png; path = Resources/error.png; sourceTree = ""; }; 791E83040B0EDAC90060E5FC /* finished.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = finished.png; path = Resources/finished.png; sourceTree = ""; }; 7946B0FC0AC0F4A400CAE90F /* CKS3Connection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKS3Connection.h; sourceTree = ""; }; 7946B0FD0AC0F4A400CAE90F /* CKS3Connection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKS3Connection.m; sourceTree = ""; }; 796DB2F609F8BB1D0065897B /* SecurityInterface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SecurityInterface.framework; path = /System/Library/Frameworks/SecurityInterface.framework; sourceTree = ""; }; 7978FC140B117D7C0048168B /* bookmark.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = bookmark.tif; sourceTree = ""; }; 797CC8C609F861770063FF9B /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 798313C70B0D67E000F5078E /* CKTransferProgressCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKTransferProgressCell.h; path = ConnectionKit/CKTransferProgressCell.h; sourceTree = ""; }; 798313C80B0D67E000F5078E /* CKTransferProgressCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CKTransferProgressCell.m; path = ConnectionKit/CKTransferProgressCell.m; sourceTree = ""; }; 7983DF8C0B0C0FAC00F5078E /* CKTransferRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKTransferRecord.h; path = ConnectionKit/CKTransferRecord.h; sourceTree = ""; }; 7983DF8D0B0C0FAC00F5078E /* CKTransferRecord.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CKTransferRecord.m; path = ConnectionKit/CKTransferRecord.m; sourceTree = ""; }; 79CFD12609F702BE00172CDD /* ConnectionKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ConnectionKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 79CFD89409F704F400172CDD /* ConnectionKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConnectionKit.h; sourceTree = ""; }; 79CFD92C09F7080B00172CDD /* libcurl.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcurl.dylib; path = /usr/lib/libcurl.dylib; sourceTree = ""; }; 79CFD92D09F7080B00172CDD /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = /usr/lib/libz.dylib; sourceTree = ""; }; 79CFD93609F7084000172CDD /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = ""; }; 79F9532B09FDC3A80041E345 /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = /System/Library/Frameworks/ApplicationServices.framework; sourceTree = ""; }; 79FB807109F74185006E7D11 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; ADEE5E17169C84DF006188C5 /* KMSState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSState.h; sourceTree = ""; }; CE1AD3DF0A7E845A0083C01E /* it */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; CE55A6620AD194740091C8AE /* zh_CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = zh_CN; path = zh_CN.lproj/Localizable.strings; sourceTree = ""; }; CE94B9101463124A00F90408 /* es */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; CE953A04102BACE70066C08F /* pt_BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pt_BR; path = pt_BR.lproj/Localizable.strings; sourceTree = ""; }; CE9795FC0EC3CF9600FA9C5F /* Framework-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Framework-Info.plist"; sourceTree = ""; }; CEA9AFD30A64224100855897 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; CEB563850A7AB7070081179A /* de */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; CEB6FA0A13A696B200C8059F /* libsasl2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsasl2.dylib; path = /usr/lib/libsasl2.dylib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 220AD7941509019B00748655 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 228E180C1700AD5600ACDE94 /* CURLHandle.framework in Frameworks */, 22CC578B1509068600F94154 /* ConnectionKit.framework in Frameworks */, 22052706165EA62500A2BBC9 /* DAVKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 22C9CF951703327A004610FE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 22C9CFBA170333A2004610FE /* ConnectionKit.framework in Frameworks */, 22C9CF991703327A004610FE /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 279117E7178B5C64006BF857 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2791183A178B5E12006BF857 /* ConnectionKit.framework in Frameworks */, 279117EC178B5C64006BF857 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 279117F8178B5C64006BF857 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 27911801178B5C64006BF857 /* ConnectionKitUI.framework in Frameworks */, 279117FE178B5C64006BF857 /* Cocoa.framework in Frameworks */, 279117FD178B5C64006BF857 /* XCTest.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 27F3372516BC1FB100E70511 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 27F3372B16BC1FB100E70511 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 79CFD12409F702BE00172CDD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 22052702165EA23800A2BBC9 /* CURLHandle.framework in Frameworks */, 79CFD92F09F7080B00172CDD /* libz.dylib in Frameworks */, 79CFD93709F7084000172CDD /* Security.framework in Frameworks */, 79FB807209F74185006E7D11 /* Carbon.framework in Frameworks */, 796DB30109F8BB1D0065897B /* SecurityInterface.framework in Frameworks */, 79F9533D09FDC3A80041E345 /* ApplicationServices.framework in Frameworks */, CEB6FA0B13A696B200C8059F /* libsasl2.dylib in Frameworks */, 220526F8165E9DE400A2BBC9 /* DAVKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 080E96DDFE201D6D7F000001 /* Public */ = { isa = PBXGroup; children = ( 79CFD89409F704F400172CDD /* ConnectionKit.h */, 2743E8071622E47600019979 /* CK2FileManager.h */, 2743E8081622E47600019979 /* CK2FileManager.m */, 278D8B77167FF35D00622468 /* CK2Authentication.h */, 278D8B78167FF35D00622468 /* CK2Authentication.m */, 27993E8216FCB30D008DC1B0 /* CK2FileOperation.h */, 27993E8316FCB30D008DC1B0 /* CK2FileOperation.m */, ); name = Public; path = ConnectionKit; sourceTree = ""; }; 09D6602E09FD37B70000BA00 /* Unit Tests */ = { isa = PBXGroup; children = ( 2264399116FCB702001FDC20 /* README.md */, 225FCA3716B046F800A9F5AE /* CKUploaderTests.m */, 220C11951715C5E10086F199 /* ErrorTests.m */, 2298E4AA17442272005A4160 /* FileTests.m */, 220526B9165E8C9D00A2BBC9 /* FTPAuthenticationTests.m */, 22AC1C111742980000AB09E1 /* FTPTests.m */, 22CC56F81509048E00F94154 /* PathTests.m */, 22AC1C121742980000AB09E1 /* SFTPTests.m */, 22AC1C1817429F8100AB09E1 /* URLAppendingTests.m */, 278CFE1216BADE030018A14B /* URLCanonicalizationTests.m */, 22AC1C1A17429FAA00AB09E1 /* URLDirectoryTests.m */, 22F6D102165A8A2200443CC9 /* URLTests.m */, 27CFEC7118E73526007158A4 /* URLs.testdata */, 22AC1C131742980000AB09E1 /* WebDAVTests.m */, 22AC1C1717429F1500AB09E1 /* Test Support */, ); name = "Unit Tests"; path = UnitTests; sourceTree = ""; }; 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { isa = PBXGroup; children = ( 79F9532B09FDC3A80041E345 /* ApplicationServices.framework */, 79FB807109F74185006E7D11 /* Carbon.framework */, 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 27448C371458100D00EB086F /* DAVKit.framework */, 22407D5D166FA17600E1EAD4 /* libcrypto.dylib */, 79CFD92C09F7080B00172CDD /* libcurl.dylib */, CEB6FA0A13A696B200C8059F /* libsasl2.dylib */, 2702E4671459D0F50085BBC4 /* libssh2.dylib */, 22407D5A166FA12500E1EAD4 /* libssl.dylib */, 79CFD92D09F7080B00172CDD /* libz.dylib */, 79CFD93609F7084000172CDD /* Security.framework */, 796DB2F609F8BB1D0065897B /* SecurityInterface.framework */, ); name = "Linked Frameworks"; sourceTree = ""; }; 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { isa = PBXGroup; children = ( 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, 29B97325FDCFA39411CA2CEA /* Foundation.framework */, ); name = "Other Frameworks"; sourceTree = ""; }; 19C28FACFE9D520D11CA2CBB /* Products */ = { isa = PBXGroup; children = ( 79CFD12609F702BE00172CDD /* ConnectionKit.framework */, 220AD7981509019B00748655 /* UnitTests.xctest */, 27F3372816BC1FB100E70511 /* AuthTester.app */, 22C9CF981703327A004610FE /* ProfilingTester.app */, 279117EB178B5C64006BF857 /* ConnectionKitUI.framework */, 279117FB178B5C64006BF857 /* ConnectionKitUITests.xctest */, ); name = Products; sourceTree = ""; }; 220526E3165E96AA00A2BBC9 /* Products */ = { isa = PBXGroup; children = ( 220526E8165E96AA00A2BBC9 /* CURLHandle.framework */, 279117E1178B5A86006BF857 /* CURLHandleTests.xctest */, 22C9CFFB1703AD4D004610FE /* Standalone Test */, ); name = Products; sourceTree = ""; }; 220526EC165E97B400A2BBC9 /* Products */ = { isa = PBXGroup; children = ( 220526F1165E97B400A2BBC9 /* DAVKit.framework */, 220526F3165E97B400A2BBC9 /* Tests.octest */, 22AC1C0E174297C500AB09E1 /* libDAVKit.a */, 22AC1C10174297C500AB09E1 /* Tests iOS.octest */, ); name = Products; sourceTree = ""; }; 2280F4A4171826A500BBEF22 /* Documentation */ = { isa = PBXGroup; children = ( 2280F4B41718276E00BBEF22 /* .appledoc.plist */, 2280F4B1171826D100BBEF22 /* Documentation */, ); name = Documentation; sourceTree = ""; }; 2280F4B0171826B700BBEF22 /* Testing */ = { isa = PBXGroup; children = ( 09D6602E09FD37B70000BA00 /* Unit Tests */, 27F3372C16BC1FB100E70511 /* AuthTester */, 22C9CF9A1703327A004610FE /* ProfilingTester */, ); name = Testing; sourceTree = ""; }; 22AC1C1717429F1500AB09E1 /* Test Support */ = { isa = PBXGroup; children = ( 09D6601E09FD37990000BA00 /* UnitTest-Info.plist */, 2248933B165D48CF006C4C7F /* UnitTests_Prefix.pch */, 27999BB8170B4BDD00A54BEE /* Framework Extensions */, 22F6D0E8165A8A2200443CC9 /* MockServer */, 22662EE2165D1EE3005FCC4A /* BaseCKTests.h */, 22662EE3165D1EE3005FCC4A /* BaseCKTests.m */, 22AC1BFE174297C500AB09E1 /* BaseCKProtocolTests.h */, 22F6D0E7165A8A2200443CC9 /* BaseCKProtocolTests.m */, ); name = "Test Support"; sourceTree = ""; }; 22C9CF9A1703327A004610FE /* ProfilingTester */ = { isa = PBXGroup; children = ( 22C9CFA61703327A004610FE /* AppDelegate.h */, 22C9CFA71703327A004610FE /* AppDelegate.m */, 22C9CFA91703327A004610FE /* MainMenu.xib */, 22C9CF9B1703327A004610FE /* Supporting Files */, ); path = ProfilingTester; sourceTree = ""; }; 22C9CF9B1703327A004610FE /* Supporting Files */ = { isa = PBXGroup; children = ( 22C9CF9C1703327A004610FE /* ProfilingTester-Info.plist */, 22C9CF9D1703327A004610FE /* InfoPlist.strings */, 22C9CFA01703327A004610FE /* main.m */, 22C9CFA21703327A004610FE /* ProfilingTester-Prefix.pch */, 22C9CFA31703327A004610FE /* Credits.rtf */, ); name = "Supporting Files"; sourceTree = ""; }; 22F6D0E8165A8A2200443CC9 /* MockServer */ = { isa = PBXGroup; children = ( 2246AF5D16B99986001D39D9 /* KMSCloseCommand.h */, 2246AF6016B99986001D39D9 /* KMSCloseCommand.m */, 2246AF6116B99986001D39D9 /* KMSCommand.h */, 2246AF5F16B99986001D39D9 /* KMSCommand.m */, 224AB371166E500E0066B1C6 /* KMSConnection.h */, 224AB372166E500E0066B1C6 /* KMSConnection.m */, 224AB373166E500E0066B1C6 /* KMSListener.h */, 224AB374166E500E0066B1C6 /* KMSListener.m */, 2246AF6216B99986001D39D9 /* KMSPauseCommand.h */, 2246AF5E16B99986001D39D9 /* KMSPauseCommand.m */, 224AB375166E500E0066B1C6 /* KMSRegExResponder.h */, 224AB376166E500E0066B1C6 /* KMSRegExResponder.m */, 224AB377166E500E0066B1C6 /* KMSResponder.h */, 224AB378166E500E0066B1C6 /* KMSResponder.m */, 224AB379166E500E0066B1C6 /* KMSResponseCollection.h */, 224AB37A166E500E0066B1C6 /* KMSResponseCollection.m */, 2246AF6316B99986001D39D9 /* KMSSendDataCommand.h */, 2246AF5A16B99986001D39D9 /* KMSSendDataCommand.m */, 2246AF6416B99986001D39D9 /* KMSSendServerDataCommand.h */, 2246AF5C16B99986001D39D9 /* KMSSendServerDataCommand.m */, 2246AF5B16B99986001D39D9 /* KMSSendStringCommand.h */, 2246AF5916B99986001D39D9 /* KMSSendStringCommand.m */, 224AB37B166E500E0066B1C6 /* KMSServer.h */, 224AB37C166E500E0066B1C6 /* KMSServer.m */, ADEE5E17169C84DF006188C5 /* KMSState.h */, 224AB388166E52680066B1C6 /* KMSTestCase.h */, 224AB389166E52680066B1C6 /* KMSTestCase.m */, 22FEB6681680818800BB778B /* KMSTranscriptEntry.h */, 22FEB6671680818800BB778B /* KMSTranscriptEntry.m */, 22F6D0FF165A8A2200443CC9 /* UnitTests */, ); name = MockServer; path = ../CurlHandle/CURLHandleSource/Tests/MockServer; sourceTree = ""; }; 22F6D0FF165A8A2200443CC9 /* UnitTests */ = { isa = PBXGroup; children = ( 227113BE168388FD00280005 /* http.json */, 22662EF1165D2EEC005FCC4A /* ftp.json */, 22662EF2165D2EEC005FCC4A /* webdav.json */, 224AB38B166E587F0066B1C6 /* KMSManualTests.m */, 224AB386166E501C0066B1C6 /* KMSCollectionTests.m */, ); path = UnitTests; sourceTree = ""; }; 273F0E13164E8D3E00588885 /* Protocols */ = { isa = PBXGroup; children = ( 2790A8271627636E000C9D9F /* CK2Protocol.h */, 2790A8281627636E000C9D9F /* CK2Protocol.m */, 27A207291671634800D8284D /* CK2CURLBasedProtocol.h */, 27A2072A1671634800D8284D /* CK2CURLBasedProtocol.m */, 27ADC5751AC0CD7D0085C7F7 /* CK2CurlTransferStackManager.h */, 27ADC5761AC0CD7D0085C7F7 /* CK2CurlTransferStackManager.m */, 2790A94616278F1D000C9D9F /* CK2FTPProtocol.h */, 2790A94716278F1D000C9D9F /* CK2FTPProtocol.m */, 27F394F3162C162900944F43 /* CK2SFTPProtocol.h */, 27F394F4162C162900944F43 /* CK2SFTPProtocol.m */, 27431C9E1630381D00F6FB58 /* CK2FileProtocol.h */, 27431C9F1630381D00F6FB58 /* CK2FileProtocol.m */, 2288CD73165A98E300F34E24 /* CK2WebDAVProtocol.h */, 2288CD74165A98E300F34E24 /* CK2WebDAVProtocol.m */, ); name = Protocols; sourceTree = ""; }; 274DA43516ED52BE008041B6 /* Legacy */ = { isa = PBXGroup; children = ( 274DA43616ED52CC008041B6 /* Uploader */, ); name = Legacy; sourceTree = ""; }; 274DA43616ED52CC008041B6 /* Uploader */ = { isa = PBXGroup; children = ( 27D03B401471787000FEA588 /* CKUploader.h */, 27D03B411471787000FEA588 /* CKUploader.m */, 7983DF8C0B0C0FAC00F5078E /* CKTransferRecord.h */, 7983DF8D0B0C0FAC00F5078E /* CKTransferRecord.m */, 798313C70B0D67E000F5078E /* CKTransferProgressCell.h */, 798313C80B0D67E000F5078E /* CKTransferProgressCell.m */, 791E83030B0EDAC90060E5FC /* error.png */, 791E83040B0EDAC90060E5FC /* finished.png */, ); name = Uploader; sourceTree = ""; }; 279117EE178B5C64006BF857 /* Supporting Files */ = { isa = PBXGroup; children = ( 279117EF178B5C64006BF857 /* ConnectionKitUI-Info.plist */, 279117F0178B5C64006BF857 /* InfoPlist.strings */, 279117F3178B5C64006BF857 /* ConnectionKitUI-Prefix.pch */, ); name = "Supporting Files"; path = ConnectionKitUI; sourceTree = SOURCE_ROOT; }; 27999BB8170B4BDD00A54BEE /* Framework Extensions */ = { isa = PBXGroup; children = ( 22C9CFED1703AD4C004610FE /* CK2FileOperationWithTestSupport.h */, 22C9CFEE1703AD4D004610FE /* CK2FileOperationWithTestSupport.m */, 228E180D1700AEA200ACDE94 /* CK2FileManagerWithTestSupport.h */, 228E180E1700AEA300ACDE94 /* CK2FileManagerWithTestSupport.m */, ); name = "Framework Extensions"; path = ../ConnectionKit; sourceTree = ""; }; 27F3372C16BC1FB100E70511 /* AuthTester */ = { isa = PBXGroup; children = ( 27F3373816BC1FB100E70511 /* AppDelegate.h */, 27F3373916BC1FB100E70511 /* AppDelegate.m */, 27F3373B16BC1FB100E70511 /* MainMenu.xib */, 27F3372D16BC1FB100E70511 /* Supporting Files */, ); path = AuthTester; sourceTree = ""; }; 27F3372D16BC1FB100E70511 /* Supporting Files */ = { isa = PBXGroup; children = ( 27F3372E16BC1FB100E70511 /* AuthTester-Info.plist */, 27F3372F16BC1FB100E70511 /* InfoPlist.strings */, 27F3373216BC1FB100E70511 /* main.m */, 27F3373416BC1FB100E70511 /* AuthTester-Prefix.pch */, 27F3373516BC1FB100E70511 /* Credits.rtf */, ); name = "Supporting Files"; sourceTree = ""; }; 27FD90C20ED5BFA40068D634 /* Experimental */ = { isa = PBXGroup; children = ( 7946B0FC0AC0F4A400CAE90F /* CKS3Connection.h */, 7946B0FD0AC0F4A400CAE90F /* CKS3Connection.m */, ); name = Experimental; path = ConnectionKit; sourceTree = ""; }; 27FD90C60ED5BFC00068D634 /* Dependencies */ = { isa = PBXGroup; children = ( 27BFFEDA15027F4100EFA319 /* CURLHandle.xcodeproj */, 27448C2414580F7500EB086F /* DAVKit.xcodeproj */, ); name = Dependencies; path = ConnectionKit; sourceTree = ""; }; 29B97314FDCFA39411CA2CEA /* Connection */ = { isa = PBXGroup; children = ( 22C7A9E316FCC8C300C88317 /* README.md */, 080E96DDFE201D6D7F000001 /* Public */, 79CFD8A009F705B400172CDD /* Private */, 79CFD8A509F705FF00172CDD /* UI */, 274DA43516ED52BE008041B6 /* Legacy */, 27FD90C20ED5BFA40068D634 /* Experimental */, 2280F4B0171826B700BBEF22 /* Testing */, 29B97315FDCFA39411CA2CEA /* Other Sources */, 27FD90C60ED5BFC00068D634 /* Dependencies */, 29B97317FDCFA39411CA2CEA /* Resources */, 2280F4A4171826A500BBEF22 /* Documentation */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, ); name = Connection; sourceTree = ""; }; 29B97315FDCFA39411CA2CEA /* Other Sources */ = { isa = PBXGroup; children = ( 220526F4165E989700A2BBC9 /* Connection_Prefix.pch */, 220526F6165E98B400A2BBC9 /* main.m */, ); name = "Other Sources"; sourceTree = ""; }; 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( 797CC8C509F861770063FF9B /* Localizable.strings */, 79CFD8E409F7071E00172CDD /* Images */, CE9795FC0EC3CF9600FA9C5F /* Framework-Info.plist */, ); path = Resources; sourceTree = ""; }; 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 27F3372A16BC1FB100E70511 /* Cocoa.framework */, 279117FC178B5C64006BF857 /* XCTest.framework */, 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, ); name = Frameworks; sourceTree = ""; }; 32B592A016A1016A0020FFE9 /* CK2OpenPanel */ = { isa = PBXGroup; children = ( 32B592A116A4960B0020FFE9 /* CK2OpenPanel.h */, 32B592A216A4960B0020FFE9 /* CK2OpenPanel.m */, 32B592A316A4960B0020FFE9 /* CK2OpenPanelController.h */, 32B592A416A4960B0020FFE9 /* CK2OpenPanelController.m */, 32B592B016A4960B0020FFE9 /* CK2OpenPanelViewController.h */, 32B592B116A4960B0020FFE9 /* CK2OpenPanelViewController.m */, 32B592B216A4960B0020FFE9 /* CK2OpenPanelIconViewController.h */, 32B592B316A4960B0020FFE9 /* CK2OpenPanelIconViewController.m */, 32B592B416A4960B0020FFE9 /* CK2OpenPanelListViewController.h */, 32B592B516A4960B0020FFE9 /* CK2OpenPanelListViewController.m */, 32B592B616A4960B0020FFE9 /* CK2OpenPanelColumnViewController.h */, 32B592B716A4960B0020FFE9 /* CK2OpenPanelColumnViewController.m */, 324D22D116AF490A001CFDE6 /* CK2NewFolderWindowController.h */, 324D22D216AF490A001CFDE6 /* CK2NewFolderWindowController.m */, 32B592A516A4960B0020FFE9 /* CK2FileCell.h */, 32B592A616A4960B0020FFE9 /* CK2FileCell.m */, 32B592A916A4960B0020FFE9 /* CK2PathControl.h */, 32B592AA16A4960B0020FFE9 /* CK2PathControl.m */, 3274F1CB170888430049DDE5 /* CK2PathFieldWindowController.h */, 3274F1CC170888430049DDE5 /* CK2PathFieldWindowController.m */, 32B592AB16A4960B0020FFE9 /* CK2IconViewItem.h */, 32B592AC16A4960B0020FFE9 /* CK2IconViewItem.m */, 32B592AD16A4960B0020FFE9 /* CK2IconItemView.h */, 32B592AE16A4960B0020FFE9 /* CK2IconItemView.m */, 324D22C716AE1762001CFDE6 /* CK2IconView.h */, 324D22C816AE1762001CFDE6 /* CK2IconView.m */, 324D22BB16AE173F001CFDE6 /* CK2BrowserPreviewController.h */, 324D22BC16AE173F001CFDE6 /* CK2BrowserPreviewController.m */, 324D22BD16AE1740001CFDE6 /* CK2BrowserPreviewView.h */, 324D22BE16AE1740001CFDE6 /* CK2BrowserPreviewView.m */, 324D22C316AE1754001CFDE6 /* CK2FileSizeFormatter.h */, 324D22C416AE1754001CFDE6 /* CK2FileSizeFormatter.m */, 32B592A716A4960B0020FFE9 /* NSURL+CK2OpenPanel.h */, 32B592A816A4960B0020FFE9 /* NSURL+CK2OpenPanel.m */, 32B3EA7C16B58FFA003D4836 /* NSImage+CK2OpenPanel.h */, 32B3EA7D16B58FFB003D4836 /* NSImage+CK2OpenPanel.m */, 323B7349170E253900219F9A /* CK2OpenPanel.xib */, 323B7356170E254800219F9A /* CK2FilePreview.xib */, 323B7359170E255200219F9A /* CK2NewFolderWindow.xib */, 323B735C170E256000219F9A /* CK2PathFieldWindow.xib */, ); name = CK2OpenPanel; sourceTree = ""; }; 79CFD8A009F705B400172CDD /* Private */ = { isa = PBXGroup; children = ( 273F0E13164E8D3E00588885 /* Protocols */, ); name = Private; path = ConnectionKit; sourceTree = ""; }; 79CFD8A509F705FF00172CDD /* UI */ = { isa = PBXGroup; children = ( 279117F4178B5C64006BF857 /* ConnectionKitUI.h */, 32B592A016A1016A0020FFE9 /* CK2OpenPanel */, 279117EE178B5C64006BF857 /* Supporting Files */, ); name = UI; path = ConnectionKit; sourceTree = ""; }; 79CFD8E409F7071E00172CDD /* Images */ = { isa = PBXGroup; children = ( 7978FC140B117D7C0048168B /* bookmark.tif */, ); name = Images; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 279117E8178B5C64006BF857 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 27911834178B5D59006BF857 /* NSImage+CK2OpenPanel.h in Headers */, 27911816178B5D59006BF857 /* CK2OpenPanelIconViewController.h in Headers */, 2791182C178B5D59006BF857 /* CK2BrowserPreviewView.h in Headers */, 27911813178B5D4F006BF857 /* CK2OpenPanelController.h in Headers */, 27911814178B5D59006BF857 /* CK2OpenPanelViewController.h in Headers */, 27911824178B5D59006BF857 /* CK2IconViewItem.h in Headers */, 2791181E178B5D59006BF857 /* CK2FileCell.h in Headers */, 2791182A178B5D59006BF857 /* CK2BrowserPreviewController.h in Headers */, 2791181C178B5D59006BF857 /* CK2NewFolderWindowController.h in Headers */, 2791188D178B6782006BF857 /* ConnectionKitUI.h in Headers */, 27911826178B5D59006BF857 /* CK2IconItemView.h in Headers */, 27911822178B5D59006BF857 /* CK2PathFieldWindowController.h in Headers */, 27911820178B5D59006BF857 /* CK2PathControl.h in Headers */, 2791182E178B5D59006BF857 /* CK2FileSizeFormatter.h in Headers */, 2791181A178B5D59006BF857 /* CK2OpenPanelColumnViewController.h in Headers */, 27911818178B5D59006BF857 /* CK2OpenPanelListViewController.h in Headers */, 27911828178B5D59006BF857 /* CK2IconView.h in Headers */, 27911810178B5D33006BF857 /* CK2OpenPanel.h in Headers */, 27911832178B5D59006BF857 /* NSURL+CK2OpenPanel.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 79CFD12109F702BE00172CDD /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 79CFD89509F704F400172CDD /* ConnectionKit.h in Headers */, 7983DF8E0B0C0FAC00F5078E /* CKTransferRecord.h in Headers */, 798313C90B0D67E000F5078E /* CKTransferProgressCell.h in Headers */, 27D03B421471787000FEA588 /* CKUploader.h in Headers */, 2743E8091622E47600019979 /* CK2FileManager.h in Headers */, 2790A8291627636E000C9D9F /* CK2Protocol.h in Headers */, 2790A94816278F1D000C9D9F /* CK2FTPProtocol.h in Headers */, 27ADC5771AC0CD7D0085C7F7 /* CK2CurlTransferStackManager.h in Headers */, 27F394F5162C162900944F43 /* CK2SFTPProtocol.h in Headers */, 27431CA01630381D00F6FB58 /* CK2FileProtocol.h in Headers */, 27A2072B1671634800D8284D /* CK2CURLBasedProtocol.h in Headers */, 278D8B79167FF35D00622468 /* CK2Authentication.h in Headers */, ADEE5E18169C84DF006188C5 /* KMSState.h in Headers */, 27993E8416FCB30D008DC1B0 /* CK2FileOperation.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 220AD7971509019B00748655 /* UnitTests */ = { isa = PBXNativeTarget; buildConfigurationList = 220AD7A71509019B00748655 /* Build configuration list for PBXNativeTarget "UnitTests" */; buildPhases = ( 220AD7931509019B00748655 /* Sources */, 220AD7941509019B00748655 /* Frameworks */, 220AD7951509019B00748655 /* Resources */, 22FA97F715090855001728B8 /* CopyFiles */, 220AD7961509019B00748655 /* ShellScript */, 22E67F1C171312B6001ECE34 /* CopyFiles */, ); buildRules = ( ); dependencies = ( 2218212A170209D300584589 /* PBXTargetDependency */, 220AD7AE1509021A00748655 /* PBXTargetDependency */, ); name = UnitTests; productName = Tests; productReference = 220AD7981509019B00748655 /* UnitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 22C9CF971703327A004610FE /* ProfilingTester */ = { isa = PBXNativeTarget; buildConfigurationList = 22C9CFB91703327A004610FE /* Build configuration list for PBXNativeTarget "ProfilingTester" */; buildPhases = ( 22C9CF941703327A004610FE /* Sources */, 22C9CF951703327A004610FE /* Frameworks */, 22C9CF961703327A004610FE /* Resources */, ); buildRules = ( ); dependencies = ( ); name = ProfilingTester; productName = ProfilingTester; productReference = 22C9CF981703327A004610FE /* ProfilingTester.app */; productType = "com.apple.product-type.application"; }; 279117EA178B5C64006BF857 /* ConnectionKitUI */ = { isa = PBXNativeTarget; buildConfigurationList = 2791180A178B5C64006BF857 /* Build configuration list for PBXNativeTarget "ConnectionKitUI" */; buildPhases = ( 279117E6178B5C64006BF857 /* Sources */, 279117E7178B5C64006BF857 /* Frameworks */, 279117E8178B5C64006BF857 /* Headers */, 279117E9178B5C64006BF857 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = ConnectionKitUI; productName = ConnectionKitUI; productReference = 279117EB178B5C64006BF857 /* ConnectionKitUI.framework */; productType = "com.apple.product-type.framework"; }; 279117FA178B5C64006BF857 /* ConnectionKitUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 2791180D178B5C64006BF857 /* Build configuration list for PBXNativeTarget "ConnectionKitUITests" */; buildPhases = ( 279117F7178B5C64006BF857 /* Sources */, 279117F8178B5C64006BF857 /* Frameworks */, 279117F9178B5C64006BF857 /* Resources */, ); buildRules = ( ); dependencies = ( 27911800178B5C64006BF857 /* PBXTargetDependency */, ); name = ConnectionKitUITests; productName = ConnectionKitUITests; productReference = 279117FB178B5C64006BF857 /* ConnectionKitUITests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 27F3372716BC1FB100E70511 /* AuthTester */ = { isa = PBXNativeTarget; buildConfigurationList = 27F3373E16BC1FB100E70511 /* Build configuration list for PBXNativeTarget "AuthTester" */; buildPhases = ( 27F3372416BC1FB100E70511 /* Sources */, 27F3372516BC1FB100E70511 /* Frameworks */, 27F3372616BC1FB100E70511 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AuthTester; productName = AuthTester; productReference = 27F3372816BC1FB100E70511 /* AuthTester.app */; productType = "com.apple.product-type.application"; }; 79CFD12509F702BE00172CDD /* ConnectionKit */ = { isa = PBXNativeTarget; buildConfigurationList = 79CFD12809F702BF00172CDD /* Build configuration list for PBXNativeTarget "ConnectionKit" */; buildPhases = ( 797CBBEB09F854B60063FF9B /* Generate Localizable.strings */, 79CFD12309F702BE00172CDD /* Sources */, 79CFD12409F702BE00172CDD /* Frameworks */, 79CFD12109F702BE00172CDD /* Headers */, 79CFD12209F702BE00172CDD /* Resources */, 27282EDA167131150057CCF7 /* CopyFiles */, ); buildRules = ( ); dependencies = ( 220526FD165EA20600A2BBC9 /* PBXTargetDependency */, ); name = ConnectionKit; productName = Framework; productReference = 79CFD12609F702BE00172CDD /* ConnectionKit.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { LastTestingUpgradeCheck = 0600; LastUpgradeCheck = 0700; TargetAttributes = { 279117FA178B5C64006BF857 = { TestTargetID = 79CFD12509F702BE00172CDD; }; }; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Connection" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 1; knownRegions = ( English, en, ja, fr, de, zh_TW, da, it, es, zh_CN, pt_BR, ); mainGroup = 29B97314FDCFA39411CA2CEA /* Connection */; projectDirPath = ""; projectReferences = ( { ProductGroup = 220526E3165E96AA00A2BBC9 /* Products */; ProjectRef = 27BFFEDA15027F4100EFA319 /* CURLHandle.xcodeproj */; }, { ProductGroup = 220526EC165E97B400A2BBC9 /* Products */; ProjectRef = 27448C2414580F7500EB086F /* DAVKit.xcodeproj */; }, ); projectRoot = ""; targets = ( 79CFD12509F702BE00172CDD /* ConnectionKit */, 279117EA178B5C64006BF857 /* ConnectionKitUI */, 220AD7971509019B00748655 /* UnitTests */, 27F3372716BC1FB100E70511 /* AuthTester */, 22C9CF971703327A004610FE /* ProfilingTester */, 279117FA178B5C64006BF857 /* ConnectionKitUITests */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ 220526E8165E96AA00A2BBC9 /* CURLHandle.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = CURLHandle.framework; remoteRef = 220526E7165E96AA00A2BBC9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 220526F1165E97B400A2BBC9 /* DAVKit.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = DAVKit.framework; remoteRef = 220526F0165E97B400A2BBC9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 220526F3165E97B400A2BBC9 /* Tests.octest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = Tests.octest; remoteRef = 220526F2165E97B400A2BBC9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 22AC1C0E174297C500AB09E1 /* libDAVKit.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libDAVKit.a; remoteRef = 22AC1C0D174297C500AB09E1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 22AC1C10174297C500AB09E1 /* Tests iOS.octest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = "Tests iOS.octest"; remoteRef = 22AC1C0F174297C500AB09E1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 22C9CFFB1703AD4D004610FE /* Standalone Test */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = "Standalone Test"; remoteRef = 22C9CFFA1703AD4D004610FE /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 279117E1178B5A86006BF857 /* CURLHandleTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = CURLHandleTests.xctest; remoteRef = 279117E0178B5A86006BF857 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ 220AD7951509019B00748655 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 27CFEC7218E73526007158A4 /* URLs.testdata in Resources */, 22662EF3165D2EEC005FCC4A /* ftp.json in Resources */, 22662EF4165D2EEC005FCC4A /* webdav.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 22C9CF961703327A004610FE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 22C9CF9F1703327A004610FE /* InfoPlist.strings in Resources */, 22C9CFA51703327A004610FE /* Credits.rtf in Resources */, 22C9CFAB1703327A004610FE /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 279117E9178B5C64006BF857 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 279117F2178B5C64006BF857 /* InfoPlist.strings in Resources */, 27911838178B5D79006BF857 /* CK2NewFolderWindow.xib in Resources */, 27911837178B5D79006BF857 /* CK2FilePreview.xib in Resources */, 27911839178B5D79006BF857 /* CK2PathFieldWindow.xib in Resources */, 27911836178B5D79006BF857 /* CK2OpenPanel.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 279117F9178B5C64006BF857 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 27F3372616BC1FB100E70511 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 27F3373116BC1FB100E70511 /* InfoPlist.strings in Resources */, 27F3373716BC1FB100E70511 /* Credits.rtf in Resources */, 27F3373D16BC1FB100E70511 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 79CFD12209F702BE00172CDD /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 797CC8C709F861770063FF9B /* Localizable.strings in Resources */, 791E83050B0EDAC90060E5FC /* error.png in Resources */, 791E83060B0EDAC90060E5FC /* finished.png in Resources */, 7978FC150B117D7C0048168B /* bookmark.tif in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 220AD7961509019B00748655 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; showEnvVarsInLog = 0; }; 797CBBEB09F854B60063FF9B /* Generate Localizable.strings */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; files = ( ); inputPaths = ( ); name = "Generate Localizable.strings"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; shellScript = "# Skip localization for now\nexit 0\n\necho \"Generating Localizable.strings ...\"\n\ncd \"${SRCROOT}\"\ngenstrings -littleEndian -q -u -s LocalizedStringInConnectionKitBundle -o en.lproj *.m\n\n# Convert UTF-16 into UTF-8 with BOM\necho -ne '\\xEF\\xBB\\xBF' > en.lproj/Localizable.strings.utf8\niconv -f UTF-16 -t UTF-8 en.lproj/Localizable.strings >> en.lproj/Localizable.strings.utf8\nmv en.lproj/Localizable.strings.utf8 en.lproj/Localizable.strings\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 220AD7931509019B00748655 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 22CC56F91509048E00F94154 /* PathTests.m in Sources */, 22F6D112165A8A2200443CC9 /* URLTests.m in Sources */, 22662EE4165D1EE4005FCC4A /* BaseCKTests.m in Sources */, 220526BA165E8C9D00A2BBC9 /* FTPAuthenticationTests.m in Sources */, 224AB37D166E500E0066B1C6 /* KMSConnection.m in Sources */, 224AB37E166E500E0066B1C6 /* KMSListener.m in Sources */, 224AB37F166E500E0066B1C6 /* KMSRegExResponder.m in Sources */, 224AB380166E500E0066B1C6 /* KMSResponder.m in Sources */, 224AB381166E500E0066B1C6 /* KMSResponseCollection.m in Sources */, 224AB382166E500E0066B1C6 /* KMSServer.m in Sources */, 224AB387166E501C0066B1C6 /* KMSCollectionTests.m in Sources */, 224AB38A166E52680066B1C6 /* KMSTestCase.m in Sources */, 224AB38C166E587F0066B1C6 /* KMSManualTests.m in Sources */, 22407D54166F9A6100E1EAD4 /* BaseCKProtocolTests.m in Sources */, 22FEB6691680818800BB778B /* KMSTranscriptEntry.m in Sources */, 225FCA3816B046F800A9F5AE /* CKUploaderTests.m in Sources */, 2246AF6516B99987001D39D9 /* KMSSendStringCommand.m in Sources */, 2246AF6616B99987001D39D9 /* KMSSendDataCommand.m in Sources */, 2246AF6716B99987001D39D9 /* KMSSendServerDataCommand.m in Sources */, 2246AF6816B99987001D39D9 /* KMSPauseCommand.m in Sources */, 2246AF6916B99987001D39D9 /* KMSCommand.m in Sources */, 2246AF6A16B99987001D39D9 /* KMSCloseCommand.m in Sources */, 278CFE1316BADE030018A14B /* URLCanonicalizationTests.m in Sources */, 27999BAB170B4B9800A54BEE /* CK2FileOperationWithTestSupport.m in Sources */, 27999BB7170B4BA200A54BEE /* CK2FileManagerWithTestSupport.m in Sources */, 220C11961715C5E20086F199 /* ErrorTests.m in Sources */, 22AC1C141742980000AB09E1 /* FTPTests.m in Sources */, 22AC1C151742980000AB09E1 /* SFTPTests.m in Sources */, 22AC1C161742980000AB09E1 /* WebDAVTests.m in Sources */, 22AC1C1917429F8200AB09E1 /* URLAppendingTests.m in Sources */, 22AC1C1B17429FAA00AB09E1 /* URLDirectoryTests.m in Sources */, 2298E4AB17442272005A4160 /* FileTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 22C9CF941703327A004610FE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 22C9CFA11703327A004610FE /* main.m in Sources */, 22C9CFA81703327A004610FE /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 279117E6178B5C64006BF857 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2791182F178B5D59006BF857 /* CK2FileSizeFormatter.m in Sources */, 27911829178B5D59006BF857 /* CK2IconView.m in Sources */, 2791182D178B5D59006BF857 /* CK2BrowserPreviewView.m in Sources */, 27911823178B5D59006BF857 /* CK2PathFieldWindowController.m in Sources */, 2791182B178B5D59006BF857 /* CK2BrowserPreviewController.m in Sources */, 27911821178B5D59006BF857 /* CK2PathControl.m in Sources */, 27911833178B5D59006BF857 /* NSURL+CK2OpenPanel.m in Sources */, 2791181F178B5D59006BF857 /* CK2FileCell.m in Sources */, 27911827178B5D59006BF857 /* CK2IconItemView.m in Sources */, 27911835178B5D59006BF857 /* NSImage+CK2OpenPanel.m in Sources */, 27911811178B5D3E006BF857 /* CK2OpenPanel.m in Sources */, 27911812178B5D4B006BF857 /* CK2OpenPanelController.m in Sources */, 27911817178B5D59006BF857 /* CK2OpenPanelIconViewController.m in Sources */, 27911819178B5D59006BF857 /* CK2OpenPanelListViewController.m in Sources */, 2791181B178B5D59006BF857 /* CK2OpenPanelColumnViewController.m in Sources */, 27911815178B5D59006BF857 /* CK2OpenPanelViewController.m in Sources */, 2791181D178B5D59006BF857 /* CK2NewFolderWindowController.m in Sources */, 27911825178B5D59006BF857 /* CK2IconViewItem.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 279117F7178B5C64006BF857 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 27F3372416BC1FB100E70511 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 27F3373316BC1FB100E70511 /* main.m in Sources */, 27F3373A16BC1FB100E70511 /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 79CFD12309F702BE00172CDD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 7983DF8F0B0C0FAC00F5078E /* CKTransferRecord.m in Sources */, 798313CA0B0D67E000F5078E /* CKTransferProgressCell.m in Sources */, 27D03B431471787000FEA588 /* CKUploader.m in Sources */, 2743E80A1622E47600019979 /* CK2FileManager.m in Sources */, 27ADC5781AC0CD7D0085C7F7 /* CK2CurlTransferStackManager.m in Sources */, 2790A82A1627636E000C9D9F /* CK2Protocol.m in Sources */, 2790A94916278F1D000C9D9F /* CK2FTPProtocol.m in Sources */, 27F394F6162C162900944F43 /* CK2SFTPProtocol.m in Sources */, 27431CA11630381D00F6FB58 /* CK2FileProtocol.m in Sources */, 2288CD76165A99FC00F34E24 /* CK2WebDAVProtocol.m in Sources */, 27A2072C1671634800D8284D /* CK2CURLBasedProtocol.m in Sources */, 278D8B7A167FF35D00622468 /* CK2Authentication.m in Sources */, 27993E8516FCB30D008DC1B0 /* CK2FileOperation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 220526FD165EA20600A2BBC9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = CURLHandle; targetProxy = 220526FC165EA20600A2BBC9 /* PBXContainerItemProxy */; }; 220AD7AE1509021A00748655 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 79CFD12509F702BE00172CDD /* ConnectionKit */; targetProxy = 220AD7AD1509021A00748655 /* PBXContainerItemProxy */; }; 2218212A170209D300584589 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = CURLHandleTests; targetProxy = 22182129170209D300584589 /* PBXContainerItemProxy */; }; 27911800178B5C64006BF857 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 279117EA178B5C64006BF857 /* ConnectionKitUI */; targetProxy = 279117FF178B5C64006BF857 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 22C9CF9D1703327A004610FE /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 22C9CF9E1703327A004610FE /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; 22C9CFA31703327A004610FE /* Credits.rtf */ = { isa = PBXVariantGroup; children = ( 22C9CFA41703327A004610FE /* en */, ); name = Credits.rtf; sourceTree = ""; }; 22C9CFA91703327A004610FE /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 22C9CFAA1703327A004610FE /* en */, ); name = MainMenu.xib; sourceTree = ""; }; 279117F0178B5C64006BF857 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 279117F1178B5C64006BF857 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; 27F3372F16BC1FB100E70511 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 27F3373016BC1FB100E70511 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; 27F3373516BC1FB100E70511 /* Credits.rtf */ = { isa = PBXVariantGroup; children = ( 27F3373616BC1FB100E70511 /* en */, ); name = Credits.rtf; sourceTree = ""; }; 27F3373B16BC1FB100E70511 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 27F3373C16BC1FB100E70511 /* en */, ); name = MainMenu.xib; sourceTree = ""; }; 323B7349170E253900219F9A /* CK2OpenPanel.xib */ = { isa = PBXVariantGroup; children = ( 323B7348170E253900219F9A /* en */, ); name = CK2OpenPanel.xib; sourceTree = ""; }; 323B7356170E254800219F9A /* CK2FilePreview.xib */ = { isa = PBXVariantGroup; children = ( 323B7355170E254800219F9A /* en */, ); name = CK2FilePreview.xib; sourceTree = ""; }; 323B7359170E255200219F9A /* CK2NewFolderWindow.xib */ = { isa = PBXVariantGroup; children = ( 323B7358170E255200219F9A /* en */, ); name = CK2NewFolderWindow.xib; sourceTree = ""; }; 323B735C170E256000219F9A /* CK2PathFieldWindow.xib */ = { isa = PBXVariantGroup; children = ( 323B735B170E256000219F9A /* en */, ); name = CK2PathFieldWindow.xib; sourceTree = ""; }; 797CC8C509F861770063FF9B /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( 797CC8C609F861770063FF9B /* en */, 3C2D8EC10A63FF31008FE1B0 /* zh_TW */, 3C2D8EC20A63FF3B008FE1B0 /* fr */, 3C2D8EC30A63FF41008FE1B0 /* da */, CEA9AFD30A64224100855897 /* ja */, CEB563850A7AB7070081179A /* de */, CE1AD3DF0A7E845A0083C01E /* it */, CE55A6620AD194740091C8AE /* zh_CN */, CE953A04102BACE70066C08F /* pt_BR */, CE94B9101463124A00F90408 /* es */, ); name = Localizable.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 220AD7A81509019B00748655 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = UnitTests/UnitTests_Prefix.pch; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; INFOPLIST_FILE = "UnitTests/UnitTest-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "\"@loader_path/../Frameworks\" \"@loader_path/../Frameworks/ConnectionKit.framework/Versions/Current/Frameworks\""; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/CurlHandle/SFTP\"", ); MACOSX_DEPLOYMENT_TARGET = 10.7; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.UnitTest; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 220AD7A91509019B00748655 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = UnitTests/UnitTests_Prefix.pch; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; INFOPLIST_FILE = "UnitTests/UnitTest-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "\"@loader_path/../Frameworks\" \"@loader_path/../Frameworks/ConnectionKit.framework/Versions/Current/Frameworks\""; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/CurlHandle/SFTP\"", ); MACOSX_DEPLOYMENT_TARGET = 10.7; PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.UnitTest; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 22C9CFAC1703327A004610FE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ProfilingTester/ProfilingTester-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; INFOPLIST_FILE = "ProfilingTester/ProfilingTester-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.8; PRODUCT_BUNDLE_IDENTIFIER = "com.karelia.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; 22C9CFAD1703327A004610FE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ProfilingTester/ProfilingTester-Prefix.pch"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; INFOPLIST_FILE = "ProfilingTester/ProfilingTester-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.8; PRODUCT_BUNDLE_IDENTIFIER = "com.karelia.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; 2791180B178B5C64006BF857 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ConnectionKitUI/ConnectionKitUI-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "ConnectionKitUI/ConnectionKitUI-Info.plist"; INSTALL_PATH = "@rpath"; PRODUCT_BUNDLE_IDENTIFIER = "com.karelia.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; }; name = Debug; }; 2791180C178B5C64006BF857 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ConnectionKitUI/ConnectionKitUI-Prefix.pch"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "ConnectionKitUI/ConnectionKitUI-Info.plist"; INSTALL_PATH = "@rpath"; PRODUCT_BUNDLE_IDENTIFIER = "com.karelia.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; }; name = Release; }; 2791180E178B5C64006BF857 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ConnectionKit.framework/Versions/A/ConnectionKit"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", ); GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ConnectionKitUI/ConnectionKitUI-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "ConnectionKitUITests/ConnectionKitUITests-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.8; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; }; name = Debug; }; 2791180F178B5C64006BF857 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ConnectionKit.framework/Versions/A/ConnectionKit"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", ); GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ConnectionKitUI/ConnectionKitUI-Prefix.pch"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "ConnectionKitUITests/ConnectionKitUITests-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.8; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; }; name = Release; }; 27F3373F16BC1FB100E70511 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AuthTester/AuthTester-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; INFOPLIST_FILE = "AuthTester/AuthTester-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.7; PRODUCT_BUNDLE_IDENTIFIER = "com.karelia.connection.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; 27F3374016BC1FB100E70511 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AuthTester/AuthTester-Prefix.pch"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; INFOPLIST_FILE = "AuthTester/AuthTester-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.7; PRODUCT_BUNDLE_IDENTIFIER = "com.karelia.connection.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; 79CFD12909F702BF00172CDD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PFE_FILE_C_DIALECTS = "objective-c c++ objective-c++"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ConnectionKit/Connection_Prefix.pch; HEADER_SEARCH_PATHS = "\"$(SOURCE_ROOT)/CURLHandle/SFTP/libssh2/include\""; INFOPLIST_FILE = "Resources/Framework-Info.plist"; INSTALL_PATH = "@rpath"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", "\"$(SRCROOT)/CURLHandle/SFTP\"", ); LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)\""; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PRODUCT_BUNDLE_IDENTIFIER = com.dlsxtreme.connection; PRODUCT_NAME = ConnectionKit; RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; WARNING_CFLAGS = "-Wdocumentation"; ZERO_LINK = NO; }; name = Debug; }; 79CFD12A09F702BF00172CDD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; DEPLOYMENT_POSTPROCESSING = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PFE_FILE_C_DIALECTS = "objective-c c++ objective-c++"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ConnectionKit/Connection_Prefix.pch; HEADER_SEARCH_PATHS = "\"$(SOURCE_ROOT)/CURLHandle/SFTP/libssh2/include\""; INFOPLIST_FILE = "Resources/Framework-Info.plist"; INSTALL_PATH = "@rpath"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", "\"$(SRCROOT)/CURLHandle/SFTP\"", ); LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)\""; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PRODUCT_BUNDLE_IDENTIFIER = com.dlsxtreme.connection; PRODUCT_NAME = ConnectionKit; RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; WARNING_CFLAGS = "-Wdocumentation"; ZERO_LINK = NO; }; name = Release; }; C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_TESTABILITY = YES; GCC_INCREASE_PRECOMPILED_HEADER_SHARING = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.6; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; C01FCF5008A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_INCREASE_PRECOMPILED_HEADER_SHARING = NO; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.6; SDKROOT = macosx; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 220AD7A71509019B00748655 /* Build configuration list for PBXNativeTarget "UnitTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 220AD7A81509019B00748655 /* Debug */, 220AD7A91509019B00748655 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 22C9CFB91703327A004610FE /* Build configuration list for PBXNativeTarget "ProfilingTester" */ = { isa = XCConfigurationList; buildConfigurations = ( 22C9CFAC1703327A004610FE /* Debug */, 22C9CFAD1703327A004610FE /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2791180A178B5C64006BF857 /* Build configuration list for PBXNativeTarget "ConnectionKitUI" */ = { isa = XCConfigurationList; buildConfigurations = ( 2791180B178B5C64006BF857 /* Debug */, 2791180C178B5C64006BF857 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2791180D178B5C64006BF857 /* Build configuration list for PBXNativeTarget "ConnectionKitUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 2791180E178B5C64006BF857 /* Debug */, 2791180F178B5C64006BF857 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 27F3373E16BC1FB100E70511 /* Build configuration list for PBXNativeTarget "AuthTester" */ = { isa = XCConfigurationList; buildConfigurations = ( 27F3373F16BC1FB100E70511 /* Debug */, 27F3374016BC1FB100E70511 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 79CFD12809F702BF00172CDD /* Build configuration list for PBXNativeTarget "ConnectionKit" */ = { isa = XCConfigurationList; buildConfigurations = ( 79CFD12909F702BF00172CDD /* Debug */, 79CFD12A09F702BF00172CDD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Connection" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4F08A954540054247B /* Debug */, C01FCF5008A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; } ================================================ FILE: Connection.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Connection.xcodeproj/xcshareddata/xcschemes/ConnectionKit.xcscheme ================================================ ================================================ FILE: ConnectionKit/CK2Authentication.h ================================================ // // CK2Authentication.h // Connection // // Created by Mike on 18/12/2012. // // #import #pragma mark SSH Host Fingerprint typedef NS_ENUM(NSInteger, CK2KnownHostMatch) { CK2KnownHostMatchOK, CK2KnownHostMatchMismatch, CK2KnownHostMatchMissing, }; typedef NS_ENUM(NSInteger, CK2KnownHostType) { CK2KnownHostTypeUnknown, CK2KnownHostTypeRSA1, CK2KnownHostTypeRSA, CK2KnownHostTypeDSS, }; @interface NSURLProtectionSpace (CK2SSHHostFingerprint) /** The result of ConnectionKit checking the host's fingerprint against a local known_hosts file. Upon connecting to an SSH server, ConnectionKit compares its host fingerprint against the contents of the local known_hosts file. An authentication challenge is then issued to the delegate asking it how it would like to handle the result of that check. This method encapsulates that result: - `CK2KnownHostMatchOK` — the server and known_hosts match. - `CK2KnownHostMatchMissing` — the server appears not to have been connected to before, as no entry for it is present in known_hosts. - `CK2KnownHostMatchMismatch` — the server's fingerprint is different to that in the known_hosts file. It is likely the server has been compromised, modified, or replaced @return One of the CK2KnownHostMatch enum. 0 for auth methods other than CK2AuthenticationMethodHostFingerprint. */ - (CK2KnownHostMatch)ck2_knownHostMatch; /** @result The raw data of the server's public SSH key. (`nil` if the `authenticationMethod` is not `CK2AuthenticationMethodHostFingerprint`) */ @property(readonly, copy, getter=ck2_serverPublicKey) NSData *serverPublicKey; /** @result The type of the server's public SSH key. (`CK2KnownHostTypeUnknown` if the `authenticationMethod` is not `CK2AuthenticationMethodHostFingerprint`) */ @property(readonly, getter=ck2_serverKnownHostType) CK2KnownHostType serverKnownHostType; /** @const CK2AuthenticationMethodHostFingerprint @abstract The authentication method used by SSH connections for checking a host's fingerprint */ extern NSString * const CK2AuthenticationMethodHostFingerprint; /** Creates a protection space object to encapsulate the result of checking an SSH server's host fingerprint Generally clients are handed `NSURLProtectionSpace`s by ConnectionKit and have no need to construct their own. So can probably ignore this method :) @param host name of the server being connected to. @param match is one of CK2KnownHostMatch's enumerations that declares the result of the check. @return a protection spaces whose `-authenticationMethod` is `CK2AuthenticationMethodHostFingerprint`. */ + (NSURLProtectionSpace *)ck2_protectionSpaceWithHost:(NSString *)host knownHostMatch:(CK2KnownHostMatch)match publicKey:(NSData *)key type:(CK2KnownHostType)keyType; @end @interface NSURLCredential (CK2SSHHostFingerprint) /** Constructs a credential to encapsulate the outcome of evaluating an SSH server's host fingerprint. @param persistence indicates whether new keys should be added to the known_hosts file @return the credential */ + (NSURLCredential *)ck2_credentialForKnownHostWithPersistence:(NSURLCredentialPersistence)persistence; @end #pragma mark SSH Public Key Auth @interface NSURLCredential (CK2SSHPublicKey) /** Constructs a credential to encapsulate the use of public key authentication @param user to log in as @param publicKey is the location of the public key file. If using OpenSSL (usually the case on OS X), pass nil here to have the public key automatically derived from the private key @param privateKey is the location of the private key file. Pass nil to use SSH-Agent instead (note: fails when sandboxed) @param passphrase is used to decrypt a passphrase-protected private key file @param persistence specifies whether to store passphrase in the keychain or not @return the credential */ + (NSURLCredential *)ck2_credentialWithUser:(NSString *)user publicKeyURL:(NSURL *)publicKey privateKeyURL:(NSURL *)privateKey password:(NSString *)passphrase persistence:(NSURLCredentialPersistence)persistence; @end ================================================ FILE: ConnectionKit/CK2Authentication.m ================================================ // // CK2Authentication.m // Connection // // Created by Mike on 18/12/2012. // // #import "CK2Authentication.h" #import #pragma mark SSH Host Fingerprint @interface CK2SSHHostFingerprintProtectionSpace : NSURLProtectionSpace { CK2KnownHostMatch _match; NSData *_publicKey; CK2KnownHostType _publicKeyType; } @end @implementation CK2SSHHostFingerprintProtectionSpace - initWithHost:(NSString *)host match:(enum curl_khmatch)match publicKey:(NSData *)key type:(CK2KnownHostType)keyType; { if (self = [self initWithHost:host port:0 protocol:@"ssh" realm:nil authenticationMethod:CK2AuthenticationMethodHostFingerprint]) { _match = match; _publicKey = [key copy]; _publicKeyType = keyType; } return self; } - (void)dealloc; { [_publicKey release]; [super dealloc]; } // Force it to return correct thing - (NSString *)authenticationMethod; { return CK2AuthenticationMethodHostFingerprint; } - (NSString *)protocol; { return @"ssh"; } - (CK2KnownHostMatch)ck2_knownHostMatch; { return _match; } - (NSData *)ck2_serverPublicKey; { return _publicKey; } - (CK2KnownHostType)ck2_serverKnownHostType; { return _publicKeyType; } // Make sure super doesn't create an actual copy - (id)copyWithZone:(NSZone *)zone; { return [self retain]; } @end @implementation NSURLProtectionSpace (CK2SSHHostFingerprint) - (CK2KnownHostMatch)ck2_knownHostMatch; { return 0; } - (NSData *)ck2_serverPublicKey; { return nil; } - (CK2KnownHostType)ck2_serverKnownHostType; { return CK2KnownHostTypeUnknown; } NSString * const CK2AuthenticationMethodHostFingerprint = @"CK2AuthenticationMethodHostFingerprint"; + (NSURLProtectionSpace *)ck2_protectionSpaceWithHost:(NSString *)host knownHostMatch:(CK2KnownHostMatch)match publicKey:(NSData *)key type:(CK2KnownHostType)keyType; { return [[[CK2SSHHostFingerprintProtectionSpace alloc] initWithHost:host match:match publicKey:key type:keyType] autorelease]; } @end @implementation NSURLCredential (CK2SSHHostFingerprint) + (NSURLCredential *)ck2_credentialForKnownHostWithPersistence:(NSURLCredentialPersistence)persistence; { return [self credentialWithUser:nil password:nil persistence:persistence]; } @end #pragma mark - @interface NSURLCredential (SFTPWrapperSuppliedMethods) + (NSURLCredential *)ck2_credentialWithUser:(NSString *)user publicKeyURL:(NSURL *)publicKey privateKeyURL:(NSURL *)privateKey; - (NSURLCredential *)ck2_credentialWithPassword:(NSString *)password persistence:(NSURLCredentialPersistence)persistence; @end @implementation NSURLCredential (CK2SSHPublicKey) + (NSURLCredential *)ck2_credentialWithUser:(NSString *)user publicKeyURL:(NSURL *)publicKey privateKeyURL:(NSURL *)privateKey password:(NSString *)password persistence:(NSURLCredentialPersistence)persistence; { NSURLCredential *result = [self ck2_credentialWithUser:user publicKeyURL:publicKey privateKeyURL:privateKey]; result = [result ck2_credentialWithPassword:password persistence:persistence]; return result; } @end ================================================ FILE: ConnectionKit/CK2BrowserPreviewController.h ================================================ // // CK2BrowserPreviewControllerViewController.h // CKTest // // Created by Paul Kim on 12/26/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // #import @interface CK2BrowserPreviewController : NSViewController { } - (id)init; @end ================================================ FILE: ConnectionKit/CK2BrowserPreviewController.m ================================================ // // CK2BrowserPreviewControllerViewController.m // CKTest // // Created by Paul Kim on 12/26/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // #import "CK2BrowserPreviewController.h" #import "CK2BrowserPreviewView.h" #import "NSURL+CK2OpenPanel.h" @implementation CK2BrowserPreviewController - (id)init; { if ((self = [super initWithNibName:@"CK2FilePreview" bundle:[NSBundle bundleForClass:[self class]]]) != nil) { } return self; } - (void)loadView { [super loadView]; } - (void)setRepresentedObject:(id)representedObject { [super setRepresentedObject:representedObject]; [(CK2BrowserPreviewView *)[self view] setURL:representedObject]; } @end ================================================ FILE: ConnectionKit/CK2BrowserPreviewView.h ================================================ // // CK2BrowserPreviewView.h // Connection // // Created by Paul Kim on 1/18/13. // // #import @interface CK2BrowserPreviewView : NSView { IBOutlet NSImageView *_iconView; IBOutlet NSTextField *_nameField; IBOutlet NSTextField *_kindField; IBOutlet NSTextField *_sizeField; IBOutlet NSTextField *_dateModifiedField; IBOutlet NSTextField *_nameLabel; IBOutlet NSTextField *_kindLabel; IBOutlet NSTextField *_sizeLabel; IBOutlet NSTextField *_dateModifiedLabel; NSRect _separatorRect; NSGradient *_separatorGradient; } - (void)setURL:(NSURL *)url; @end ================================================ FILE: ConnectionKit/CK2BrowserPreviewView.m ================================================ // // CK2BrowserPreviewView.m // Connection // // Created by Paul Kim on 1/18/13. // // #import "CK2BrowserPreviewView.h" #import "NSURL+CK2OpenPanel.h" #define MARGIN 24.0 #define LABELS_TOP_MARGIN 10.0 #define LABELS_LEFT_MARGIN 20.0 #define LABELS_RIGHT_MARGIN 10.0 @implementation CK2BrowserPreviewView - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [_separatorGradient release]; [super dealloc]; } - (void)drawRect:(NSRect)dirtyRect { if (_separatorGradient == nil) { _separatorGradient = [[NSGradient alloc] initWithColorsAndLocations: [NSColor colorWithCalibratedWhite:1.0 alpha:0.0], 0.0, [NSColor colorWithCalibratedWhite:0.83 alpha:1.0], 0.25, [NSColor colorWithCalibratedWhite:0.83 alpha:1.0], 0.75, [NSColor colorWithCalibratedWhite:1.0 alpha:0.0], 1.0, nil]; } [_separatorGradient drawInRect:_separatorRect angle:0.0]; } - (void)setURL:(NSURL *)url { if (url != nil) { [_iconView setObjectValue:[url ck2_icon]]; [_nameField setStringValue:[url ck2_displayName]]; [_sizeField setObjectValue:[url ck2_size]]; [_kindField setStringValue:[url ck2_kind]]; [_dateModifiedField setObjectValue:[url ck2_dateModified]]; } else { [_iconView setObjectValue:nil]; [_nameField setStringValue:@""]; [_sizeField setObjectValue:@""]; [_kindField setStringValue:@""]; [_dateModifiedField setObjectValue:@""]; } } - (void)setFrame:(NSRect)frameRect { [super setFrame:frameRect]; [self tile]; } - (void)tileLabelField:(NSTextField *)labelField valueField:(NSTextField *)valueField { NSRect bounds, rect; CGFloat xSep; bounds = [self bounds]; xSep = NSWidth(bounds) * .4; rect = [labelField frame]; rect.origin.x = MAX(xSep - NSWidth(rect), LABELS_LEFT_MARGIN); [labelField setFrame:rect]; rect = [valueField frame]; rect.origin.x = xSep; rect.size.width = NSMaxX(bounds) - LABELS_RIGHT_MARGIN - rect.origin.x; [valueField setFrame:rect]; } - (void)tileLabels { NSRect rect; NSSize size; CGFloat y, height; [self tileLabelField:_dateModifiedLabel valueField:_dateModifiedField]; [self tileLabelField:_sizeLabel valueField:_sizeField]; [self tileLabelField:_kindLabel valueField:_kindField]; [self tileLabelField:_nameLabel valueField:_nameField]; // Allow name field to take multiple lines (up to 3) height = NSHeight([_nameLabel frame]); rect = [_nameField frame]; rect.size.height = CGFLOAT_MAX; size = [[_nameField cell] cellSizeForBounds:rect]; rect.size.height = MAX(size.height, height); rect.size.height = MIN(rect.size.height, 3 * height); [_nameField setFrame:rect]; y = NSMaxY(rect); rect = [_nameLabel frame]; rect.origin.y = y - NSHeight(rect); [_nameLabel setFrame:rect]; } - (void)tile { NSRect bounds, iconFrame; bounds = [self bounds]; [self tileLabels]; _separatorRect.size.width = NSWidth(bounds) * .8; _separatorRect.size.height = 1.0; _separatorRect.origin.x = NSMinX(bounds) + (NSWidth(bounds) - NSWidth(_separatorRect)) / 2.0; _separatorRect.origin.y = NSMaxY([_nameLabel frame]) + LABELS_TOP_MARGIN; iconFrame.origin = NSZeroPoint; // Unnecessary but added to shut up the static analyzer iconFrame.size.width = MIN(NSWidth(bounds) - 2 * MARGIN, NSHeight(bounds) - NSMaxY(_separatorRect) - 2 * MARGIN); iconFrame.size.height = NSWidth(iconFrame); iconFrame.origin.x = NSMinX(bounds) + (NSWidth(bounds) - NSWidth(iconFrame)) / 2.0; iconFrame.origin.y = NSMaxY(_separatorRect) + MARGIN; [_iconView setFrame:iconFrame]; [self setNeedsDisplay:YES]; } - (void)superviewFrameChanged:(NSNotification *)notification { [self setFrame:[[self superview] bounds]]; } - (void)removeFromSuperview { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:[self superview]]; [super removeFromSuperview]; } - (void)viewDidMoveToSuperview { NSView *superview; superview = [self superview]; if (superview != nil) { [superview setPostsFrameChangedNotifications:YES]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(superviewFrameChanged:) name:NSViewFrameDidChangeNotification object:superview]; [self superviewFrameChanged:nil]; } } @end ================================================ FILE: ConnectionKit/CK2CURLBasedProtocol.h ================================================ // // CK2CURLBasedProtocol.h // Connection // // Created by Mike on 06/12/2012. // // #import "CK2Protocol.h" #import @class CK2RemoteURL; @interface CK2CURLBasedProtocol : CK2Protocol { CURLTransfer *_transfer; BOOL _cancelled; NSString *_user; void (^_completionHandler)(NSError *error); void (^_dataBlock)(NSData *data); int64_t _totalBytesWritten; int64_t _totalBytesExpectedToWrite; } #pragma mark Initialisation // In any of these methods, if completion handler is nil, the standard behaviour of reporting to the client will be performed - (id)initWithRequest:(NSURLRequest *)request client:(id )client completionHandler:(void (^)(NSError *))handler; - (id)initWithRequest:(NSURLRequest *)request client:(id )client dataHandler:(void (^)(NSData *))dataBlock completionHandler:(void (^)(NSError *))handler; - (id)initWithCustomCommands:(NSArray *)commands request:(NSURLRequest *)request createIntermediateDirectories:(BOOL)createIntermediates client:(id )client completionHandler:(void (^)(NSError *error))handler; // Already handled for you; can override in a subclass if you want - (id)initForEnumeratingDirectoryWithRequest:(NSURLRequest *)request includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask client:(id)client; - (id)initForCreatingFileWithRequest:(NSURLRequest *)request size:(int64_t)size withIntermediateDirectories:(BOOL)createIntermediates client:(id)client completionHandler:(void (^)(NSError *error))handler; #pragma mark Loading // If the protocol requires authentication, override -start to fire off an authentication challenge to the client. When a response is received to the challenge, CK2CURLBasedProtocol automatically handles it to start up the handle/request - (void)start; - (void)startWithProtectionSpace:(NSURLProtectionSpace *)protectionSpace; - (void)startWithRequest:(NSURLRequest *)request credential:(NSURLCredential *)credential; #pragma mark Progress @property(nonatomic, readonly) int64_t totalBytesWritten; @property(nonatomic, readonly) int64_t totalBytesExpectedToWrite; #pragma mark Customization + (BOOL)usesMultiHandle; // defaults to YES. Subclasses can override to be NO and fall back to the old synchronous "easy" backend - (void)popCompletionHandlerByExecutingWithError:(NSError *)error; - (void)reportToProtocolWithError:(NSError*)error; @end ================================================ FILE: ConnectionKit/CK2CURLBasedProtocol.m ================================================ // // CK2CURLBasedProtocol.m // Connection // // Created by Mike on 06/12/2012. // // #import "CK2CURLBasedProtocol.h" #import "CK2CurlTransferStackManager.h" #import #import #import // for NSImage #import @implementation CK2CURLBasedProtocol - (id)initWithRequest:(NSURLRequest *)request client:(id )client completionHandler:(void (^)(NSError *))handler; { if (self = [self initWithRequest:request client:client]) { [self pushCompletionHandler:^(NSError *error) { // Update cache if (!error) { [self updateHomeDirectoryStore]; } // Report the completion to handler or protocol if (handler) { handler(error); } else { [self reportToProtocolWithError:error]; } // Clean up transfer [_transfer release]; _transfer = nil; }]; } return self; } - (id)initWithRequest:(NSURLRequest *)request client:(id )client dataHandler:(void (^)(NSData *))dataBlock completionHandler:(void (^)(NSError *))handler { if (self = [self initWithRequest:request client:client completionHandler:handler]) { _dataBlock = [dataBlock copy]; } return self; } - (id)initWithCustomCommands:(NSArray *)commands request:(NSURLRequest *)sourceRequest createIntermediateDirectories:(BOOL)createIntermediates client:(id )client completionHandler:(void (^)(NSError *error))handler; { // Navigate to the directory // @"HEAD" => CURLOPT_NOBODY, which stops libcurl from trying to list the directory's contents // If the connection is already at that directory then curl wisely does nothing NSMutableURLRequest *request = [sourceRequest mutableCopy]; [request setHTTPMethod:@"HEAD"]; [request curl_setCreateIntermediateDirectories:createIntermediates]; // Custom commands once we're in the correct directory // CURLOPT_PREQUOTE does much the same thing, but sometimes runs the command twice in my testing [request curl_setPostTransferCommands:commands]; self = [self initWithRequest:request client:client dataHandler:nil completionHandler:handler]; [request release]; return self; } - (id)initForCreatingFileWithRequest:(NSURLRequest *)request size:(int64_t)size withIntermediateDirectories:(BOOL)createIntermediates client:(id)client completionHandler:(void (^)(NSError *error))handler; { if ([request curl_createIntermediateDirectories] != createIntermediates) { NSMutableURLRequest *mutableRequest = [[request mutableCopy] autorelease]; [mutableRequest curl_setCreateIntermediateDirectories:createIntermediates]; request = mutableRequest; } if (self = [self initWithRequest:request client:client completionHandler:handler]) { _totalBytesExpectedToWrite = size; } return self; } #pragma mark Directory Enumeration - (BOOL)shouldEnumerateFilename:(NSString *)name options:(NSDirectoryEnumerationOptions)mask; { // SFTP and some FTP servers report . and .. which we don't care about if ([name isEqualToString:@"."] || [name isEqualToString:@".."]) { return NO; } if ((mask & NSDirectoryEnumerationSkipsHiddenFiles) && [name hasPrefix:@"."]) return NO; return YES; } - (NSError*)processData:(NSMutableData*)data request:(NSURLRequest *)request url:(NSURL*)directoryURL path:(NSString*)directoryPath keys:(NSArray*)keys options:(NSDirectoryEnumerationOptions)mask { NSError* result = nil; // Process the data to make a directory listing while (1) { CFDictionaryRef parsedDict = NULL; CFIndex bytesConsumed = CFFTPCreateParsedResourceListing(NULL, [data bytes], [data length], &parsedDict); if (bytesConsumed > 0) { [data replaceBytesInRange:NSMakeRange(0, bytesConsumed) withBytes:NULL length:0]; // Make sure CFFTPCreateParsedResourceListing was able to properly // parse the incoming data if (parsedDict) { NSString *name = [self pathForKey:kCFFTPResourceName inDictionary:parsedDict]; if ([self shouldEnumerateFilename:name options:mask]) { NSNumber *type = CFDictionaryGetValue(parsedDict, kCFFTPResourceType); BOOL isDirectory = [type intValue] == DT_DIR; NSURL *aURL = [directoryURL URLByAppendingPathComponent:name]; if (isDirectory && !CFURLHasDirectoryPath((CFURLRef)aURL)) { aURL = [aURL URLByAppendingPathComponent:@""]; // http://www.mikeabdullah.net/guaranteeing-directory-urls.html } // Fill in requested keys as best we can NSArray *keysToFill = (keys ? keys : self.class.defaultPropertyKeys); for (NSString *aKey in keysToFill) { if ([aKey isEqualToString:NSURLContentModificationDateKey]) { [CK2FileManager setTemporaryResourceValue:CFDictionaryGetValue(parsedDict, kCFFTPResourceModDate) forKey:aKey inURL:aURL]; } else if ([aKey isEqualToString:NSURLIsDirectoryKey]) { [CK2FileManager setTemporaryResourceValue:@(isDirectory) forKey:aKey inURL:aURL]; } else if ([aKey isEqualToString:NSURLIsHiddenKey]) { [CK2FileManager setTemporaryResourceValue:@([name hasPrefix:@"."]) forKey:aKey inURL:aURL]; } else if ([aKey isEqualToString:NSURLIsRegularFileKey]) { [CK2FileManager setTemporaryResourceValue:@([type intValue] == DT_REG) forKey:aKey inURL:aURL]; } else if ([aKey isEqualToString:NSURLIsSymbolicLinkKey]) { [CK2FileManager setTemporaryResourceValue:@([type intValue] == DT_LNK) forKey:aKey inURL:aURL]; } else if ([aKey isEqualToString:NSURLLocalizedTypeDescriptionKey]) { // Could guess from extension } else if ([aKey isEqualToString:NSURLNameKey]) { [CK2FileManager setTemporaryResourceValue:name forKey:aKey inURL:aURL]; } else if ([aKey isEqualToString:NSURLParentDirectoryURLKey]) { [CK2FileManager setTemporaryResourceValue:directoryPath forKey:NSURLParentDirectoryURLKey inURL:aURL]; } else if ([aKey isEqualToString:NSURLTypeIdentifierKey]) { // Guess from symlink, extension, and directory if ([type intValue] == DT_LNK) { [CK2FileManager setTemporaryResourceValue:(NSString *)kUTTypeSymLink forKey:aKey inURL:aURL]; } else { NSString *extension = [name pathExtension]; if ([extension length]) { CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)extension, (isDirectory ? kUTTypeDirectory : kUTTypeData)); [CK2FileManager setTemporaryResourceValue:(NSString *)type forKey:aKey inURL:aURL]; CFRelease(type); } else { [CK2FileManager setTemporaryResourceValue:(NSString *)kUTTypeData forKey:aKey inURL:aURL]; } } } else if ([aKey isEqualToString:NSURLFileSizeKey]) { [CK2FileManager setTemporaryResourceValue:CFDictionaryGetValue(parsedDict, kCFFTPResourceSize) forKey:aKey inURL:aURL]; } else if ([aKey isEqualToString:CK2URLSymbolicLinkDestinationKey]) { NSString *path = [self pathForKey:kCFFTPResourceLink inDictionary:parsedDict]; if ([path length]) { // Servers in my experience hand include a trailing slash to indicate if the target is a directory // Could generate a CK2RemoteURL instead so as to explicitly mark it as a directory, but that seems unecessary for now // According to the original CKConnectionOpenPanel source, some servers use a backslash instead. I don't know what though – Windows based ones? If so, do they use backslashes for all path components? [CK2FileManager setTemporaryResourceValue:[self.class URLWithPath:path relativeToURL:directoryURL] forKey:aKey inURL:aURL]; } } else if (&NSURLFileResourceTypeKey && // not available till 10.7 [aKey isEqualToString:NSURLFileResourceTypeKey]) { NSString *typeValue; switch ([type integerValue]) { case DT_CHR: typeValue = NSURLFileResourceTypeCharacterSpecial; break; case DT_DIR: typeValue = NSURLFileResourceTypeDirectory; break; case DT_BLK: typeValue = NSURLFileResourceTypeBlockSpecial; break; case DT_REG: typeValue = NSURLFileResourceTypeRegular; break; case DT_LNK: typeValue = NSURLFileResourceTypeSymbolicLink; break; case DT_SOCK: typeValue = NSURLFileResourceTypeSocket; break; default: typeValue = NSURLFileResourceTypeUnknown; } [CK2FileManager setTemporaryResourceValue:typeValue forKey:aKey inURL:aURL]; } else if (&NSURLFileSecurityKey && [aKey isEqualToString:NSURLFileSecurityKey]) { CFFileSecurityRef security = CFFileSecurityCreate(NULL); NSNumber *mode = CFDictionaryGetValue(parsedDict, kCFFTPResourceMode); if (CFFileSecuritySetMode(security, mode.unsignedShortValue)) { [CK2FileManager setTemporaryResourceValue:(NSFileSecurity *)security forKey:aKey inURL:aURL]; } CFRelease(security); } } [self.client protocol:self didDiscoverItemAtURL:aURL]; } CFRelease(parsedDict); } } else if (bytesConsumed < 0) { // error! NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys: [request URL], NSURLErrorFailingURLErrorKey, [[request URL] absoluteString], NSURLErrorFailingURLStringErrorKey, nil]; result = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotParseResponse userInfo:userInfo]; [userInfo release]; break; } else { break; } } return result; } // Retrieves the path/filename for a given key, and then tries to take into account tricky encoding issues // https://github.com/karelia/ConnectionKit/issues/41 - (NSString *)pathForKey:(CFStringRef)key inDictionary:(CFDictionaryRef)dictionary; { NSString *result = CFDictionaryGetValue(dictionary, key); // For strings which fall outside of ASCII, hope that they're UTF-8 if (![result canBeConvertedToEncoding:NSASCIIStringEncoding]) { NSData *source = [result dataUsingEncoding:NSMacOSRomanStringEncoding]; // technically, this is a little dodgy. -dataUsingEncoding: could generate some sort of BOM, but I don't believe MacRoman has such a concept so we're safe for now if (source) { NSString *utf8 = [[NSString alloc] initWithData:source encoding:NSUTF8StringEncoding]; if (utf8) { result = [utf8 autorelease]; } } } return result; } - (id)initForEnumeratingDirectoryWithRequest:(NSURLRequest *)request includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask client:(id)client; { request = [[self class] newRequestWithRequest:request isDirectory:YES]; NSMutableData *totalData = [[NSMutableData alloc] init]; self = [self initWithRequest:request client:client dataHandler:^(NSData *data) { [totalData appendData:data]; } completionHandler:^(NSError *error) { if (error) { [client protocol:self didCompleteWithError:error]; } else { // Correct relative paths if we can NSURL *directoryURL = request.URL; NSString *directoryPath = [self.class pathOfURLRelativeToHomeDirectory:directoryURL]; NSURL *home = [self.class homeDirectoryURLForServerAtURL:directoryURL]; if (home && ![directoryPath isAbsolutePath]) { if (directoryPath.length && ![directoryPath hasSuffix:@"/"]) directoryPath = [directoryPath stringByAppendingString:@"/"]; directoryURL = [home URLByAppendingPathComponent:directoryPath]; } // Report directory itself if (mask & CK2DirectoryEnumerationIncludesDirectory) { [self.client protocol:self didDiscoverItemAtURL:directoryURL]; } // Process the data to make a directory listing NSError* error = [self processData:totalData request:request url:directoryURL path:directoryPath keys:keys options:mask]; [self.client protocol:self didCompleteWithError:error]; } }]; [totalData release]; [request release]; return self; } + (NSArray *)defaultPropertyKeys; { NSArray *result = @[NSURLContentModificationDateKey, NSURLIsDirectoryKey, NSURLIsRegularFileKey, NSURLIsSymbolicLinkKey, NSURLNameKey, NSURLFileSizeKey, CK2URLSymbolicLinkDestinationKey]; if (&NSURLFileResourceTypeKey) result = [result arrayByAddingObject:NSURLFileResourceTypeKey]; if (&NSURLFileSecurityKey) result = [result arrayByAddingObject:NSURLFileSecurityKey]; return result; } #pragma mark Dealloc - (void)dealloc; { [_transfer release]; [_user release]; [_completionHandler release]; [_dataBlock release]; [super dealloc]; } #pragma mark Loading - (void)start; { return [self startWithRequest:self.request credential:nil]; } - (void)startWithProtectionSpace:(NSURLProtectionSpace *)protectionSpace; { NSURLAuthenticationChallenge *challenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:protectionSpace proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:nil]; [self sendAuthChallenge:challenge]; [challenge release]; } - (void)sendAuthChallenge:(NSURLAuthenticationChallenge *)challenge; { NSParameterAssert(challenge); [self.client protocol:self didReceiveChallenge:challenge completionHandler:^(CK2AuthChallengeDisposition disposition, NSURLCredential *credential) { // By default, try once with proposed credential, then give up // Otherwise, can go round in a loop of failed auth https://karelia.fogbugz.com/f/cases/248882 if (disposition == CK2AuthChallengePerformDefaultHandling && challenge.previousFailureCount == 0) { credential = challenge.proposedCredential; disposition = CK2AuthChallengeUseCredential; } switch (disposition) { case CK2AuthChallengeUseCredential: { // Swap out existing handler for one that retries after an auth failure. Stores credential if requested upon success [self pushCompletionHandler:^(NSError *error) { if (error.code == NSURLErrorUserAuthenticationRequired && [error.domain isEqualToString:NSURLErrorDomain]) { [self.client protocol:self appendString:[NSString stringWithFormat:@"Authentication failed for user %@", credential.user] toTranscript:CK2TranscriptText]; // Retry auth NSURLAuthenticationChallenge *newChallenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:challenge.protectionSpace proposedCredential:credential previousFailureCount:(challenge.previousFailureCount + 1) failureResponse:nil error:error sender:nil]; [self sendAuthChallenge:newChallenge]; [newChallenge release]; } else { if (!error) { [[NSURLCredentialStorage sharedCredentialStorage] setCredential:credential forProtectionSpace:challenge.protectionSpace]; } [self popCompletionHandlerByExecutingWithError:error]; } }]; [self startWithRequest:self.request credential:credential]; break; } default: { [self.client protocol:self didCompleteWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUserCancelledAuthentication userInfo:nil]]; } } }]; } - (void)startWithRequest:(NSURLRequest *)request credential:(NSURLCredential *)credential; { _user = [credential.user copy]; request = [self.client protocol:self willSendRequest:request redirectResponse:nil]; CURLTransferStack* multi = nil; if ([request respondsToSelector:@selector(ck2_multi)]) // should only be a testing/debugging feature { multi = [request performSelector:@selector(ck2_multi)]; // typically this is nil, meaning use the default, but we can override it for test purposes } _totalBytesWritten = 0; if ([[self class] usesMultiHandle]) { // Nasty, nasty HACK here, to share across the file manager CK2FileManager *fileManager = [self valueForKeyPath:@"client.fileManager"]; @synchronized(fileManager) { static void *key = &key; multi = [objc_getAssociatedObject(fileManager, key) transferStack]; if (!multi) { CK2CurlTransferStackManager *manager = [[CK2CurlTransferStackManager alloc] init]; multi = manager.transferStack; objc_setAssociatedObject(fileManager, key, manager, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [manager release]; } } _transfer = [[multi transferWithRequest:request credential:credential delegate:self] retain]; [_transfer resume]; } else { // Create the queue & handle for whole app to share static CURLTransfer *transfer; static dispatch_queue_t queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ transfer = [[CURLTransfer alloc] init]; queue = dispatch_queue_create("com.karelia.connection.fallback-CURLTransfer", NULL); }); CURLTransfer* transferToUse; if (multi) // although we're not using the multi, we use it being set here as a signal to use a new handle for this transaction { transferToUse = [[[CURLTransfer alloc] init] autorelease]; } else { transferToUse = transfer; } // Let the work commence! dispatch_async(queue, ^{ if (_cancelled) return; _transfer = [transferToUse retain]; [_transfer sendSynchronousRequest:request credential:credential delegate:self]; }); } } - (void)reportToProtocolWithError:(NSError*)error { [[self client] protocol:self didCompleteWithError:error]; } - (void)stop; { // Mark as cancelled before actually cancelling so _cancelled has to be YES for any other transfers on the queue _cancelled = YES; [_transfer cancel]; } #pragma mark Progress @synthesize totalBytesWritten = _totalBytesWritten; @synthesize totalBytesExpectedToWrite = _totalBytesExpectedToWrite; #pragma mark Managing the Completion Handler /* This code is devious and perhaps even evil. Manages a "stack" of completion * handlers by actually only having a single block. When the block runs, it * replaces itself with the next one down the stack. */ - (void)pushCompletionHandler:(void (^)(NSError*))block; { NSParameterAssert(block); id previousHandler = _completionHandler; _completionHandler = ^(NSError *error) { // Put the old handler back, then execute what was actually requested of us [_completionHandler release]; _completionHandler = previousHandler; block(error); }; _completionHandler = [_completionHandler copy]; } - (void)popCompletionHandlerByExecutingWithError:(NSError *)error; { // If the block is nil, that means the entire stack of handlers has already been popped, which should be a programmer error id keepAlive = _completionHandler; [keepAlive retain]; _completionHandler(error); [keepAlive release]; } #pragma mark URLs + (NSURLRequest *)newRequestWithRequest:(NSURLRequest *)request isDirectory:(BOOL)directory; { NSURL *url = [request URL]; // CURL is very particular about whether URLs passed to it have directory terminator or not if (directory != CFURLHasDirectoryPath((CFURLRef)url)) { if (directory) { url = [url URLByAppendingPathComponent:@""]; } else { CFStringRef lastComponent = CFURLCopyLastPathComponent((CFURLRef)url); // keeps %2F kinda intact as a regular slash url = [[url URLByDeletingLastPathComponent] URLByAppendingPathComponent:(NSString *)lastComponent]; // any slash from %2F will go back in to give a URL containing an extra slash, which should be good enough for libcurl to handle CFRelease(lastComponent); } } NSMutableURLRequest *result = [request mutableCopy]; [result setURL:url]; return result; } #pragma mark Home Directory Store + (BOOL)isHomeDirectoryAtURL:(NSURL *)url; { NSURL *home = [self homeDirectoryURLForServerAtURL:url]; BOOL result = [[self pathOfURLRelativeToHomeDirectory:url] isEqualToString:[self pathOfURLRelativeToHomeDirectory:home]]; return result; } + (NSURL *)homeDirectoryURLForServerAtURL:(NSURL *)hostURL; { NSString *host = [[NSURL URLWithString:@"/" relativeToURL:hostURL] absoluteString].lowercaseString; NSMutableDictionary *store = [self homeURLsByHostURL]; @synchronized (store) { return [store objectForKey:host]; } } - (void)updateHomeDirectoryStore; { NSString *homeDirectoryPath = [_transfer initialFTPPath]; if ([homeDirectoryPath isAbsolutePath]) { if (homeDirectoryPath.length > 1 && ![homeDirectoryPath hasSuffix:@"/"]) // ensure it's a directory path { homeDirectoryPath = [homeDirectoryPath stringByAppendingString:@"/"]; } NSURL *homeDirectoryURL = [self.class URLWithPath:homeDirectoryPath relativeToURL:self.request.URL].absoluteURL; homeDirectoryURL = [self.class URLByReplacingUserInfoInURL:homeDirectoryURL withUser:_user]; // include username NSString *host = [[NSURL URLWithString:@"/" relativeToURL:homeDirectoryURL] absoluteString].lowercaseString; NSMutableDictionary *store = [self.class homeURLsByHostURL]; @synchronized (store) { [store setObject:homeDirectoryURL forKey:host]; } } } + (NSMutableDictionary *)homeURLsByHostURL; { static NSMutableDictionary *sHomeURLsByHostURL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sHomeURLsByHostURL = [[NSMutableDictionary alloc] initWithCapacity:1]; }); return sHomeURLsByHostURL; } #pragma mark CURLTransferDelegate - (void)transfer:(CURLTransfer *)transfer didReceiveData:(NSData *)data; { [self updateHomeDirectoryStore]; // Make sure is updated before parsing of directory listing if (_dataBlock) _dataBlock(data); } - (void)transfer:(CURLTransfer *)transfer willSendBodyDataOfLength:(NSUInteger)bytesWritten { _totalBytesWritten += bytesWritten; [self.client protocol:self didSendBodyData:bytesWritten totalBytesSent:_totalBytesWritten totalBytesExpectedToSend:self.totalBytesExpectedToWrite]; } - (void)transfer:(CURLTransfer *)transfer didCompleteWithError:(NSError *)error; { [self popCompletionHandlerByExecutingWithError:error]; } - (void)transfer:(CURLTransfer *)transfer didReceiveDebugInformation:(NSString *)string ofType:(curl_infotype)type; { CK2TranscriptType ckType; switch (type) { case CURLINFO_HEADER_IN: ckType = CK2TranscriptHeaderIn; break; case CURLINFO_HEADER_OUT: ckType = CK2TranscriptHeaderOut; break; case CURLINFO_TEXT: ckType = CK2TranscriptText; break; default: return; } [[self client] protocol:self appendString:string toTranscript:ckType]; } #pragma mark Customization // Much to my annoyance, multi-socket backend doesn't seem to be working right at the moment // But we're now using the regular multi API, which seems to be working a treat + (BOOL)usesMultiHandle; { return YES; } @end ================================================ FILE: ConnectionKit/CK2CurlTransferStackManager.h ================================================ // // CK2CurlTransferStackManager.h // Connection // // Created by Mike on 23/03/2015. // // #import /** This is a little wrapper around a CURLTransferStack. We use it to tie each CK2FileManager to a transfer stack, and invalidate that stack when appropriate. */ @interface CK2CurlTransferStackManager : NSObject { CURLTransferStack *_transferStack; } /** The manager automatically creates a transfer stack for itself */ @property(nonatomic, readonly) CURLTransferStack *transferStack; @end ================================================ FILE: ConnectionKit/CK2CurlTransferStackManager.m ================================================ // // CK2CurlTransferStackManager.m // Connection // // Created by Mike on 23/03/2015. // // #import "CK2CurlTransferStackManager.h" @implementation CK2CurlTransferStackManager - (instancetype)init { if (self = [super init]) { _transferStack = [CURLTransferStack transferStackWithDelegate:nil delegateQueue:nil]; } return self; } - (void)dealloc { // We're being torn down, so figure now is the time to invalidate transfer stack. Crude, but // there you go. [self.transferStack finishTransfersAndInvalidate]; [super dealloc]; } @synthesize transferStack = _transferStack; @end ================================================ FILE: ConnectionKit/CK2FTPProtocol.h ================================================ // // CK2FTPProtocol.h // Connection // // Created by Mike on 12/10/2012. // // #import "CK2CURLBasedProtocol.h" @interface CK2FTPProtocol : CK2CURLBasedProtocol { @private BOOL _atEnd; // SSL NSURLCredential *_credential; NSUInteger _sslFailures; } @end ================================================ FILE: ConnectionKit/CK2FTPProtocol.m ================================================ // // CK2FTPProtocol.m // Connection // // Created by Mike on 12/10/2012. // // #import "CK2FTPProtocol.h" #import @interface CK2FTPSProtectionSpace : NSURLProtectionSpace { @private SecTrustRef _trust; } - initWithServerTrust:(SecTrustRef)trust host:(NSString *)host port:(NSInteger)port; @end #pragma mark - @implementation CK2FTPProtocol #pragma mark URLs + (BOOL)canHandleURL:(NSURL *)url; { NSString *scheme = [url scheme]; return ([@"ftp" caseInsensitiveCompare:scheme] == NSOrderedSame || [@"ftpes" caseInsensitiveCompare:scheme] == NSOrderedSame || [@"ftps" caseInsensitiveCompare:scheme] == NSOrderedSame); } + (NSURL *)URLWithPath:(NSString *)path relativeToURL:(NSURL *)baseURL; { // FTP is special. Absolute paths need to specified with an extra prepended slash // According to libcurl's docs that should be enough. But with our current build of it, it seems they've gotten stricter // The FTP spec could be interpreted that the only way to refer to the root directly is with the sequence @"%2F", which decodes as a slash // That makes it very clear to the library etc. this particular slash is meant to be transmitted to the server, rather than treated as a path component separator // Happily it also simplifies our code, as coaxing a double slash into NSURL is a mite tricky if ([path isAbsolutePath]) { // Pare it back to just a plain path of @"/" and no latter components NSString *urlString = [[NSURL URLWithString:@"/" relativeToURL:baseURL] absoluteString]; // Tack on the path given to us // http://www.mikeabdullah.net/escaping-url-paths-in-cocoa.html CFStringRef escaped = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)path, NULL, CFSTR(";?#"), kCFStringEncodingUTF8); NSURL *result = [NSURL URLWithString:[urlString stringByAppendingString:(NSString *)escaped]]; CFRelease(escaped); return result; } return [super URLWithPath:path relativeToURL:baseURL]; } + (NSString *)pathOfURLRelativeToHomeDirectory:(NSURL *)URL; { // FTP is special. The first slash of the path is to be ignored // As above, the library seems to be stricter on how the slash is to be encoded these days. I'm not sure whether we should be similarly strict when decoding. Leaving it be for now CFStringRef strictPath = CFURLCopyStrictPath((CFURLRef)[URL absoluteURL], NULL); NSString *result = [(NSString *)strictPath stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; if (strictPath) CFRelease(strictPath); return result; } #pragma mark URL Requests - (id)initWithRequest:(NSURLRequest *)request client:(id)client; { // libcurl doesn't understand ftpes: URLs natively, so convert them back into ftp: with the appropriate connection settings NSURL *url = request.URL; if ([url.scheme caseInsensitiveCompare:@"ftpes"] == NSOrderedSame) { url = [NSURL URLWithString:[url.absoluteString stringByReplacingCharactersInRange:NSMakeRange(0, 5) // bit hacky withString:@"ftp"]]; NSMutableURLRequest *mutableRequest = [[request mutableCopy] autorelease]; mutableRequest.URL = url; [mutableRequest curl_setDesiredSSLLevel:CURLUSESSL_ALL]; request = mutableRequest; } return [super initWithRequest:request client:client]; } #pragma mark Operations - (id)initWithCustomCommands:(NSArray *)commands request:(NSURLRequest *)childRequest createIntermediateDirectories:(BOOL)createIntermediates client:(id)client completionHandler:(void (^)(NSError *))handler; { NSMutableURLRequest *request = [childRequest mutableCopy]; request.URL = [childRequest.URL URLByDeletingLastPathComponent]; self = [super initWithCustomCommands:commands request:request createIntermediateDirectories:createIntermediates client:client completionHandler:handler]; [request release]; return self; } - (id)initForCreatingDirectoryWithRequest:(NSURLRequest *)request withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { return [self initWithCustomCommands:[NSArray arrayWithObject:[@"MKD " stringByAppendingString:[[request URL] lastPathComponent]]] request:request createIntermediateDirectories:createIntermediates client:client completionHandler:^(NSError *error) { if (error) { error = [self translateStandardErrors:error]; } [self reportToProtocolWithError:error]; } ]; } - (id)initForCreatingFileWithRequest:(NSURLRequest *)request size:(int64_t)size withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { return [self initForCreatingFileWithRequest:request size:size withIntermediateDirectories:createIntermediates client:client completionHandler:^(NSError *error) { if (error) { if ([self looksLikeFTPIdleTimeoutProblem:error]) { error = nil; } // Give a bit of a clue for debugging how far we got through else if (self.totalBytesWritten) { NSMutableDictionary *info = [error.userInfo mutableCopy]; NSString *description; if (self.totalBytesExpectedToWrite == NSURLResponseUnknownLength) { description = [[info objectForKey:NSLocalizedDescriptionKey] stringByAppendingFormat: @" (%@ KB sent.)", @(self.totalBytesWritten / 1024)]; } else { description = [[info objectForKey:NSLocalizedDescriptionKey] stringByAppendingFormat: @" (%@ of %@ KB sent.)", @(self.totalBytesWritten / 1024), @(self.totalBytesExpectedToWrite / 1024)]; } [info setObject:description forKey:NSLocalizedDescriptionKey]; error = [NSError errorWithDomain:error.domain code:error.code userInfo:info]; [info release]; } } [client protocol:self didCompleteWithError:error]; }]; } - (id)initForRenamingItemWithRequest:(NSURLRequest *)request newName:(NSString *)newName client:(id)client { NSString* sourcePath = [[request URL] lastPathComponent]; return [self initWithCustomCommands:[NSArray arrayWithObjects: [@"RNFR " stringByAppendingString:sourcePath], [@"RNTO " stringByAppendingString:newName], nil ] request:request createIntermediateDirectories:NO client:client completionHandler:^(NSError *error) { error = [self translateStandardErrors:error]; [self reportToProtocolWithError:error]; }]; } - (id)initForRemovingItemWithRequest:(NSURLRequest *)request client:(id)client; { NSURL *url = request.URL; // Pick an appropriate command // DELE is only intended to delete files, but in our testing, some FTP servers happily support deleting a directory using it NSString *command = @"DELE "; NSNumber *isDirectory; if ([url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL] && isDirectory.boolValue) { command = @"RMD "; } else if (CFURLHasDirectoryPath((CFURLRef)url)) { command = @"RMD "; } return [self initWithCustomCommands:[NSArray arrayWithObject:[command stringByAppendingString:url.lastPathComponent]] request:request createIntermediateDirectories:NO client:client completionHandler:^(NSError *error) { if (error) { error = [self translateStandardErrors:error]; } [self reportToProtocolWithError:error]; }]; } - (id)initForSettingAttributes:(NSDictionary *)keyedValues ofItemWithRequest:(NSURLRequest *)request client:(id)client; { NSNumber *permissions = [keyedValues objectForKey:NSFilePosixPermissions]; if (permissions) { NSString* path = [[request URL] lastPathComponent]; NSArray *commands = [NSArray arrayWithObject:[NSString stringWithFormat: @"SITE CHMOD %lo %@", [permissions unsignedLongValue], path]]; return [self initWithCustomCommands:commands request:request createIntermediateDirectories:NO client:client completionHandler:^(NSError *error) { if (error) { NSString* domain = error.domain; NSInteger code = error.code; // CHMOD failures for unsupported or unrecognized command should go ignored if (code== CURLE_QUOTE_ERROR && [domain isEqualToString:CURLcodeErrorDomain]) { NSUInteger responseCode = [error curlResponseCode]; if (responseCode == 500 || responseCode == 502 || responseCode == 504) { error = nil; } } if (error) { error = [self translateStandardErrors:error]; } } [self reportToProtocolWithError:error]; }]; } else { self = [self initWithRequest:nil client:client]; return self; } } - (void)dealloc; { [_credential release]; [super dealloc]; } #pragma mark Errors - (NSError*)translateStandardErrors:(NSError*)error { NSString* domain = error.domain; NSInteger code = error.code; if (code == CURLE_QUOTE_ERROR && [domain isEqualToString:CURLcodeErrorDomain]) { NSUInteger responseCode = [error curlResponseCode]; if (responseCode == 550) { error = [self standardCouldntWriteErrorWithUnderlyingError:error]; } } else if (code == CURLE_REMOTE_ACCESS_DENIED && [domain isEqualToString:CURLcodeErrorDomain]) { // Could be a permissions problem, or could be that a CWD command failed because the directory doesn't exist error = [self standardCouldntReadErrorWithUnderlyingError:error]; } else if ((code == NSURLErrorNoPermissionsToReadFile) && ([domain isEqualToString:NSURLErrorDomain])) { // CURLTransfer helpfully returns a URL error here, but we want to return a cocoa error instead error = [self standardCouldntWriteErrorWithUnderlyingError:error]; } else { NSLog(@"untranslated error for %@ %@", NSStringFromSelector(_cmd), error); } return error; } /** FTP suffers a fairly notorious problem. During a long upload, the control connection is completely idle. As such, there's a tendency for internet routers to then cut it off. When the data connection completes, Curl goes to use the control connection to finish up and finds that it no longer can, thanks to that cutoff. The really nasty thing is Curl can't know for sure if the cutoff was because of idling (and the upload finished successfully), or if there was a proper connection problem while the last packet of data was being sent. Understandably, users can be rather annoyed if they keep being told their long uploads have failed (particularly in an app like Sandvox where a failure will result in the upload being retried on another occasion until it does succeed). So our best bet seems to be a heuristic: If all file data was *transmitted* (not necessarily *received*) and the failure could be caused by an idle timeout, assume the upload was actually a success. */ - (BOOL)looksLikeFTPIdleTimeoutProblem:(NSError *)error { if (!_atEnd) return NO; // We've seen more than a single error code so far! if (error.code == NSURLErrorTimedOut && [error.domain isEqualToString:NSURLErrorDomain]) return YES; if (error.code == CURLE_RECV_ERROR && [error.domain isEqualToString:CURLcodeErrorDomain]) return YES; // https://karelia.fogbugz.com/f/cases/248867/ return NO; } #pragma mark Lifecycle - (void)start; { // If there's no request, that means we were asked to do nothing possible over FTP. Most likely, storing attributes that aren't POSIX permissions // So jump straight to completion NSURLRequest *request = self.request; if (!request) { [[self client] protocol:self didCompleteWithError:nil]; return; } NSURL *url = request.URL; NSString *scheme = url.scheme; NSNumber *port = url.port; if (!port) { port = ([scheme isEqualToString:@"ftps"] ? @(990) : @(21)); } NSString *protocol = NSURLProtectionSpaceFTP; if (request.curl_desiredSSLLevel >= CURLUSESSL_CONTROL || [@"ftps" caseInsensitiveCompare:scheme] == NSOrderedSame) { protocol = @"ftps"; } NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:[url host] port:port.integerValue protocol:protocol realm:nil authenticationMethod:NSURLAuthenticationMethodDefault]; [self startWithProtectionSpace:space]; [space release]; } - (void)startWithRequest:(NSURLRequest *)request credential:(NSURLCredential *)credential; { // Cache the credential in case we need to retry FTPS [credential retain]; [_credential release]; _credential = credential; [super startWithRequest:request credential:credential]; } #pragma mark Home Directory /*- (void)findHomeDirectoryWithCompletionHandler:(void (^)(NSString *path, NSError *error))handler; { // Deliberately want a request that should avoid doing any work NSMutableURLRequest *request = [[self request] mutableCopy]; [request setURL:[NSURL URLWithString:@"/" relativeToURL:[request URL]]]; [request setHTTPMethod:@"HEAD"]; [self sendRequest:request dataHandler:nil completionHandler:^(CURLTransfer *transfer, NSError *error) { if (error) { handler(nil, error); } else { handler([transfer initialFTPPath], error); } }]; [request release]; }*/ #pragma mark CURLTransferDelegate - (void)transfer:(CURLTransfer *)transfer didCompleteWithError:(NSError *)error; { // For SSL errors, report extra info SecTrustRef trust = (SecTrustRef)[error.userInfo objectForKey:NSURLErrorFailingURLPeerTrustErrorKey]; if (trust) { NSURL *url = self.request.URL; NSURLProtectionSpace *space = [[CK2FTPSProtectionSpace alloc] initWithServerTrust:trust host:url.host port:url.port.integerValue]; _sslFailures++; NSURLAuthenticationChallenge *challenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:_sslFailures failureResponse:nil error:error sender:nil]; [self.client protocol:self didReceiveChallenge:challenge completionHandler:^(CK2AuthChallengeDisposition disposition, NSURLCredential *credential) { if (disposition == CK2AuthChallengeUseCredential && credential) { // Retry // Ideally we'd adjust libcurl to only accept this one new // certificate, but I can't spot a proper API for that, so we'll // have to live with a minor security flaw for now. NSMutableURLRequest *request = [self.request mutableCopy]; [request curl_setShouldVerifySSLHost:NO]; // disabling host check is all Sandvox needs [self startWithRequest:request credential:_credential]; [request release]; } else if (disposition == CK2AuthChallengeCancelAuthenticationChallenge) { [super transfer:transfer didCompleteWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUserCancelledAuthentication userInfo:nil]]; } else { [super transfer:transfer didCompleteWithError:error]; } }]; [challenge release]; [space release]; return; } [super transfer:transfer didCompleteWithError:error]; } - (void)transfer:(CURLTransfer *)transfer willSendBodyDataOfLength:(NSUInteger)bytesWritten; { // Watch for the file end being reached before passing onto the original requester if (bytesWritten == 0) _atEnd = YES; [super transfer:transfer willSendBodyDataOfLength:bytesWritten]; } - (void)transfer:(CURLTransfer *)transfer didReceiveDebugInformation:(NSString *)string ofType:(curl_infotype)type; { // Don't want to include password in transcripts usually! if (type == CURLINFO_HEADER_OUT && [string hasPrefix:@"PASS"] && ![[NSUserDefaults standardUserDefaults] boolForKey:@"AllowPasswordToBeLogged"]) { string = @"PASS ####"; } [super transfer:transfer didReceiveDebugInformation:string ofType:type]; } @end #pragma mark - @implementation CK2FTPSProtectionSpace - initWithServerTrust:(SecTrustRef)trust host:(NSString *)host port:(NSInteger)port; { if (self = [self initWithHost:host port:port protocol:@"ftps" realm:nil authenticationMethod:NSURLAuthenticationMethodServerTrust]) { _trust = trust; CFRetain(_trust); } return self; } - (void)dealloc; { CFRelease(_trust); [super dealloc]; } - (SecTrustRef)serverTrust; { return _trust; } @end ================================================ FILE: ConnectionKit/CK2FileCell.h ================================================ /* This class is based on FileSystemBrowserCell. The original version of which can be found here: http://developer.apple.com/library/mac/#samplecode/ComplexBrowser/Listings/FileSystemBrowserCell_m.html The original code falls under the copyright and license specified there. Modifications to the code for this project fall under the following license: Copyright (c) 2013, Karelia Software All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Karelia Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import @interface CK2FileCell : NSTextFieldCell { @private NSImage *_image; NSColor *_labelColor; BOOL _isTextOnly; } @property(retain) NSImage *image; @property(retain) NSColor *labelColor; @property(assign, getter=isTextOnly, nonatomic) BOOL textOnly; @end ================================================ FILE: ConnectionKit/CK2FileCell.m ================================================ /* This class is based on FileSystemBrowserCell. The original version of which can be found here: http://developer.apple.com/library/mac/#samplecode/ComplexBrowser/Listings/FileSystemBrowserCell_m.html The original code falls under the copyright and license specified there. Modifications to the code for this project fall under the following license: Copyright (c) 2013, Karelia Software All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Karelia Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "CK2FileCell.h" @implementation CK2FileCell #define ICON_SIZE 16.0 // Our Icons are ICON_SIZE x ICON_SIZE #define ICON_INSET_HORIZ 4.0 // Distance to inset the icon from the left edge. #define ICON_TEXT_SPACING 2.0 // Distance between the end of the icon and the text part #define ICON_INSET_VERT 2.0 // Distance from top/bottom of icon - (id)init { self = [super init]; [self setLineBreakMode:NSLineBreakByTruncatingMiddle]; return self; } - (id)copyWithZone:(NSZone *)zone { CK2FileCell *result = [super copyWithZone:zone]; result->_image = nil; result.image = self.image; result->_labelColor = nil; result.labelColor = self.labelColor; return result; } - (void)dealloc { [_image release]; [_labelColor release]; [super dealloc]; } @synthesize image = _image; @synthesize labelColor = _labelColor; @synthesize textOnly = _isTextOnly; - (void)setTextOnly:(BOOL)flag { _isTextOnly = flag; if (_isTextOnly) { [self setLineBreakMode:NSLineBreakByTruncatingTail]; } else { [self setLineBreakMode:NSLineBreakByTruncatingMiddle]; } } - (NSRect)imageRectForBounds:(NSRect)bounds { bounds.origin.x += ICON_INSET_HORIZ; bounds.size.width = ICON_SIZE; bounds.origin.y += trunc((bounds.size.height - ICON_SIZE) / 2.0); bounds.size.height = ICON_SIZE; return bounds; } - (NSRect)titleRectForBounds:(NSRect)bounds { if (!_isTextOnly) { // Inset the title for the image CGFloat inset = (ICON_INSET_HORIZ + ICON_SIZE + ICON_TEXT_SPACING); bounds.origin.x += inset; bounds.size.width -= inset; } return [super titleRectForBounds:bounds]; } - (NSSize)cellSizeForBounds:(NSRect)aRect { // Make our cells a bit higher than normal to give some additional space for the icon to fit. NSSize theSize = [super cellSizeForBounds:aRect]; theSize.width += (ICON_INSET_HORIZ + ICON_SIZE + ICON_TEXT_SPACING); theSize.height = ICON_INSET_VERT + ICON_SIZE + ICON_INSET_VERT; return theSize; } - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { // First draw a label background color if (self.labelColor != nil) { [[self.labelColor colorWithAlphaComponent:0.2] set]; NSRectFillUsingOperation(cellFrame, NSCompositeSourceOver); } if (!_isTextOnly) { NSRect imageRect = [self imageRectForBounds:cellFrame]; [self.image drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:YES hints:nil]; CGFloat inset = (ICON_INSET_HORIZ + ICON_SIZE + ICON_TEXT_SPACING); cellFrame.origin.x += inset; cellFrame.size.width -= inset; } cellFrame.origin.y += 1; // Looks better cellFrame.size.height -= 1; [super drawInteriorWithFrame:cellFrame inView:controlView]; } - (void)drawWithExpansionFrame:(NSRect)cellFrame inView:(NSView *)view { // We want to exclude the icon from the expansion frame when you hover over the cell [super drawInteriorWithFrame:cellFrame inView:view]; } @end ================================================ FILE: ConnectionKit/CK2FileManager.h ================================================ // // CK2FileManager // Connection // // Created by Mike on 08/10/2012. // #import typedef void (^CK2ProgressBlock)(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToSend); extern NSString * const CK2FileMIMEType; typedef NS_OPTIONS(NSInteger, CK2DirectoryEnumerationOptions) { CK2DirectoryEnumerationIncludesDirectory = 1L << 31, // see directory methods below for details }; @protocol CK2FileManagerDelegate; @class CK2FileOperation; /** ConnectionKit's equivalent of NSFileManager All operations are asynchronous, including ones on the local file system. Supports remote file servers, currently over FTP, SFTP and WebDAV. Allocate and initialise as many file managers as you wish; there is no +defaultManager method Provide a file manager with a delegate to handle authentication in the same fashion as NSURLConnection Behind the scenes, ConnectionKit takes care of creating as many connections to servers as are needed. This means you can perform multiple operations at once, but please avoid performing too many at once as that could easily upset a server. Supported protocols and their URL schemes: Scheme | Protocol ------ | -------- file | Local files ftp | FTP ftps | FTP with Implicit SSL ftpes | FTP with TLS/SSL http | WebDAV https | WebDAV over HTTPS sftp | SFTP Note that on OS releases where `-[NSURLConnection setDelegateQueue:]` is unavailable, WebDAV operations rely on the main thread running its runloop in the default mode. */ @interface CK2FileManager : NSObject { @private id _delegate; NSOperationQueue *_delegateQueue; } #pragma mark Creating a File Manager /** Creates a CK2FileManager instance. @param delegate A delegate object that handles authentication etc. @param queue A queue for scheduling the delegate calls and completion handlers. If `nil`, ConnectionKit creates a serial operation queue for performing all delegate method calls and completion handler calls. */ + (CK2FileManager *)fileManagerWithDelegate:(id )delegate delegateQueue:(NSOperationQueue *)queue; #pragma mark Discovering Directory Contents /** Performs a shallow search of the specified directory and returns URLs for the contained items. This method performs a shallow search of the directory and therefore does not traverse symbolic links or return the contents of any subdirectories. This method also does not return URLs for the current directory (“.”), parent directory (“..”) but it can return hidden files (files that begin with a period character) The order of the files in the returned array generally follows that returned by the server, which is likely undefined. Paths are standardized if possible (i.e. case is corrected if needed, and relative paths resolved). Any user info you pass in as part of `url` (username or password) will be included back in returned URLs. Note that if you use the authentication callback to specify a user different to the one in `url`, the results you get back won't reflect that change in user; it becomes your responsibility to adjust the resultant URLs if your app needs it. Many protocols provide a decent error code indicating *why* the operation failed. Unfortunately, FTP cannot. The FTP spec means the only machine-readable response is a 550 code. This covers pretty much any sort of filesystem access problem (as opposed to an issue with the connection itself). Thus FTP ops cannot distinguish between a folder not existing, not actually being a folder, and the user having insufficient permissions to access it. Instead you'll get back plain old `NSFileReadUnknownError`. @param url for the directory whose contents you want to enumerate. @param keys to try and include from the server. Pass nil to get a default set. Include NSURLParentDirectoryURLKey to get @param mask of options. In addition to NSDirectoryEnumerationOptions, accepts CK2DirectoryEnumerationIncludesDirectory @param block called with URLs, each of which identifies a file, directory, or symbolic link. If the directory contains no entries, the array is empty. If an error occurs, contents is nil and error should be non-nil. @return The new file operation. */ - (CK2FileOperation *)contentsOfDirectoryAtURL:(NSURL *)url includingPropertiesForKeys:(NSArray *)keys options:(NSUInteger)mask completionHandler:(void (^)(NSArray *contents, NSError *error))block __attribute((nonnull(1,4))); extern NSString * const CK2URLSymbolicLinkDestinationKey; // The destination URL of a symlink #pragma mark Creating Items /** Creates a directory at the specified URL. If a file or directory already exists at `url`, it is at the server's discretion whether the operation succeeds by replacing the existing item, or fails. Only some protocols/servers support/respect applying attributes to a directory as part of creating it. Indeed, some servers don't really support attributes at all! So any attributes you pass here might well go ignored. In practice, at present you should see something like this: - FTP: Attributes are completely ignored - SFTP: Only `NSFilePosixPermissions` is used; some servers choose to ignore it - WebDAV: Attributes are ignored - file: The full suite of attributes supported by `NSFileManager` should be available If you particularly care about setting attributes on a remote server, then a follow-up call to -setAttributes:… is needed. Note: Even though this is a "write" operation, it is still possible to get back something like `NSFileReadUnknownError`. In particular, FTP must traverse the directory hierarchy which can fail if the target directory turns out not to exist, or the user has insufficient permissions to access it. @param url A URL that specifies the directory to create. This parameter must not be nil. @param createIntermediates If YES, this method creates any non-existent parent directories as part of creating the directory in url. If NO, this method fails if any of the intermediate parent directories does not exist. @param attributes to apply *only* if the server supports supplying them at creation time. See discussion for more details. @param handler Called at the end of the operation. A non-nil error indicates failure. @return The new file operation. */ - (CK2FileOperation *)createDirectoryAtURL:(NSURL *)url withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes completionHandler:(void (^)(NSError *error))handler __attribute((nonnull(1))); /** Creates a file with the specified content at the specified URL. If a file or directory already exists at `url`, it is at the server's discretion whether the operation succeeds by replacing the existing item, or fails. Only some protocols/servers support/respect applying attributes to a file as part of creating it. Indeed, some servers don't really support attributes at all! So any attributes you pass here might well go ignored. In practice, at present you should see something like this: - FTP: Attributes are completely ignored - SFTP: Only `NSFilePosixPermissions` is used; some servers choose to ignore it - WebDAV: Only `CK2FileMIMEType` is supported - file: Attributes are ignored If you particularly care about setting attributes on a remote server, then a follow-up call to -setAttributes:… is needed. Protocol Quirks To Be Aware Of: FTP: Even though this is a "write" operation, it is still possible to get back `NSFileReadUnknownError`. FTP clients must traverse the directory hierarchy which can fail if the target directory turns out not to exist (and you haven't asked to create it), or the user has insufficient permissions to access it. WebDAV: Some servers out there choose to treat `createIntermediates` as if it's always YES. When asking to create intermediate directories, most WebDAV servers will require uploading the entire file data before finding out if directories actually do need to be created. For a large file, that could be quite a waste of time and bandwidth. In some applications it may be better to create the intermediate directories first, regardless of need, and then try the upload. @param url A URL that specifies the file to create. This parameter must not be nil. @param data A data object containing the contents of the new file. @param createIntermediates If YES, this method creates any non-existent parent directories as part of creating the file in url. If NO, this method should fail if any of the intermediate parent directories does not exist (see WebDAV caveat above). @param attributes to apply *only* if the server supports supplying them at creation time. See discussion for more details. @param progressBlock Called as each "chunk" of the file is written. In some cases, uploads have to be restarted from the beginning; the previousAttemptCount argument tells you how many times that has happened so far @param handler Called at the end of the operation. A non-nil error indicates failure. @return The new file operation. */ - (CK2FileOperation *)createFileAtURL:(NSURL *)url contents:(NSData *)data withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes progressBlock:(CK2ProgressBlock)progressBlock completionHandler:(void (^)(NSError *error))handler __attribute((nonnull(1,2))); - (CK2FileOperation *)createFileOperationWithURL:(NSURL *)url fromData:(NSData *)data withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes completionHandler:(void (^)(NSError *error))handler __attribute((nonnull(1,2))); /** Creates a file by copying the content of the specified URL. If a file or directory already exists at `destinationURL`, it is at the server's discretion whether the operation succeeds by replacing the existing item, or fails. Only some protocols/servers support/respect applying attributes to a file as part of creating it. Indeed, some servers don't really support attributes at all! So any attributes you pass here might well go ignored. In practice, at present you should see something like this: - FTP: Attributes are completely ignored - SFTP: Only `NSFilePosixPermissions` is used; some servers choose to ignore it - WebDAV: Only `CK2FileMIMEType` is supported - file: Attributes are ignored If you particularly care about setting attributes on a remote server, then a follow-up call to -setAttributes:… is needed. It's up to the individual protocol implementation, but generally ConnectionKit will avoid loading the entire source file into memory at once. FTP: Even though this is a "write" operation, it is still possible to get back `NSFileReadUnknownError`. FTP clients must traverse the directory hierarchy which can fail if the target directory turns out not to exist (and you haven't asked to create it), or the user has insufficient permissions to access it. WebDAV: Some servers out there choose to treat `createIntermediates` as if it's always YES. When asking to create intermediate directories, most WebDAV servers will require uploading the entire file data before finding out if directories actually do need to be created. For a large file, that could be quite a waste of time and bandwidth. In some applications it may be better to create the intermediate directories first, regardless of need, and then try the upload. @param destinationURL A URL that specifies the file to create. This parameter must not be nil. @param sourceURL The file whose contents to use for creating the new file. @param createIntermediates If YES, this method creates any non-existent parent directories as part of creating the file in url. If NO, this method should fail if any of the intermediate parent directories does not exist (see WebDAV caveat above). @param attributes to apply *only* if the server supports supplying them at creation time. See discussion for more details. @param progressBlock Called as each "chunk" of the file is written. In some cases, uploads have to be restarted from the beginning; the previousAttemptCount argument tells you how many times that has happened so far @param handler Called at the end of the operation. A non-nil error indicates failure. @return The new file operation. */ - (CK2FileOperation *)createFileAtURL:(NSURL *)destinationURL withContentsOfURL:(NSURL *)sourceURL withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes progressBlock:(CK2ProgressBlock)progressBlock completionHandler:(void (^)(NSError *error))handler __attribute((nonnull(1,2))); - (CK2FileOperation *)createFileOperationWithURL:(NSURL *)destinationURL fromFile:(NSURL *)sourceURL withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes completionHandler:(void (^)(NSError *error))handler __attribute((nonnull(1,2))); #pragma mark Deleting Items /** Removes the item at the specified URL. The handling of files versus directories is heavily at the discretion of the protocol implementation at present: - file: Like NSFileManager, cheerfully deletes files or directories, including contents. - WebDAV: Provided the server adheres to the WebDAV spec, has the same behaviour as local files. - SFTP: If the URL has `NSURLIsDirectoryKey` set to `YES`, or a trailing slash, is treated as a directory. Only empty directories can be deleted though, so your code is responsible for making the directory (and it's subdirectories) empty first. Otherwise, the URL is treated as a file. As far as I am aware, SFTP servers are quite strict on the difference between files and directories. - FTP: The same as SFTP except there are definitely plenty of servers in the wild which will delete empty directories when asked to delete a *file* of that name. Even though this is a "write" operation, it is still possible to get back something like `NSFileReadUnknownError`, as FTP may require traversing the directory hierarchy which can fail if the target directory turns out not to exist, or the user has insufficient permissions to access it. @param url A file URL specifying the file or directory to remove. @param handler Called at the end of the operation. A non-nil error indicates failure. @return The new file operation. */ - (CK2FileOperation *)removeItemAtURL:(NSURL *)url completionHandler:(void (^)(NSError *error))handler __attribute((nonnull(1))); - (CK2FileOperation *)removeOperationWithURL:(NSURL *)url completionHandler:(void (^)(NSError *error))handler __attribute((nonnull(1))); #pragma mark Moving Items /** Renames the item at the specified URL @param srcURL The file or directory to rename. @param newName The new name for the file. Note that some FTP servers seem to cope poorly with filenames containing a space, truncating at the first space character. @param handler Called at the end of the operation. A non-nil error indicates failure. @return The new file operation. */ - (CK2FileOperation *)renameItemAtURL:(NSURL *)srcURL toFilename:(NSString *)newName completionHandler:(void (^)(NSError *error))handler; #pragma mark Getting and Setting Attributes /** Sets the attributes of the specified file or directory. Unsupported attributes are ignored. Failure is only considered to have occurred when an attribute appears to be supported by the server/protocol in use, but actually fails to set. In practice at present the supported attributes should be: - FTP: Only NSFilePosixPermissions is supported, and not by all servers - SFTP: Only NSFilePosixPermissions is supported - WebDAV: No attributes are supported - file: Same attributes as NSFileManager supports Note: Even though this is a "write" operation, it is still possible to get back something like `NSFileReadUnknownError`. In particular, FTP must traverse the directory hierarchy which can fail if the target directory turns out not to exist, or the user has insufficient permissions to access it. @param keyedValues A dictionary containing as keys the attributes to set for path and as values the corresponding value for the attribute. @param url The URL of a file or directory. @param handler Called at the end of the operation. A non-nil error indicates failure. @return The new file operation. */ - (CK2FileOperation *)setAttributesOperationWithURL:(NSURL *)url attributes:(NSDictionary *)keyedValues completionHandler:(void (^)(NSError *error))handler __attribute((nonnull(1,2))); // To retrieve attributes, instead perform a listing of the *parent* directory, and pick out resource properties from the returned URLs that you're interested in #pragma mark Delegate /** The file manager's delegate. Delegate methods are delivered on an arbitrary queue/thread. Changing delegate might mean you still receive messages shortly after the change. Not ideal I know! */ @property(assign) id delegate; /** The operation queue provided when this object was created. (read-only) All delegate method calls and completion handlers are performed on this queue. */ @property(readonly, retain) NSOperationQueue *delegateQueue; @end @interface CK2FileManager (URLs) /** Initializes and returns a newly created NSURL object by changing `baseURL` to the specified path. Some protocols differentiate between absolute paths, and those relative to the user's home directory. This method constructs URLs to accomodate that and the quirks of different protocols. Here are some example URLs: Protocol | `/absolute` path | `relative` path -------- | ----------------------------- | ------------------------------- HTTP | `http://example.com/absolute` | `http://example.com/relative` FTP | `ftp://example.com//absolute` | `ftp://example.com/relative` SSH | `sftp://example.com/absolute` | `sftp://example.com/~/relative` There is a subtle bug in 10.6's handling of relative file URLs. This method stops it hitting you. @param path The path that the NSURL object will represent. If path is a relative path, it is treated as being relative to the user's home directory once connected to `baseURL`. Passing nil for this parameter produces an exception. @param isDir A Boolean value that specifies whether path is treated as a directory path when resolving against relative path components. Pass YES if the path indicates a directory, NO otherwise. @param baseURL A URL providing at least a scheme and host for the result to be based on. Any path as part of this URL is ignored. @return An NSURL object initialized with path. `nil` if `baseURL` proved unsuitable. */ + (NSURL *)URLWithPath:(NSString *)path isDirectory:(BOOL)isDir hostURL:(NSURL *)baseURL __attribute((nonnull(1,3))); // NOTE: +URLWithPath:relativeToURL: tends to return relative URLs. You may well find it preferable to call -absoluteURL on the result in your app to keep things simple // I'm seriously considering removing this method as it tends not to be that useful in practice. +URLWithPath:isDirectory:hostURL: does exactly what it says on the tin + (NSURL *)URLWithPath:(NSString *)path relativeToURL:(NSURL *)baseURL __attribute((nonnull(1,2))); /** Extracts the path component of a URL, accounting for the subtleties of FTP etc. Normally, paths in URLs are absolute. FTP and SSH-based protocols both have the concept of a "home" directory though. i.e. the working directory upon login. Thus, their URLs must distinguish between absolute and relative paths (the latter are interpreted relative to the home directory). This method makes that same distinction to return an absolute or relative path, as interpreted by the specific protocol. Some examples: ftp://example.com/relative => relative ftp://example.com//absolute => /absolute sftp://example.com/absolute => /absolute sftp://example.com/~/relative => relative @param URL to extract the path from. @return the URL's path. If the path has a trailing slash it is stripped. */ + (NSString *)pathOfURL:(NSURL *)URL; /** Equivalent of CFURLSetTemporaryResourcePropertyForKey() that supports non-file URLs Calls through to Core Foundation for file URLs, but provides its own storage for others When first used for a non-file URL, -[NSURL getResourceValue:forKey:error:] is swizzled so the value can be easily retreived by clients later This method is primarily used by non-file protocols to populate URLs returned during a directory listing. But it could be helpful to clients for adding in other info CRITICAL: keys are tested using POINTER equality for non-file URLs, so you must pass in a CONSTANT @param value to cache. Retained @param key to store under. Any existing value is overwritten @param url to cache for */ + (void)setTemporaryResourceValue:(id)value forKey:(NSString *)key inURL:(NSURL *)url __attribute((nonnull(2,3))); @end /** * Disposition options for auth challenge delegate message */ typedef NS_ENUM(NSInteger, CK2AuthChallengeDisposition) { CK2AuthChallengeUseCredential = 0, /* Use the specified credential, which may be `nil` */ CK2AuthChallengePerformDefaultHandling = 1, /* Default handling for the challenge - as if this delegate were not implemented; the credential parameter is ignored. */ CK2AuthChallengeCancelAuthenticationChallenge = 2, /* The entire request will be canceled; the credential parameter is ignored. */ CK2AuthChallengeRejectProtectionSpace = 3, /* This challenge is rejected and the next authentication protection space should be tried;the credential parameter is ignored. */ }; @protocol CK2FileManagerDelegate @optional /** EXPERIMENTAL Gives the delegate a chance to customise requests for those protocols which use them (currently WebDAV only) */ - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLRequest *))completionHandler; /** The task has received an authentication challenge. If this delegate is not implemented, the behavior will be the same as using the default handling disposition. */ - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler; /** * Sent periodically to notify the delegate of upload progress. This * information is also available as properties of the operation. */ - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation didWriteBodyData:(int64_t)bytesSent totalBytesWritten:(int64_t)totalBytesSent totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToSend; typedef NS_ENUM(NSUInteger, CK2TranscriptType) { CK2TranscriptText, CK2TranscriptHeaderIn, CK2TranscriptHeaderOut, }; // deliberately aligned with curl_infotype for convenience /** Reports received transcript info. @param manager The file manager. @param info The received transcript line(s). Should end in a newline character. @param transcript The type of transcript received. */ - (void)fileManager:(CK2FileManager *)manager appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; /** * Sent as the last message related to a specific operation. Error may be * `nil`, which implies that no error occurred and this operation is finished. */ - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation didCompleteWithError:(NSError *)error; - (void)fileManager:(CK2FileManager *)manager didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge __attribute((deprecated("implement -fileManager:operation:didReceiveChallenge:completionHandler: instead"))); @end @interface CK2FileManager (Deprecated) - (void)cancelOperation:(CK2FileOperation *)operation __attribute((nonnull(1), deprecated("Use -[CK2FileOperation cancel] instead"))); @end ================================================ FILE: ConnectionKit/CK2FileManager.m ================================================ // // CK2FileManager // Connection // // Created by Mike on 08/10/2012. // // #import "CK2FileManager.h" #import "CK2FileOperation.h" #import "CK2Protocol.h" #import NSString * const CK2FileMIMEType = @"CK2FileMIMEType"; #pragma mark - @interface CK2FileOperation (Private) - (id)initEnumerationOperationWithURL:(NSURL *)url includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask manager:(CK2FileManager *)manager enumerationBlock:(void (^)(NSURL *))enumBlock completionBlock:(void (^)(NSError *))block; - (id)initDirectoryCreationOperationWithURL:(NSURL *)url withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes manager:(CK2FileManager *)manager completionBlock:(void (^)(NSError *))block; - (id)initFileCreationOperationWithURL:(NSURL *)url data:(NSData *)data withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes manager:(CK2FileManager *)manager progressBlock:(CK2ProgressBlock)progressBlock completionBlock:(void (^)(NSError *))block; - (id)initFileCreationOperationWithURL:(NSURL *)remoteURL file:(NSURL *)localURL withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes manager:(CK2FileManager *)manager progressBlock:(CK2ProgressBlock)progressBlock completionBlock:(void (^)(NSError *))block; - (id)initRemovalOperationWithURL:(NSURL *)url manager:(CK2FileManager *)manager completionBlock:(void (^)(NSError *))block; - (id)initRenameOperationWithSourceURL:(NSURL *)srcURL newName:(NSString *)newName manager:(CK2FileManager *)manager completionBlock:(void (^)(NSError *))block; - (id)initResourceValueSettingOperationWithURL:(NSURL *)url values:(NSDictionary *)keyedValues manager:(CK2FileManager *)manager completionBlock:(void (^)(NSError *))block; @end @interface CK2Protocol (Internals) + (Class)classForURL:(NSURL *)url; // only suitable for stateless calls to the protocol class @end #pragma mark - NSString * const CK2URLSymbolicLinkDestinationKey = @"CK2URLSymbolicLinkDestination"; @interface CK2FileManager() - (Class)classForOperation; @end @implementation CK2FileManager #pragma mark Creating a File Manager + (CK2FileManager *)fileManagerWithDelegate:(id )delegate delegateQueue:(NSOperationQueue *)queue; { return [[[self alloc] initWithDelegate:delegate delegateQueue:queue] autorelease]; } - initWithDelegate:(id )delegate delegateQueue:(NSOperationQueue *)queue; { if (self = [super init]) { // Create our own serial queue if needed _delegateQueue = [queue retain]; if (!_delegateQueue) { _delegateQueue = [[NSOperationQueue alloc] init]; _delegateQueue.maxConcurrentOperationCount = 1; } self.delegate = delegate; } return self; } - init; { return [self initWithDelegate:nil delegateQueue:nil]; } - (void)dealloc { [_delegateQueue release]; [super dealloc]; } #pragma mark Discovering Directory Contents - (CK2FileOperation *)contentsOfDirectoryAtURL:(NSURL *)url includingPropertiesForKeys:(NSArray *)keys options:(NSUInteger)mask completionHandler:(void (^)(NSArray *, NSError *))block; { NSMutableArray *contents = [[NSMutableArray alloc] init]; CK2FileOperation * result = [self enumerateContentsOfURL:url includingPropertiesForKeys:keys options:(mask|NSDirectoryEnumerationSkipsSubdirectoryDescendants) usingBlock:^(NSURL *aURL) { [contents addObject:aURL]; } completionHandler:^(NSError *error) { block((error ? nil : contents), // don't confuse clients should we have recieved only a partial listing error); [contents release]; }]; return result; } - (CK2FileOperation *)enumerateContentsOfURL:(NSURL *)url includingPropertiesForKeys:(NSArray *)keys options:(NSUInteger)mask usingBlock:(void (^)(NSURL *))block completionHandler:(void (^)(NSError *))completionBlock; { NSParameterAssert(url); CK2FileOperation *operation = [[[self classForOperation] alloc] initEnumerationOperationWithURL:url includingPropertiesForKeys:keys options:mask manager:self enumerationBlock:block completionBlock:completionBlock]; [operation resume]; return [operation autorelease]; } #pragma mark Creating and Deleting Items - (CK2FileOperation *)createDirectoryAtURL:(NSURL *)url withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes completionHandler:(void (^)(NSError *error))handler; { NSParameterAssert(url); CK2FileOperation *operation = [[[self classForOperation] alloc] initDirectoryCreationOperationWithURL:url withIntermediateDirectories:createIntermediates openingAttributes:attributes manager:self completionBlock:handler]; [operation resume]; return [operation autorelease]; } - (CK2FileOperation *)createFileAtURL:(NSURL *)url contents:(NSData *)data withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes progressBlock:(CK2ProgressBlock)progressBlock completionHandler:(void (^)(NSError *error))handler; { CK2FileOperation *result = [self createFileOperationWithURL:url fromData:data withIntermediateDirectories:createIntermediates openingAttributes:attributes progressBlock:progressBlock completionHandler:handler]; [result resume]; return result; } - (CK2FileOperation *)createFileOperationWithURL:(NSURL *)url fromData:(NSData *)data withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes completionHandler:(void (^)(NSError *error))handler; { return [self createFileOperationWithURL:url fromData:data withIntermediateDirectories:createIntermediates openingAttributes:attributes progressBlock:NULL completionHandler:handler]; } - (CK2FileOperation *)createFileOperationWithURL:(NSURL *)url fromData:(NSData *)data withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes progressBlock:(CK2ProgressBlock)progressBlock completionHandler:(void (^)(NSError *))handler; { CK2FileOperation *result = [[[self classForOperation] alloc] initFileCreationOperationWithURL:url data:data withIntermediateDirectories:createIntermediates openingAttributes:attributes manager:self progressBlock:progressBlock completionBlock:handler]; return [result autorelease]; } - (CK2FileOperation *)createFileAtURL:(NSURL *)destinationURL withContentsOfURL:(NSURL *)sourceURL withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes progressBlock:(CK2ProgressBlock)progressBlock completionHandler:(void (^)(NSError *error))handler; { CK2FileOperation *result = [self createFileOperationWithURL:destinationURL fromFile:sourceURL withIntermediateDirectories:createIntermediates openingAttributes:attributes progressBlock:progressBlock completionHandler:handler]; [result resume]; return result; } - (CK2FileOperation *)createFileOperationWithURL:(NSURL *)destinationURL fromFile:(NSURL *)sourceURL withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes completionHandler:(void (^)(NSError *error))handler; { __block CK2FileOperation *result = [self createFileOperationWithURL:destinationURL fromFile:sourceURL withIntermediateDirectories:createIntermediates openingAttributes:attributes progressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToSend) { id delegate = self.delegate; if ([delegate respondsToSelector:@selector(fileManager:operation:didWriteBodyData:totalBytesWritten:totalBytesExpectedToWrite:)]) { [delegate fileManager:self operation:result didWriteBodyData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToSend]; } } completionHandler:handler]; return result; } - (CK2FileOperation *)createFileOperationWithURL:(NSURL *)destinationURL fromFile:(NSURL *)sourceURL withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes progressBlock:(CK2ProgressBlock)progressBlock completionHandler:(void (^)(NSError *error))handler; { CK2FileOperation *operation = [[[self classForOperation] alloc] initFileCreationOperationWithURL:destinationURL file:sourceURL withIntermediateDirectories:createIntermediates openingAttributes:attributes manager:self progressBlock:progressBlock completionBlock:handler]; return [operation autorelease]; } - (CK2FileOperation *)removeItemAtURL:(NSURL *)url completionHandler:(void (^)(NSError *error))handler; { CK2FileOperation *operation = [self removeOperationWithURL:url completionHandler:handler]; [operation resume]; return operation; } - (CK2FileOperation *)removeOperationWithURL:(NSURL *)url completionHandler:(void (^)(NSError *))handler; { CK2FileOperation *operation = [[[self classForOperation] alloc] initRemovalOperationWithURL:url manager:self completionBlock:handler]; return [operation autorelease]; } #pragma mark Renaming Items - (CK2FileOperation *)renameItemAtURL:(NSURL *)srcURL toFilename:(NSString *)newName completionHandler:(void (^)(NSError *))handler { CK2FileOperation *operation = [[[self classForOperation] alloc] initRenameOperationWithSourceURL:srcURL newName:newName manager:self completionBlock:handler]; [operation resume]; return [operation autorelease]; } #pragma mark Getting and Setting Attributes - (CK2FileOperation *)setAttributesOperationWithURL:(NSURL *)url attributes:(NSDictionary *)keyedValues completionHandler:(void (^)(NSError *))handler { NSParameterAssert(url); NSParameterAssert(keyedValues); CK2FileOperation *operation = [[[self classForOperation] alloc] initResourceValueSettingOperationWithURL:url values:keyedValues manager:self completionBlock:handler]; return [operation autorelease]; } #pragma mark Delegate @synthesize delegate = _delegate; @synthesize delegateQueue = _delegateQueue; #pragma mark Operations - (Class)classForOperation { return [CK2FileOperation class]; } - (void)cancelOperation:(CK2FileOperation *)operation; { [operation cancel]; } @end @implementation CK2FileManager (URLs) #pragma mark URLs + (NSURL *)URLWithPath:(NSString *)path isDirectory:(BOOL)isDir hostURL:(NSURL *)baseURL; { NSParameterAssert(path); NSParameterAssert(baseURL); // Make a directory if demanded if (isDir && ![path hasSuffix:@"/"] && path.length) { path = [path stringByAppendingString:@"/"]; } // Strip down to just host URL CFIndex length = CFURLGetBytes((CFURLRef)baseURL, NULL, 0); CFRange pathRange = CFURLGetByteRangeForComponent((CFURLRef)baseURL, kCFURLComponentPath, NULL); if (pathRange.location != kCFNotFound && pathRange.location < length) { NSMutableData *data = [[NSMutableData alloc] initWithLength:pathRange.location]; CFURLGetBytes((CFURLRef)baseURL, data.mutableBytes, pathRange.location); NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; baseURL = [NSURL URLWithString:string]; [string release]; [data release]; } NSURL *result = [self URLWithPath:path relativeToURL:baseURL].absoluteURL; // Make sure is a directory if requested if (isDir) NSAssert(CFURLHasDirectoryPath((CFURLRef)result), @"Not a directory: %@", result); return result; } + (NSURL *)URLWithPath:(NSString *)path relativeToURL:(NSURL *)baseURL; { Class protocolClass = [CK2Protocol classForURL:baseURL]; if (!protocolClass) { protocolClass = [CK2Protocol class]; if ([path isAbsolutePath]) { // On 10.6, file URLs sometimes behave strangely when combined with an absolute path. Force it to be resolved if ([baseURL isFileURL]) [baseURL absoluteString]; } } return [protocolClass URLWithPath:path relativeToURL:baseURL]; } + (NSString *)pathOfURL:(NSURL *)URL; { Class protocolClass = [CK2Protocol classForURL:URL]; if (!protocolClass) protocolClass = [CK2Protocol class]; NSString *result = [protocolClass pathOfURLRelativeToHomeDirectory:URL]; // Forcefully strip trailing slashes NSUInteger length = result.length; if (length >= 2) // ignore leading slash { NSRange searchRange = NSMakeRange(1, length - 1); // ignore leading slash do { NSRange trailingSlashRange = [result rangeOfString:@"/" options:NSBackwardsSearch|NSAnchoredSearch range:searchRange]; if (trailingSlashRange.location == NSNotFound) break; result = [result substringToIndex:trailingSlashRange.location]; searchRange.length -= trailingSlashRange.length; } while (searchRange.length); } return result; } + (void)setTemporaryResourceValue:(id)value forKey:(NSString *)key inURL:(NSURL *)url; { // File URLs are already handled by the system // Ideally, would use CFURLSetTemporaryResourcePropertyForKey() first for all URLs as a test, but on 10.7.5 at least, it crashes with non-file URLs if ([url isFileURL]) { CFURLSetTemporaryResourcePropertyForKey((CFURLRef)value, (CFStringRef)key, value); } else { [self setTemporaryResourceValueForKey:key inURL:url asBlock:^id{ return value; }]; } } // The block is responsible for returning the value on-demand + (void)setTemporaryResourceValueForKey:(NSString *)key inURL:(NSURL *)url asBlock:(id (^)(void))block; { // Store the block as an associated object objc_setAssociatedObject(url, key, block, OBJC_ASSOCIATION_COPY); // Swizzle so getter method includes cache in its search static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = NSURL.class; Method originalMethod = class_getInstanceMethod(class, @selector(getResourceValue:forKey:error:)); Method overrideMethod = class_getInstanceMethod(class, @selector(ck2_getResourceValue:forKey:error:)); method_exchangeImplementations(originalMethod, overrideMethod); }); } /*! @method canHandleURL: @abstract Performs a "preflight" operation that performs some speculative checks to see if a URL has a suitable protocol registered to handle it. @discussion The result of this method is valid only as long as no protocols are registered or unregistered, and as long as the request is not mutated (if the request is mutable). Hence, clients should be prepared to handle failures even if they have performed request preflighting by calling this method. @param url The URL to preflight. @result YES if it is likely that the given request can be used to perform a file operation and the associated I/O can be started */ + (BOOL)canHandleURL:(NSURL *)url; { return ([CK2Protocol classForURL:url] != nil); } @end #pragma mark - @implementation NSURL (CK2TemporaryResourceProperties) #pragma mark Getting and Setting File System Resource Properties - (BOOL)ck2_getResourceValue:(out id *)value forKey:(NSString *)key error:(out NSError **)error; { // Special case, as for the setter method if ([self isFileURL]) { return [self ck2_getResourceValue:value forKey:key error:error]; // calls the original implementation } // See if key has been cached id (^block)(void) = objc_getAssociatedObject(self, key); if (block) { *value = block(); return YES; } // A few special keys we generate on-demand pretty much by guessing since the server isn't up to providing that sort of info if ([key isEqualToString:NSURLHasHiddenExtensionKey]) { *value = [NSNumber numberWithBool:NO]; return YES; } else if ([key isEqualToString:NSURLLocalizedNameKey]) { *value = [self lastPathComponent]; return YES; } // Have to define NSURLPathKey as a macro for older releases: #if (!defined MAC_OS_X_VERSION_10_8) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 #define NSURLPathKey @"_NSURLPathKey" #endif else if ([key isEqualToString:NSURLPathKey]) { *value = [CK2FileManager pathOfURL:self]; return YES; } #undef NSURLPathKey else if ([key isEqualToString:NSURLIsPackageKey]) { NSString *extension; *value = @NO; extension = [self pathExtension]; if ([extension length] > 0) { NSArray *baseUTIs; baseUTIs = (NSArray *)UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, (CFStringRef)extension, NULL); for (NSString *uti in baseUTIs) { if (UTTypeConformsTo((CFStringRef)uti, CFSTR("com.apple.package"))) { *value = @YES; break; } } [baseUTIs release]; } return YES; } else { return [self ck2_getResourceValue:value forKey:key error:error]; // calls the original implementation } return YES; } @end ================================================ FILE: ConnectionKit/CK2FileManagerWithTestSupport.h ================================================ // // Created by Sam Deane on 27/03/2013. // Copyright (c) 2013 Karelia Software. All rights reserved. #import "CK2FileManager.h" @class CURLTransferStack; /** CK2FileManager with some additional API for test purposes. */ @interface CK2FileManagerWithTestSupport : CK2FileManager { BOOL _dontShareConnections; CURLTransferStack* _multi; } /** An alternative CURLMultiHandle to use instead of the default one. This is generated on demand, if dontShareConnections is set. */ @property (strong, readonly, nonatomic) CURLTransferStack* multi; /** Set this property to force CURL based protocols use an alternative CURLTransfer instead of the default one */ @property (assign, nonatomic) BOOL dontShareConnections; @end @interface NSURLRequest(CK2FileManagerDebugging) - (CURLTransferStack*)ck2_multi; @end @interface NSMutableURLRequest(CK2FileManagerDebugging) - (void)ck2_setMulti:(CURLTransferStack*)multi; @end ================================================ FILE: ConnectionKit/CK2FileManagerWithTestSupport.m ================================================ // // Created by Sam Deane on 27/03/2013. // Copyright (c) 2013 Karelia Software. All rights reserved. #import "CK2FileManagerWithTestSupport.h" #import "CK2FileOperationWithTestSupport.h" #import @interface CURLTransfer (Testing) + (void)cleanupStandaloneMulti:(CURLTransferStack *)multi; + (CURLTransferStack *)standaloneMultiForTestPurposes; @end @implementation CK2FileManagerWithTestSupport @synthesize dontShareConnections = _dontShareConnections; @synthesize multi = _multi; - (void)dealloc { [CURLTransfer cleanupStandaloneMulti:_multi]; [_multi release]; [super dealloc]; } - (Class)classForOperation { return [CK2FileOperationWithTestSupport class]; } - (CURLTransferStack*)multi { if (_dontShareConnections && !_multi) { _multi = [[CURLTransfer standaloneMultiForTestPurposes] retain]; } return _dontShareConnections ? _multi : nil; } @end @implementation NSURLRequest(CK2FileManagerDebugging) - (CURLTransferStack*)ck2_multi { return [NSURLProtocol propertyForKey:@"ck2_multi" inRequest:self]; } @end @implementation NSMutableURLRequest(CK2FileManagerDebugging) - (void)ck2_setMulti:(CURLTransferStack*)multi { [NSURLProtocol setProperty:multi forKey:@"ck2_multi" inRequest:self]; } @end ================================================ FILE: ConnectionKit/CK2FileOperation.h ================================================ // // CK2FileOperation.h // Connection // // Created by Mike on 22/03/2013. // // #import "CK2FileManager.h" @class CK2Protocol; typedef NS_ENUM(NSInteger, CK2FileOperationState) { CK2FileOperationStateRunning = 0, /* The operation is currently being serviced by the file manager */ CK2FileOperationStateSuspended = 1, /* The operation is yet to start. */ CK2FileOperationStateCanceling = 2, /* The operation has been told to cancel and will complete shortly. */ CK2FileOperationStateCompleted = 3, /* The operation has completed and the file manager will receive no more delegate notifications */ }; @class CK2FileOperationCallbacks; /** All @properties are KVO-compliant. */ @interface CK2FileOperation : NSObject // retains self when copying { @private CK2FileManager *_manager; NSURL *_originalURL; NSString *_descriptionForErrors; dispatch_queue_t _queue; CK2Protocol *_protocol; CK2FileOperationCallbacks *_callbacks; void (^_completionBlock)(NSError *); void (^_enumerationBlock)(NSURL *); NSURL *_localURL; int64_t _bytesWritten; int64_t _bytesExpectedToWrite; CK2ProgressBlock _progressBlock; // Temporary hack which gets us to fire off extra directory creating requests should the main op fail BOOL _createIntermediateDirectories; CK2FileOperationState _state; NSError *_error; } /** @return a deep copy of the original connection request. You can think of this as the "primary" URL for a given operation. Normally this is fairly obvious: if uploading, it's the URL being uploaded to. When we come to support downloads, it's the URL being downloaded from. This can potentially get a bit tricky doing something like renaming/moving a file; in which case, this URL will be that of the _source_ file. ConnectionKit doesn't currently support redirects, but were it to, this URL would remain constant and we'd likely introduce a new `currentURL` property for retrieving the redirected URL if need be. */ @property (readonly, copy) NSURL *originalURL; /** * Number of body bytes already written. * * Excludes any headers, such as in HTTP messages, or FTP control connection. */ @property (readonly) int64_t countOfBytesWritten; /** * Number of body bytes we expect to write. * * Excludes any headers, such as in HTTP messages, or FTP control connection. */ @property (readonly) int64_t countOfBytesExpectedToWrite; /** * `-cancel` returns immediately, but marks an operation as being canceled. * The operation will signal its completion handler with an * error value of `{ NSURLErrorDomain, NSURLErrorCancelled }`. In some * cases, the operation may signal other work before it acknowledges the * cancelation. */ - (void)cancel; /** * The current state of the operation. */ @property (readonly) CK2FileOperationState state; /** * The error, if any. Also delivered to completion handler. * This property will be `nil` in the event that no error occured. */ @property (readonly, copy) NSError *error; /** Sets an operation going if it hasn't already. */ - (void)resume; @end ================================================ FILE: ConnectionKit/CK2FileOperation.m ================================================ // // CK2FileOperation.m // Connection // // Created by Mike on 22/03/2013. // // #import "CK2FileOperation.h" #import "CK2Protocol.h" #import // so icon handling can use NSImage and NSWorkspace for now @interface CK2FileOperationCallbacks : NSObject { CK2Protocol *(^_protocolCreator)(CK2FileOperation *fileOp, Class protocolClass); } /** @param protocolCreator The block is passed the file operation it applies to — so don't wind up with a retain cycle — and the class of the protocol to be created. */ + (instancetype)callbacksWithProtocolCreator:(CK2Protocol *(^)(CK2FileOperation *fileOp, Class protocolClass))protocolCreator; - (CK2Protocol *)createProtocolForFileOperation:(CK2FileOperation *)fileOp class:(Class)protocolClass NS_RETURNS_RETAINED; @end #pragma mark - @interface CK2FileOperation () - (id)initWithURL:(NSURL *)url errorDescription:(NSString *)errorDescription manager:(CK2FileManager *)manager completionHandler:(void (^)(NSError *))completionBlock callbacks:(CK2FileOperationCallbacks *)callbacks NS_DESIGNATED_INITIALIZER; @property(readonly) CK2FileManager *fileManager; // goes to nil once finished/failed @property (readwrite) int64_t countOfBytesWritten; @property (readwrite) int64_t countOfBytesExpectedToWrite; @property(readwrite) CK2FileOperationState state; @property (readwrite, copy) NSError *error; @end #pragma mark - @interface CK2Protocol (Internals) // Completion block is guaranteed to be called on our private serial queue + (void)classForURL:(NSURL *)url completionHandler:(void (^)(Class protocolClass))block; @end @interface CK2FileManager (Internals) + (void)setTemporaryResourceValueForKey:(NSString *)key inURL:(NSURL *)url asBlock:(id (^)(void))block; @end #pragma mark - @implementation CK2FileOperation #pragma mark Lifecycle - (id)initWithURL:(NSURL *)url errorDescription:(NSString *)errorDescription manager:(CK2FileManager *)manager completionHandler:(void (^)(NSError *))completionBlock callbacks:(CK2FileOperationCallbacks *)callbacks; { NSParameterAssert(url); NSParameterAssert(manager); if (self = [super init]) { _state = CK2FileOperationStateSuspended; _manager = [manager retain]; _originalURL = [url copy]; _descriptionForErrors = [errorDescription copy]; if (!completionBlock) { completionBlock = ^(NSError *error) { id delegate = manager.delegate; if ([delegate respondsToSelector:@selector(fileManager:operation:didCompleteWithError:)]) { [delegate fileManager:manager operation:self didCompleteWithError:error]; } }; } _completionBlock = [completionBlock copy]; _callbacks = [callbacks retain]; _queue = dispatch_queue_create("com.karelia.connection.file-operation", NULL); } return self; } - (id)initEnumerationOperationWithURL:(NSURL *)url includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask manager:(CK2FileManager *)manager enumerationBlock:(void (^)(NSURL *))enumBlock completionBlock:(void (^)(NSError *))block; { NSString *name = url.lastPathComponent; NSString *description; if (name.length) { description = [NSString stringWithFormat:NSLocalizedString(@"The folder “%@” could not be accessed.", "error descrption"), url.lastPathComponent]; } else { description = NSLocalizedString(@"The server could not be accessed.", "error description"); } CK2FileOperationCallbacks *callbacks = [CK2FileOperationCallbacks callbacksWithProtocolCreator:^CK2Protocol *(CK2FileOperation *fileOp, Class protocolClass) { // If we try to do this outside the block there's a risk the protocol object will be created *before* the enum block has been stored, which ends real badly fileOp->_enumerationBlock = [enumBlock copy]; return [[protocolClass alloc] initForEnumeratingDirectoryWithRequest:[fileOp requestWithURL:url] includingPropertiesForKeys:keys options:mask client:fileOp]; }]; return [self initWithURL:url errorDescription:description manager:manager completionHandler:block callbacks:callbacks]; } - (id)initDirectoryCreationOperationWithURL:(NSURL *)url withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes manager:(CK2FileManager *)manager completionBlock:(void (^)(NSError *))block; { NSString *description = [NSString stringWithFormat:NSLocalizedString(@"The folder “%@” could not be created.", "error descrption"), url.lastPathComponent]; CK2FileOperationCallbacks *callbacks = [CK2FileOperationCallbacks callbacksWithProtocolCreator:^CK2Protocol *(CK2FileOperation *fileOp, Class protocolClass) { return [[protocolClass alloc] initForCreatingDirectoryWithRequest:[fileOp requestWithURL:url] withIntermediateDirectories:createIntermediates openingAttributes:attributes client:fileOp]; }]; self = [self initWithURL:url errorDescription:description manager:manager completionHandler:block callbacks:callbacks]; // Special case SFTP for now. if ([url.scheme caseInsensitiveCompare:@"sftp"] == NSOrderedSame) { _createIntermediateDirectories = createIntermediates; } return self; } - (id)initFileCreationOperationWithURL:(NSURL *)url data:(NSData *)data withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes manager:(CK2FileManager *)manager progressBlock:(CK2ProgressBlock)progressBlock completionBlock:(void (^)(NSError *))block; { NSString *description = [NSString stringWithFormat:NSLocalizedString(@"The file “%@” could not be uploaded.", "error description"), url.lastPathComponent]; CK2FileOperationCallbacks *callbacks = [CK2FileOperationCallbacks callbacksWithProtocolCreator:^CK2Protocol *(CK2FileOperation *fileOp, Class protocolClass) { NSMutableURLRequest *request = [[fileOp requestWithURL:url] mutableCopy]; request.HTTPBody = data; CK2Protocol *result = [[protocolClass alloc] initForCreatingFileWithRequest:request size:data.length withIntermediateDirectories:createIntermediates openingAttributes:attributes client:fileOp]; [request release]; return result; }]; self = [self initWithURL:url errorDescription:description manager:manager completionHandler:block callbacks:callbacks]; _bytesExpectedToWrite = data.length; _progressBlock = [progressBlock copy]; // Special case SFTP for now. if ([url.scheme caseInsensitiveCompare:@"sftp"] == NSOrderedSame) { _createIntermediateDirectories = createIntermediates; } return self; } - (id)initFileCreationOperationWithURL:(NSURL *)url file:(NSURL *)sourceURL withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes manager:(CK2FileManager *)manager progressBlock:(CK2ProgressBlock)progressBlock completionBlock:(void (^)(NSError *))block; { NSString *description = [NSString stringWithFormat:NSLocalizedString(@"The file “%@” could not be uploaded.", "error descrption"), url.lastPathComponent]; NSNumber *fileSize; if (![sourceURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL]) fileSize = nil;; CK2FileOperationCallbacks *callbacks = [CK2FileOperationCallbacks callbacksWithProtocolCreator:^CK2Protocol *(CK2FileOperation *fileOp, Class protocolClass) { fileOp->_localURL = [sourceURL copy]; NSMutableURLRequest *request = [[fileOp requestWithURL:url] mutableCopy]; // Read the data using an input stream if possible, and know file size int64_t size = (fileSize ? fileSize.longLongValue : NSURLResponseUnknownLength); if (size >= 0) { NSInputStream *stream = [fileOp protocol:nil needNewBodyStream:nil]; if (stream) { [request setHTTPBodyStream:stream]; } } if (!request.HTTPBodyStream) { NSError *error; NSData *data = [[NSData alloc] initWithContentsOfURL:sourceURL options:0 error:&error]; if (data) { [request setHTTPBody:data]; size = data.length; [data release]; } else { [request release]; if (!error) error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadUnknownError userInfo:nil]; [fileOp protocol:nil didCompleteWithError:error]; return nil; } } CK2Protocol *result = [[protocolClass alloc] initForCreatingFileWithRequest:request size:size withIntermediateDirectories:createIntermediates openingAttributes:attributes client:fileOp]; [request release]; return result; }]; self = [self initWithURL:url errorDescription:description manager:manager completionHandler:block callbacks:callbacks]; _bytesExpectedToWrite = fileSize.longLongValue; _progressBlock = [progressBlock copy]; // Special case SFTP for now. if ([url.scheme caseInsensitiveCompare:@"sftp"] == NSOrderedSame) { _createIntermediateDirectories = createIntermediates; } return self; } - (id)initRemovalOperationWithURL:(NSURL *)url manager:(CK2FileManager *)manager completionBlock:(void (^)(NSError *))block; { NSString *description = [NSString stringWithFormat:NSLocalizedString(@"The file “%@” could not be deleted.", "error descrption"), url.lastPathComponent]; CK2FileOperationCallbacks *callbacks = [CK2FileOperationCallbacks callbacksWithProtocolCreator:^CK2Protocol *(CK2FileOperation *fileOp, Class protocolClass) { return [[protocolClass alloc] initForRemovingItemWithRequest:[fileOp requestWithURL:url] client:fileOp]; }]; return [self initWithURL:url errorDescription:description manager:manager completionHandler:block callbacks:callbacks]; } - (id)initRenameOperationWithSourceURL:(NSURL *)srcURL newName:(NSString *)newName manager:(CK2FileManager *)manager completionBlock:(void (^)(NSError *))block; { NSString *description = [NSString stringWithFormat:NSLocalizedString(@"The file “%@” could not be renamed.", "error descrption"), srcURL.lastPathComponent]; CK2FileOperationCallbacks *callbacks = [CK2FileOperationCallbacks callbacksWithProtocolCreator:^CK2Protocol *(CK2FileOperation *fileOp, Class protocolClass) { return [[protocolClass alloc] initForRenamingItemWithRequest:[fileOp requestWithURL:srcURL] newName:newName client:fileOp]; }]; return [self initWithURL:srcURL errorDescription:description manager:manager completionHandler:block callbacks:callbacks]; } - (id)initResourceValueSettingOperationWithURL:(NSURL *)url values:(NSDictionary *)keyedValues manager:(CK2FileManager *)manager completionBlock:(void (^)(NSError *))block; { NSString *description = [NSString stringWithFormat:NSLocalizedString(@"The file “%@” could not be updated.", "error descrption"), url.lastPathComponent]; CK2FileOperationCallbacks *callbacks = [CK2FileOperationCallbacks callbacksWithProtocolCreator:^CK2Protocol *(CK2FileOperation *fileOp, Class protocolClass) { return [[protocolClass alloc] initForSettingAttributes:keyedValues ofItemWithRequest:[fileOp requestWithURL:url] client:fileOp]; }]; return [self initWithURL:url errorDescription:description manager:manager completionHandler:block callbacks:callbacks]; } - (void)completeWithError:(NSError *)error; { // Run completion block on own queue so that: // A) It doesn't potentially hold up the calling queue for too long // B) Serialises access dispatch_async(_queue, ^{ if (_completionBlock) // only allow "completion" to happen the once! { // It's now safe to stop the protocol as it can't misinterpret the message and issue its own cancellation error (or at least if it does, goes ignored) [_protocol stop]; // Store the error and notify completion handler // Make all notifications — including KVO — happen on the delegate queue // Grab the handler now since we're about to clear out the original storage. The // delegate block should capture this so we don't need to retain it ourselves void (^handler)(NSError *) = _completionBlock; [self tryToMessageDelegateSelector:NULL usingBlock:^(id delegate) { // NULL selector so always executes self.error = error; self.state = CK2FileOperationStateCompleted; handler(error); }]; // Clean up too so as to break retain cycles. HAS to happen within this block (and not // e.g. during the delegate call back) so _completionBlock is cleared out and never // allowed to run twice [_completionBlock release]; _completionBlock = nil; [_progressBlock release]; _progressBlock = nil; [_enumerationBlock release];_enumerationBlock = nil; // Break retain cycle, but deliberately keep weak reference so we know we're associated with it [_protocol release]; } }); } - (void)dealloc { //[_protocol release]; DON'T release protocol. It should be a weak reference by the time deallocation happens [_manager release]; [_originalURL release]; if (_queue) dispatch_release(_queue); [_completionBlock release]; [_enumerationBlock release]; [_callbacks release]; [_progressBlock release]; [_localURL release]; [_error release]; [super dealloc]; } #pragma mark Manager @synthesize fileManager = _manager; - (void)tryToMessageDelegateSelector:(SEL)selector usingBlock:(void (^)(id delegate))block; { CK2FileManager *manager = self.fileManager; NSAssert(manager, @"%@ disconnected from its manager too early", self.class); // Clients could change the delegate at any time. If we trust them to do so in concert with the // delegate queue, then that can be made reasonably safe by only accessing the delegate from // within the queue. // It's still inherently a bit dangerous though, as the client could change it on a different // queue, or could have specified a non-serial delegate queue. [manager.delegateQueue addOperationWithBlock:^{ id delegate = manager.delegate; if (!selector || [delegate respondsToSelector:selector]) { block(delegate); } }]; } #pragma mark URL & Requests @synthesize originalURL = _originalURL; - (NSURLRequest *)requestWithURL:(NSURL *)url; { return [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:60.0]; } #pragma mark Body Data @synthesize countOfBytesWritten = _bytesWritten; @synthesize countOfBytesExpectedToWrite = _bytesExpectedToWrite; #pragma mark Cancellation - (void)cancel; { if (self.state >= CK2FileOperationStateCanceling) return; /* Any already-enqueued delegate messages will likely still run. That's fine as it seems we might as well report things that are already known to have happened */ // FIXME: There's a race condition here where .state could change after our intitial check of it self.state = CK2FileOperationStateCanceling; // Report cancellation to completion handler. If protocol has already finished or failed, it'll go ignored NSError *cancellationError = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:nil]; [self completeWithError:cancellationError]; [cancellationError release]; } #pragma mark State @synthesize state = _state; @synthesize error = _error; - (void)resume; { if (self.state == CK2FileOperationStateSuspended) { self.state = CK2FileOperationStateRunning; [self createProtocolAndStart]; } } /** Private method that does the work of creating the protocol instance, and getting it going */ - (void)createProtocolAndStart { NSURL *url = self.originalURL; [CK2Protocol classForURL:url completionHandler:^(Class protocolClass) { if (protocolClass) { // Bounce over to operation's own queue for kicking off the real work // Keep an eye out for early opportunity to bail out if get cancelled dispatch_async(_queue, ^{ if (self.state == CK2FileOperationStateRunning) { NSAssert(_protocol == nil, @"Protocol has already been created"); _protocol = [_callbacks createProtocolForFileOperation:self class:protocolClass]; if (!_protocol) { // it's likely that the protocol has already called protocol:didFailWithError:, which will have called finishWithError:, which means that a call to the completion // block is queue up already with an error in it // just in case though, we can report a more generic error here - once the completion block is called once it will be cleared out, the protocol's error will win // if there is one NSDictionary *info = @{NSURLErrorKey : url, NSURLErrorFailingURLErrorKey : url, NSURLErrorFailingURLStringErrorKey : [url absoluteString]}; NSError *error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorUnsupportedURL userInfo:info]; // TODO: what's the correct error to report here? [self completeWithError:error]; [error release]; } if (self.state == CK2FileOperationStateRunning) [_protocol start]; } }); } else { NSDictionary *info = @{NSURLErrorKey : url, NSURLErrorFailingURLErrorKey : url, NSURLErrorFailingURLStringErrorKey : [url absoluteString]}; NSError *error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorUnsupportedURL userInfo:info]; [self completeWithError:error]; [error release]; } }]; } #pragma mark CK2ProtocolClient - (void)protocol:(CK2Protocol *)protocol didCompleteWithError:(NSError *)error; { NSAssert(protocol == _protocol, @"Message received from unexpected protocol: %@ (should be %@)", protocol, _protocol); // Errors should start with our description if (error) { if (_createIntermediateDirectories) { NSString *path = [CK2FileManager pathOfURL:self.originalURL]; if (path.length && ![path isEqualToString:@"/"]) { NSURL *directoryURL = [self.originalURL URLByDeletingLastPathComponent]; _createIntermediateDirectories = NO; // avoid doing this again [self.fileManager createDirectoryAtURL:directoryURL withIntermediateDirectories:YES openingAttributes:nil // probably ought to provide something better, but I'm being lazy completionHandler:^(NSError *directoryError) { // If creating directory also fails, give up. Otherwise let's try again! if (directoryError) { [self protocol:protocol didCompleteWithError:error]; } else { [_protocol release]; _protocol = nil; [self createProtocolAndStart]; } }]; return; } } if (_descriptionForErrors) { NSMutableDictionary *info = [error.userInfo mutableCopy]; NSString *description = [_descriptionForErrors stringByAppendingFormat:@" %@", error.localizedDescription]; [info setObject:description forKey:NSLocalizedDescriptionKey]; error = [NSError errorWithDomain:error.domain code:error.code userInfo:info]; [info release]; } } [self completeWithError:error]; } - (NSURLRequest *)protocol:(CK2Protocol *)protocol willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response; { NSAssert(protocol == _protocol, @"Message received from unexpected protocol: %@ (should be %@)", protocol, _protocol); id delegate = self.fileManager.delegate; if ([delegate respondsToSelector:@selector(fileManager:operation:willSendRequest:redirectResponse:completionHandler:)]) { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); __block NSURLRequest *weakRequest; [delegate fileManager:self.fileManager operation:self willSendRequest:request redirectResponse:response completionHandler:^(NSURLRequest *request) { weakRequest = [request retain]; dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); request = [weakRequest autorelease]; dispatch_release(semaphore); } return request; } - (void)protocol:(CK2Protocol *)protocol didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential *))completionHandler; { NSAssert(protocol == _protocol, @"Message received from unexpected protocol: %@ (should be %@)", protocol, _protocol); if (self.state >= CK2FileOperationStateCanceling) return; // don't care about auth once cancelled // Invent a default credential if needed if (!challenge.proposedCredential) { NSURLProtectionSpace *space = challenge.protectionSpace; NSString *user = self.originalURL.user; NSString *password = self.originalURL.password; NSURLCredential *credential; if (user) { if (password) { credential = [NSURLCredential credentialWithUser:user password:password persistence:NSURLCredentialPersistenceNone]; } else { credential = [[NSURLCredentialStorage.sharedCredentialStorage credentialsForProtectionSpace:space] objectForKey:user]; } } else { credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:space]; } challenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:credential previousFailureCount:challenge.previousFailureCount failureResponse:challenge.failureResponse error:challenge.error sender:challenge.sender]; [challenge autorelease]; } // Notify the delegate CK2FileManager *manager = self.fileManager; id delegate = manager.delegate; if ([delegate respondsToSelector:@selector(fileManager:operation:didReceiveChallenge:completionHandler:)]) { [manager.delegateQueue addOperationWithBlock:^{ __block BOOL handlerCalled = NO; [delegate fileManager:manager operation:self didReceiveChallenge:challenge completionHandler:^(CK2AuthChallengeDisposition disposition, NSURLCredential *credential) { if (handlerCalled) [NSException raise:NSInvalidArgumentException format:@"Auth Challenge completion handler block called more than once"]; handlerCalled = YES; dispatch_async(_queue, ^{ completionHandler(disposition, credential); }); }]; }]; } else if ([delegate respondsToSelector:@selector(fileManager:didReceiveAuthenticationChallenge:)]) { NSLog(@"%@ implements the old CK2FileManager authentication delegate method instead of the new one", delegate.class); } else { dispatch_async(_queue, ^{ completionHandler(CK2AuthChallengePerformDefaultHandling, nil); }); } // TODO: Cache credentials per protection space } - (void)protocol:(CK2Protocol *)protocol appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; { NSAssert(protocol == _protocol, @"Message received from unexpected protocol: %@ (should be %@)", protocol, _protocol); // Pass straight onto delegate and trust it not to take too long handling it // We used to dispatch off onto one of the global queues, but that does have the nasty downside of messages sometimes arriving out-of-order or concurrently [self tryToMessageDelegateSelector:@selector(fileManager:appendString:toTranscript:) usingBlock:^(id delegate) { [delegate fileManager:self.fileManager appendString:info toTranscript:transcript]; }]; } - (void)protocol:(CK2Protocol *)protocol didDiscoverItemAtURL:(NSURL *)url; { NSAssert(protocol == _protocol, @"Message received from unexpected protocol: %@ (should be %@)", protocol, _protocol); // Even if cancelled, allow through as the discovery still stands; might be useful for caching elsewhere // Provide ancestry and other fairly generic keys on-demand [self.class setResourceValueBlocksForURL:url protocolClass:protocol.class]; [self tryToMessageDelegateSelector:NULL usingBlock:^(id delegate) { if (_enumerationBlock) _enumerationBlock(url); }]; // It seems poor security to vend out passwords here, so have a quick sanity check if (CFURLGetByteRangeForComponent((CFURLRef)url, kCFURLComponentPassword, NULL).location != kCFNotFound) { NSLog(@"%@ is reporting URLs with a password, such as %@\nThis seems poor security practice", protocol, url); } } + (void)setResourceValueBlocksForURL:(NSURL *)strongURL protocolClass:(Class)protocolClass; { __block NSURL *url = strongURL; // URL retains its resource values; so if the blocks retained the URL would be a cycle NSString *path = [protocolClass pathOfURLRelativeToHomeDirectory:url]; if ([path isAbsolutePath]) { [CK2FileManager setTemporaryResourceValueForKey:NSURLParentDirectoryURLKey inURL:url asBlock:^id { if (path.pathComponents.count > 1) // stop at root { NSURL *result = [url URLByDeletingLastPathComponent]; [CK2FileManager setTemporaryResourceValue:@YES forKey:NSURLIsDirectoryKey inURL:result]; // Recurse [self setResourceValueBlocksForURL:result protocolClass:protocolClass]; return result; } return nil; }]; // Only need supply icon if protocol hasn't done so NSImage *icon; if (![url getResourceValue:&icon forKey:NSURLEffectiveIconKey error:NULL] || !icon) { // Fill in icon as best we can [CK2FileManager setTemporaryResourceValueForKey:NSURLEffectiveIconKey inURL:url asBlock:^id{ NSString *fileType = url.pathExtension; if (path.pathComponents.count == 1) { fileType = NSFileTypeForHFSTypeCode(kGenericFileServerIcon); } else if ([fileType isEqual:@"app"]) { fileType = NSFileTypeForHFSTypeCode(kGenericApplicationIcon); } else { NSNumber *isDirectory; if (![url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL] || isDirectory == nil) { isDirectory = @(CFURLHasDirectoryPath((CFURLRef)url)); } // Guess based on file type if (isDirectory.boolValue) { if ([protocolClass isHomeDirectoryAtURL:url]) { fileType = NSFileTypeForHFSTypeCode(kUserFolderIcon); } else { NSNumber *package; if (![url getResourceValue:&package forKey:NSURLIsPackageKey error:NULL] || !package.boolValue) { return [NSImage imageNamed:NSImageNameFolder]; } } } } return [[NSWorkspace sharedWorkspace] iconForFileType:fileType]; }]; } } } - (void)protocol:(CK2Protocol *)protocol didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend; { self.countOfBytesWritten = totalBytesSent; self.countOfBytesExpectedToWrite = totalBytesExpectedToSend; CK2FileManager *manager = self.fileManager; [manager.delegateQueue addOperationWithBlock:^{ if (_progressBlock) { _progressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend); } else { id delegate = manager.delegate; if ([delegate respondsToSelector:@selector(fileManager:operation:didWriteBodyData:totalBytesWritten:totalBytesExpectedToWrite:)]) { [delegate fileManager:manager operation:self didWriteBodyData:bytesSent totalBytesWritten:totalBytesSent totalBytesExpectedToWrite:totalBytesExpectedToSend]; } } }]; } - (NSInputStream *)protocol:(CK2Protocol *)protocol needNewBodyStream:(NSURLRequest *)request; { NSAssert(protocol == _protocol, @"Message received from unexpected protocol: %@ (should be %@)", protocol, _protocol); NSInputStream *stream = [[NSInputStream alloc] initWithURL:_localURL]; return [stream autorelease]; } #pragma mark NSCopying - (id)copyWithZone:(NSZone *)zone; { // For easy stashing in dictionaries return [self retain]; } @end #pragma mark - @implementation CK2FileOperationCallbacks + (instancetype)callbacksWithProtocolCreator:(CK2Protocol *(^)(CK2FileOperation *, Class))protocolCreator { CK2FileOperationCallbacks *result = [[self alloc] init]; result->_protocolCreator = [protocolCreator copy]; return [result autorelease]; } - (CK2Protocol *)createProtocolForFileOperation:(CK2FileOperation *)fileOp class:(Class)protocolClass { return _protocolCreator(fileOp, protocolClass); } @end ================================================ FILE: ConnectionKit/CK2FileOperationWithTestSupport.h ================================================ // // Created by Sam Deane on 27/03/2013. // Copyright (c) 2013 Karelia Software. All rights reserved. #import "CK2FileOperation.h" @interface CK2FileOperationWithTestSupport : CK2FileOperation @end ================================================ FILE: ConnectionKit/CK2FileOperationWithTestSupport.m ================================================ // // Created by Sam Deane on 27/03/2013. // Copyright (c) 2013 Karelia Software. All rights reserved. #import "CK2FileOperationWithTestSupport.h" #import "CK2FileManagerWithTestSupport.h" @implementation CK2FileOperationWithTestSupport - (NSURLRequest *)requestWithURL:(NSURL *)url; { NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:60.0]; CK2FileManagerWithTestSupport* manager = [self valueForKey:@"_manager"]; if (manager.dontShareConnections) { [request ck2_setMulti:manager.multi]; } return request; } @end ================================================ FILE: ConnectionKit/CK2FileProtocol.h ================================================ // // CK2FileProtocol.h // Connection // // Created by Mike on 18/10/2012. // // #import "CK2Protocol.h" @interface CK2FileProtocol : CK2Protocol { @private void (^_block)(void); BOOL _cancelled; dispatch_queue_t _queue; int64_t _bytesExpectedToWrite; } @end ================================================ FILE: ConnectionKit/CK2FileProtocol.m ================================================ // // CK2FileProtocol.m // Connection // // Created by Mike on 18/10/2012. // // #import "CK2FileProtocol.h" #import "CK2CURLBasedProtocol.h" // alternate implementations for initForCreatingFileWithRequest // whilst we're developing, I'm keeping around the code for all of them typedef enum { kCreateWithCURL, kCreateWithStreams, kCreateWithPOSIXAndGCD } CreateMode; static const CreateMode kCreateMode = kCreateWithPOSIXAndGCD; static size_t kCopyBufferSize = 4096; @implementation CK2FileProtocol + (BOOL)canHandleURL:(NSURL *)url; { return [url isFileURL]; } - (id)initWithBlock:(void (^)(void))block; { if (self = [self init]) { NSAssert(block != nil, @"should have a valid block"); _block = [block copy]; } return self; } - (void)dealloc { if (_queue) { dispatch_release(_queue); } [_block release]; [super dealloc]; } - (id)initForEnumeratingDirectoryWithRequest:(NSURLRequest *)request includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask client:(id)client; { return [self initWithBlock:^{ // Report the main directory first if (mask & CK2DirectoryEnumerationIncludesDirectory) { NSURL *directory = [request.URL URLByStandardizingPath]; if (!directory) { [client protocol:self didCompleteWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadUnknownError userInfo:nil]]; return; } [client protocol:self didDiscoverItemAtURL:directory]; } // Enumerate contents __block NSError* enumerationError = nil; NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:[request URL] includingPropertiesForKeys:keys options:mask errorHandler:^BOOL(NSURL *url, NSError *error) { enumerationError = error; return NO; // TODO: are there errors that we want to ignore here? }]; NSURL *aURL; while (!_cancelled && !enumerationError && (aURL = [enumerator nextObject])) { [client protocol:self didDiscoverItemAtURL:aURL]; } if (_cancelled) return; // bail should we be cancelled [client protocol:self didCompleteWithError:enumerationError]; }]; } - (id)initForCreatingDirectoryWithRequest:(NSURLRequest *)request withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { return [self initWithBlock:^{ NSURL *url = request.URL; NSError *error; #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7 if ([[NSFileManager defaultManager] createDirectoryAtURL:url withIntermediateDirectories:createIntermediates attributes:attributes error:&error]) #else if ([[NSFileManager defaultManager] createDirectoryAtPath:[url path] withIntermediateDirectories:createIntermediates attributes:attributes error:&error]) #endif { [client protocol:self didCompleteWithError:nil]; } else { NSAssert(error, @"-[NSFileManager createDirectory…] failed to vend out error upon failure"); [client protocol:self didCompleteWithError:error]; } }]; } - (id)initForCreatingFileWithRequest:(NSURLRequest *)request size:(int64_t)size withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { return [self initWithBlock:^{ _bytesExpectedToWrite = size; // Sadly libcurl doesn't support creating intermediate directories for local files, so do it ourself if (createIntermediates) { NSURL* intermediates = [[request URL] URLByDeletingLastPathComponent]; NSError *error; #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7 if (![[NSFileManager defaultManager] createDirectoryAtURL:intermediates withIntermediateDirectories:YES attributes:nil error:&error]) #else if (![[NSFileManager defaultManager] createDirectoryAtPath:[intermediates path] withIntermediateDirectories:YES attributes:nil error:&error]) #endif { NSAssert(error, @"-[NSFileManager createDirectory…] failed to vend out error upon failure"); [client protocol:self didCompleteWithError:error]; return; } } // whilst testing, support all three creation methods switch (kCreateMode) { case kCreateWithCURL: { [self createFileWithCURLForRequest:request openingAttributes:attributes client:client]; break; } case kCreateWithStreams: { [self createFileSyncForRequest:request openingAttributes:attributes client:client]; break; } default: [self createFileAsyncForRequest:request openingAttributes:attributes client:client]; } }]; } - (id)initForRenamingItemWithRequest:(NSURLRequest *)request newName:(NSString *)newName client:(id)client { return [self initWithBlock:^{ NSError *error; NSURL* srcURL = [request URL]; NSURL* dstURL = [[srcURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:newName]; if ([[NSFileManager defaultManager] moveItemAtURL:srcURL toURL:dstURL error:&error]) { [client protocol:self didCompleteWithError:nil]; } else { NSAssert(error, @"-[NSFileManager moveItemAtURL…] failed to vend out error upon failure"); [client protocol:self didCompleteWithError:error]; } }]; } - (id)initForRemovingItemWithRequest:(NSURLRequest *)request client:(id)client; { return [self initWithBlock:^{ NSError *error; if ([[NSFileManager defaultManager] removeItemAtURL:[request URL] error:&error]) { [client protocol:self didCompleteWithError:nil]; } else { NSAssert(error, @"-[NSFileManager removeItemAtURL…] failed to vend out error upon failure"); [client protocol:self didCompleteWithError:error]; } }]; } - (id)initForSettingAttributes:(NSDictionary *)keyedValues ofItemWithRequest:(NSURLRequest *)request client:(id)client; { return [self initWithBlock:^{ NSError *error; if ([[NSFileManager defaultManager] setAttributes:keyedValues ofItemAtPath:[[request URL] path] error:&error]) { [client protocol:self didCompleteWithError:nil]; } else { NSAssert(error, @"-[NSFileManager setAttributes…] failed to vend out error upon failure"); [client protocol:self didCompleteWithError:error]; } }]; } - (void)start; { _block(); } - (void)stop; { // Most operations are synchronous; we can't stop them _cancelled = YES; // TODO: File creation is DEFINITELY cancellable } #pragma mark - File Copying - (NSError*)modifiedErrorForFileError:(NSError*)error { if ([error.domain isEqualToString:NSPOSIXErrorDomain]) { if (error.code == ENOENT) { error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileNoSuchFileError userInfo:[NSDictionary dictionaryWithObject:error forKey:NSUnderlyingErrorKey]]; } else if (error.code == EACCES) { error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteNoPermissionError userInfo:[NSDictionary dictionaryWithObject:error forKey:NSUnderlyingErrorKey]]; } } return error; } - (NSError*)noInputStreamError { NSError* error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadInvalidFileNameError userInfo:nil]; return error; } - (NSError*)currentPOSIXError { return [self modifiedErrorForFileError:[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]]; } - (NSInputStream*)inputStreamForRequest:(NSURLRequest*)request { NSInputStream *inputStream = nil; NSData* inputData = [request HTTPBody]; if (inputData) { inputStream = [[[NSInputStream alloc] initWithData:inputData] autorelease]; } else { inputStream = [request HTTPBodyStream]; } return inputStream; } /** File creation implementation that uses CURLTransfer. The main problem with this is that CURLTransfer doesn't return very good error information. */ - (void)createFileWithCURLForRequest:(NSURLRequest*)request openingAttributes:(NSDictionary *)attributes client:(id)client; { // Hand off to CURLTransfer to create the file __block CK2CURLBasedProtocol *curlProtocol = [[CK2CURLBasedProtocol alloc] initWithRequest:request client:nil completionHandler:^(NSError *error) { [client protocol:self didCompleteWithError:error]; [curlProtocol autorelease]; }]; [curlProtocol start]; } /** Simple file creation implementation which just works synchronously, copying between two streams. */ - (void)createFileSyncForRequest:(NSURLRequest*)request openingAttributes:(NSDictionary *)attributes client:(id)client; { NSInputStream *inputStream = [self inputStreamForRequest:request]; [inputStream open]; NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:[request URL] append:NO]; [outputStream open]; NSError* error = nil; if (inputStream && outputStream) { uint8_t buffer[kCopyBufferSize]; while ([inputStream hasBytesAvailable]) { NSInteger length = [inputStream read:buffer maxLength:kCopyBufferSize]; if (length < 0) { error = [inputStream streamError]; break; } NSUInteger written = [outputStream write:buffer maxLength:length]; if (written != length) { error = [outputStream streamError]; break; } [self.client protocol:self didSendBodyData:length totalBytesSent:[[inputStream propertyForKey:NSStreamFileCurrentOffsetKey] longValue] totalBytesExpectedToSend:_bytesExpectedToWrite]; } [inputStream close]; [outputStream close]; [outputStream release]; } else { error = [self noInputStreamError]; } [client protocol:self didCompleteWithError:[self modifiedErrorForFileError:error]]; } /** File creation implementation which works asynchronously. We open an input stream for the input. We open an output file for writing using POSIX open/write calls. We then attach a gcd dispatch source to it, and use that to pull input from the stream and write it to the file. We make a dedicated queue to attach the source to, and release it in the cancel handler of the source, so it only lives for the duration of the operation. */ - (void)createFileAsyncForRequest:(NSURLRequest*)request openingAttributes:(NSDictionary *)attributes client:(id)client; { NSInputStream *inputStream = [self inputStreamForRequest:request]; NSError* error = nil; if (inputStream != nil) { [inputStream open]; NSURL* url = [request URL]; NSAssert([url isFileURL], @"wrong URL scheme: %@", url); NSString* path = [url path]; int perms = [attributes filePosixPermissions]; if (perms == 0) { perms = 0744; } int outfile = open([path UTF8String], O_CREAT | O_TRUNC | O_WRONLY, perms); if (outfile != -1) { dispatch_queue_t queue = dispatch_queue_create("CK2FileProtocol", NULL); dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, outfile, 0, queue); dispatch_source_set_event_handler(source, ^{ uint8_t buffer[kCopyBufferSize]; NSInteger length = [inputStream read:buffer maxLength:kCopyBufferSize]; if (length <= 0) { dispatch_source_cancel(source); [client protocol:self didCompleteWithError:[self modifiedErrorForFileError:[inputStream streamError]]]; } else { ssize_t written = write(outfile, buffer, length); if (written != length) { [client protocol:self didCompleteWithError:[self currentPOSIXError]]; } } }); dispatch_source_set_cancel_handler(source, ^{ close(outfile); dispatch_release(source); dispatch_release(queue); }); dispatch_resume(source); } else { error = [self currentPOSIXError]; } } else { error = [self noInputStreamError]; } if (error) { [client protocol:self didCompleteWithError:error]; } } @end ================================================ FILE: ConnectionKit/CK2FileSizeFormatter.h ================================================ // // CK2FileSizeFormatter.h // Connection // // Created by Paul Kim on 1/16/13. // // #import @interface CK2FileSizeFormatter : NSNumberFormatter @end ================================================ FILE: ConnectionKit/CK2FileSizeFormatter.m ================================================ // // CK2FileSizeFormatter.m // Connection // // Created by Paul Kim on 1/16/13. // // #import "CK2FileSizeFormatter.h" #define FRACTION_TRESHOLD_INDEX 2 static NSArray *_unitLabels; @implementation CK2FileSizeFormatter + (void)initialize { if ([[self class] isEqual:[CK2FileSizeFormatter class]]) { _unitLabels = [@[ @"bytes", @"KB", @"MB", @"TB", @"PB", @"EB", @"ZB", @"YB" ] retain]; } } - (NSString *)stringForObjectValue:(id)anObject { if ([anObject isKindOfClass:[NSNumber class]]) { NSUInteger i, count; double size; NSString *formattedNumber; size = [anObject doubleValue]; count = [_unitLabels count]; for (i = 0; (i < count) && (size >= 1000); i++) { size /= 1000; } if (i >= count) { i = count - 1; } if (i < FRACTION_TRESHOLD_INDEX) { [self setMaximumFractionDigits:0]; } else { [self setMaximumFractionDigits:1]; } formattedNumber = [super stringForObjectValue:[NSNumber numberWithDouble:size]]; return [NSString stringWithFormat:@"%@ %@", formattedNumber, [_unitLabels objectAtIndex:i]]; } else if ([anObject isKindOfClass:[NSString class]]) { return @"--"; } return @""; } @end ================================================ FILE: ConnectionKit/CK2IconItemView.h ================================================ // // CK2IconItemView.h // ConnectionKit // // Created by Paul Kim on 12/23/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import @class CK2IconViewItem; @interface CK2IconItemView : NSView { IBOutlet CK2IconViewItem *_item; NSTextFieldCell *_textCell; } @property (readwrite, assign, nonatomic) CK2IconViewItem *item; @end ================================================ FILE: ConnectionKit/CK2IconItemView.m ================================================ // // CK2IconItemView.m // ConnectionKit // // Created by Paul Kim on 12/23/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "CK2IconItemView.h" #import "CK2IconViewItem.h" #import "NSURL+CK2OpenPanel.h" #import "CK2IconView.h" #import "NSImage+CK2OpenPanel.h" #define MARGIN 4.0 #define ICON_SIZE 64.0 #define ICON_SELECTION_MARGIN 4.0 #define ICON_TEXT_MARGIN 8.0 #define TEXT_WIDTH 114.0 #define TEXT_HEIGHT 34.0 #define SELECTION_RADIUS 8.5 #define INNER_SELECTION_RADIUS 3.0 @implementation CK2IconItemView @synthesize item = _item; - (id)initWithFrame:(NSRect)frameRect { if ((self = [super initWithFrame:frameRect]) != nil) { [self createTextField]; } return self; } - (void)dealloc { [self setItem:nil]; [_textCell release]; [super dealloc]; } - (void)createTextField { _textCell = [[NSTextFieldCell alloc] initTextCell:@""]; [_textCell setEditable:NO]; [_textCell setAlignment:NSCenterTextAlignment]; [_textCell setLineBreakMode:NSLineBreakByWordWrapping]; [_textCell setTruncatesLastVisibleLine:YES]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqual:@"selected"] || [keyPath isEqual:@"enabled"]) { [self setNeedsDisplay:YES]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)setItem:(CK2IconViewItem *)item { if (item != _item) { [_item removeObserver:self forKeyPath:@"selected"]; [_item removeObserver:self forKeyPath:@"enabled"]; _item = item; [_item addObserver:self forKeyPath:@"selected" options:NSKeyValueObservingOptionNew context:NULL]; [_item addObserver:self forKeyPath:@"enabled" options:NSKeyValueObservingOptionNew context:NULL]; } } - (NSRect)iconRectForBounds:(NSRect)bounds { return NSMakeRect(NSMinX(bounds) + (NSWidth(bounds) - ICON_SIZE) / 2.0, NSMinY(bounds) + (NSHeight(bounds) - (2 * ICON_SELECTION_MARGIN + 2 * MARGIN + ICON_SIZE + ICON_TEXT_MARGIN + TEXT_HEIGHT)) / 2.0 + TEXT_HEIGHT + ICON_TEXT_MARGIN + ICON_SELECTION_MARGIN + MARGIN, ICON_SIZE, ICON_SIZE); } - (NSRect)textRectForBounds:(NSRect)bounds { NSRect rect; if ([[[self item] representedObject] ck2_isPlaceholder]) { NSSize size; rect = [self bounds]; rect.size.height = CGFLOAT_MAX; rect.size.width = NSWidth(bounds) * .75; size = [_textCell cellSizeForBounds:rect]; rect = NSMakeRect(NSMinX(bounds) + (NSWidth(bounds) - size.width) / 2.0, NSMinY(bounds) + (NSHeight(bounds) - size.height) / 2.0, size.width, size.height); } else { rect = NSMakeRect(NSMinX(bounds) + (NSWidth(bounds) - TEXT_WIDTH) / 2.0, NSMinY(bounds) + (NSHeight(bounds) - (2 * ICON_SELECTION_MARGIN + ICON_SIZE + ICON_TEXT_MARGIN + TEXT_HEIGHT)) / 2.0, TEXT_WIDTH, TEXT_HEIGHT); } return rect; } - (NSColor *)selectionColor { return [NSColor colorWithCalibratedWhite:0.76 alpha:1.0]; } - (void)drawSelectionForText:(NSString *)label inRect:(NSRect)rect { // Draw text selection NSTextView *fieldEditor; NSLayoutManager *layoutManager; NSRectArray rects; NSUInteger count; CGFloat yPos; NSBezierPath *path; NSRange range; NSColor *selectionColor; fieldEditor = (NSTextView *)[[self window] fieldEditor:YES forObject:self]; [_textCell setUpFieldEditorAttributes:fieldEditor]; [fieldEditor setString:label]; [fieldEditor setFrame:rect]; [fieldEditor setTextContainerInset:NSZeroSize]; [fieldEditor setFont:[_textCell font]]; layoutManager = [fieldEditor layoutManager]; range = NSMakeRange(0, [label length]); rects = [layoutManager rectArrayForCharacterRange:range withinSelectedCharacterRange:range inTextContainer:[fieldEditor textContainer] rectCount:&count]; if ([[self window] isKeyWindow]) { selectionColor = [NSColor alternateSelectedControlColor]; [_textCell setTextColor:[NSColor whiteColor]]; } else { selectionColor = [self selectionColor]; } [selectionColor set]; yPos = NSMaxY(rect) - 1.0; // A little fudge count = MIN(2, count); for (NSUInteger i = 0; i < count; i++) { // The rects aren't positioned correctly so we correct them here. Also some fudge on the height rects[i].size.height += 1.0; rects[i].origin.x = NSMinX(rect) + (NSWidth(rect) - NSWidth(rects[i])) / 2.0; rects[i].origin.y = yPos - NSHeight(rects[i]); if (!NSContainsRect(rect, rects[i]) && (i != 0)) { // We only care about the rects within the visible region. For instance, we sometimes get one rect for // the two lines plus another rect out of bounds count = i; } yPos = NSMinY(rects[i]); } path = nil; if (count > 1) { CGFloat diff, height; diff = NSWidth(rects[0]) - NSWidth(rects[1]); if (diff < SELECTION_RADIUS + INNER_SELECTION_RADIUS) { // The two lines of text are pretty close to eachother so just construct one rect to encompass them both count = 1; height = NSHeight(rects[0]) + NSHeight(rects[1]); rects[0] = NSMakeRect(MIN(NSMinX(rects[0]), NSMinX(rects[1])), NSMaxY(rect) - height, MAX(NSWidth(rects[0]), NSWidth(rects[1])), height); rects[0] = NSInsetRect(rects[0], -SELECTION_RADIUS, 0.0); path = [NSBezierPath bezierPathWithRoundedRect:rects[0] xRadius:SELECTION_RADIUS yRadius:SELECTION_RADIUS]; } else { // Lines of text are varying widths. Construct a path to follow the general shape, rounding the ends/corners. CGFloat radius; radius = NSHeight(rects[0]) / 2.0; path = [NSBezierPath bezierPath]; // Top left corner [path moveToPoint:NSMakePoint(NSMinX(rects[0]), NSMaxY(rects[0]))]; if (diff > 0.0) { // First line is wider than the second // Right cap, top rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rects[0]), NSMinY(rects[0]) + radius) radius:radius startAngle:90 endAngle:270 clockwise:YES]; // Upper right corner, bottom rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rects[1]) + radius + INNER_SELECTION_RADIUS, NSMaxY(rects[1]) - INNER_SELECTION_RADIUS) radius:INNER_SELECTION_RADIUS startAngle:90 endAngle:180 clockwise:NO]; // Lower right corner, bottom rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rects[1]), NSMinY(rects[1]) + radius) radius:radius startAngle:0 endAngle:270 clockwise:YES]; // Lower left corner, bottom rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rects[1]), NSMinY(rects[1]) + radius) radius:radius startAngle:270 endAngle:180 clockwise:YES]; // Upper left corner, bottom rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rects[1]) - radius - INNER_SELECTION_RADIUS, NSMaxY(rects[1]) - INNER_SELECTION_RADIUS) radius:INNER_SELECTION_RADIUS startAngle:0 endAngle:90 clockwise:NO]; // Left cap, top rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rects[0]), NSMinY(rects[0]) + radius) radius:radius startAngle:270 endAngle:90 clockwise:YES]; } else { // Upper right corner, top rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rects[0]), NSMaxY(rects[0]) - radius) radius:radius startAngle:90 endAngle:0 clockwise:YES]; // Lower right corner, top rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rects[0]) + radius + INNER_SELECTION_RADIUS, NSMinY(rects[0]) + INNER_SELECTION_RADIUS) radius:INNER_SELECTION_RADIUS startAngle:180 endAngle:270 clockwise:NO]; // Right cap, bottom rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rects[1]), NSMinY(rects[1]) + radius) radius:radius startAngle:90 endAngle:270 clockwise:YES]; // Left cap, bottom rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rects[1]), NSMinY(rects[1]) + radius) radius:radius startAngle:180 endAngle:90 clockwise:YES]; // Lower left corner, top rect [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rects[0]) - radius - INNER_SELECTION_RADIUS, NSMinY(rects[0]) + INNER_SELECTION_RADIUS) radius:INNER_SELECTION_RADIUS startAngle:270 endAngle:0 clockwise:NO]; [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rects[0]), NSMaxY(rects[0]) - radius) radius:radius startAngle:180 endAngle:90 clockwise:YES]; } [path closePath]; } } else { rects[0] = NSInsetRect(rects[0], -SELECTION_RADIUS, 0); path = [NSBezierPath bezierPathWithRoundedRect:rects[0] xRadius:SELECTION_RADIUS yRadius:SELECTION_RADIUS]; } [path fill]; } - (void)drawRect:(NSRect)dirtyRect { CK2IconViewItem *item; NSRect iconRect, rect, bounds; NSColor *color, *selectionColor; NSString *label; NSURL *url; item = [self item]; NSAssert([item view] == self, @"Item view for view %@ not properly set.", self); url = [item representedObject]; bounds = [self bounds]; selectionColor = [NSColor colorWithCalibratedWhite:0.76 alpha:1.0]; if (![url ck2_isPlaceholder]) { // Draw icon (and its selection) iconRect = [self iconRectForBounds:bounds]; if ([item isSelected]) { NSBezierPath *path; rect = NSInsetRect(iconRect, -ICON_SELECTION_MARGIN, -ICON_SELECTION_MARGIN); path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:ICON_SELECTION_MARGIN yRadius:ICON_SELECTION_MARGIN]; [selectionColor set]; [path fill]; } [[url ck2_icon] drawInRect:iconRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; if (![item isEnabled]) { [[NSColor colorWithDeviceWhite:1.0 alpha:0.5] set]; NSRectFillUsingOperation(iconRect, NSCompositeSourceAtop); } } color = [item isEnabled] ? [NSColor controlTextColor] : [NSColor disabledControlTextColor]; rect = [self textRectForBounds:bounds]; label = [url ck2_displayName]; [_textCell setStringValue:label]; if ([[self item] isSelected]) { [self drawSelectionForText:label inRect:rect]; } else { [_textCell setTextColor:color]; } [_textCell drawWithFrame:rect inView:self]; } -(void)mouseDown:(NSEvent *)theEvent { NSInteger clickCount; CK2IconViewItem *item; NSPoint point; NSRect bounds; BOOL isFlipped; point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; bounds = [self bounds]; item = [self item]; isFlipped = [self isFlipped]; if ([item isEnabled] && (NSMouseInRect(point, [self iconRectForBounds:bounds], isFlipped) || NSMouseInRect(point, [self textRectForBounds:bounds], isFlipped))) { [super mouseDown:theEvent]; clickCount = [theEvent clickCount]; if (clickCount == 1) { [NSApp sendAction:[item action] to:[item target] from:[self item]]; } else if (clickCount == 2) { [NSApp sendAction:[item doubleAction] to:[item target] from:[self item]]; } } } // Suppress animations - (id)animator { return self; } - (id)animationForKey:(NSString *)key { return nil; } #pragma mark NSCoding #define ITEM_KEY @"item" - (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder]) != nil) { [self createTextField]; [self setItem:[aDecoder decodeObjectForKey:ITEM_KEY]]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeObject:[self item] forKey:ITEM_KEY]; } @end ================================================ FILE: ConnectionKit/CK2IconView.h ================================================ // // CK2IconView.h // Connection // // Created by Paul Kim on 1/17/13. // // #import @interface CK2IconView : NSCollectionView { NSMutableString *_typeSelectBuffer; NSTimer *_typeSelectTimer; BOOL _messageMode; } @property (readwrite, assign, nonatomic) BOOL messageMode; @end ================================================ FILE: ConnectionKit/CK2IconView.m ================================================ // // CK2IconView.m // Connection // // Created by Paul Kim on 1/17/13. // // #import "CK2IconView.h" #import "NSURL+CK2OpenPanel.h" @implementation CK2IconView @synthesize messageMode = _messageMode; - (id)initWithFrame:(NSRect)frame { if (self = [super initWithFrame:frame]) { [self initNotifications]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; if ([_typeSelectTimer isValid]) { [_typeSelectTimer invalidate]; [_typeSelectTimer release]; } [_typeSelectBuffer release]; [super dealloc]; } - (void)initNotifications { // Tried doing this by overriding setFrame: but it seems to cause spastic behavior. Seems to work more smoothly // when done this way. [self setPostsFrameChangedNotifications:YES]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tile) name:NSViewFrameDidChangeNotification object:self]; } - (void)awakeFromNib { [self initNotifications]; } - (void)setMessageMode:(BOOL)messageMode { _messageMode = messageMode; [self tile]; } - (void)setContent:(NSArray *)content { [super setContent:content]; [self tile]; } - (void)tile { NSRect bounds; bounds = [self bounds]; if (_messageMode) { [self setMinItemSize:bounds.size]; [self setMaxItemSize:bounds.size]; } else { NSUInteger colCount; CGFloat calcWidth; NSSize size, minSize; // NSCollectionView tends to align things towards the left. We want the icons to be evenly distributed so we // set the minimum width of each item to force such a layout. minSize = [[[self itemPrototype] view] frame].size; colCount = NSWidth(bounds) / minSize.width; calcWidth = floor(NSWidth(bounds) / colCount); [self setMaxNumberOfColumns:colCount]; size = NSMakeSize(MIN(calcWidth, minSize.width), minSize.height); [self setMinItemSize:size]; // Setting the max size gets rid of odd scroller behavior [self setMaxItemSize:size]; } } - (void)resetTypeSelectTimer { if ([_typeSelectTimer isValid]) { [_typeSelectTimer invalidate]; [_typeSelectTimer release]; } // You have to type the name within a short period of time otherwise it resets. We use -keyRepeatDelay as that // seems like a reasonable match. May have to do some experimentation to find out what the delay really is (and // if it correlates to any existing settings). _typeSelectTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:[NSEvent keyRepeatDelay]] interval:0 target:self selector:@selector(clearTypeSelectBuffer:) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:_typeSelectTimer forMode:NSRunLoopCommonModes]; } - (void)clearTypeSelectBuffer:(id)sender { if ([sender isKindOfClass:[NSTimer class]]) { [_typeSelectTimer invalidate]; [_typeSelectTimer release]; _typeSelectTimer = nil; } [_typeSelectBuffer setString:@""]; } - (void)keyDown:(NSEvent *)event { if ([event type] == NSKeyDown) { NSString *string; NSUInteger flags; string = [event characters]; flags = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask; if (([string isEqual:@"/"] || [string isEqual:@"~"]) && ((flags & NSCommandKeyMask) == 0)) { // Let the window handle it [[self nextResponder] keyDown:event]; return; } } [super keyDown:event]; } - (void)insertText:(id)aString { NSUInteger i; NSArray *urls; if (_typeSelectBuffer == nil) { _typeSelectBuffer = [[NSMutableString alloc] init]; } if ([_typeSelectBuffer length] == 0) { [self resetTypeSelectTimer]; } [_typeSelectBuffer appendString:aString]; urls = [self content]; i = [urls indexOfObject:_typeSelectBuffer inSortedRange:NSMakeRange(0, [urls count]) options:NSBinarySearchingInsertionIndex | NSBinarySearchingFirstEqual usingComparator: ^ NSComparisonResult (id obj1, id obj2) { NSString *string1, *string2; string1 = obj1; if ([obj1 isKindOfClass:[NSURL class]]) { string1 = [obj1 ck2_displayName]; } string2 = obj2; if ([obj2 isKindOfClass:[NSURL class]]) { string2 = [obj2 ck2_displayName]; } return [string1 caseInsensitiveCompare:string2]; }]; if (i == NSNotFound) { NSBeep(); } else { [self setSelectionIndexes:[NSIndexSet indexSetWithIndex:i]]; [self scrollRectToVisible:[self frameForItemAtIndex:i]]; } } - (void)viewWillMoveToWindow:(NSWindow *)newWindow { NSWindow *window; window = [self window]; if (window != nil) { NSNotificationCenter *notificationCenter; notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification object:window]; [notificationCenter removeObserver:self name:NSWindowDidResignKeyNotification object:window]; } } - (void)viewDidMoveToWindow { NSWindow *window; window = [self window]; if (window != nil) { NSNotificationCenter *notificationCenter; notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:window]; [notificationCenter addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:window]; } } - (void)windowDidBecomeKey:(NSNotification *)notification { [self setNeedsDisplay:YES]; } - (void)windowDidResignKey:(NSNotification *)notification { [self setNeedsDisplay:YES]; } @end ================================================ FILE: ConnectionKit/CK2IconViewItem.h ================================================ // // CK2IconViewItem.h // ConnectionKit // // Created by Paul Kim on 12/19/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import @interface CK2IconViewItem : NSCollectionViewItem { IBOutlet id _target; SEL _action; SEL _doubleAction; BOOL _isEnabled; } @property (readwrite, assign) id target; @property (readwrite, assign) SEL action; @property (readwrite, assign) SEL doubleAction; @property (readwrite, assign, getter=isEnabled) BOOL enabled; @end ================================================ FILE: ConnectionKit/CK2IconViewItem.m ================================================ // // CK2IconViewItem.m // ConnectionKit // // Created by Paul Kim on 12/19/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "CK2IconViewItem.h" #import "CK2IconItemView.h" @interface CK2IconViewItem () @end @implementation CK2IconViewItem @synthesize target = _target; @synthesize action = _action; @synthesize doubleAction = _doubleAction; @synthesize enabled = _isEnabled; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) != nil) { [self setEnabled:YES]; } return self; } - (void)dealloc { [(CK2IconItemView *)[self view] setItem:nil]; [super dealloc]; } - (void)setView:(NSView *)view { [super setView:view]; [(CK2IconItemView *)[self view] setItem:self]; } #pragma mark NSCopying - (id)copyWithZone:(NSZone *)zone { CK2IconViewItem *copy; copy = [super copyWithZone:zone]; [copy setTarget:[self target]]; [copy setAction:[self action]]; [copy setDoubleAction:[self doubleAction]]; return copy; } #pragma mark NSCoding #define TARGET_KEY @"target" #define ACTION_KEY @"action" #define DOUBLE_ACTION_KEY @"doubleAction" #define ENABLED_ACTION_KEY @"enabled" - (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder]) != nil) { [self setTarget:[aDecoder decodeObjectForKey:TARGET_KEY]]; [self setAction:NSSelectorFromString([aDecoder decodeObjectForKey:ACTION_KEY])]; [self setDoubleAction:NSSelectorFromString([aDecoder decodeObjectForKey:DOUBLE_ACTION_KEY])]; [self setEnabled:[aDecoder decodeBoolForKey:ENABLED_ACTION_KEY]]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeObject:[self target] forKey:TARGET_KEY]; [aCoder encodeObject:NSStringFromSelector([self action]) forKey:ACTION_KEY]; [aCoder encodeObject:NSStringFromSelector([self doubleAction]) forKey:DOUBLE_ACTION_KEY]; [aCoder encodeBool:[self isEnabled] forKey:ENABLED_ACTION_KEY]; } @end ================================================ FILE: ConnectionKit/CK2NewFolderWindowController.h ================================================ // // CK2NewFolderWindowWindowController.h // Connection // // Created by Paul Kim on 1/22/13. // // #import @class CK2OpenPanelController; @interface CK2NewFolderWindowController : NSWindowController { IBOutlet NSTextField *_nameField; IBOutlet NSButton *_okButton; IBOutlet NSTextField *_statusField; IBOutlet NSProgressIndicator *_progressIndicator; CK2OpenPanelController *_controller; NSURL *_folderURL; NSArray *_existingNames; CK2FileOperation *_operation; NSError *_error; } @property (readonly, retain) NSURL *folderURL; @property (readonly, retain) NSError *error; - (id)initWithController:(CK2OpenPanelController *)controller;; - (BOOL)runModalForURL:(NSURL *)url; - (IBAction)ok:(id)sender; - (IBAction)cancel:(id)sender; @end ================================================ FILE: ConnectionKit/CK2NewFolderWindowController.m ================================================ // // CK2NewFolderWindowWindowController.m // Connection // // Created by Paul Kim on 1/22/13. // // #import "CK2NewFolderWindowController.h" #import "CK2OpenPanelController.h" #import #define DEFAULT_NAME @"untitled folder" @interface CK2NewFolderWindowController () @property (readwrite, retain) NSURL *folderURL; @property (readwrite, retain) NSError *error; @end @implementation CK2NewFolderWindowController @synthesize folderURL = _folderURL; @synthesize error = _error; - (id)initWithController:(CK2OpenPanelController *)controller { if ((self = [super initWithWindowNibName:@"CK2NewFolderWindow"]) != nil) { _controller = controller; } return self; } - (void)dealloc { [_operation cancel]; [_operation release]; [_folderURL release]; [_error release]; [_existingNames release]; [super dealloc]; } - (void)controlTextDidChange:(NSNotification *)aNotification { NSText *fieldEditor; NSString *string; BOOL nameExists; fieldEditor = [[aNotification userInfo] objectForKey:@"NSFieldEditor"]; string = [fieldEditor string]; nameExists = [_existingNames containsObject:string]; [_statusField setHidden:!nameExists]; [_okButton setEnabled:([string length] != 0) && !nameExists]; //PENDING: //Replace ':' with '-' } - (BOOL)runModalForURL:(NSURL *)url { NSArray *children; NSInteger resultCode; NSString *name; NSUInteger i; [self setFolderURL:url]; children = [_controller childrenForURL:url]; _existingNames = [[children valueForKey:@"lastPathComponent"] retain]; name = DEFAULT_NAME; i = 2; while ([_existingNames containsObject:name]) { name = [NSString stringWithFormat:@"%@ %lu", DEFAULT_NAME, (unsigned long)i++]; } // Window may not be loaded yet. [self window]; [_nameField setStringValue:name]; resultCode = [NSApp runModalForWindow:[self window]]; return ((resultCode == NSOKButton) && (_error == nil)); } - (void)endWithCode:(NSInteger)code { NSWindow *window; [_progressIndicator stopAnimation:self]; window = [self window]; [window close]; [_operation release]; _operation = nil; [_existingNames release]; _existingNames = nil; if ([window isModalPanel]) { [NSApp stopModalWithCode:code]; } } - (void)doIt:sender { [self endWithCode:NSOKButton]; } - (IBAction)ok:(id)sender { NSString *folderName; NSURL *url, *parentURL; folderName = [_nameField stringValue]; parentURL = [self folderURL]; url = [parentURL URLByAppendingPathComponent:folderName isDirectory:YES]; // Need to set this since the url is not vended by CK2 [CK2FileManager setTemporaryResourceValue:parentURL forKey:NSURLParentDirectoryURLKey inURL:url]; [_okButton setEnabled:NO]; _operation = [[[_controller fileManager] createDirectoryAtURL:url withIntermediateDirectories:NO openingAttributes:nil completionHandler: ^(NSError *blockError) { NSEvent *event; [self setError:blockError]; [self setFolderURL:url]; [self endWithCode:NSOKButton]; [_okButton setEnabled:YES]; // It seems that the run loop doesn't wake up after ending the modal session from a block (also happens with // -performSelectorOnMainThread:) so we create a fake event here to "jiggle its rat" (rdar://13079612) event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:[[self window] windowNumber] context:NULL subtype:NSApplicationDefined data1:0 data2:0]; [NSApp postEvent:event atStart:YES]; }] retain]; [_progressIndicator startAnimation:self]; } - (IBAction)cancel:(id)sender { [_operation cancel]; [self endWithCode:NSCancelButton]; } @end ================================================ FILE: ConnectionKit/CK2OpenPanel.h ================================================ // // CK2OpenPanel.h // ConnectionKit // // Created by Paul Kim on 12/14/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import //TODO: Handle/display errors better //TODO: Add save panel functionality? //TODO: Save window/view dimensions? //TODO: Autocomplete in the path field? @class CK2OpenPanel; @protocol CK2OpenPanelDelegate @optional - (void)panel:(CK2OpenPanel *)sender didChangeToDirectoryURL:(NSURL *)url; - (BOOL)panel:(CK2OpenPanel *)sender shouldEnableURL:(NSURL *)url; - (BOOL)panel:(CK2OpenPanel *)sender validateURL:(NSURL *)url error:(NSError **)outError; - (void)panelSelectionDidChange:(CK2OpenPanel *)sender; - (void)panel:(CK2OpenPanel *)sender didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential *))completionHandler; - (void)panel:(CK2OpenPanel *)sender appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; - (void)panel:(CK2OpenPanel *)sender didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge __attribute((deprecated("implement -panel:didReceiveChallenge:completionHandler instead"))); @end @class CK2OpenPanelController; /** CK2OpenPanel is the equivalent of NSOpenPanel, but built on ConnectionKit so that it can allow you to browse filesystems on other machines via the protocols ConnectionKit supports. The goal of this was to emulate NSOpenPanel as far as it made sense. It's user and programming interfaces remain fairly close to NSOpenPanel with the following exceptions: User Interface: - Added a header to indicate the host. Should help in cases where multiple CK2OpenPanels are showing. - Since this is oriented towards selecting an existing file or directory, it does not have NSSavePanel's support for save operations, such as the field to enter a name for the file. Will consider adding this in if there is demand. - No sidebar. Besides a home directory, there are no common standard directories. It's unclear whether users would use this enough to keep around favorite directories on different servers. - A home button has been added to quickly jump to the home directory. - There is no search field as we cannot do anything resembling a Spotlight search over network protocols. An exhaustive search would be time-consuming and probably not very friendly to the server. May consider doing search just in the current directory, though. - No "arrange" pull-down. Not sure if anyone would miss this and didn't seem worth the effort. - No coverflow view. Not very useful (no previews) and not worth the effort. - The UI operates asynchronously from any operations it performs. Since these are network operations, the UI should not beachball while performing long operations. A progress indicator/reload button has been added and a loading message appears in directories whose contents are being retrieved. - No preview or application icons (a generic app icon is used). It's a bit resource heavy and server-unfriendly downloading application icons. Previews for other files would end up just downloading files outright. - No QuickLook. See above about previews. - Does not support drag and drop. Doesn't make sense to have the open panel navigate to a file on the server based on a locally dropped file. API: - No API's related to saving files (name field, expansion state, hide extension). - -setDirectoryURL: can also take a completion block, since the operation is asynchronous. Despite all of the above, CK2OpenPanel should be a near-drop-in replacement for NSOpenPanel in terms of code usage and user experience. The emulation of NSOpenPanel's behavior goes fairly deep and you may be surprised by the features supported. */ @interface CK2OpenPanel : NSPanel { CK2OpenPanelController *_viewController; NSString *_title; NSString *_prompt; NSString *_message; BOOL _canChooseFiles; BOOL _canChooseDirectories; BOOL _allowsMultipleSelection; BOOL _showsHiddenFiles; BOOL _treatsFilePackagesAsDirectories; BOOL _canCreateDirectories; NSArray *_allowedFileTypes; void (^_completionBlock)(NSInteger result); } @property (readwrite, copy) NSString *title; @property (readwrite, copy) NSString *prompt; @property (readwrite, copy) NSString *message; @property (readwrite, retain) NSView *accessoryView; @property (readwrite, assign) BOOL canChooseFiles; @property (readwrite, assign) BOOL canChooseDirectories; @property (readwrite, assign) BOOL allowsMultipleSelection; @property (readwrite, assign) BOOL showsHiddenFiles; @property (readwrite, assign) BOOL treatsFilePackagesAsDirectories; @property (readwrite, assign) BOOL canCreateDirectories; @property (readwrite, copy) NSArray *allowedFileTypes; @property (readwrite, retain, nonatomic) NSURL *directoryURL; @property (readonly, retain) NSURL *URL; @property (readonly, copy) NSArray *URLs; + (CK2OpenPanel *)openPanel; @property(assign) id delegate; /** The completion block will not be called until the given URL's contents are fully loaded. That way, if you show the panel in the completion block, nothing will be "in progress". You can still show the panel beforehand but much of the UI will be disabled until the URL is loaded. The UI will still be responsive in that you'll see progress indicators spinning and you can still cancel the panel. The completion block is called on the main thread, and as a nicety, deliberately *avoids* using a dispatch queue to do so, so you can call `-runModal` from within it if desired (see the `-runModal` docs for details). Note that directoryURL immediately reflects the URL this method was called with but may change when the operation is complete as the server may resolve the URL to something else. It's best to not rely on the directoryURL until the completion block has fired. Most commonly, we expect you'll want to show the user's home directory. To get that URL, consult CK2FileManager like so: NSURL *homeDir = [CK2FileManager URLWithPath:@"" isDirectory:YES hostURL:[NSURL URLWithString:@"sftp://example.com"]]; @param directoryURL The URL of the host/directory to connect to. @param block The completion block that is called when the operation is complete. */ - (void)setDirectoryURL:(NSURL *)directoryURL completionHandler:(void (^)(NSError *error))block; - (void)beginSheetModalForWindow:(NSWindow *)window completionHandler:(void (^)(NSInteger result))handler; - (void)beginWithCompletionHandler:(void (^)(NSInteger result))handler; /** Displays the panel and begins its event loop with the current working (or last selected) directory as the default starting point. @return `NSFileHandlingPanelOKButton` (if the user clicks the OK button) or `NSFileHandlingPanelCancelButton` (if the user clicks the Cancel button). This method invokes `-[NSApplication runModalForWindow:` with `self` as the argument. You must **NOT** run an open panel modally from the main dispatch queue. Doing so will block the queue, preventing important internal callbacks from arriving, including from ConnectionKit itself, and the springy scrolling behaviour introduced in 10.7+. Instead, call as part of regular runloop activity, such as directly from a user- generated event, or deferred execution using something like `-performSelector:afterDelay:…` */ - (NSInteger)runModal; - (IBAction)ok:(id)sender; - (IBAction)cancel:(id)sender; - (void)validateVisibleColumns; @end ================================================ FILE: ConnectionKit/CK2OpenPanel.m ================================================ // // CKRemoteOpenPanel.m // ConnectionKit // // Created by Paul Kim on 12/14/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "CK2OpenPanel.h" #import "CK2OpenPanelController.h" #import "NSURL+CK2OpenPanel.h" @interface CK2OpenPanel () @property (readwrite, copy) void (^completionBlock)(NSInteger result); - (void)endWithCode:(NSInteger)code; @end @implementation CK2OpenPanel @synthesize title = _title; @synthesize prompt = _prompt; @synthesize message = _message; @synthesize canChooseFiles = _canChooseFiles; @synthesize canChooseDirectories = _canChooseDirectories; @synthesize allowsMultipleSelection = _allowsMultipleSelection; @synthesize showsHiddenFiles = _showsHiddenFiles; @synthesize treatsFilePackagesAsDirectories = _treatsFilePackagesAsDirectories; @synthesize canCreateDirectories = _canCreateDirectories; @synthesize allowedFileTypes = _allowedFileTypes; @synthesize completionBlock = _completionBlock; + (CK2OpenPanel *)openPanel { return [[[CK2OpenPanel alloc] init] autorelease]; } - (id)init { if ((self = [super initWithContentRect:NSMakeRect(0.0, 0.0, 525.0, 350.0) styleMask:NSTitledWindowMask | NSResizableWindowMask backing:NSBackingStoreBuffered defer:NO]) != nil) { NSRect rect; NSView *view; [self setTitle:NSLocalizedStringFromTableInBundle(@"Open", nil, [NSBundle bundleForClass:[self class]], @"Default open panel title")]; [self setHidesOnDeactivate:NO]; _viewController = [[CK2OpenPanelController alloc] initWithPanel:self]; view = [_viewController view]; rect = [[self class] frameRectForContentRect:[view frame] styleMask:[self styleMask]]; [self setFrame:rect display:NO]; [self setContentView:view]; [self setCanChooseDirectories:YES]; [self setCanChooseFiles:NO]; [self setCanCreateDirectories:NO]; [self setTreatsFilePackagesAsDirectories:NO]; [self setShowsHiddenFiles:NO]; [self setMinSize:NSMakeSize(515.0, 475.0)]; } return self; } - (void)dealloc { [_viewController close]; // holds a weak ref to us which needs breaking [_viewController release]; [_title release]; [_prompt release]; [_message release]; [_allowedFileTypes release]; [_completionBlock release]; [super dealloc]; } - (id )delegate { // Both protocols (this one and NSWindowDelegate) are totally optional so we // can cast with impunity, as NSSavePanel most likely does for its delegate return (id )[super delegate]; } - (void)setDelegate:(id )delegate { // Both protocols (this one and NSWindowDelegate) are totally optional so we // can cast with impunity, as NSSavePanel most likely does for its delegate [super setDelegate:(id )delegate]; } - (NSView *)accessoryView { return [_viewController accessoryView]; } - (void)setAccessoryView:(NSView *)accessoryView { [_viewController setAccessoryView:accessoryView]; } - (NSURL *)directoryURL { return [_viewController directoryURL]; } - (void)setDirectoryURL:(NSURL *)directoryURL; { [self setDirectoryURL:directoryURL completionHandler:nil]; } - (void)setDirectoryURL:(NSURL *)directoryURL completionHandler:(void (^)(NSError *))block; { // Kick off async loading of the URL, but also store our own copy for clients to immediately pull out again if they wish [_viewController changeDirectory:directoryURL completionHandler:^(NSError *error) { // Re-dispatch using runloop so there's no danger from clients deciding // to call -runModal within it NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ block(error); }]; [[NSRunLoop currentRunLoop] performSelector:@selector(start) target:operation argument:nil order:0 modes:@[NSRunLoopCommonModes]]; }]; } - (NSURL *)URL { return [_viewController URL]; } - (NSArray *)URLs { return [_viewController URLs]; } - (void)willAppear; { // Default to root if no-one's supplied anything better if (![self directoryURL]) [self setDirectoryURL:[NSURL fileURLWithPath:@"/"]]; [_viewController reload]; } - (void)beginSheetModalForWindow:(NSWindow *)window completionHandler:(void (^)(NSInteger result))handler { [self willAppear]; CFRetain(self); [NSApp beginSheet:self modalForWindow:window modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:[handler copy]]; } - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { if (sheet == self) { void (^completionBlock)(NSInteger result); completionBlock = contextInfo; if (completionBlock != nil) { completionBlock(returnCode); [completionBlock release]; } CFRelease(self); // cancels out retain in -beginSheet } } - (void)beginWithCompletionHandler:(void (^)(NSInteger result))handler { [self willAppear]; [self setCompletionBlock:handler]; [self center]; [self makeKeyAndOrderFront:self]; } - (NSInteger)runModal { [self willAppear]; [self center]; return [NSApp runModalForWindow:self]; } - (void)endWithCode:(NSInteger)code { [self close]; if ([self isModalPanel]) { [NSApp stopModalWithCode:code]; } else if ([self isSheet]) { [NSApp endSheet:self returnCode:code]; } else { void (^block)(NSInteger); block = [self completionBlock]; if (block != nil) { block(code); [self setCompletionBlock:nil]; } } [_viewController resetSession]; } - (IBAction)ok:(id)sender { if ([[self delegate] respondsToSelector:@selector(panel:validateURL:error:)]) { NSError *error; for (NSURL *url in [self URLs]) { error = nil; if (![[self delegate] panel:self validateURL:url error:&error]) { [self presentError:error modalForWindow:self delegate:nil didPresentSelector:NULL contextInfo:NULL]; return; } } } [self endWithCode:NSFileHandlingPanelOKButton]; } - (IBAction)cancel:(id)sender { [self endWithCode:NSFileHandlingPanelCancelButton]; } - (BOOL)performKeyEquivalent:(NSEvent *)event { // Certain keys, like up and down arrows, will trigger this method being called even if the command key isn't down // so we need to check here. if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask & NSCommandKeyMask) != 0) { NSString *string; string = [event charactersIgnoringModifiers]; if ([string isEqual:@">"]) { [self setShowsHiddenFiles:![self showsHiddenFiles]]; return YES; } else if ([string isEqual:@"H"]) { [_viewController home:self]; return YES; } else if ([string isEqual:@"G"]) { [_viewController showPathFieldWithString:[[self directoryURL] path]]; return YES; } else if ([string isEqual:@"["]) { [_viewController back:self]; return YES; } else if ([string isEqual:@"]"]) { [_viewController forward:self]; return YES; } else if ([string isEqual:[NSString stringWithFormat:@"%C", (unichar)NSUpArrowFunctionKey]]) { NSURL *parentURL; parentURL = [[self directoryURL] ck2_parentURL]; if (parentURL != nil) { [_viewController changeDirectory:parentURL completionHandler: ^(NSError *error) { if (error != nil) { NSLog(@"Could not navigate to enclosing folder from URL %@: %@", [self directoryURL], error); NSBeginCriticalAlertSheet(NSLocalizedStringFromTableInBundle(@"Could not go to enclosing folder.", nil, [NSBundle bundleForClass:[self class]], @"Enclosing folder error"), NSLocalizedStringFromTableInBundle(@"OK", nil, [NSBundle bundleForClass:[self class]], @"OK Button"), nil, nil, self, nil, NULL, NULL, NULL, @"%@", [error localizedDescription]); [_viewController back:self]; } }]; } return YES; } else if ([string isEqual:[NSString stringWithFormat:@"%C", (unichar)NSDownArrowFunctionKey]]) { [_viewController goToSelectedItem:self]; return YES; } } return [super performKeyEquivalent:event]; } - (void)keyDown:(NSEvent *)event { if ([event type] == NSKeyDown) { NSString *string; NSUInteger flags; string = [event characters]; flags = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask; if (([string isEqual:@"/"] || [string isEqual:@"~"]) && ((flags & NSCommandKeyMask) == 0)) { [_viewController showPathFieldWithString:string]; return; } } [super keyDown:event]; } - (void)validateVisibleColumns { [_viewController validateVisibleColumns]; } @end ================================================ FILE: ConnectionKit/CK2OpenPanelColumnViewController.h ================================================ // // CK2OpenPanelBrowserController.h // ConnectionKit // // Created by Paul Kim on 12/29/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import "CK2OpenPanelViewController.h" @class CK2BrowserPreviewController; @interface CK2OpenPanelColumnViewController : CK2OpenPanelViewController { IBOutlet NSBrowser *_browser; CK2BrowserPreviewController *_previewController; NSURL *_rootURL; } @property (readwrite, retain) NSURL *rootURL; @end ================================================ FILE: ConnectionKit/CK2OpenPanelColumnViewController.m ================================================ // // CK2OpenPanelBrowserController.m // ConnectionKit // // Created by Paul Kim on 12/29/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "CK2OpenPanelColumnViewController.h" #import "CK2OpenPanelController.h" #import "CK2FileCell.h" #import "CK2BrowserPreviewController.h" #import "NSURL+CK2OpenPanel.h" #import "NSImage+CK2OpenPanel.h" @implementation CK2OpenPanelColumnViewController @synthesize rootURL = _rootURL; - (void)dealloc { [_previewController release]; [_rootURL release]; [super dealloc]; } - (void)awakeFromNib { [_browser setCellClass:[CK2FileCell class]]; [_browser setDoubleAction:@selector(itemDoubleClicked:)]; } - (BOOL)allowsMutipleSelection { return [_browser allowsMultipleSelection]; } - (void)setAllowsMutipleSelection:(BOOL)allowsMutipleSelection { [_browser setAllowsMultipleSelection:allowsMutipleSelection]; } - (BOOL)hasFixedRoot { return YES; } // Gets the index path of the directory url and the indexset of the URLs under it. "urls" is passed by reference as // this method can change the list if only a subset can be selected (like when switching from the outline view to this // one). Will return YES if the list of urls has changed. - (BOOL)getIndexPath:(NSIndexPath **)indexPath indexSet:(NSIndexSet **)indexSet ofURLs:(NSArray **)urls inDirectory:(NSURL *)directoryURL { CK2OpenPanelController *controller; NSURL *root, *tempURL; NSUInteger count; __block NSUInteger *indexes, indexCount; NSMutableIndexSet *resultIndexes; NSMutableArray *newURLs; if (![_browser isLoaded]) { [_browser loadColumnZero]; } controller = [self controller]; if (directoryURL == nil) { if ([*urls count] > 0) { directoryURL = [*urls objectAtIndex:0]; } else { return NO; } } if (![controller URLCanHazChildren:directoryURL]) { directoryURL = [directoryURL ck2_parentURL]; } root = [self rootURL]; if (indexPath != NULL) { NSMutableArray *ancestorURLs; NSURL *parentURL; NSArray *children; NSUInteger row; ancestorURLs = [NSMutableArray array]; tempURL = directoryURL; while (![tempURL isEqual:root]) { if (tempURL == nil) { NSLog(@"Ancestor URL not found going up from %@ to %@", directoryURL, root); *urls = @[]; return YES; } [ancestorURLs insertObject:tempURL atIndex:0]; tempURL = [tempURL ck2_parentURL]; } [ancestorURLs insertObject:root atIndex:0]; count = [ancestorURLs count]; indexes = NULL; if (count > 0) { indexes = (NSUInteger *)malloc(sizeof(NSUInteger) * count); indexCount = 0; parentURL = nil; for (tempURL in ancestorURLs) { if (parentURL != nil) { children = [controller childrenForURL:parentURL]; row = [children indexOfObject:tempURL]; if (row == NSNotFound) { NSLog(@"Can't find entry in browser %@", tempURL); break; } else { indexes[indexCount++] = row; } } parentURL = tempURL; } if (indexCount > 0) { *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:indexCount]; } free(indexes); } } resultIndexes = [NSMutableIndexSet indexSet]; newURLs = [NSMutableArray array]; for (tempURL in *urls) { NSArray *children; NSUInteger row; children = [controller childrenForURL:directoryURL]; row = [children indexOfObject:tempURL]; if (row == NSNotFound) { tempURL = [tempURL ck2_URLByDeletingTrailingSlash]; row = [children indexOfObject:tempURL]; } if (row != NSNotFound) { [resultIndexes addIndex:row]; [newURLs addObject:tempURL]; } } if (indexSet != NULL) { *indexSet = resultIndexes; } if ([newURLs count] != [*urls count]) { *urls = newURLs; return YES; } return NO; } - (void)reload { [_browser loadColumnZero]; } - (void)update { CK2OpenPanelController *controller; NSArray *urls; NSIndexPath *indexPath; NSIndexSet *indexSet; controller = [self controller]; urls = [controller URLs]; indexPath = nil; indexSet = nil; if ([self getIndexPath:&indexPath indexSet:&indexSet ofURLs:&urls inDirectory:[controller directoryURL]]) { [controller setURLs:urls updateDirectory:NO sender:self]; } if (indexPath != nil) { NSUInteger column; [_browser setSelectionIndexPath:indexPath]; column = [indexPath length]; if (column > 0) { [_browser selectRowIndexes:indexSet inColumn:column]; [_browser scrollColumnToVisible:column]; [_browser scrollRowToVisible:[indexSet lastIndex] inColumn:column]; [_browser scrollRowToVisible:[indexSet firstIndex] inColumn:column]; } } else { [_browser selectRowIndexes:nil inColumn:0]; } } - (void)urlDidLoad:(NSURL *)url { NSURL *viewRoot; viewRoot = [self rootURL]; // Only care about the url if it's visible (under the current view root). if ([viewRoot ck2_isAncestorOfURL:url]) { NSIndexPath *indexPath; NSArray *urls; indexPath = nil; urls = @[ url ]; [self getIndexPath:&indexPath indexSet:NULL ofURLs:&urls inDirectory:nil]; if (indexPath != nil) { [_browser reloadColumn:[indexPath length]]; } else { [_browser loadColumnZero]; } [_browser setNeedsDisplay:YES]; } } - (NSArray *)selectedURLs { NSIndexPath *indexPath; NSIndexSet *indexSet; NSInteger column; NSMutableArray *urls; urls = [NSMutableArray array]; indexPath = [_browser selectionIndexPath]; column = [indexPath length] - 1; if (column >= 0) { indexSet = [_browser selectedRowIndexesInColumn:column]; [indexSet enumerateIndexesUsingBlock: ^(NSUInteger idx, BOOL *stop) { NSURL *url; url = [_browser itemAtRow:idx inColumn:column]; if (url != nil) { [urls addObject:url]; } }]; } return urls; } - (IBAction)itemDoubleClicked:(id)sender { [self goToSelectedItem:sender]; } - (IBAction)goToSelectedItem:(id)sender { NSArray *urls; BOOL isValid; CK2OpenPanelController *controller; controller = [self controller]; urls = [self selectedURLs]; isValid = YES; for (NSURL *url in urls) { if (![controller isURLValid:url]) { isValid = NO; break; } } if (isValid) { [controller ok:self]; } } #pragma mark NSBrowserDelegate methods - (id)rootItemForBrowser:(NSBrowser *)browser { return [self rootURL]; } - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item { NSArray *children; children = [[self controller] childrenForURL:item]; if (index >= [children count]) { NSLog(@"Got out of range index for some reason"); } return [children objectAtIndex:index]; } - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item { return ![[self controller] URLCanHazChildren:item]; } - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item { return [[[self controller] childrenForURL:item] count]; } - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item { return [item ck2_displayName]; } - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item { return NO; } - (void)browser:(NSBrowser *)browser willDisplayCell:(id)cell atRow:(NSInteger)row column:(NSInteger)column { NSURL *url; CK2OpenPanelController *controller; controller = [self controller]; url = [browser itemAtRow:row inColumn:column]; [cell setImage:[url ck2_icon]]; if ([controller isURLValid:url] || [controller URLCanHazChildren:url]) { [cell setTextColor:[NSColor controlTextColor]]; } else { [cell setTextColor:[NSColor disabledControlTextColor]]; } [cell setTextOnly:[url ck2_isPlaceholder]]; } - (NSIndexSet *)browser:(NSBrowser *)browser selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes inColumn:(NSInteger)column { NSMutableIndexSet *indexSet; CK2OpenPanelController *controller; controller = [self controller]; indexSet = [NSMutableIndexSet indexSet]; [proposedSelectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { NSURL *url; url = [browser itemAtRow:idx inColumn:column]; if ([controller isURLValid:url] || [controller URLCanHazChildren:url]) { [indexSet addIndex:idx]; } }]; // If we are not selecting any new items, returning an empty indexset will nuke the original selection. We // rectify that here. if ([indexSet count] == 0) { [indexSet addIndexes:[browser selectedRowIndexesInColumn:column]]; } return indexSet; } - (BOOL)browser:(NSBrowser *)browser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column withEvent:(NSEvent *)event { return NO; } - (BOOL)browser:(NSBrowser *)browser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column toPasteboard:(NSPasteboard *)pasteboard { return NO; } - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item { if (_previewController == nil) { _previewController = [[CK2BrowserPreviewController alloc] init]; } return _previewController; } @end ================================================ FILE: ConnectionKit/CK2OpenPanelController.h ================================================ // // CKRemoteViewController.h // ConnectionKit // // Created by Paul Kim on 12/14/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import @class CK2OpenPanel, CK2BrowserPreviewController, CK2PathControl, CK2FileManager, CK2OpenPanelColumnViewController, CK2OpenPanelListViewController, CK2OpenPanelIconViewController, CK2PathFieldWindowController; @interface CK2OpenPanelController : NSViewController { IBOutlet NSView *_topSection; IBOutlet NSTextField *_hostField; IBOutlet NSView *_buttonSection; IBOutlet CK2PathControl *_pathControl; IBOutlet NSSegmentedControl *_viewPicker; IBOutlet NSView *_middleSection; IBOutlet NSTabView *_tabView; IBOutlet NSView *_bottomSection; IBOutlet NSButton *_okButton; IBOutlet NSButton *_cancelButton; IBOutlet NSButton *_refreshButton; IBOutlet NSProgressIndicator *_progressIndicator; IBOutlet NSSegmentedControl *_historyButtons; IBOutlet NSButton *_newFolderButton; IBOutlet NSSegmentedControl *_homeButton; IBOutlet NSTextField *_messageLabel; IBOutlet NSBox *_accessoryContainer; NSView *_initialAccessoryView; IBOutlet CK2OpenPanel *_openPanel; IBOutlet CK2OpenPanelColumnViewController *_browserController; IBOutlet CK2OpenPanelListViewController *_listViewController; IBOutlet CK2OpenPanelIconViewController *_iconViewController; NSURL *_directoryURL; NSArray *_urls; NSURL *_homeURL; NSMutableDictionary *_urlCache; NSMutableDictionary *_runningOperations; CK2FileManager *_fileManager; NSUndoManager *_historyManager; NSTabViewItem *_lastTab; CK2FileOperation *_currentLoadingOperation; CK2PathFieldWindowController *_pathFieldController; } @property (readonly, assign) CK2OpenPanel *openPanel; @property (readonly, retain) NSURL *directoryURL; @property (readwrite, retain) NSURL *URL; @property (readwrite, copy, nonatomic) NSArray *URLs; @property (readwrite, retain, nonatomic) NSURL *homeURL; @property (readwrite, retain) NSView *accessoryView; @property (readonly, retain) CK2FileManager *fileManager; - (id)initWithPanel:(CK2OpenPanel *)panel; - (void)close; // Open panel MUST call this to clear out weak reference to itself, otherwise BOOM dangling pointer! - (void)changeDirectory:(NSURL *)directoryURL completionHandler:(void (^)(NSError *error))block; - (IBAction)pathControlItemSelected:(id)sender; - (void)resetSession; - (IBAction)ok:(id)sender; - (IBAction)cancel:(id)sender; - (IBAction)refresh:(id)sender; - (IBAction)newFolder:(id)sender; - (IBAction)changeHistory:(id)sender; - (IBAction)back:(id)sender; - (IBAction)forward:(id)sender; - (IBAction)home:(id)sender; - (void)goToSelectedItem:(id)sender; - (void)showPathFieldWithString:(NSString *)string; - (void)setURLs:(NSArray *)urls updateDirectory:(BOOL)updateDir sender:(id)sender; - (void)setURLs:(NSArray *)urls updateDirectory:(BOOL)updateDir updateRoot:(BOOL)updateRoot sender:(id)sender; - (BOOL)isURLValid:(NSURL *)url; - (BOOL)URLCanHazChildren:(NSURL *)url; - (NSArray *)childrenForURL:(NSURL *)url; - (void)addToHistory; - (void)reload; - (void)validateVisibleColumns; @end ================================================ FILE: ConnectionKit/CK2OpenPanelController.m ================================================ // // CKRemoteViewController.m // ConnectionKit // // Created by Paul Kim on 12/14/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //NOTES: // - Besides the normal UI stuff which must be done in the main thread, the URL cache must also only be modified in the // main thread. This is to prevent a case where NSBrowser or NSOutlineView query for the children of an URL first for // the number of children, then query again for the actual children. In this case, if the URL cache is updated in // between these two calls, those UI classes can end up asking for an invalid index. // - Because most things should only be modified in the main thread, you may not see the results until the next // run loop cycle. Since we're using GCD, it's a queue so you can at least rely on order of operations corresponding // to the order you queued things up. // - The previous point is exploited to avoid race conditions. Otherwise, a semaphore (wait/signal) would have to be // used in those cases. #import "CK2OpenPanelController.h" #import "CK2OpenPanel.h" #import "CK2FileCell.h" #import "NSURL+CK2OpenPanel.h" #import "CK2OpenPanelViewController.h" #import "CK2OpenPanelIconViewController.h" #import "CK2OpenPanelListViewController.h" #import "CK2OpenPanelColumnViewController.h" #import "CK2PathControl.h" #import "CK2NewFolderWindowController.h" #import "CK2IconView.h" #import "CK2PathFieldWindowController.h" #import #define DEFAULT_OPERATION_TIMEOUT 20 #define MIN_PROMPT_BUTTON_WIDTH 82 #define PROMPT_BUTTON_RIGHT_MARGIN 18 #define MESSAGE_SIDE_MARGIN 16 #define MESSAGE_HEIGHT 24 #define HISTORY_DIRECTORY_URL_KEY @"directoryURL" #define HISTORY_DIRECTORY_VIEW_INDEX_KEY @"viewIndex" #define ICON_VIEW_IDENTIFIER @"icon" #define LIST_VIEW_IDENTIFIER @"list" #define COLUMN_VIEW_IDENTIFIER @"column" #define BLANK_VIEW_IDENTIFIER @"blank" #define CK2OpenPanelLastViewPrefKey @"CK2NavPanelFileLastListModeForOpenModeKey" @interface CK2OpenPanelController () @property (readwrite, retain) NSURL *directoryURL; @end @implementation CK2OpenPanelController @synthesize openPanel = _openPanel; @synthesize directoryURL = _directoryURL; @synthesize URLs = _urls; @synthesize homeURL = _homeURL; @synthesize fileManager = _fileManager; #pragma mark Lifecycle - (id)initWithPanel:(CK2OpenPanel *)panel { NSBundle *bundle; bundle = [NSBundle bundleForClass:[self class]]; if ((self = [super initWithNibName:@"CK2OpenPanel" bundle:bundle]) != nil) { _openPanel = panel; _urlCache = [[NSMutableDictionary alloc] init]; _runningOperations = [[NSMutableDictionary alloc] init]; _historyManager = [[NSUndoManager alloc] init]; _fileManager = [[CK2FileManager fileManagerWithDelegate:self delegateQueue:[NSOperationQueue mainQueue]] retain]; [_openPanel addObserver:self forKeyPath:@"prompt" options:NSKeyValueObservingOptionNew context:NULL]; [_openPanel addObserver:self forKeyPath:@"message" options:NSKeyValueObservingOptionOld context:NULL]; [_openPanel addObserver:self forKeyPath:@"canChooseFiles" options:NSKeyValueObservingOptionNew context:NULL]; [_openPanel addObserver:self forKeyPath:@"canChooseDirectories" options:NSKeyValueObservingOptionNew context:NULL]; [_openPanel addObserver:self forKeyPath:@"allowsMultipleSelection" options:NSKeyValueObservingOptionNew context:NULL]; [_openPanel addObserver:self forKeyPath:@"showsHiddenFiles" options:NSKeyValueObservingOptionNew context:NULL]; [_openPanel addObserver:self forKeyPath:@"treatsFilePackagesAsDirectories" options:NSKeyValueObservingOptionNew context:NULL]; [_openPanel addObserver:self forKeyPath:@"allowedFileTypes" options:NSKeyValueObservingOptionNew context:NULL]; [_openPanel addObserver:self forKeyPath:@"canCreateDirectories" options:NSKeyValueObservingOptionNew context:NULL]; } return self; } - (void)close { [_openPanel removeObserver:self forKeyPath:@"prompt"]; [_openPanel removeObserver:self forKeyPath:@"message"]; [_openPanel removeObserver:self forKeyPath:@"canChooseFiles"]; [_openPanel removeObserver:self forKeyPath:@"canChooseDirectories"]; [_openPanel removeObserver:self forKeyPath:@"allowsMultipleSelection"]; [_openPanel removeObserver:self forKeyPath:@"showsHiddenFiles"]; [_openPanel removeObserver:self forKeyPath:@"treatsFilePackagesAsDirectories"]; [_openPanel removeObserver:self forKeyPath:@"allowedFileTypes"]; [_openPanel removeObserver:self forKeyPath:@"canCreateDirectories"]; if (_openPanel) { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidBecomeKeyNotification object:_openPanel]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidResignKeyNotification object:_openPanel]; } _openPanel = nil; } - (void)dealloc { [_initialAccessoryView release]; for (CK2FileOperation *operation in [_runningOperations allValues]) { [operation cancel]; } if (_currentLoadingOperation != nil) { [_currentLoadingOperation cancel]; [_currentLoadingOperation release]; } [self close]; // just to be sure [_directoryURL release]; [_urls release]; [_homeURL release]; [_urlCache release]; [_runningOperations release]; [_fileManager release]; [_historyManager release]; [_pathFieldController release]; [super dealloc]; } - (void)awakeFromNib { NSTabViewItem *item; id value; _initialAccessoryView = [[_accessoryContainer contentView] retain]; [_hostField setStringValue:@""]; [self validateHistoryButtons]; [self validateOKButton]; [self validateProgressIndicator]; value = [[NSUserDefaults standardUserDefaults] stringForKey:CK2OpenPanelLastViewPrefKey]; if (value == nil) { // If not set, use the global default used by NSOpenPanel value = [[NSUserDefaults standardUserDefaults] stringForKey:@"NSNavPanelFileLastListModeForOpenModeKey"]; switch ([value integerValue]) { case 1: value = COLUMN_VIEW_IDENTIFIER; break; case 2: value = LIST_VIEW_IDENTIFIER; break; case 3: value = ICON_VIEW_IDENTIFIER; break; default: value = ICON_VIEW_IDENTIFIER; } } item = [_tabView tabViewItemAtIndex:[_tabView indexOfTabViewItemWithIdentifier:value]]; [_tabView selectTabViewItem:item]; [_viewPicker setSelectedSegment:[_tabView indexOfTabViewItem:item]]; [_openPanel makeFirstResponder:[item initialFirstResponder]]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqual:@"prompt"]) { NSRect rect1, rect2, superBounds; [_okButton setTitle:[_openPanel prompt]]; superBounds = [[_okButton superview] bounds]; [_okButton sizeToFit]; rect1 = [_okButton frame]; rect1.size.width = MAX(MIN_PROMPT_BUTTON_WIDTH, NSWidth(rect1)); rect1.origin.x = NSMaxX(superBounds) - PROMPT_BUTTON_RIGHT_MARGIN - NSWidth(rect1); [_okButton setFrame:rect1]; rect2 = [_cancelButton frame]; rect2.origin.x = NSMinX(rect1) - NSWidth(rect2); [_cancelButton setFrame:rect2]; } else if ([keyPath isEqual:@"message"]) { id oldMessage; NSString *message; CGFloat height; NSRect rect, bounds; oldMessage = [change objectForKey:NSKeyValueChangeOldKey]; oldMessage = oldMessage == [NSNull null] ? nil : oldMessage; message = [_openPanel message]; if ([message length] > 0) { [_messageLabel setStringValue:message]; } bounds = [[self view] bounds]; height = 0.0; if (([oldMessage length] == 0) && ([message length] > 0)) { height = MESSAGE_HEIGHT; } else if (([oldMessage length] > 0) && ([message length] == 0)) { height = -MESSAGE_HEIGHT; } if (height != 0.0) { NSUInteger buttonMask, middleMask; rect = [[self view] frame]; rect.size.height += height; buttonMask = [_buttonSection autoresizingMask]; [_buttonSection setAutoresizingMask:NSViewMaxYMargin | NSViewWidthSizable]; middleMask = [_middleSection autoresizingMask]; [_middleSection setAutoresizingMask:NSViewMaxYMargin | NSViewWidthSizable]; rect = [_openPanel frameRectForContentRect:rect]; [_openPanel setFrame:rect display:YES]; [_buttonSection setAutoresizingMask:buttonMask]; [_middleSection setAutoresizingMask:middleMask]; if ([_messageLabel superview] != [self view]) { rect = [_messageLabel frame]; rect.origin.x = NSMinX(bounds) + MESSAGE_SIDE_MARGIN; rect.origin.y = NSMaxY([_buttonSection frame]); rect.size.width = NSWidth(bounds) - 2 * MESSAGE_SIDE_MARGIN; [_messageLabel setFrame:rect]; [[self view] addSubview:_messageLabel]; } else { [_messageLabel removeFromSuperview]; } } } else if ([keyPath isEqual:@"canChooseFiles"] || [keyPath isEqual:@"canChooseDirectories"] || [keyPath isEqual:@"showsHiddenFiles"] || [keyPath isEqual:@"treatsFilePackagesAsDirectories"] || [keyPath isEqual:@"allowedFileTypes"]) { [self validateVisibleColumns]; } else if ([keyPath isEqual:@"canCreateDirectories"]) { if ([_openPanel canCreateDirectories]) { [_newFolderButton setHidden:NO]; } else { [_newFolderButton setHidden:YES]; } } else if ([keyPath isEqual:@"allowsMultipleSelection"]) { [_iconViewController setAllowsMutipleSelection:[_openPanel allowsMultipleSelection]]; [_listViewController setAllowsMutipleSelection:[_openPanel allowsMultipleSelection]]; [_browserController setAllowsMutipleSelection:[_openPanel allowsMultipleSelection]]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (NSArray *)fileProperties { return @[ NSURLIsDirectoryKey, NSURLFileSizeKey, NSURLContentModificationDateKey, NSURLLocalizedNameKey, NSURLIsSymbolicLinkKey, CK2URLSymbolicLinkDestinationKey, NSURLIsPackageKey, NSURLIsHiddenKey, NSURLEffectiveIconKey, NSURLParentDirectoryURLKey ]; } - (NSView *)accessoryView { NSView *view; view = [_accessoryContainer contentView]; if (view == _initialAccessoryView) { return nil; } return view; } - (void)setAccessoryView:(NSView *)accessoryView { NSRect rect; CGFloat height; NSUInteger mask; // Save off the mask and tweak it. We want the middle section to have different resizing behavior while we // resize the window mask = [_middleSection autoresizingMask]; [_middleSection setAutoresizingMask:NSViewMinYMargin | NSViewWidthSizable]; if (accessoryView != nil) { NSRect accessoryFrame, containerFrame; height = 0.0; if ([_accessoryContainer superview] == [self view]) { height = NSHeight([[_accessoryContainer contentView] frame]); } accessoryFrame = [accessoryView frame]; // Remove the accessory container while we muck around with window dimensions and such. We want to preserve the // accessory view's original size. Also, no need to retain because it's a top-level object in the xib. [_accessoryContainer removeFromSuperview]; [_accessoryContainer setFrameFromContentFrame:accessoryFrame]; [_accessoryContainer setContentView:accessoryView]; // Tile the container containerFrame = [_accessoryContainer frame]; containerFrame.origin.x = 0.0; containerFrame.origin.y = NSMaxY([_bottomSection frame]); [_accessoryContainer setFrame:containerFrame]; // Resize the window to accommodate rect = [[self view] frame]; //PENDING: shrink to fit? Check against windows min and max? rect.size.width = NSWidth(containerFrame); rect.size.height += NSHeight(containerFrame) - height; rect = [_openPanel frameRectForContentRect:rect]; [_openPanel setFrame:rect display:YES]; [[self view] addSubview:_accessoryContainer]; } else { CGFloat height; height = NSHeight([_accessoryContainer frame]); [_accessoryContainer setContentView:_initialAccessoryView]; [_accessoryContainer removeFromSuperview]; rect = [[self view] frame]; rect.size.height -= height; rect = [_openPanel frameRectForContentRect:rect]; [_openPanel setFrame:rect display:YES]; } [_middleSection setAutoresizingMask:mask]; } - (void)validateVisibleColumns { [[_browserController view] setNeedsDisplay:YES]; [[_listViewController view] setNeedsDisplay:YES]; [_iconViewController reload]; [self validateOKButton]; } - (void)resetSession { for (CK2FileOperation *operation in [_runningOperations allValues]) { [operation cancel]; } [_runningOperations removeAllObjects]; if (_currentLoadingOperation != nil) { [_currentLoadingOperation cancel]; [_currentLoadingOperation release]; _currentLoadingOperation = nil; } [_urlCache removeAllObjects]; [_historyManager removeAllActions]; [self setURLs:nil]; } - (NSArray *)URLs { if ([_urls count] == 0) { NSURL *directoryURL; directoryURL = [self directoryURL]; if (directoryURL != nil) { return @[ [self directoryURL] ]; } return nil; } return _urls; } - (NSURL *)URL { NSArray *urls; urls = [self URLs]; if ([urls count] > 0) { return [urls objectAtIndex:0]; } return nil; } - (void)setURL:(NSURL *)URL { [self setURLs:(URL ? @[URL] : nil)]; } // Called by the open panel. The completion block will not be called until the given URL is loaded. - (void)changeDirectory:(NSURL *)directoryURL completionHandler:(void (^)(NSError *error))block { // Set it now so that the value can be returned if queried. Don't bother syncing with the UI as it will be set // again (possibly with a different value) later. [self setDirectoryURL:directoryURL]; //PENDING: compare url if (_currentLoadingOperation != nil) { [_currentLoadingOperation cancel]; [_currentLoadingOperation release]; } _currentLoadingOperation = [_fileManager contentsOfDirectoryAtURL:directoryURL includingPropertiesForKeys:[self fileProperties] options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants | CK2DirectoryEnumerationIncludesDirectory) completionHandler: ^(NSArray *contents, NSError *blockError) { if (blockError != nil) { NSLog(@"Error loading contents of URL %@: %@", directoryURL, blockError); } else { NSURL *resolvedURL; NSArray *children; // The first url returned is the rootURL properly resolved (in case the URL is relative to the user's home // directory, for instance). resolvedURL = [contents objectAtIndex:0]; children = [contents subarrayWithRange:NSMakeRange(1, [contents count] - 1)]; [self setDirectoryURL:resolvedURL]; [self cacheChildren:children forURL:resolvedURL]; [self urlDidLoad:resolvedURL]; [_currentLoadingOperation release]; _currentLoadingOperation = nil; if (blockError == nil) { [self setURLs:@[ resolvedURL ] updateDirectory:YES updateRoot:YES sender:self]; } [self validateViews]; } if (block != NULL) { block(blockError); } }]; // There shouldn't be a race condition with the block above since this should be on the main thread and // the above block won't run on the main thread until this code completes and returns to the run loop. [_currentLoadingOperation retain]; [_hostField setStringValue:[directoryURL host]]; [self validateViews]; } - (void)setURLs:(NSArray *)urls updateDirectory:(BOOL)flag { [self setURLs:urls updateDirectory:flag updateRoot:NO sender:nil]; } - (void)setURLs:(NSArray *)urls updateDirectory:(BOOL)updateDir sender:(id)sender { [self setURLs:urls updateDirectory:updateDir updateRoot:NO sender:sender]; } - (void)setURLs:(NSArray *)urls updateDirectory:(BOOL)updateDir updateRoot:(BOOL)updateRoot sender:(id)sender { CK2OpenPanelViewController *currentController; NSTabViewItem *selectedTab; [self setURLs:urls]; [self validateOKButton]; if (updateDir) { NSURL *directoryURL; directoryURL = [urls objectAtIndex:0]; if (![self URLCanHazChildren:directoryURL]) { directoryURL = [directoryURL ck2_parentURL]; } [self setDirectoryURL:directoryURL]; if (sender != _pathControl) { [_pathControl setURL:directoryURL]; } if ([[_openPanel delegate] respondsToSelector:@selector(panel:didChangeToDirectoryURL:)]) { [[_openPanel delegate] panel:_openPanel didChangeToDirectoryURL:directoryURL]; } } [self validateNewFolderButton]; selectedTab = [_tabView selectedTabViewItem]; currentController = [self viewControllerForIdentifier:[selectedTab identifier]]; if ((sender != currentController) || (![currentController hasFixedRoot] && updateDir)) { if (updateRoot) { [_browserController setRootURL:[self directoryURL]]; } if (updateDir) { [currentController reload]; } [currentController update]; } [_openPanel makeFirstResponder:[selectedTab initialFirstResponder]]; if ([[_openPanel delegate] respondsToSelector:@selector(panelSelectionDidChange:)]) { [[_openPanel delegate] panelSelectionDidChange:_openPanel]; } } - (void)cacheChildren:(NSArray *)children forURL:(NSURL *)url { if (children == nil) { [_urlCache removeObjectForKey:url]; } else { NSArray *sortedChildren; sortedChildren = [children sortedArrayUsingComparator:[NSURL ck2_displayComparator]]; [_urlCache setObject:sortedChildren forKey:url]; } } - (NSArray *)childrenForURL:(NSURL *)url { id children; children = nil; if ((url != nil) && [self URLCanHazChildren:url]) { children = [_urlCache objectForKey:url]; if (children == nil) { CK2FileOperation *operation; // Placeholder while children are being fetched children = @[ [NSURL ck2_loadingURL] ]; [self cacheChildren:children forURL:url]; if ([_runningOperations objectForKey:url] == nil) { operation = [_fileManager contentsOfDirectoryAtURL:url includingPropertiesForKeys:[self fileProperties] options:NSDirectoryEnumerationSkipsSubdirectoryDescendants completionHandler: ^(NSArray *contents, NSError *blockError) { id value; value = contents; if (value == nil) { if (blockError != nil) { NSString *errorMessage = blockError.localizedFailureReason; if (!errorMessage) errorMessage = blockError.localizedDescription; value = @[ [NSURL ck2_errorURLWithMessage:errorMessage] ]; NSLog(@"Error loading contents of URL %@: %@", url, blockError); } else { // Shouldn't happen NSLog(@"Received nil from -contentsOfDirectoryAtURL: for URL %@ but no error set", url); value = [NSArray array]; } } [self cacheChildren:value forURL:url]; [_runningOperations removeObjectForKey:url]; [self validateProgressIndicator]; [self urlDidLoad:url]; }]; // There shouldn't be a race condition with the block above since this should be on the main thread and // the above block won't run on the main thread until this code completes and returns to the run loop. [_runningOperations setObject:operation forKey:url]; [self validateProgressIndicator]; } } else { if (![[self openPanel] showsHiddenFiles]) { children = [children filteredArrayUsingPredicate:[NSPredicate predicateWithBlock: ^BOOL(id evaluatedObject, NSDictionary *bindings) { return ![evaluatedObject ck2_isHidden]; }]]; } } } return children; } - (BOOL)isURLValid:(NSURL *)url { if (![url ck2_isPlaceholder]) { id delegate; BOOL delegateValid, fileTypeValid; NSArray *allowedFileTypes; delegate = [_openPanel delegate]; delegateValid = (![delegate respondsToSelector:@selector(panel:shouldEnableURL:)] || [delegate panel:_openPanel shouldEnableURL:url]); allowedFileTypes = [_openPanel allowedFileTypes]; fileTypeValid = ([allowedFileTypes count] == 0) || [allowedFileTypes containsObject:[url pathExtension]]; if ([self URLCanHazChildren:url]) { return [_openPanel canChooseDirectories] && delegateValid && fileTypeValid; } else { return [_openPanel canChooseFiles] && delegateValid && fileTypeValid; } } return NO; } - (BOOL)URLCanHazChildren:(NSURL *)url { return [url ck2_isDirectory] && (![url ck2_isPackage] || [_openPanel treatsFilePackagesAsDirectories]); } - (void)urlDidLoad:(NSURL *)url { [[self currentViewController] urlDidLoad:url]; [self validateNewFolderButton]; } - (CK2OpenPanelViewController *)currentViewController { return [self viewControllerForIdentifier:[[_tabView selectedTabViewItem] identifier]]; } - (CK2OpenPanelViewController *)viewControllerForIdentifier:(NSString *)identifier { if ([identifier isEqual:COLUMN_VIEW_IDENTIFIER]) { return _browserController; } else if ([identifier isEqual:LIST_VIEW_IDENTIFIER]) { return _listViewController; } else if ([identifier isEqual:ICON_VIEW_IDENTIFIER]) { return _iconViewController; } return nil; } - (void)validateViews { BOOL enable; enable = (_currentLoadingOperation == nil); [_viewPicker setEnabled:enable]; [_homeButton setEnabled:enable]; if (!enable) { if (_lastTab == nil) { _lastTab = [_tabView selectedTabViewItem]; } [_tabView selectTabViewItemWithIdentifier:BLANK_VIEW_IDENTIFIER]; } else { if (_lastTab == nil) { _lastTab = [_tabView tabViewItemAtIndex:[_tabView indexOfTabViewItemWithIdentifier:COLUMN_VIEW_IDENTIFIER]]; } [_tabView selectTabViewItem:_lastTab]; _lastTab = nil; } [self validateHistoryButtons]; [self validateOKButton]; [self validateProgressIndicator]; [self validateHomeButton]; [self validateNewFolderButton]; } - (void)reload { [_browserController reload]; [_iconViewController reload]; [_listViewController reload]; [self validateViews]; } - (void)validateProgressIndicator { NSURL *url; NSArray *urls; BOOL urlIsLoading; CK2OpenPanelViewController *currentController; urlIsLoading = NO; currentController = [self currentViewController]; urls = [currentController selectedURLs]; if ([urls count] > 0) { for (url in urls) { if ([_runningOperations objectForKey:url] != nil) { urlIsLoading = YES; break; } } } else { urlIsLoading = ([_runningOperations objectForKey:[self directoryURL]] != nil); } if ((_currentLoadingOperation == nil) && !urlIsLoading) { [_progressIndicator stopAnimation:self]; [_progressIndicator setHidden:YES]; [_refreshButton setHidden:NO]; } else { [_refreshButton setHidden:YES]; [_progressIndicator startAnimation:self]; [_progressIndicator setHidden:NO]; } } - (IBAction)refresh:(id)sender { NSURL *url; NSArray *urls; BOOL urlIsLoading; CK2OpenPanelViewController *viewController; NSResponder *firstResponder; urlIsLoading = NO; urls = [self URLs]; if ([urls count] == 0) { urls = @[ [self directoryURL] ]; } for (url in urls) { if ([_runningOperations objectForKey:url] != nil) { urlIsLoading = YES; break; } } if (!urlIsLoading) { for (url in urls) { [self cacheChildren:nil forURL:url]; } } viewController = [self currentViewController]; firstResponder = [[self openPanel] firstResponder]; if ([firstResponder isKindOfClass:[NSView class]] && [(NSView *)firstResponder isDescendantOf:[viewController view]]) { firstResponder = [viewController view]; } [viewController reload]; [viewController update]; [[self openPanel] makeFirstResponder:firstResponder]; [self validateNewFolderButton]; [self validateProgressIndicator]; } - (void)validateHomeButton { } - (void)validateOKButton { BOOL isValid; isValid = NO; if (_currentLoadingOperation == nil) { isValid = YES; for (NSURL *url in [self URLs]) { if (![self isURLValid:url]) { isValid = NO; break; } } } [_okButton setEnabled:isValid]; } - (IBAction)ok:(id)sender { [_openPanel ok:sender]; } - (IBAction)cancel:(id)sender { [self setURL:nil]; [_openPanel cancel:sender]; } - (void)validateNewFolderButton { if (_currentLoadingOperation != nil) { [_newFolderButton setEnabled:NO]; } else { NSArray *children; BOOL childrenLoaded; children = [self childrenForURL:[self directoryURL]]; childrenLoaded = (children != nil) && (([children count] != 1) || ![[children objectAtIndex:0] ck2_isPlaceholder]); [_newFolderButton setEnabled:childrenLoaded]; } } - (IBAction)newFolder:(id)sender { CK2NewFolderWindowController *controller; NSURL *parentURL; controller = [[CK2NewFolderWindowController alloc] initWithController:self]; parentURL = [self directoryURL]; if ([controller runModalForURL:parentURL]) { NSURL *url; NSArray *children; NSMutableArray *newChildren; NSUInteger i; url = [controller folderURL]; children = [_urlCache objectForKey:parentURL]; newChildren = [NSMutableArray arrayWithArray:children]; i = [newChildren indexOfObject:url inSortedRange:NSMakeRange(0, [newChildren count]) options:NSBinarySearchingInsertionIndex usingComparator:[NSURL ck2_displayComparator]]; [newChildren insertObject:url atIndex:i]; [self cacheChildren:newChildren forURL:parentURL]; [self setURLs:@[ url ] updateDirectory:YES updateRoot:NO sender:nil]; } else if ([controller error] != nil) { NSLog(@"Could not create directory under URL %@: %@", parentURL, [controller error]); NSBeginCriticalAlertSheet(NSLocalizedStringFromTableInBundle(@"Could not create folder", nil, [NSBundle bundleForClass:[self class]], @"Create folder error"), NSLocalizedStringFromTableInBundle(@"OK", nil, [NSBundle bundleForClass:[self class]], @"OK Button"), nil, nil, [self openPanel], nil, NULL, NULL, NULL, @"%@", [[controller error] localizedDescription]); } [controller release]; } - (void)validateHistoryButtons { [_historyButtons setEnabled:((_currentLoadingOperation == nil) && ([_historyManager canUndo])) forSegment:0]; [_historyButtons setEnabled:((_currentLoadingOperation == nil) && ([_historyManager canRedo])) forSegment:1]; } - (IBAction)changeHistory:(id)sender { NSInteger selectedIndex; selectedIndex = [_historyButtons selectedSegment]; switch (selectedIndex) { case 0: [self back:sender]; break; case 1: [self forward:sender]; break; } } - (IBAction)back:(id)sender { [_historyManager undo]; [self validateHistoryButtons]; } - (IBAction)forward:(id)sender { [_historyManager redo]; [self validateHistoryButtons]; } - (void)changeView:(NSDictionary *)dict { NSTabViewItem *tabItem; NSUInteger i; [self addToHistory]; [self setURLs:@[ [dict objectForKey:HISTORY_DIRECTORY_URL_KEY] ] updateDirectory:YES]; i = [[dict objectForKey:HISTORY_DIRECTORY_VIEW_INDEX_KEY] unsignedIntegerValue]; tabItem = [_tabView tabViewItemAtIndex:i]; [[self viewControllerForIdentifier:[tabItem identifier]] restoreViewHistoryState:dict]; [_tabView selectTabViewItemAtIndex:i]; } - (void)addToHistory { NSMutableDictionary *dict; CK2OpenPanelViewController *currentController; NSTabViewItem *tabItem; NSURL *directoryURL; directoryURL = [self directoryURL]; // If nil, indicates an error so should not push it on the undo stack if (directoryURL != nil) { tabItem = [_tabView selectedTabViewItem]; dict = [NSMutableDictionary dictionary]; [dict setObject:[self directoryURL] forKey:HISTORY_DIRECTORY_URL_KEY]; [dict setObject:[NSNumber numberWithUnsignedInteger:[_tabView indexOfTabViewItem:tabItem]] forKey:HISTORY_DIRECTORY_VIEW_INDEX_KEY]; currentController = [self viewControllerForIdentifier:[tabItem identifier]]; [currentController saveViewHistoryState:dict]; [_historyManager registerUndoWithTarget:self selector:@selector(changeView:) object:dict]; [self validateHistoryButtons]; } } - (IBAction)home:(id)sender { NSURL *homeURL; homeURL = [self homeURL]; if (homeURL == nil) { if (_currentLoadingOperation != nil) { [_currentLoadingOperation cancel]; [_currentLoadingOperation release]; } // The homeURL isn't resolved so we resolve it here and also load/cache its children. homeURL = [CK2FileManager URLWithPath:@"" isDirectory:YES hostURL:[self directoryURL]]; _currentLoadingOperation = [_fileManager contentsOfDirectoryAtURL:homeURL includingPropertiesForKeys:[self fileProperties] options:CK2DirectoryEnumerationIncludesDirectory completionHandler: ^(NSArray *contents, NSError *blockError) { NSArray *children; NSURL *resolvedURL; resolvedURL = homeURL; if (blockError != nil) { NSString *errorMessage = blockError.localizedFailureReason; if (!errorMessage) errorMessage = blockError.localizedDescription; children = @[ [NSURL ck2_errorURLWithMessage:errorMessage] ]; NSLog(@"Error loading contents of URL %@: %@", homeURL, blockError); } else { resolvedURL = [contents objectAtIndex:0]; children = [contents subarrayWithRange:NSMakeRange(1, [contents count] - 1)]; } [self setHomeURL:resolvedURL]; [self cacheChildren:children forURL:resolvedURL]; [_currentLoadingOperation release]; _currentLoadingOperation = nil; [self validateProgressIndicator]; [self urlDidLoad:resolvedURL]; [self setURLs:@[ resolvedURL ] updateDirectory:YES updateRoot:YES sender:self ]; }]; // There shouldn't be a race condition with the block above since this should be on the main thread and // the above block won't run on the main thread until this code completes and returns to the run loop. [_currentLoadingOperation retain]; [self validateProgressIndicator]; } else { [self setURLs:@[ homeURL ] updateDirectory:YES updateRoot:YES sender:self ]; } } - (void)goToSelectedItem:(id)sender { [[self currentViewController] goToSelectedItem:(id)sender]; } - (void)showPathFieldWithString:(NSString *)string { if (_pathFieldController == nil) { _pathFieldController = [[CK2PathFieldWindowController alloc] init]; } [_pathFieldController setStringValue:string]; [_pathFieldController beginSheetModalForWindow:[self openPanel] completionHandler: ^(NSInteger result) { if (result == NSOKButton) { NSString *path; path = [_pathFieldController stringValue]; if ([path hasPrefix:@"~"]) { path = [path substringFromIndex:1]; if ([path hasPrefix:@"/"]) { path = [path substringFromIndex:1]; } } [self addToHistory]; [self changeDirectory:[CK2FileManager URLWithPath:path isDirectory:YES hostURL:[self directoryURL]] completionHandler: ^(NSError *error) { if (error != nil) { NSLog(@"Could not switch to URL %@: %@", [self directoryURL], error); NSBeginCriticalAlertSheet(NSLocalizedStringFromTableInBundle(@"Could not switch to folder", nil, [NSBundle bundleForClass:[self class]], @"Switch folder error"), NSLocalizedStringFromTableInBundle(@"OK", nil, [NSBundle bundleForClass:[self class]], @"OK Button"), nil, nil, [self openPanel], nil, NULL, NULL, NULL, @"%@", [error localizedDescription]); // NSOpenPanel will try and select as much of the URL as is valid. We don't do that here since it may // take a while to resolve each ancestor so we just revert back to the previous directory. [self back:self]; } }]; } }]; } - (IBAction)pathControlItemSelected:(id)sender { NSURL *url; url = [_pathControl URL]; if (![url isEqual:[self directoryURL]]) { [self addToHistory]; [self setURLs:@[ url ] updateDirectory:YES updateRoot:YES sender:_pathControl]; } } #pragma mark NSPathControlDelegate - (BOOL)pathControl:(NSPathControl *)pathControl shouldDragPathComponentCell:(NSPathComponentCell *)pathComponentCell withPasteboard:(NSPasteboard *)pasteboard { return NO; } #pragma mark NSTabViewDelegate - (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem { CK2OpenPanelViewController *viewController; viewController = [self viewControllerForIdentifier:[tabViewItem identifier]]; [viewController reload]; [viewController update]; } - (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem { NSString *identifier; [_openPanel makeFirstResponder:[tabViewItem initialFirstResponder]]; identifier = [tabViewItem identifier]; if (![identifier isEqual:BLANK_VIEW_IDENTIFIER]) { [[NSUserDefaults standardUserDefaults] setObject:identifier forKey:CK2OpenPanelLastViewPrefKey]; } } #pragma mark CK2FileManagerDelegate - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential *))completionHandler; { id delegate; delegate = [[self openPanel] delegate]; if ([delegate respondsToSelector:@selector(panel:didReceiveChallenge:completionHandler:)]) { [delegate panel:self.openPanel didReceiveChallenge:challenge completionHandler:completionHandler]; } else { completionHandler(CK2AuthChallengePerformDefaultHandling, nil); } } - (void)fileManager:(CK2FileManager *)manager appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; { id delegate; delegate = [[self openPanel] delegate]; if ([delegate respondsToSelector:@selector(panel:appendString:toTranscript:)]) { [delegate panel:[self openPanel] appendString:info toTranscript:transcript]; } } @end ================================================ FILE: ConnectionKit/CK2OpenPanelIconViewController.h ================================================ // // CK2OpenPanelIconViewController.h // ConnectionKit // // Created by Paul Kim on 1/9/13. // Copyright (c) 2013 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "CK2OpenPanelViewController.h" @class CK2IconView; @interface CK2OpenPanelIconViewController : CK2OpenPanelViewController { IBOutlet CK2IconView *_iconView; } @end ================================================ FILE: ConnectionKit/CK2OpenPanelIconViewController.m ================================================ // // CK2OpenPanelIconViewController.m // ConnectionKit // // Created by Paul Kim on 1/9/13. // Copyright (c) 2013 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "CK2OpenPanelIconViewController.h" #import "CK2OpenPanelController.h" #import "NSURL+CK2OpenPanel.h" #import "CK2IconViewItem.h" #import "CK2IconItemView.h" #import "CK2IconView.h" @interface CK2OpenPanelIconViewController () @end @implementation CK2OpenPanelIconViewController - (void)awakeFromNib { CK2IconViewItem *iconItem; iconItem = (CK2IconViewItem *)[_iconView itemPrototype]; [iconItem setTarget:self]; [iconItem setAction:@selector(itemSelected:)]; [iconItem setDoubleAction:@selector(itemDoubleClicked:)]; } - (BOOL)allowsMutipleSelection { return [_iconView allowsMultipleSelection]; } - (void)setAllowsMutipleSelection:(BOOL)allowsMutipleSelection { [_iconView setAllowsMultipleSelection:allowsMutipleSelection]; } - (void)reload { CK2OpenPanelController *controller; NSArray *children; NSUInteger i, count; NSURL *url; controller = [self controller]; children = [controller childrenForURL:[[controller openPanel] directoryURL]]; [_iconView setContent:children]; if (([children count] == 1) && [[children lastObject] ck2_isPlaceholder]) { [_iconView setMessageMode:YES]; } else { [_iconView setMessageMode:NO]; } count = [children count]; for (i = 0; i < count; i++) { url = [children objectAtIndex:i]; [(CK2IconViewItem *)[_iconView itemAtIndex:i] setEnabled:([controller isURLValid:url] || [controller URLCanHazChildren:url])]; } [_iconView setNeedsDisplay:YES]; } - (void)update { CK2OpenPanelController *controller; NSArray *children, *urls; NSUInteger i; NSURL *url, *directoryURL; NSMutableIndexSet *indexSet; NSRect rect; NSMutableArray *newURLs; [self reload]; controller = [self controller]; directoryURL = [[controller openPanel] directoryURL]; children = [controller childrenForURL:directoryURL]; urls = [controller URLs]; indexSet = [NSMutableIndexSet indexSet]; rect = NSZeroRect; newURLs = [NSMutableArray array]; for (url in urls) { if (![url isEqual:directoryURL]) { i = [children indexOfObject:url]; if (i != NSNotFound) { [indexSet addIndex:i]; rect = NSUnionRect(rect, [_iconView frameForItemAtIndex:i]); [newURLs addObject:url]; } } } [_iconView setSelectionIndexes:indexSet]; [_iconView scrollRectToVisible:rect]; if ([newURLs count] != [urls count]) { // Only a subset of the URLs actually are visible so we update the internal URLs to match. [controller setURLs:newURLs updateDirectory:NO sender:self]; } } - (void)urlDidLoad:(NSURL *)url { [self reload]; } - (NSArray *)selectedURLs { NSIndexSet *indexSet; NSArray *urls; indexSet = [_iconView selectionIndexes]; if ([indexSet count] > 0) { urls = [[_iconView content] objectsAtIndexes:indexSet]; } else { urls = [NSArray array]; } return urls; } - (IBAction)itemDoubleClicked:(id)sender { [self goToSelectedItem:sender]; } - (IBAction)goToSelectedItem:(id)sender { CK2OpenPanelController *controller; NSArray *urls; controller = [self controller]; urls = [self selectedURLs]; if ([urls count] == 1) { NSURL *url; url = [urls objectAtIndex:0]; if ([controller URLCanHazChildren:url]) { if (![url isEqual:[[controller openPanel] directoryURL]]) { [controller addToHistory]; [controller setURLs:urls updateDirectory:YES sender:self]; } } } else { BOOL isValid; isValid = YES; for (NSURL *url in urls) { if (![controller isURLValid:url]) { isValid = NO; } } if (isValid) { [controller ok:self]; } } } @end ================================================ FILE: ConnectionKit/CK2OpenPanelListViewController.h ================================================ // // CK2OpenPanelListController.h // ConnectionKit // // Created by Paul Kim on 12/29/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "CK2OpenPanelViewController.h" @interface CK2OpenPanelListViewController : CK2OpenPanelViewController { IBOutlet NSOutlineView *_outlineView; } @end ================================================ FILE: ConnectionKit/CK2OpenPanelListViewController.m ================================================ // // CK2OpenPanelListController.m // ConnectionKit // // Created by Paul Kim on 12/29/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "CK2OpenPanelListViewController.h" #import "CK2OpenPanelController.h" #import "NSURL+CK2OpenPanel.h" #import "NSImage+CK2OpenPanel.h" #import "CK2FileCell.h" @interface CK2OpenPanelListViewController () @end @implementation CK2OpenPanelListViewController - (void)awakeFromNib { [_outlineView setDoubleAction:@selector(itemDoubleClicked:)]; [_outlineView setTarget:self]; } - (BOOL)allowsMutipleSelection { return [_outlineView allowsMultipleSelection]; } - (void)setAllowsMutipleSelection:(BOOL)allowsMutipleSelection { [_outlineView setAllowsMultipleSelection:allowsMutipleSelection]; } - (void)reload { [_outlineView reloadData]; } - (void)update { NSArray *urls; NSURL *url; CK2OpenPanelController *controller; NSMutableIndexSet *indexSet; NSRect rect; controller = [self controller]; [_outlineView reloadData]; urls = [controller URLs]; indexSet = [NSMutableIndexSet indexSet]; rect = NSZeroRect; for (url in urls) { if (![url isEqual:[[controller openPanel] directoryURL]]) { NSInteger row; row = [_outlineView rowForItem:url]; if (row > 0) { [indexSet addIndex:row]; rect = NSUnionRect(rect, [_outlineView rectOfRow:row]); } } } [_outlineView selectRowIndexes:indexSet byExtendingSelection:NO]; [_outlineView scrollRectToVisible:rect]; } - (void)urlDidLoad:(NSURL *)url { if ([url isEqual:[[[self controller] openPanel] directoryURL]]) { [_outlineView reloadData]; } else { [_outlineView reloadItem:url reloadChildren:YES]; } } - (NSArray *)selectedURLs { NSIndexSet *indexSet; NSMutableArray *urls; indexSet = [_outlineView selectedRowIndexes]; urls = [NSMutableArray array]; [indexSet enumerateIndexesUsingBlock: ^(NSUInteger idx, BOOL *stop) { NSURL *url; url = [_outlineView itemAtRow:idx]; if (url != nil) { [urls addObject:url]; } }]; return urls; } - (IBAction)itemDoubleClicked:(id)sender { [self goToSelectedItem:sender]; } - (IBAction)goToSelectedItem:(id)sender { CK2OpenPanelController *controller; NSArray *urls; controller = [self controller]; urls = [self selectedURLs]; if ([urls count] == 1) { NSURL *url; url = [urls objectAtIndex:0]; if ([controller URLCanHazChildren:url]) { if (![url isEqual:[[controller openPanel] directoryURL]]) { [controller addToHistory]; [controller setURLs:urls updateDirectory:YES sender:self]; } } else { if ([controller isURLValid:url]) { [controller ok:self]; } } } } #define HISTORY_LIST_EXPANDED_ITEMS_KEY @"listViewExpandedItems" - (void)saveViewHistoryState:(NSMutableDictionary *)dict { NSUInteger count, i; NSMutableArray *items; id item; count = [_outlineView numberOfRows]; items = [NSMutableArray array]; for (i = 0; i < count; i++) { item = [_outlineView itemAtRow:i]; if ([_outlineView isItemExpanded:item]) { [items addObject:item]; } } [dict setObject:items forKey:HISTORY_LIST_EXPANDED_ITEMS_KEY]; } - (void)restoreViewHistoryState:(NSDictionary *)dict { NSArray *items; items = [dict objectForKey:HISTORY_LIST_EXPANDED_ITEMS_KEY]; for (id item in items) { [_outlineView expandItem:item]; } } #pragma mark NSOutlineViewDataSource methods - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { CK2OpenPanelController *controller; controller = [self controller]; if (item == nil) { item = [[controller openPanel] directoryURL]; } return [[controller childrenForURL:item] objectAtIndex:index]; } - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { CK2OpenPanelController *controller; controller = [self controller]; if (item == nil) { item = [[controller openPanel] directoryURL]; } return [controller URLCanHazChildren:item]; } - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { CK2OpenPanelController *controller; controller = [self controller]; if (item == nil) { item = [[controller openPanel] directoryURL]; } return [[controller childrenForURL:item] count]; } - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { id identifier; if (item == nil) { item = [[[self controller] openPanel] directoryURL]; } identifier = [tableColumn identifier]; if ([identifier isEqual:@"Name"]) { return [item ck2_displayName]; } else if ([identifier isEqual:@"Date Modified"]) { return [item ck2_dateModified]; } else if ([identifier isEqual:@"Size"]) { id value; value = [(NSURL *)item ck2_size]; if (value == nil) { return @"--"; } return value; } else if ([identifier isEqual:@"Kind"]) { return [(NSURL *)item ck2_kind]; } return nil; } - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item { CK2OpenPanelController *controller; controller = [self controller]; if ([[tableColumn identifier] isEqual:@"Name"]) { [cell setImage:[item ck2_icon]]; [cell setTextOnly:[item ck2_isPlaceholder]]; } if ([controller isURLValid:item] || [controller URLCanHazChildren:item]) { [cell setTextColor:[NSColor controlTextColor]]; } else { [cell setTextColor:[NSColor disabledControlTextColor]]; } } - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item { CK2OpenPanelController *controller; controller = [self controller]; return [controller isURLValid:item] || [controller URLCanHazChildren:item]; } - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index { return NO; } - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index { return NSDragOperationNone; } - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard { return NO; } - (id)outlineView:(NSOutlineView *)outlineView itemForPersistentObject:(id)object { //PENDING: return [NSURL URLWithString:object]; } - (id)outlineView:(NSOutlineView *)outlineView persistentObjectForItem:(id)item { //PENDING: return [item description]; } @end ================================================ FILE: ConnectionKit/CK2OpenPanelViewController.h ================================================ // // CK2OpenPanelViewController.h // ConnectionKit // // Created by Paul Kim on 12/29/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import @class CK2OpenPanelController; @interface CK2OpenPanelViewController : NSViewController { IBOutlet CK2OpenPanelController *_controller; } @property (readonly, assign) CK2OpenPanelController *controller; @property (readwrite, assign) BOOL allowsMutipleSelection; - (id)init; - (void)reload; - (void)update; - (BOOL)hasFixedRoot; - (void)urlDidLoad:(NSURL *)url; - (NSArray *)selectedURLs; - (IBAction)itemSelected:(id)sender; - (IBAction)itemDoubleClicked:(id)sender; - (void)saveViewHistoryState:(NSMutableDictionary *)dict; - (void)restoreViewHistoryState:(NSDictionary *)dict; - (IBAction)goToSelectedItem:(id)sender; @end ================================================ FILE: ConnectionKit/CK2OpenPanelViewController.m ================================================ // // CK2OpenPanelViewController.m // ConnectionKit // // Created by Paul Kim on 12/29/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "CK2OpenPanelViewController.h" #import "CK2OpenPanelController.h" @implementation CK2OpenPanelViewController - (id)init { if ((self = [super initWithNibName:nil bundle:nil]) != nil) { } return self; } @synthesize controller = _controller; - (BOOL)allowsMutipleSelection { return NO; } - (void)setAllowsMutipleSelection:(BOOL)allowsMutipleSelection { } - (void)reload { } - (void)update { } - (BOOL)hasFixedRoot { return NO; } - (void)urlDidLoad:(NSURL *)url { } - (NSArray *)selectedURLs { return [NSArray array]; } - (IBAction)itemSelected:(id)sender { NSArray *urls; urls = [self selectedURLs]; if ([urls count] > 0) { [[self controller] setURLs:urls updateDirectory:[self hasFixedRoot] sender:self]; } } - (IBAction)itemDoubleClicked:(id)sender { } - (void)saveViewHistoryState:(NSMutableDictionary *)dict { } - (void)restoreViewHistoryState:(NSDictionary *)dict { } - (IBAction)goToSelectedItem:(id)sender { } @end ================================================ FILE: ConnectionKit/CK2PathControl.h ================================================ // // CK2PathControl.h // ConnectionKit // // Created by Paul Kim on 12/27/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import @class CK2OpenPanelController; @interface CK2PathControl : NSPopUpButton { NSURL *_url; } @property (readwrite, retain, nonatomic) NSURL *URL; @end ================================================ FILE: ConnectionKit/CK2PathControl.m ================================================ // // CK2PathControl.m // ConnectionKit // // Created by Paul Kim on 12/27/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "CK2PathControl.h" #import "NSURL+CK2OpenPanel.h" #import "CK2OpenPanelController.h" #import "NSImage+CK2OpenPanel.h" #define ICON_SIZE 16.0 @implementation CK2PathControl @synthesize URL = _url; - (id)initWithFrame:(NSRect)frame { if ((self = [super initWithFrame:frame pullsDown:NO]) != nil) { [[self menu] setAutoenablesItems:NO]; } return self; } - (void)dealloc { [_url release]; [super dealloc]; } - (void)urlSelected:(id)sender { [self setURL:[sender representedObject]]; [self sendAction:[self action] to:[self target]]; } - (void)setURL:(NSURL *)url { if (![url isEqual:_url]) { NSMenu *menu; [_url release]; _url = [url retain]; menu = [self menu]; [menu removeAllItems]; if (url != nil) { NSSize size; NSURL *tempURL; size = NSMakeSize(ICON_SIZE, ICON_SIZE); tempURL = _url; while (tempURL != nil) { NSImage *image; NSString *title; NSMenuItem *item; image = [[[tempURL ck2_icon] copy] autorelease]; title = [tempURL ck2_displayName]; if ([title isEqual:@"/"] || title.length == 0) // happens for URLs like ftpes://user@example.com { title = [tempURL host]; } item = [[NSMenuItem alloc] initWithTitle:title action:@selector(urlSelected:) keyEquivalent:@""]; [item setTarget:self]; [image setSize:size]; [item setImage:image]; [item setRepresentedObject:tempURL]; [menu addItem:item]; [item release]; tempURL = [tempURL ck2_parentURL]; } } [self selectItemAtIndex:0]; } } @end ================================================ FILE: ConnectionKit/CK2PathFieldWindowController.h ================================================ // // CK2PathFieldWindowController.h // Connection // // Created by Paul Kim on 3/25/13. // // #import @interface CK2PathFieldWindowController : NSWindowController { IBOutlet NSTextField *_field; IBOutlet NSButton *_goButton; NSString *_stringValue; } @property (readwrite, copy) NSString *stringValue; - (id)init; - (void)beginSheetModalForWindow:(NSWindow *)window completionHandler:(void (^)(NSInteger result))handler; - (IBAction)go:(id)sender; - (IBAction)cancel:(id)sender; @end ================================================ FILE: ConnectionKit/CK2PathFieldWindowController.m ================================================ // // CK2PathFieldWindowController.m // Connection // // Created by Paul Kim on 3/25/13. // // #import "CK2PathFieldWindowController.h" @interface CK2PathFieldWindowController () @end @implementation CK2PathFieldWindowController @synthesize stringValue = _stringValue; - (id)init { if ((self = [super initWithWindowNibName:@"CK2PathFieldWindow"]) != nil) { } return self; } - (void)beginSheetModalForWindow:(NSWindow *)window completionHandler:(void (^)(NSInteger result))handler { NSWindow *sheet; NSText *fieldEditor; NSString *string; sheet = [self window]; string = [self stringValue]; [_field setStringValue:string]; [sheet makeFirstResponder:_field]; fieldEditor = [sheet fieldEditor:YES forObject:_field]; [fieldEditor setSelectedRange:NSMakeRange([string length], 0)]; [NSApp beginSheet:sheet modalForWindow:window modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:[handler copy]]; } - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { [sheet orderOut:self]; if (returnCode == NSOKButton) { [self setStringValue:[_field stringValue]]; } if (contextInfo != NULL) { void (^block)(NSInteger); block = contextInfo; block(returnCode); [block release]; } } - (IBAction)cancel:(id)sender { [NSApp endSheet:[self window] returnCode:NSCancelButton]; } - (IBAction)go:(id)sender { [NSApp endSheet:[self window] returnCode:NSOKButton]; } - (void)controlTextDidChange:(NSNotification *)aNotification { NSText *fieldEditor; NSString *string; fieldEditor = [[aNotification userInfo] objectForKey:@"NSFieldEditor"]; string = [fieldEditor string]; [_goButton setEnabled:([string length] != 0)]; } @end ================================================ FILE: ConnectionKit/CK2Protocol.h ================================================ // // CK2Protocol // Connection // // Created by Mike on 11/10/2012. // // #import #import "CK2FileManager.h" @protocol CK2ProtocolClient; @interface CK2Protocol : NSObject { @private NSURLRequest *_request; id _client; } #pragma mark For Subclasses to Implement // Generally, subclasses check the URL's scheme to see if they support it + (BOOL)canHandleURL:(NSURL *)url; // Override these methods to get setup ready for performing the operation. The request is used to indicate the URL to operate on, and the timeout to apply - (id)initForEnumeratingDirectoryWithRequest:(NSURLRequest *)request // MUST "discover" the directory itself, first includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask client:(id )client; - (id)initForCreatingDirectoryWithRequest:(NSURLRequest *)request withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id )client; // The data is supplied as -HTTPBodyData or -HTTPBodyStream on the request - (id)initForCreatingFileWithRequest:(NSURLRequest *)request size:(int64_t)size withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id )client; - (id)initForRemovingItemWithRequest:(NSURLRequest *)request client:(id )client; // The source and destination schemes or hosts might differ. If so, that's currently considered an error by the client, so do whatever the hell pleases you - (id)initForRenamingItemWithRequest:(NSURLRequest *)request newName:(NSString *)newName client:(id )client; - (id)initForSettingAttributes:(NSDictionary *)keyedValues ofItemWithRequest:(NSURLRequest *)request client:(id )client; // Override to kick off the requested operation - (void)start; // Your cue to stop doing any more work. Once this is called, the client will ignore you should you choose to continue // Called on an arbitrary thread, so bounce over to your own queue/thread if needed - (void)stop; #pragma mark For Subclasses to Customize // Session consults registered protocols to find out which is qualified to handle paths for a specific URL // Default behaviour is generic path-handling. Override if your protocol has some special requirements. e.g. SFTP indicates home directory with a ~ + (NSURL *)URLWithPath:(NSString *)path relativeToURL:(NSURL *)baseURL; + (NSString *)pathOfURLRelativeToHomeDirectory:(NSURL *)URL; // Default is whether path is @"". Override to have a stab at the question if your protocol does have an idea of what absolute path is the home directory + (BOOL)isHomeDirectoryAtURL:(NSURL *)url; #pragma mark For Subclasses to Use // Most subclasses will want to use this to store the request and client upon initialization, but they're not obliged to - (id)initWithRequest:(NSURLRequest *)request client:(id )client; @property(nonatomic, readonly, copy) NSURLRequest *request; @property(nonatomic, readonly, retain) id client; /** Returned to represent general failures to create or write to something, in situations where we can't get a more specific error. */ - (NSError*)standardCouldntWriteErrorWithUnderlyingError:(NSError*)error; /** Returned to represent general failures to find or read something, in situations where we can't get a more specific error. */ - (NSError*)standardCouldntReadErrorWithUnderlyingError:(NSError*)error; /** Returned to represent a failure that we *know* was caused by the requested item not being found. If the protocol can't be that specific, it should use standardCouldntWriteErrorWithUnderlyingError or standardCouldntReadErrorWithUnderlyingError instead. */ - (NSError*)standardFileNotFoundErrorWithUnderlyingError:(NSError*)error; /** Returned to represent an authentication error, in situations where the protocol can't be more specific. */ - (NSError*)standardAuthenticationErrorWithUnderlyingError:(NSError*)error; /** Replaces the user info portion of `aURL` (i.e. the username and password) with a single desired username. */ + (NSURL *)URLByReplacingUserInfoInURL:(NSURL *)aURL withUser:(NSString *)nsUser; #pragma mark Registration /*! @method registerClass: @abstract This method registers a protocol class, making it visible to several other CK2Protocol class methods. @discussion When the system begins to perform an operation, each protocol class that has been registered is consulted in turn to see if it can be initialized with a given request. The first protocol handler class to provide a YES answer to +canHandleURL: "wins" and that protocol implementation is used to perform the URL load. There is no guarantee that all registered protocol classes will be consulted. Hence, it should be noted that registering a class places it first on the list of classes that will be consulted in calls to +canHandleURL:, moving it in front of all classes that had been registered previously. Throws an exception if protocolClass isn't a subclass of CK2Protocol @param protocolClass the class to register. */ + (void)registerClass:(Class)protocolClass; @end @protocol CK2ProtocolClient #pragma mark General - (void)protocol:(CK2Protocol *)protocol didCompleteWithError:(NSError *)error; /** Experimental; for URLRequest-based protocols */ - (NSURLRequest *)protocol:(CK2Protocol *)protocol willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response; /*! @method protocoldidReceiveAuthenticationChallenge: @abstract Start authentication for the specified request @param protocol The protocol object requesting authentication. @param challenge The authentication challenge. @param completionHandler Called by the system to specify what it would like done. @discussion The protocol client answers the request on the same queue as -start was called on. It may add a default credential to the challenge it issues to the connection delegate, if the protocol did not provide one. */ - (void)protocol:(CK2Protocol *)protocol didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential*))completionHandler; - (void)protocol:(CK2Protocol *)protocol appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; #pragma mark Operation-Specific // Only made use of by directory enumeration at present, but hey, maybe something else will in future // URL should be pre-populated with properties requested by client - (void)protocol:(CK2Protocol *)protocol didDiscoverItemAtURL:(NSURL *)url; - (void)protocol:(CK2Protocol *)protocol didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend; // Call if reading from a stream needs to be retried. The client will provide you with a fresh, unopened stream to read from - (NSInputStream *)protocol:(CK2Protocol *)protocol needNewBodyStream:(NSURLRequest *)request; @end ================================================ FILE: ConnectionKit/CK2Protocol.m ================================================ // // CK2Protocol // Connection // // Created by Mike on 11/10/2012. // // #import "CK2Protocol.h" #import "CK2FTPProtocol.h" #import "CK2SFTPProtocol.h" #import "CK2FileProtocol.h" #import "CK2WebDAVProtocol.h" @implementation CK2Protocol #pragma mark Serialization + (dispatch_queue_t)queue; { static dispatch_queue_t queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ queue = dispatch_queue_create("CK2FileTransferSystem", NULL); // Register built-in protocols too sRegisteredProtocols = [[NSMutableArray alloc] initWithObjects:[CK2FileProtocol class], [CK2SFTPProtocol class], [CK2FTPProtocol class], [CK2WebDAVProtocol class], nil]; }); return queue; } #pragma mark For Subclasses to Implement + (BOOL)canHandleURL:(NSURL *)url; { [self doesNotRecognizeSelector:_cmd]; return NO; } - (id)initForEnumeratingDirectoryWithRequest:(NSURLRequest *)request includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask client:(id)client; { [self doesNotRecognizeSelector:_cmd]; return nil; } - (id)initForCreatingDirectoryWithRequest:(NSURLRequest *)request withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { [self doesNotRecognizeSelector:_cmd]; return nil; } - (id)initForCreatingFileWithRequest:(NSURLRequest *)request size:(int64_t)size withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { [self doesNotRecognizeSelector:_cmd]; return nil; } - (id)initForRemovingItemWithRequest:(NSURLRequest *)request client:(id)client; { [self doesNotRecognizeSelector:_cmd]; return nil; } - (id)initForRenamingItemWithRequest:(NSURLRequest *)request newName:(NSString *)newName client:(id)client { [self doesNotRecognizeSelector:_cmd]; return nil; } - (id)initForSettingAttributes:(NSDictionary *)keyedValues ofItemWithRequest:(NSURLRequest *)request client:(id)client; { [self doesNotRecognizeSelector:_cmd]; return nil; } - (void)start; { [self doesNotRecognizeSelector:_cmd]; } - (void)stop; { [self doesNotRecognizeSelector:_cmd]; } #pragma mark For Subclasses to Customize + (NSURL *)URLWithPath:(NSString *)path relativeToURL:(NSURL *)baseURL; { if (path.length == 0) path = @"/"; // special case CFStringRef encodedPath = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)path, NULL, CFSTR(";?#"), kCFStringEncodingUTF8); NSURL* result = [NSURL URLWithString:(NSString *)encodedPath relativeToURL:baseURL]; CFRelease(encodedPath); return result; } + (NSString *)pathOfURLRelativeToHomeDirectory:(NSURL *)URL; { return [URL path]; } + (BOOL)isHomeDirectoryAtURL:(NSURL *)url; { return [[self pathOfURLRelativeToHomeDirectory:url] isEqualToString:@""]; } #pragma mark For Subclasses to Use - (id)initWithRequest:(NSURLRequest *)request client:(id)client; { if (self = [self init]) { _request = [request copy]; _client = [client retain]; } return self; } - (void)dealloc { [_request release]; [_client release]; [super dealloc]; } - (NSError*)standardCouldntWriteErrorWithUnderlyingError:(NSError *)error { NSDictionary* info = error ? @{NSUnderlyingErrorKey : error} : nil; NSError* result = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:info]; return result; } - (NSError*)standardFileNotFoundErrorWithUnderlyingError:(NSError *)error { NSDictionary* info = error ? @{NSUnderlyingErrorKey : error} : nil; NSError* result = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileNoSuchFileError userInfo:info]; return result; } - (NSError*)standardCouldntReadErrorWithUnderlyingError:(NSError *)error { NSDictionary* info = error ? @{NSUnderlyingErrorKey : error} : nil; NSError* result = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadUnknownError userInfo:info]; return result; } - (NSError*)standardAuthenticationErrorWithUnderlyingError:(NSError *)error { NSDictionary* info = error ? @{NSUnderlyingErrorKey : error} : nil; NSError* result = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUserAuthenticationRequired userInfo:info]; return result; } + (NSURL *)URLByReplacingUserInfoInURL:(NSURL *)aURL withUser:(NSString *)nsUser; { // Canonicalize URLs by making sure username is included. Strip out password in the process CFStringRef user = (CFStringRef)nsUser; if (user) { // -stringByAddingPercentEscapesUsingEncoding: doesn't cover things like the @ symbol, so // drop down to CoreFoundation user = CFURLCreateStringByAddingPercentEscapes(NULL, user, NULL, CFSTR(":/?#[]@!$&'()*+,;="), // going by RFC3986 kCFStringEncodingUTF8); } CFIndex length = CFURLGetBytes((CFURLRef)aURL, NULL, 0); NSMutableData *data = [[NSMutableData alloc] initWithLength:length]; CFURLGetBytes((CFURLRef)aURL, [data mutableBytes], length); CFRange authSeparatorsRange; CFRange authRange = CFURLGetByteRangeForComponent((CFURLRef)aURL, kCFURLComponentUserInfo, &authSeparatorsRange); if (authRange.location == kCFNotFound) { NSData *replacement = [[(NSString *)user stringByAppendingString:@"@"] dataUsingEncoding:NSUTF8StringEncoding]; CFDataReplaceBytes((CFMutableDataRef)data, authSeparatorsRange, [replacement bytes], replacement.length); } else { NSData *replacement = [(NSString *)user dataUsingEncoding:NSUTF8StringEncoding]; if (!user) authRange.length = authSeparatorsRange.location + authSeparatorsRange.length - authRange.location; CFDataReplaceBytes((CFMutableDataRef)data, authRange, [replacement bytes], replacement.length); } aURL = NSMakeCollectable(CFURLCreateWithBytes(NULL, [data bytes], data.length, kCFStringEncodingUTF8, NULL)); [data release]; if (user) CFRelease(user); return [aURL autorelease]; } @synthesize request = _request; @synthesize client = _client; #pragma mark Registration static NSMutableArray *sRegisteredProtocols; + (void)registerClass:(Class)protocolClass; { NSParameterAssert([protocolClass isSubclassOfClass:[CK2Protocol class]]); dispatch_async([self queue], ^{ // might as well be async as queue might be blocked momentarily by a protocol [sRegisteredProtocols insertObject:protocolClass atIndex:0]; // so newest is consulted first }); } + (void)classForURL:(NSURL *)url completionHandler:(void (^)(Class protocol))block; { // Search for correct protocol dispatch_async([self queue], ^{ Class result = nil; for (Class aProtocol in sRegisteredProtocols) { if ([aProtocol canHandleURL:url]) { result = aProtocol; break; } } block(result); }); } + (Class)classForURL:(NSURL *)url; { __block Class result = nil; // Search for correct protocol dispatch_sync([self queue], ^{ for (Class aProtocol in sRegisteredProtocols) { if ([aProtocol canHandleURL:url]) { result = aProtocol; break; } } }); return result; } @end ================================================ FILE: ConnectionKit/CK2SFTPProtocol.h ================================================ // // CK2SFTPProtocol.h // Connection // // Created by Mike on 15/10/2012. // // #import "CK2CURLBasedProtocol.h" @interface CK2SFTPProtocol : CK2CURLBasedProtocol { @private NSURLAuthenticationChallenge *_fingerprintChallenge; enum curl_khstat _knownHostsStat; dispatch_semaphore_t _fingerprintSemaphore; NSString *_transcriptMessage; } @end ================================================ FILE: ConnectionKit/CK2SFTPProtocol.m ================================================ // // CK2SFTPProtocol.m // Connection // // Created by Mike on 15/10/2012. // // #import "CK2SFTPProtocol.h" #import "CK2Authentication.h" #import #import #import #import @implementation CK2SFTPProtocol + (BOOL)canHandleURL:(NSURL *)url; { NSString *scheme = [url scheme]; return ([@"scp" caseInsensitiveCompare:scheme] == NSOrderedSame || [@"sftp" caseInsensitiveCompare:scheme] == NSOrderedSame); } + (NSURL *)URLWithPath:(NSString *)path relativeToURL:(NSURL *)baseURL; { // SCP and SFTP represent the home directory using ~/ at the start of the path if (![path isAbsolutePath] && [[baseURL path] length] <= 1) { path = [@"/~/" stringByAppendingString:path]; // stringByAppendingPathComponent: will strip out any trailing slash; want to keep them } return [super URLWithPath:path relativeToURL:baseURL]; } + (NSString *)pathOfURLRelativeToHomeDirectory:(NSURL *)URL; { NSString *result = [super pathOfURLRelativeToHomeDirectory:URL]; // SCP and SFTP represent the home directory using ~/ at the start of the path if ([result hasPrefix:@"/~/"]) { result = [result substringFromIndex:3]; } else if ([result isEqualToString:@"/~"]) { result = @""; // seems a bit weird to have empty path, but enumeration acts badly if we return @"." instead } return result; } #pragma mark Operations - (id)initForEnumeratingDirectoryWithRequest:(NSURLRequest *)request includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask client:(id)client; { if (self = [super initForEnumeratingDirectoryWithRequest:request includingPropertiesForKeys:keys options:mask client:client]) { NSString *path = [self.class pathOfURLRelativeToHomeDirectory:request.URL]; _transcriptMessage = [[NSString alloc] initWithFormat:@"Listing %@", path]; } return self; } - (id)initForCreatingDirectoryWithRequest:(NSURLRequest *)request withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { NSMutableURLRequest *mutableRequest = [request mutableCopy]; [mutableRequest curl_setNewDirectoryPermissions:[attributes objectForKey:NSFilePosixPermissions]]; NSString* path = [self.class pathOfURLRelativeToHomeDirectory:[request URL]]; // Escape special characters in path so libcurl doesn't misinterpret them // Needing to escape here for the compiler makes this very confusing to read, sorry! Gist is: // Backslashes need to be escaped *first* as double backslashes so that: // Quotes can be escaped with a backslash // Then overall path is quoted so libcurl knows to treat spaces as part of the path path = [path stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; path = [path stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; NSString* command = [@"mkdir " stringByAppendingFormat:@"\"%@\"",path]; self = [self initWithCustomCommands:@[command] request:mutableRequest createIntermediateDirectories:NO // rely on file operation to do this instead client:client completionHandler:^(NSError *error) { if (error) { // if the mkdir command failed, try to extract a more meaningful error if ([error code] == CURLE_QUOTE_ERROR && [[error domain] isEqualToString:CURLcodeErrorDomain]) { error = [self standardCouldntWriteErrorWithUnderlyingError:error]; // TODO: can we distinguish here between failure because the directory exists, and failure for some other reason? } } [self reportToProtocolWithError:error]; }]; _transcriptMessage = [[NSString alloc] initWithFormat:@"Making directory %@\n", path]; [mutableRequest release]; return self; } - (id)initForCreatingFileWithRequest:(NSURLRequest *)request size:(int64_t)size withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { NSMutableURLRequest *mutableRequest = [request mutableCopy]; [mutableRequest curl_setNewFilePermissions:[attributes objectForKey:NSFilePosixPermissions]]; if (self = [self initForCreatingFileWithRequest:mutableRequest size:size withIntermediateDirectories:NO // rely on file operation to do this instead client:client completionHandler:NULL]) { NSString* path = [self.class pathOfURLRelativeToHomeDirectory:[request URL]]; NSString* name = [path lastPathComponent]; _transcriptMessage = [[NSString alloc] initWithFormat:@"Uploading %@ to %@\n", name, path]; } [mutableRequest release]; return self; } - (id)initForRenamingItemWithRequest:(NSURLRequest *)request newName:(NSString *)newName client:(id)client { NSString* srcPath = [self.class pathOfURLRelativeToHomeDirectory:[request URL]]; NSString* dstPath = [[srcPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName]; return [self initWithCustomCommands:[NSArray arrayWithObject:[NSString stringWithFormat:@"rename %@ %@", srcPath, dstPath]] request:request createIntermediateDirectories:NO client:client completionHandler:^(NSError *error) { if (error) { if ([error code] == CURLE_QUOTE_ERROR && [[error domain] isEqualToString:CURLcodeErrorDomain]) { error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{ NSUnderlyingErrorKey : error }]; } } [self reportToProtocolWithError:error]; }]; } - (id)initForRemovingItemWithRequest:(NSURLRequest *)request client:(id)client; { NSURL *url = request.URL; // Pick an appropriate command NSString *command = @"rm "; NSNumber *isDirectory; if ([url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL] && isDirectory.boolValue) { command = @"rmdir "; } else if (CFURLHasDirectoryPath((CFURLRef)url)) { command = @"rmdir "; } NSString* path = [self.class pathOfURLRelativeToHomeDirectory:url]; self = [self initWithCustomCommands:[NSArray arrayWithObject:[command stringByAppendingString:path]] request:request createIntermediateDirectories:NO client:client completionHandler:^(NSError *error) { if (error) { if ([error code] == CURLE_QUOTE_ERROR && [[error domain] isEqualToString:CURLcodeErrorDomain]) { //NSUInteger sshError = [error curlResponseCode]; error = [self standardCouldntWriteErrorWithUnderlyingError:error]; } } [self reportToProtocolWithError:error]; }]; _transcriptMessage = [[NSString alloc] initWithFormat:@"Removing %@\n", path]; return self; } - (id)initForSettingAttributes:(NSDictionary *)keyedValues ofItemWithRequest:(NSURLRequest *)request client:(id)client; { NSNumber *permissions = [keyedValues objectForKey:NSFilePosixPermissions]; if (permissions) { NSString* path = [self.class pathOfURLRelativeToHomeDirectory:[request URL]]; NSArray *commands = [NSArray arrayWithObject:[NSString stringWithFormat: @"chmod %lo %@", [permissions unsignedLongValue], path]]; self = [self initWithCustomCommands:commands request:request createIntermediateDirectories:NO client:client completionHandler:^(NSError *error) { if (error) { if ([error code] == CURLE_QUOTE_ERROR && [[error domain] isEqualToString:CURLcodeErrorDomain]) { NSUInteger sshError = [error curlResponseCode]; switch (sshError) { case LIBSSH2_FX_NO_SUCH_FILE: error = [self standardFileNotFoundErrorWithUnderlyingError:error]; break; default: error = [self standardCouldntWriteErrorWithUnderlyingError:error]; break; } } } [self reportToProtocolWithError:error]; }]; _transcriptMessage = [[NSString alloc] initWithFormat:@"Changing mode on %@\n", path]; } else { self = [self initWithRequest:nil client:client]; } return self; } #pragma mark Lifecycle - (void)start; { // If there's no request, that means we were asked to do nothing possible over SFTP. Most likely, storing attributes that aren't POSIX permissions // So jump straight to completion if (![self request]) { [[self client] protocol:self didCompleteWithError:nil]; return; } // Note what we're up to if (_transcriptMessage) { [self.client protocol:self appendString:_transcriptMessage toTranscript:CK2TranscriptHeaderOut]; [_transcriptMessage release]; _transcriptMessage = nil; } // Grab the login credential NSURL *url = [[self request] URL]; NSURLProtectionSpace *space = [[CK2SSHProtectionSpace alloc] initWithHost:[url host] port:[[url port] integerValue] protocol:@"ssh" realm:nil authenticationMethod:NSURLAuthenticationMethodDefault]; [self startWithProtectionSpace:space]; [space release]; } - (void)stop; { [super stop]; // Cancel the fingerprint semaphore // TODO: I think this isn't quite threadsafe if the cancellation happened // while the challenge is being set up or torn down. We ideally need a // synchronization mechanism, probably around the CURLTransfer's queue if (_fingerprintChallenge) { [self useKnownHostsStat:CURLKHSTAT_REJECT]; } } - (void)dealloc; { [_fingerprintChallenge release]; if (_fingerprintSemaphore) dispatch_release(_fingerprintSemaphore); [_transcriptMessage release]; [super dealloc]; } #pragma mark Auth - (id)initWithRequest:(NSURLRequest *)request client:(id)client; { // Add the known hosts file setting to the request NSMutableURLRequest *mutableRequest = [request mutableCopy]; [mutableRequest curl_setSSHKnownHostsFileURL:[NSURL fileURLWithPath:[@"~/.ssh/known_hosts" stringByExpandingTildeInPath] isDirectory:NO]]; self = [super initWithRequest:mutableRequest client:client]; [mutableRequest release]; return self; } - (void)popCompletionHandlerByExecutingWithError:(NSError *)error; { if (error) { // adjust the reported URL so that it's actually the full one (libcurl only got given one with the last component removed) NSURL* url = [self.request URL]; if (![[error.userInfo objectForKey:NSURLErrorFailingURLErrorKey] isEqual:url]) { NSMutableDictionary* modifiedInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo]; [modifiedInfo setObject:url forKey:NSURLErrorFailingURLErrorKey]; [modifiedInfo setObject:[url absoluteString] forKey:NSURLErrorFailingURLStringErrorKey]; error = [NSError errorWithDomain:error.domain code:error.code userInfo:modifiedInfo]; } // Re-package host key failures as something more in the vein of NSURLConnection if (error.code == CURLE_PEER_FAILED_VERIFICATION && [error.domain isEqualToString:CURLcodeErrorDomain]) { error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorServerCertificateUntrusted userInfo:[error userInfo]]; } } [super popCompletionHandlerByExecutingWithError:error]; } #pragma mark Host Fingerprint - (enum curl_khstat)transfer:(CURLTransfer *)transfer didFindHostFingerprint:(const struct curl_khkey *)foundKey knownFingerprint:(const struct curl_khkey *)knownkey match:(enum curl_khmatch)match; { // Once cancelled, we can't handle it. Perhaps CURLTransfer ought to protect against that happenstance itself; not sure if (transfer.state >= CURLTransferStateCanceling) { return CURLKHSTAT_REJECT; } if (!_fingerprintSemaphore) { // Report the key back to delegate to see how it feels about this. Unfortunately have to uglily use a semaphore to do so NSData *key; if (foundKey->len == 0) // base64 encoded { NSString *key64 = [[NSString alloc] initWithCString:foundKey->key encoding:NSASCIIStringEncoding]; key = [[NSData alloc] initWithBase64Encoding:key64]; [key64 release]; } else { key = [[NSData alloc] initWithBytes:foundKey->key length:foundKey->len]; } NSURLProtectionSpace *space = [NSURLProtectionSpace ck2_protectionSpaceWithHost:self.request.URL.host knownHostMatch:match publicKey:key type:foundKey->keytype]; [key release]; NSURLCredential *credential = nil; if (match != CURLKHMATCH_MISMATCH) credential = [NSURLCredential ck2_credentialForKnownHostWithPersistence:NSURLCredentialPersistencePermanent]; _fingerprintChallenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:credential previousFailureCount:0 failureResponse:nil error:nil sender:nil]; _fingerprintSemaphore = dispatch_semaphore_create(0); // must be setup before handing off to client [[self client] protocol:self didReceiveChallenge:_fingerprintChallenge completionHandler:^(CK2AuthChallengeDisposition disposition, NSURLCredential *credential) { switch (disposition) { case CK2AuthChallengePerformDefaultHandling: credential = _fingerprintChallenge.proposedCredential; case CK2AuthChallengeUseCredential: if (credential) { [self.client protocol:self appendString:@"Host fingerprint is fine" toTranscript:CK2TranscriptHeaderIn]; [self useKnownHostsStat:(credential.persistence == NSURLCredentialPersistencePermanent ? CURLKHSTAT_FINE_ADD_TO_FILE : CURLKHSTAT_FINE)]; } else { [self useKnownHostsStat:CURLKHSTAT_REJECT]; } break; default: [self.client protocol:self didCompleteWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:nil]]; [self useKnownHostsStat:CURLKHSTAT_REJECT]; break; } }]; } // Until the client replies, give libcurl a chance to process anything else. Ugly isn't it? if (dispatch_semaphore_wait(_fingerprintSemaphore, 100 * NSEC_PER_MSEC)) { return CURLKHSTAT_DEFER; } // Finished waiting; cleanup dispatch_release(_fingerprintSemaphore); _fingerprintSemaphore = NULL; return _knownHostsStat; } - (void)useKnownHostsStat:(enum curl_khstat)stat; { NSAssert(_fingerprintChallenge, @"Somehow been told to use curl_khstat without having issued a challenge"); [_fingerprintChallenge release]; _fingerprintChallenge = nil; // Use semaphore to signal we know have a result _knownHostsStat = stat; dispatch_semaphore_signal(_fingerprintSemaphore); // can't dispose of yet, as might not be currently waiting on it } @end ================================================ FILE: ConnectionKit/CK2WebDAVProtocol.h ================================================ // // CK2WebDAVProtocol.h // // Created by Sam Deane on 19/11/2012. // // #import "CK2Protocol.h" #import typedef void (^CK2WebDAVCompletionHandler)(id result); typedef void (^CK2WebDAVErrorHandler)(NSError* error); @interface CK2WebDAVProtocol : CK2Protocol { @private DAVSession* _session; NSOperationQueue* _queue; CK2WebDAVCompletionHandler _completionHandler; CK2WebDAVErrorHandler _errorHandler; BOOL _isWriteOp; // minor hack for now } @end ================================================ FILE: ConnectionKit/CK2WebDAVProtocol.m ================================================ // // CK2WebDAVProtocol.h // // Created by Sam Deane on 19/11/2012. // #import "CK2WebDAVProtocol.h" #ifndef CK2WebDAVLog #define CK2WebDAVLog NSLog #endif @interface CK2WebDAVProtocol() @property (copy, nonatomic) CK2WebDAVCompletionHandler completionHandler; @property (copy, nonatomic) CK2WebDAVErrorHandler errorHandler; @property (strong, nonatomic) NSOperationQueue* queue; @end @implementation CK2WebDAVProtocol @synthesize completionHandler = _completionHandler; @synthesize errorHandler = _errorHandler; @synthesize queue = _queue; + (BOOL)canHandleURL:(NSURL *)url; { NSString *scheme = url.scheme; return [@"http" caseInsensitiveCompare:scheme] == NSOrderedSame || [@"https" caseInsensitiveCompare:scheme] == NSOrderedSame; } #pragma mark Lifecycle - (id)initWithRequest:(NSURLRequest *)request client:(id )client { if (self = [super initWithRequest:request client:client]) { [self setupQueue]; _session = [[DAVSession alloc] initWithRootURL:request.URL delegate:self]; } return self; } - (void)dealloc; { CK2WebDAVLog(@"dealloced"); [_completionHandler release]; [_errorHandler release]; [_queue release]; [_session release]; [super dealloc]; } #pragma mark - Operations - (id)initForEnumeratingDirectoryWithRequest:(NSURLRequest *)request includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask client:(id)client; { CK2WebDAVLog(@"enumerating directory"); if ((self = [self initWithRequest:request client:client]) != nil) { NSString* path = [self pathForRequest:request]; DAVRequest* davRequest = [[DAVListingRequest alloc] initWithPath:path session:_session delegate:self]; [_queue addOperation:davRequest]; self.completionHandler = ^(NSArray* items) { CK2WebDAVLog(@"enumerating directory results"); // We're hunting an issue where some requests come back with no error, but no items either. // For now, fail with a fairly generic error to try and dig out a little more detail if (items.count == 0) { [client protocol:self didCompleteWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotParseResponse userInfo:@{ NSURLErrorFailingURLErrorKey : request.URL }]]; return; } // first item should always be the directory itself if (items && !(mask & CK2DirectoryEnumerationIncludesDirectory)) { items = [items subarrayWithRange:NSMakeRange(1, [items count] - 1)]; } for (DAVResponseItem* item in items) { NSString *href = item.href; NSString *name = [href lastPathComponent]; if (!((mask & NSDirectoryEnumerationSkipsHiddenFiles) && [name hasPrefix:@"."])) { NSURL* url = [[davRequest concatenatedURLWithPath:href] absoluteURL]; NSAssert(url, @"-concatenatedURLWithPath: returned nil URL. Shouldn't happen unless davRequest has no URL, and that shouldn't ever happen!"); [CK2FileManager setTemporaryResourceValue:[item modificationDate] forKey:NSURLContentModificationDateKey inURL:url]; [CK2FileManager setTemporaryResourceValue:[item creationDate] forKey:NSURLCreationDateKey inURL:url]; [CK2FileManager setTemporaryResourceValue:@(item.contentLength) forKey:NSURLFileSizeKey inURL:url]; BOOL isDirectory = [[item.fileAttributes objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory]; [CK2FileManager setTemporaryResourceValue:@(isDirectory) forKey:NSURLIsDirectoryKey inURL:url]; NSString *mimeType = item.contentType; if (mimeType) { CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)mimeType, NULL); [CK2FileManager setTemporaryResourceValue:(NSString *)uti forKey:NSURLTypeIdentifierKey inURL:url]; CFRelease(uti); } [client protocol:self didDiscoverItemAtURL:url]; CK2WebDAVLog(@"%@", url); } } [self reportFinished]; }; } return self; } - (id)initForCreatingDirectoryWithRequest:(NSURLRequest *)request withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { CK2WebDAVLog(@"creating directory"); if ((self = [self initWithRequest:request client:client]) != nil) { _isWriteOp = YES; NSString* path = [self pathForRequest:request]; // when done, we just report that we're finished CK2WebDAVCompletionHandler handleCompletion = ^(id result) { CK2WebDAVLog(@"create directory done %@", path); [self reportFinished]; }; // when a real error occurs, report it CK2WebDAVErrorHandler handleRealError = ^(NSError *error) { CK2WebDAVLog(@"create directory %@ failed with error %@", path, error); [self reportFailedWithError:error]; }; // the first time an error occurs, if we were asked to create intermediates, try again with that flag actually set to YES CK2WebDAVErrorHandler handleFirstError = ^(NSError *error) { // only bother trying again if we actually got a relevant error: // 409 Conflict - A collection cannot be made at the Request-URI until one or more intermediate collections have been created. if ([error.domain isEqualToString:DAVClientErrorDomain] && (error.code == 409)) { if (createIntermediates) { CK2WebDAVLog(@"making directory failed with error %@, retrying making each intermediate %@", error, path); [self addCreateDirectoryRequestForPath:path withIntermediateDirectories:YES errorHandler:handleRealError completionHandler:handleCompletion]; } else { handleRealError([self standardFileNotFoundErrorWithUnderlyingError:error]); } } else { handleRealError(error); } }; // for the sake of efficiency, the first time we always try the creation without making intermediates // if that fails, and if we were asked to make intermediates, we try again [self addCreateDirectoryRequestForPath:path withIntermediateDirectories:NO errorHandler:handleFirstError completionHandler:handleCompletion]; }; return self; } - (id)initForCreatingFileWithRequest:(NSURLRequest *)request size:(int64_t)size withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { CK2WebDAVLog(@"creating file"); if ((self = [self initWithRequest:request client:client]) != nil) { if (request.HTTPBodyStream) { NSMutableURLRequest *mutableRequest = [request mutableCopy]; [mutableRequest setValue:[NSString stringWithFormat:@"%llu", size] forHTTPHeaderField:@"Content-Length"]; request = [mutableRequest autorelease]; } _isWriteOp = YES; NSString* path = [self pathForRequest:request]; CK2WebDAVCompletionHandler handleCompletion = ^(id result) { CK2WebDAVLog(@"creating file done"); [self reportFinished]; }; CK2WebDAVErrorHandler handleRealError = ^(NSError* error) { CK2WebDAVLog(@"creating file failed"); [self reportFailedWithError:error]; }; // the first time an error occurs, if we were asked to create intermediates, try again with that flag actually set to YES CK2WebDAVErrorHandler handleFirstError = ^(NSError *error) { // only bother trying again if we actually got a relevant error: // 409 Conflict - A collection cannot be made at the Request-URI until one or more intermediate collections have been created. if ([error.domain isEqualToString:DAVClientErrorDomain] && (error.code == 409)) { if (createIntermediates) { CK2WebDAVLog(@"making directory failed, retrying making each intermediate %@", path); [self addCreateFileRequestForPath:path originalRequest:request withIntermediateDirectories:YES errorHandler:handleRealError completionHandler:handleCompletion]; } else { handleRealError([self standardFileNotFoundErrorWithUnderlyingError:error]); } } else { handleRealError(error); } }; // for the sake of efficiency, the first time we always try the creation without making intermediates // if that fails, and if we were asked to make intermediates, we try again [self addCreateFileRequestForPath:path originalRequest:request withIntermediateDirectories:NO errorHandler:handleFirstError completionHandler:handleCompletion]; } return self; } - (id)initForRenamingItemWithRequest:(NSURLRequest *)request newName:(NSString *)newName client:(id)client { CK2WebDAVLog(@"renaming file"); if ((self = [self initWithRequest:request client:client]) != nil) { NSString* path = [self pathForRequest:request]; DAVMoveRequest* davRequest = [[DAVMoveRequest alloc] initWithPath:path session:_session delegate:self]; davRequest.destinationPath = [[path stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName]; davRequest.overwrite = NO; [_queue addOperation:davRequest]; [davRequest release]; self.completionHandler = ^(id result) { CK2WebDAVLog(@"renaming file done"); [self reportFinished]; }; } return self; } - (id)initForRemovingItemWithRequest:(NSURLRequest *)request client:(id)client; { CK2WebDAVLog(@"removing file"); if ((self = [self initWithRequest:request client:client]) != nil) { _isWriteOp = YES; NSString* path = [self pathForRequest:request]; DAVRequest* davRequest = [[DAVDeleteRequest alloc] initWithPath:path session:_session delegate:self]; [_queue addOperation:davRequest]; [davRequest release]; self.completionHandler = ^(id result) { CK2WebDAVLog(@"removing file done"); [self reportFinished]; }; } return self; } - (id)initForSettingAttributes:(NSDictionary *)keyedValues ofItemWithRequest:(NSURLRequest *)request client:(id)client; { CK2WebDAVLog(@"setting resource values"); if ((self = [self initWithRequest:request client:client]) != nil) { _isWriteOp = YES; [self reportFinished]; } return self; } - (void)start; { CK2WebDAVLog(@"started"); self.queue.suspended = NO; } - (void)stop { CK2WebDAVLog(@"stopped"); self.queue.suspended = YES; [self.queue cancelAllOperations]; } - (NSString*)pathForRequest:(NSURLRequest*)request { NSString *path = [CK2WebDAVProtocol pathOfURLRelativeToHomeDirectory:request.URL]; if (!path) path = @"/"; // In some cases, a client can pass in to us a URL that contains multiple slashes in the path // (e.g. Sandvox is guilty of this in one particular circumstance). Because we then pass that as // a path into DAVKit, it needs cleaning up to avoid being mis-resolved. while ([path hasPrefix:@"//"]) { path = [path substringFromIndex:1]; } return path; } #pragma mark Request Delegate - (void)requestDidBegin:(DAVRequest *)aRequest; { CK2WebDAVLog(@"webdav request began"); } - (void)request:(DAVRequest *)aRequest didSucceedWithResult:(id)result; { CK2WebDAVLog(@"webdav request succeeded"); // if there is a completion handler set, it is expected to call protocolDidFinish // this lets us build chains of requests where only the final one makes the // didFinish call if (self.completionHandler) { self.completionHandler(result); } // if not, we call it else { [self reportFinished]; } } - (void)request:(DAVRequest *)aRequest didFailWithError:(NSError *)error; { CK2WebDAVLog(@"webdav request failed"); if (self.errorHandler) { self.errorHandler(error); } else { [self reportFailedWithError:error]; } } - (NSURLRequest *)request:(DAVRequest *)aRequest willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse; { if (redirectResponse) { // Disallow redirects as we have no security mechanism to manage them currently return nil; } else { return [self.client protocol:self willSendRequest:request redirectResponse:redirectResponse]; } } - (void)webDAVRequest:(DAVRequest *)request didSendDataOfLength:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { CK2WebDAVLog(@"webdav sent data"); [self.client protocol:self didSendBodyData:bytesWritten totalBytesSent:totalBytesWritten totalBytesExpectedToSend:totalBytesExpectedToWrite]; } - (NSInputStream*)webDAVRequest:(DAVRequest *)request needNewBodyStream:(NSURLRequest *)urlRequest { NSInputStream* result = [[self client] protocol:self needNewBodyStream:urlRequest]; return result; } #pragma mark WebDAV Authentication - (void)webDAVSession:(DAVSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSInteger, NSURLCredential *))completionHandler; { CK2WebDAVLog(@"webdav received challenge"); [self.client protocol:self didReceiveChallenge:challenge completionHandler:^(CK2AuthChallengeDisposition disposition, NSURLCredential *credential) { completionHandler(disposition, credential); }]; } - (void)webDAVSession:(DAVSession *)session appendStringToTranscript:(NSString *)string sent:(BOOL)sent; { CK2WebDAVLog(sent ? @"--> %@ " : @"<-- %@", string); // Tack on a newline to match libcurl output string = [string stringByAppendingString:@"\n"]; [[self client] protocol:self appendString:string toTranscript:(sent ? CK2TranscriptHeaderOut : CK2TranscriptHeaderIn)]; } #pragma mark - Utilities - (void)setupQueue { NSOperationQueue* queue = [[NSOperationQueue alloc] init]; queue.suspended = YES; queue.name = @"CK2WebDAVProtocol"; queue.maxConcurrentOperationCount = 1; self.queue = queue; [queue release]; } - (void)reportFinished { [self.queue addOperationWithBlock:^{ [self.client protocol:self didCompleteWithError:nil]; }]; } - (void)reportFailedWithError:(NSError*)error { if ([error.domain isEqualToString:DAVClientErrorDomain]) { switch (error.code) { case 404: error = [self standardFileNotFoundErrorWithUnderlyingError:error]; break; case 405: // Leave as-is when trying to do a read op, like directory listing on a non-WebDAV server if (_isWriteOp) error = [self standardCouldntWriteErrorWithUnderlyingError:error]; break; default: break; } } NSAssert(error, @"%@ called with nil error", NSStringFromSelector(_cmd)); [self.queue addOperationWithBlock:^{ [self.client protocol:self didCompleteWithError:error]; }]; } /** Create a createFile requesst, and potentially a chain of createDirectory requests. If createIntermediates is NO, we just create one request to create the file, and set the completion handler for the operation to whatever we were given. If it's YES, we queue up requests to create the parent directory and all intermediates, and we only call the file creation stuff if the directory creation succeeds (or fails because the directories already existed). */ - (void)addCreateFileRequestForPath:(NSString*)path originalRequest:(NSURLRequest*)request withIntermediateDirectories:(BOOL)createIntermediates errorHandler:(CK2WebDAVErrorHandler)errorHandler completionHandler:(CK2WebDAVCompletionHandler)completionHandler; { CK2WebDAVLog(@"adding create file request for %@", path); CK2WebDAVCompletionHandler createFileBlock = Block_copy(^(id result) { DAVPutRequest* davRequest = [[DAVPutRequest alloc] initWithPath:path originalRequest:request session:_session delegate:self]; [_queue addOperation:davRequest]; [davRequest release]; self.completionHandler = completionHandler; self.errorHandler = errorHandler; }); CK2WebDAVErrorHandler errorBlock = Block_copy(^(NSError* error) { CK2WebDAVLog(@"create directory (during create file) failed for %@ with %@", path, error); if ([self shouldCreateIntermediateDirectoriesAfterError:error]) { // ignore failure to create the directories createFileBlock(nil); } else { // other errors are passed on errorHandler(error); } }); BOOL recursed = NO; if (createIntermediates) { NSString* parent = [path stringByDeletingLastPathComponent]; if (![parent isEqualToString:@"/"]) { [self addCreateDirectoryRequestForPath:parent withIntermediateDirectories:YES errorHandler:errorBlock completionHandler:createFileBlock]; recursed = YES; } } if (!recursed) { createFileBlock(nil); } Block_release(errorHandler); Block_release(createFileBlock); } /** When creating a file, it might fail because an intermediate directory doesn't exist yet. If so, we need to detect and handle that error. */ - (BOOL)shouldCreateIntermediateDirectoriesAfterError:(NSError *)error { NSString *domain = error.domain; if (![domain isEqualToString:DAVClientErrorDomain]) return NO; switch (error.code) { case 405: // as per the WebDAV spec case 404: // be lenient https://github.com/karelia/ConnectionKit/issues/76 return YES; default: return NO; } } /** Create a chain of createDirectory requests. If createIntermediates is NO, we just create one request for the specified path, and set the completion handler for the operation to whatever we were given. If it's YES, we recurse down to the root of the path, and make a request which creates the root directory. We then set the completion of this request to create the next level up, and so on, until the final completion which creates the path we were initially given, and calls the completion block we were given. */ - (void)addCreateDirectoryRequestForPath:(NSString*)path withIntermediateDirectories:(BOOL)createIntermediates errorHandler:(CK2WebDAVErrorHandler)errorHandler completionHandler:(CK2WebDAVCompletionHandler)completionHandler { CK2WebDAVLog(@"adding create directory request for %@", path); CK2WebDAVCompletionHandler createDirectoryBlock = Block_copy(^(id result) { DAVRequest* davRequest = [[DAVMakeCollectionRequest alloc] initWithPath:path session:_session delegate:self]; [_queue addOperation:davRequest]; [davRequest release]; self.completionHandler = completionHandler; self.errorHandler = errorHandler; }); CK2WebDAVErrorHandler errorBlock = Block_copy(^(NSError* error) { CK2WebDAVLog(@"create directory failed for %@ with %@", path, error); if (([error.domain isEqualToString:DAVClientErrorDomain]) && (error.code == 405)) { // ignore failure to create for all but the top directory, on the basis that they may well exist already createDirectoryBlock(nil); } else { // other errors are passed on errorHandler(error); } }); BOOL recursed = NO; if (createIntermediates) { NSString* parent = [path stringByDeletingLastPathComponent]; if (![parent isEqualToString:@"/"]) { [self addCreateDirectoryRequestForPath:parent withIntermediateDirectories:YES errorHandler:errorBlock completionHandler:createDirectoryBlock]; recursed = YES; } } if (!recursed) { createDirectoryBlock(nil); } Block_release(errorHandler); Block_release(createDirectoryBlock); } @end ================================================ FILE: ConnectionKit/CKConnectionProtocol.h ================================================ /* Copyright (c) 2004-2006 Karelia Software. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Karelia Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import #define LocalizedStringInConnectionKitBundle(key, comment) \ [[NSBundle bundleForClass:[self class]] localizedStringForKey:(key) value:@"" table:nil] @class CKTransferRecord; // Some shared Error Codes enum { ConnectionErrorUploading = 49101, ConnectionErrorDownloading, ConnectionErrorCreatingDirectory, ConnectionErrorChangingDirectory, ConnectionErrorDeleting, ConnectionErrorConnecting, ConnectionErrorDisconnecting, ConnectionErrorUnexpectedlyDisconnected, ConnectionErrorListingDirectory, ConnectionErrorGeneric, }; typedef enum { CKTranscriptSent, CKTranscriptReceived, CKTranscriptData, CKTranscriptInfo, } CKTranscriptType; /* A lightweight version of CKConnection, selfishly for Sandvox's benefit */ @protocol CKPublishingConnection /*! @method URLSchemes @result An array of the URL schemes supported by the connection. */ + (NSArray *)URLSchemes; /*! @method initWithRequest: @abstract The designated initializer for connections. @param request The request to connect with. The request object is deep-copied as part of the initialization process. Changes made to request after this method returns do not affect the request that is used for the loading process. @result Returns an initialized connection object or nil if the request was unsuitable. */ - (id)initWithRequest:(NSURLRequest *)request; /*! @discussion The delegate is not retained. The delegate should implement any of the methods in the CKConnectionDelegate informal protocol to receive callbacks when connection events occur. */ @property(nonatomic, assign) NSObject *delegate; /*! @method connect @abstract Causes the receiver to start the connection, if it has not already. This is generally asynchronous. */ - (void)connect; /*! @method isConnected @result Returns YES once the connection has successfully connected to the server */ - (BOOL)isConnected; /*! @method disconnect @abstract Ends the connection after any other items in the queue have been processed. */ - (void)disconnect; /*! @method forceDisconnect @abstract Ends the connection at the next available opportunity. */ - (void)forceDisconnect; /* New method that allows you to set a custom delegate for the upload. You must implement the ConnectionTransferDelegate informal protocol. By default the transfer record returned is the delegate of the transfer. SFTP connections require permissions to be explicitly specified up-front as part of creating a file. (in practice some servers then ignore them). Other connection types tend to apply default permissions of their own. You should generally pass in 0644 for broad compatibility. Importantly the connection makes NO GUARANTEE the permissions will be respected; they're just an attempt for SFTP and similar */ - (CKTransferRecord *)uploadFileAtURL:(NSURL *)url toPath:(NSString *)path openingPosixPermissions:(unsigned long)permissions; /* New method that allows you to set a custom delegate for the upload. You must implement the ConnectionTransferDelegate informal protocol. By default the transfer record returned is the delegate of the transfer. See openingPosixPermissions advice above */ - (CKTransferRecord *)uploadData:(NSData *)data toPath:(NSString *)path openingPosixPermissions:(unsigned long)permissions; - (void)setPermissions:(unsigned long)permissions forFile:(NSString *)path; - (void)deleteFile:(NSString *)path; - (void)createDirectoryAtPath:(NSString *)path posixPermissions:(NSNumber *)permissions; - (void)changeToDirectory:(NSString *)dirPath; - (NSString *)currentDirectory; - (void)directoryContents; @end @protocol CKConnection + (NSString *)name; /*! @method port @discussion Return 0 for abstract classes or connections that do not use a port. @result The default port for connections of the receiver's class. */ + (NSInteger)defaultPort; /*! @method request @discussion Please do NOT modify this request in any way! @result Returns the request supplied when creating the connection. */ - (NSURLRequest *)request; // you can set a name on a connection to help with debugging. // TODO: Should this really be part of the protocol, or a CKAbstractConnection implementation detail? - (NSString *)name; - (void)setName:(NSString *)name; - (BOOL)isBusy; - (void)cleanupConnection; - (NSString *)rootDirectory; - (void)rename:(NSString *)fromPath to:(NSString *)toPath; - (void)recursivelyRenameS3Directory:(NSString *)fromDirectoryPath to:(NSString *)toDirectoryPath; - (void)deleteDirectory:(NSString *)dirPath; - (void)recursivelyDeleteDirectory:(NSString *)path; /* returns CKTransferRecord as a heirarchy of what will be upload, remote and local files can be found in the records node properties */ - (CKTransferRecord *)recursivelyUpload:(NSString *)localPath to:(NSString *)remotePath; - (CKTransferRecord *)recursivelyUpload:(NSString *)localPath to:(NSString *)remotePath ignoreHiddenFiles:(BOOL)flag; - (CKTransferRecord *)resumeUploadFile:(NSString *)localPath toFile:(NSString *)remotePath fileOffset:(unsigned long long)offset delegate:(id)delegate; - (CKTransferRecord *)resumeUploadFromData:(NSData *)data toFile:(NSString *)remotePath fileOffset:(unsigned long long)offset delegate:(id)delegate; /* New method that allows you to set a custom delegate for the download. You must implement the CKConnectionTransferDelegate informal protocol. By default the transfer record returned is the delegate of the transfer. */ - (CKTransferRecord *)downloadFile:(NSString *)remotePath toDirectory:(NSString *)dirPath overwrite:(BOOL)flag delegate:(id)delegate; - (CKTransferRecord *)resumeDownloadFile:(NSString *)remotePath toDirectory:(NSString *)dirPath fileOffset:(unsigned long long)offset delegate:(id)delegate; - (CKTransferRecord *)recursivelyDownload:(NSString *)remotePath to:(NSString *)localPath overwrite:(BOOL)flag; - (void)checkExistenceOfPath:(NSString *)path; - (unsigned)numberOfTransfers; - (void)cancelTransfer; - (void)cancelAll; - (void)contentsOfDirectory:(NSString *)dirPath; - (double)uploadSpeed; // bytes per second - (double)downloadSpeed; - (void)editFile:(NSString *)remoteFile; @end #pragma mark - @interface NSObject (CKConnectionDelegate) // There are 29 callbacks & flags. // Need to keep NSObject Category, __flags list, setDelegate: updated #pragma mark Overall connection - (void)connection:(id )con didConnectToHost:(NSString *)host error:(NSError *)error; // this only guarantees that the socket connected. - (void)connection:(id )con didDisconnectFromHost:(NSString *)host; - (void)connection:(id )con didReceiveError:(NSError *)error; #pragma mark Authentication /*! @method connection:didReceiveAuthenticationChallenge: @abstract Operates just like the NSURLConnection delegate method -connection:didReceiveAuthenticationChallenge: @param connection The connection for which authentication is needed @param challenge The NSURLAuthenticationChallenge to start authentication for */ - (void)connection:(id )connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; /*! @method connection:didCancelAuthenticationChallenge: @abstract Operates exactly the same as its NSURLConnection counterpart. @param connection The connection sending the message. @param challenge The challenge that was canceled. */ - (void)connection:(id )connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; - (NSString *)connection:(id )con passphraseForHost:(NSString *)host username:(NSString *)username publicKeyPath:(NSString *)publicKeyPath; //SFTP Passphrase Support #pragma mark Other - (void)connection:(id )con didCreateDirectory:(NSString *)dirPath error:(NSError *)error; - (void)connection:(id )con didDeleteDirectory:(NSString *)dirPath error:(NSError *)error; - (void)connection:(id )con didDeleteFile:(NSString *)path error:(NSError *)error; // recursivelyDeleteDirectory // These methods may change soon -- Seth - (void)connection:(id )con didDiscoverFilesToDelete:(NSArray *)contents inAncestorDirectory:(NSString *)ancestorDirPath; - (void)connection:(id )con didDiscoverFilesToDelete:(NSArray *)contents inDirectory:(NSString *)dirPath; - (void)connection:(id )con didDeleteDirectory:(NSString *)dirPath inAncestorDirectory:(NSString *)ancestorDirPath error:(NSError *)error; - (void)connection:(id )con didDeleteFile:(NSString *)path inAncestorDirectory:(NSString *)ancestorDirPath error:(NSError *)error; - (void)connection:(id )con didChangeToDirectory:(NSString *)dirPath error:(NSError *)error; - (void)connection:(id )con didReceiveContents:(NSArray *)contents ofDirectory:(NSString *)dirPath error:(NSError *)error; - (void)connection:(id )con didReceiveContents:(NSArray *)contents ofDirectory:(NSString *)dirPath moreComing:(BOOL)flag; - (void)connection:(id )con didRename:(NSString *)fromPath to:(NSString *)toPath error:(NSError *)error; - (void)connection:(id )con didSetPermissionsForFile:(NSString *)path error:(NSError *)error; - (void)connection:(id )con download:(NSString *)path progressedTo:(NSNumber *)percent; - (void)connection:(id )con download:(NSString *)path receivedDataOfLength:(unsigned long long)length; - (void)connection:(id )con downloadDidBegin:(NSString *)remotePath; - (void)connection:(id )con downloadDidFinish:(NSString *)remotePath error:(NSError *)error; - (void)connection:(id )con upload:(NSString *)remotePath progressedTo:(NSNumber *)percent; - (void)connection:(id )con upload:(NSString *)remotePath sentDataOfLength:(unsigned long long)length; - (void)connection:(id )con uploadDidBegin:(NSString *)remotePath; - (void)connection:(id )con uploadDidFinish:(NSString *)remotePath error:(NSError *)error; - (void)connectionDidCancelTransfer:(id )con; // this is deprecated. Use method below - (void)connection:(id )con didCancelTransfer:(NSString *)remotePath; - (void)connection:(id )con checkedExistenceOfPath:(NSString *)path pathExists:(BOOL)exists error:(NSError *)error; #pragma mark Transcript /*! @method connection:appendString:toTranscript: @abstract Called when the connection has something to add to the connection transcript. @discussion Delegates should implement this method if they are interested in keeping a transcript. This could be to log the string to the console or add it to a text view. @param connection The connection sending the message @param string The string to add to the transcript @param transcript The nature of the string that is to be transcribed. CKAbstractConnection has class methods to apply formatting to the transcript. */ - (void)connection:(id )connection appendString:(NSString *)string toTranscript:(CKTranscriptType)transcript; @end #pragma mark - @interface NSObject (CKConnectionTransferDelegate) - (void)transferDidBegin:(CKTransferRecord *)transfer; - (void)transfer:(CKTransferRecord *)transfer transferredDataOfLength:(unsigned long long)length; - (void)transfer:(CKTransferRecord *)transfer progressedTo:(NSNumber *)percent; - (void)transfer:(CKTransferRecord *)transfer receivedError:(NSError *)error; - (void)transferDidFinish:(CKTransferRecord *)transfer error:(NSError *)error; @end // Attributes for which there isn't a corresponding NSFileManager key extern NSString *cxFilenameKey; extern NSString *cxSymbolicLinkTargetKey; //User Info Keys for Errors extern NSString *ConnectionHostKey; extern NSString *ConnectionDirectoryExistsKey; extern NSString *ConnectionDirectoryExistsFilenameKey; /* * The InputStream and OutputStream protocols, provides a transparent way to interchange * the implementation specific streams. */ @protocol InputStream - (void)open; - (void)close; - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; - (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; - (void)setDelegate:(id)delegate; - (id)delegate; - (BOOL)setProperty:(id)property forKey:(NSString *)key; - (id)propertyForKey:(NSString *)key; - (NSError *)streamError; - (NSStreamStatus)streamStatus; - (BOOL)hasBytesAvailable; - (int)read:(uint8_t *)buffer maxLength:(unsigned int)len; @end @protocol OutputStream - (void)open; - (void)close; - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; - (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; - (void)setDelegate:(id)delegate; - (id)delegate; - (BOOL)setProperty:(id)property forKey:(NSString *)key; - (id)propertyForKey:(NSString *)key; - (NSError *)streamError; - (NSStreamStatus)streamStatus; - (BOOL) hasSpaceAvailable; - (int)write:(const uint8_t *)buffer maxLength:(unsigned int)len; @end ================================================ FILE: ConnectionKit/CKS3Connection.h ================================================ /* Copyright (c) 2004-2006, Greg Hulands All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Greg Hulands nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "CKHTTPConnection.h" @interface CKS3Connection : CKHTTPConnection { //When we receive a directory listing that is truncated, we keep around the contents in here until we've received all the directory's contents to return the delegate. NSMutableArray *incompleteDirectoryContents; NSMutableArray *incompleteKeyNames; NSString *myCurrentDirectory; unsigned long long bytesTransferred; unsigned long long bytesToTransfer; unsigned long long transferHeaderLength; unsigned int myLastPercent; NSFileHandle *myDownloadHandle; @private // Authentication NSURLCredential *_credential; //NSURLAuthenticationChallenge *_currentAuthenticationChallenge; } @end extern NSString *S3StorageClassKey; // file attribute extension keys extern NSString *S3ErrorDomain; enum { S3DownloadFileExists = 100 }; ================================================ FILE: ConnectionKit/CKS3Connection.m ================================================ /* Copyright (c) 2004-2006, Greg Hulands All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Greg Hulands nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "CKS3Connection.h" #import "CKHTTPRequest.h" #import "NSData+Connection.h" #import "NSString+Connection.h" #import "NSCalendarDate+Connection.h" #import "CKHTTPPutRequest.h" #import "CKHTTPFileDownloadRequest.h" #import "CKHTTPResponse.h" #import "CKInternalTransferRecord.h" #import "CKTransferRecord.h" #import "NSFileManager+Connection.h" #import "CKConnectionProtocol.h" NSString *S3ErrorDomain = @"S3ErrorDomain"; NSString *S3StorageClassKey = @"S3StorageClassKey"; NSString *S3PathSeparator = @":"; //@"0xKhTmLbOuNdArY"; @implementation CKS3Connection + (void)load // registration of this class { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [[CKConnectionRegistry sharedConnectionRegistry] registerClass:self forName:[self name] URLScheme:@"s3"]; [pool release]; } + (NSString *)name { return @"Amazon S3"; } + (NSArray *)URLSchemes { return [NSArray arrayWithObjects:@"s3", @"http", nil]; } #pragma mark init methods - (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate { // allow for subdomains of s3 if ([[[request URL] host] rangeOfString:@"s3.amazonaws.com"].location == NSNotFound) { NSURL *URL = [[NSURL alloc] initWithScheme:@"http" host:@"s3.amazonaws.com" path:nil]; NSMutableURLRequest *newRequest = [request mutableCopy]; [newRequest setURL:URL]; [URL release]; self = [super initWithRequest:newRequest]; [newRequest release]; } else { self = [super initWithRequest:request]; } if (self) { incompleteDirectoryContents = [[NSMutableArray array] retain]; incompleteKeyNames = [[NSMutableArray array] retain]; myCurrentDirectory = @"/"; } return self; } - (void)dealloc { [incompleteDirectoryContents release]; [incompleteKeyNames release]; [myCurrentDirectory release]; [myDownloadHandle release]; [_credential release]; [_currentAuthenticationChallenge release]; [super dealloc]; } - (NSString *)standardizePath:(NSString *)unstandardPath { if (![unstandardPath hasPrefix:@"/"]) unstandardPath = [@"/" stringByAppendingString:unstandardPath]; return unstandardPath; } - (NSString *)fixPathToBeDirectoryPath:(NSString *)dirPath { if (![dirPath hasSuffix:@"/"]) dirPath = [dirPath stringByAppendingString:@"/"]; return [self standardizePath:dirPath]; } - (NSString *)fixPathToBeFilePath:(NSString *)filePath { if ([filePath hasSuffix:@"/"]) filePath = [filePath substringToIndex:[filePath length] - 1]; return [self standardizePath:filePath]; } #pragma mark - #pragma mark HTTP Overrides - (void)setAuthenticationWithRequest:(CKHTTPRequest *)request { // S3 needs decent credentials to operate NSAssert(_credential, @"S3 requires credentials to operate"); NSAssert([_credential user], @"S3 connection has no access key ID"); NSAssert([_credential persistence] == NSURLCredentialPersistenceNone, @"S3 passwords cannot be persisted"); NSAssert([_credential password], @"S3 connection has no secret key"); NSString *method = [request method]; NSString *md5 = @""; //[[request content] length] > 0 ? [[[request content] md5Digest] base64Encoding] : @""; NSString *ct = [request headerForKey:@"Content-Type"]; NSString *date = [request headerForKey:@"Date"]; [request setHeader:date forKey:@"Date"]; NSMutableString *auth = [NSMutableString stringWithFormat:@"%@\n%@\n%@\n%@\n", method, md5, ct ? ct : @"", date]; NSEnumerator *e = [[[[request headers] allKeys] sortedArrayUsingSelector:@selector(compare:)] objectEnumerator]; NSString *key; while ((key = [e nextObject])) { if ([[key lowercaseString] hasPrefix:@"x-amz"]) { [auth appendFormat:@"%@:%@\n", [key lowercaseString], [request headerForKey:key]]; } } NSString *uri = [request uri]; NSRange r = [uri rangeOfString:@"?"]; if (r.location != NSNotFound) { uri = [uri substringToIndex:r.location]; } [auth appendString:[uri encodeLegally]]; NSString *sha1 = [[[auth dataUsingEncoding:NSUTF8StringEncoding] sha1HMacWithKey:[_credential password]] base64Encoding]; [request setHeader:[NSString stringWithFormat:@"AWS %@:%@", [_credential user], sha1] forKey:@"Authorization"]; } - (void)processResponse:(CKHTTPResponse *)response { NSError *error = nil; if ([response code] >= 400 && [response code] < 500) { NSXMLDocument *doc = [[NSXMLDocument alloc] initWithData:[response content] options:NSXMLDocumentTidyXML error:&error]; NSString *desc = [[[[doc rootElement] nodesForXPath:@"//Error/Message" error:&error] objectAtIndex:0] stringValue]; NSString *code = [[[[doc rootElement] nodesForXPath:@"//Error/Code" error:&error] objectAtIndex:0] stringValue]; [doc release]; if ([code isEqualToString:@"SignatureDoesNotMatch"]) { // TODO: Send a fresh authentication request and try again } else { if (desc) error = [NSError errorWithDomain:S3ErrorDomain code:1 userInfo:[NSDictionary dictionaryWithObject:desc forKey:NSLocalizedDescriptionKey]]; else KTLog(S3ErrorDomain, KTLogError, @"An unknown error occurred:\n%@", response); } } switch (GET_STATE) { case CKConnectionAwaitingDirectoryContentsState: { if ([response code] / 100 == 2) { NSError *error = nil; NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithData:[response content] options:NSXMLDocumentTidyXML error:&error] autorelease]; KTLog(CKProtocolDomain, KTLogDebug, @"\n%@", [doc XMLStringWithOptions:NSXMLNodePrettyPrint]); //do the buckets first NSXMLElement *cur; NSEnumerator *e; NSMutableArray *contents = [NSMutableArray array]; BOOL isTruncated = NO; NSArray *isTruncatedNodes = [[doc rootElement] nodesForXPath:@"//IsTruncated" error:&error]; if ([isTruncatedNodes count] > 0) { NSXMLNode *isTruncatedNode = [isTruncatedNodes objectAtIndex:0]; NSString *isTruncatedValue = [isTruncatedNode stringValue]; isTruncated = [isTruncatedValue isEqualToString:@"true"]; } if ([myCurrentDirectory isEqualToString:@"/"]) { NSArray *buckets = [[doc rootElement] nodesForXPath:@"//Bucket" error:&error]; e = [buckets objectEnumerator]; while ((cur = [e nextObject])) { NSString *name = [[[cur elementsForName:@"Name"] objectAtIndex:0] stringValue]; NSString *date = [[[cur elementsForName:@"CreationDate"] objectAtIndex:0] stringValue]; NSMutableDictionary *d = [NSMutableDictionary dictionary]; [d setObject:name forKey:cxFilenameKey]; [d setObject:[NSCalendarDate calendarDateWithZuluFormat:date] forKey:NSFileCreationDate]; [d setObject:NSFileTypeDirectory forKey:NSFileType]; [contents addObject:d]; } } // contents inside a bucket NSArray *bucketContents = [[doc rootElement] nodesForXPath:@"//Contents" error:&error]; e = [bucketContents objectEnumerator]; NSString *currentPath = [myCurrentDirectory stringByDeletingFirstPathComponent]; NSMutableArray *keyNames = [NSMutableArray arrayWithArray:incompleteKeyNames]; while ((cur = [e nextObject])) { NSString *rawKeyName = [[[cur elementsForName:@"Key"] objectAtIndex:0] stringValue]; NSString *name = [self standardizePath:rawKeyName]; if ([name length] < [currentPath length]) continue; // this is a record from a parent folder if ([name rangeOfString:currentPath].location == NSNotFound) continue; // this is an element in a different folder if ([name hasPrefix:currentPath]) name = [name substringFromIndex:[currentPath length]]; /*We receive _all_ directory contents at once, so even when we ask for /brianamerige, we get /brianamerige/wp-admin/page.php, for example. Consequently, when currentpath is /wp-admin, we are only looking for things immediately inside /wp-admin. To achieve this, we only keep _one_ of each of the same first path components. */ if ([keyNames containsObject:[name firstPathComponent]]) continue; [keyNames addObject:[name firstPathComponent]]; if (![[name firstPathComponent] isEqualToString:[name lastPathComponent]]) { //We have /wp-admin/page.php while we're only trying to list /wp-admin name = [[name firstPathComponent] stringByAppendingString:@"/"]; } NSString *date = [[[cur elementsForName:@"LastModified"] objectAtIndex:0] stringValue]; NSString *size = [[[cur elementsForName:@"Size"] objectAtIndex:0] stringValue]; NSString *class = [[[cur elementsForName:@"StorageClass"] objectAtIndex:0] stringValue]; NSMutableDictionary *d = [NSMutableDictionary dictionary]; if ([name hasSuffix:@"/"]) { name = [name substringToIndex:[name length] - 1]; [d setObject:NSFileTypeDirectory forKey:NSFileType]; } else { [d setObject:NSFileTypeRegular forKey:NSFileType]; } if ([name isEqualToString:@""]) continue; // skip current path name that is returned in results [d setObject:name forKey:cxFilenameKey]; [d setObject:[NSCalendarDate calendarDateWithZuluFormat:date] forKey:NSFileModificationDate]; [d setObject:class forKey:S3StorageClassKey]; NSScanner *scanner = [NSScanner scannerWithString:size]; long long filesize; [scanner scanLongLong:&filesize]; [d setObject:[NSNumber numberWithLongLong:filesize] forKey:NSFileSize]; [contents addObject:d]; } if (isTruncated) { //Keep the contents for the next time around [incompleteDirectoryContents addObjectsFromArray:contents]; [incompleteKeyNames addObjectsFromArray:keyNames]; //We aren't done yet. There are more keys to be listed in this 'directory' NSString *bucketName = [myCurrentDirectory firstPathComponent]; NSString *prefixString = @""; if ([bucketName length] > 1) { NSString *subpath = [myCurrentDirectory substringFromIndex:[bucketName length] + 2]; if ([subpath length] > 0) prefixString = [NSString stringWithFormat:@"?prefix=%@", subpath]; } if ([prefixString length] == 0) prefixString = @"?"; //If we have no prefix, we need the ? to be /brianamerige?marker=bleh else prefixString = [prefixString stringByAppendingString:@"&"]; //If we do have a prefix, we need the & to be /brianameige?prefix=dir/&marker=bleh NSString *lastKeyName = [[[[bucketContents lastObject] elementsForName:@"Key"] objectAtIndex:0] stringValue]; NSString *markerString = [NSString stringWithFormat:@"marker=%@", lastKeyName]; NSString *uri = [NSString stringWithFormat:@"/%@%@%@", bucketName, prefixString, markerString]; CKHTTPRequest *request = [[CKHTTPRequest alloc] initWithMethod:@"GET" uri:[uri encodeLegallyForS3]]; [myCurrentRequest autorelease]; myCurrentRequest = request; [self sendCommand:request]; return; //Don't break. We are not idle yet, so we can't set CKConnectionIdleState as we do at the bottom of the method. } [contents addObjectsFromArray:incompleteDirectoryContents]; [incompleteDirectoryContents removeAllObjects]; [incompleteKeyNames removeAllObjects]; [self cacheDirectory:myCurrentDirectory withContents:contents]; //We use fixPathToBeFilePath to strip the / from the end –– we don't traditionally have this in the last path component externally. [[self client] connectionDidReceiveContents:contents ofDirectory:[self fixPathToBeFilePath:myCurrentDirectory] error:error]; } break; } case CKConnectionUploadingFileState: { CKInternalTransferRecord *upload = [[self currentUpload] retain]; [self dequeueUpload]; [[self client] uploadDidFinish:[upload remotePath] error:error]; if ([upload delegateRespondsToTransferDidFinish]) [[upload delegate] transferDidFinish:[upload delegate] error:error]; [upload release]; break; } case CKConnectionDeleteFileState: { [[self client] connectionDidDeleteFile:[self currentDeletion] error:error]; [self dequeueDeletion]; break; } case CKConnectionDeleteDirectoryState: { [[self client] connectionDidDeleteDirectory:[self currentDeletion] error:error]; [self dequeueDeletion]; break; } case CKConnectionCreateDirectoryState: { [[self client] connectionDidCreateDirectory:[self fixPathToBeDirectoryPath:[[response request] uri]] error:error]; break; } case CKConnectionAwaitingRenameState: { [[self client] connectionDidRename:[_fileRenames objectAtIndex:0] to:[_fileRenames objectAtIndex:1] error:error]; [_fileRenames removeObjectAtIndex:0]; [_fileRenames removeObjectAtIndex:0]; break; } case CKConnectionRenameFromState: { [self setState:CKConnectionRenameToState]; return; } default: break; } [self setState:CKConnectionIdleState]; } - (BOOL)processBufferWithNewData:(NSData *)data { if (GET_STATE == CKConnectionDownloadingFileState) { if (bytesToTransfer == 0) { NSDictionary *headers = [CKHTTPResponse headersWithData:myResponseBuffer]; NSString *length = [headers objectForKey:@"Content-Length"]; if (length > 0) { NSScanner *scanner = [NSScanner scannerWithString:length]; long long daBytes = 0; [scanner scanLongLong:&daBytes]; bytesToTransfer = daBytes; CKTransferRecord *record = (CKTransferRecord *)[[self currentDownload] userInfo]; NSFileManager *fm = [NSFileManager defaultManager]; BOOL isDir; if ([fm fileExistsAtPath:[record propertyForKey:CKQueueDownloadDestinationFileKey] isDirectory:&isDir] && !isDir) { [fm removeFileAtPath:[record propertyForKey:CKQueueDownloadDestinationFileKey] handler:nil]; } [fm createFileAtPath:[record propertyForKey:CKQueueDownloadDestinationFileKey] contents:nil attributes:nil]; [myDownloadHandle release]; myDownloadHandle = [[NSFileHandle fileHandleForWritingAtPath:[record propertyForKey:CKQueueDownloadDestinationFileKey]] retain]; // file data starts after the header NSRange headerRange = [myResponseBuffer rangeOfData:[[NSString stringWithString:@"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; NSString *header = [[myResponseBuffer subdataWithRange:NSMakeRange(0, headerRange.location)] descriptionAsUTF8String]; [[self client] appendString:header toTranscript:CKTranscriptReceived]; unsigned start = headerRange.location + headerRange.length; unsigned len = [myResponseBuffer length] - start; NSData *fileData = [myResponseBuffer subdataWithRange:NSMakeRange(start,len)]; [myDownloadHandle writeData:fileData]; [myResponseBuffer setLength:0]; bytesTransferred += [fileData length]; [[self client] download:[record propertyForKey:CKQueueDownloadRemoteFileKey] didReceiveDataOfLength:[fileData length]]; NSInteger percent = (bytesToTransfer == 0) ? 0 : (100 * bytesTransferred) / bytesToTransfer; [[self client] download:[record propertyForKey:CKQueueDownloadRemoteFileKey] didProgressToPercent:[NSNumber numberWithInteger:percent]]; } } else //add the data at the end of the file { [myDownloadHandle writeData:data]; [myResponseBuffer setLength:0]; bytesTransferred += [data length]; CKInternalTransferRecord *downloadInfo = [self currentDownload]; CKTransferRecord *record = (CKTransferRecord *)[downloadInfo userInfo]; [[self client] download:[record propertyForKey:CKQueueDownloadRemoteFileKey] didReceiveDataOfLength:[data length]]; if ([downloadInfo delegateRespondsToTransferTransferredData]) { [[downloadInfo delegate] transfer:record transferredDataOfLength:[data length]]; } NSInteger percent = (100 * bytesTransferred) / bytesToTransfer; [[self client] download:[record propertyForKey:CKQueueDownloadRemoteFileKey] didProgressToPercent:[NSNumber numberWithInteger:percent]]; if ([downloadInfo delegateRespondsToTransferProgressedTo]) { NSInteger percent = (100 * bytesTransferred) / bytesToTransfer; [[downloadInfo delegate] transfer:record progressedTo:[NSNumber numberWithInteger:percent]]; } } //check for completion, if the file is really small, then the transfer might be complete on the first pass //through this method, so check for completion everytime // if (bytesTransferred >= bytesToTransfer) //sometimes more data is received than required (i assume on small size file) { [myDownloadHandle closeFile]; [myDownloadHandle release]; myDownloadHandle = nil; CKInternalTransferRecord *downloadInfo = [[self currentDownload] retain]; [self dequeueDownload]; CKTransferRecord *record = (CKTransferRecord *)[downloadInfo userInfo]; [[self client] downloadDidFinish:[record propertyForKey:CKQueueDownloadRemoteFileKey] error:nil]; if ([downloadInfo delegateRespondsToTransferDidFinish]) [[downloadInfo delegate] transferDidFinish:[downloadInfo userInfo] error:nil]; [myCurrentRequest release]; myCurrentRequest = nil; [downloadInfo release]; [self setState:CKConnectionIdleState]; } return NO; } return YES; } - (void)initiatingNewRequest:(CKHTTPRequest *)req withPacket:(NSData *)packet { // if we are uploading or downloading set up the transfer sizes if (GET_STATE == CKConnectionUploadingFileState) { transferHeaderLength = [req headerLength]; bytesToTransfer = [packet length] - transferHeaderLength; bytesTransferred = 0; CKInternalTransferRecord *upload = [self currentUpload]; [[self client] uploadDidBegin:[upload remotePath]]; if ([upload delegateRespondsToTransferDidBegin]) { [[upload delegate] transferDidBegin:[upload delegate]]; } } if (GET_STATE == CKConnectionDownloadingFileState) { bytesToTransfer = 0; bytesTransferred = 0; CKInternalTransferRecord *download = [self currentDownload]; [[self client] downloadDidBegin:[download remotePath]]; if ([download delegateRespondsToTransferDidBegin]) { [[download delegate] transferDidBegin:[download delegate]]; } } } - (void)stream:(id)stream sentBytesOfLength:(unsigned)length { [super stream:stream sentBytesOfLength:length]; // call http if (length == 0) return; if (GET_STATE == CKConnectionUploadingFileState) { CKInternalTransferRecord *upload = [self currentUpload]; if (transferHeaderLength > 0) { if (length <= transferHeaderLength) { transferHeaderLength -= length; } else { length -= transferHeaderLength; transferHeaderLength = 0; bytesTransferred += length; } } else { bytesTransferred += length; } if (bytesToTransfer > 0) { NSInteger percent = (100 * bytesTransferred) / bytesToTransfer; if (percent != myLastPercent) { [[self client] upload:[upload remotePath] didProgressToPercent:[NSNumber numberWithInteger:percent]]; if ([upload delegateRespondsToTransferProgressedTo]) { [[upload delegate] transfer:[upload delegate] progressedTo:[NSNumber numberWithInteger:percent]]; } myLastPercent = percent; } } [[self client] upload:[upload remotePath] didSendDataOfLength:length]; if ([upload delegateRespondsToTransferTransferredData]) { [[upload delegate] transfer:[upload delegate] transferredDataOfLength:length]; } } } #pragma mark - #pragma mark Connection Overrides - (void)s3DidChangeToDirectory:(NSString *)dirPath { if (![dirPath hasSuffix:@"/"]) { dirPath = [dirPath stringByAppendingString:@"/"]; } [myCurrentDirectory autorelease]; myCurrentDirectory = [dirPath copy]; [[self client] connectionDidChangeToDirectory:dirPath error:nil]; [myCurrentRequest release]; myCurrentRequest = nil; [self setState:CKConnectionIdleState]; } - (void)changeToDirectory:(NSString *)dirPath { NSInvocation *inv = [NSInvocation invocationWithSelector:@selector(s3DidChangeToDirectory:) target:self arguments:[NSArray arrayWithObjects: dirPath, nil]]; CKConnectionCommand *cmd = [CKConnectionCommand command:inv awaitState:CKConnectionIdleState sentState:CKConnectionChangedDirectoryState dependant:nil userInfo:nil]; [self queueCommand:cmd]; } - (NSString *)currentDirectory { //We use fixPathToBeFilePath to strip the / from the end –– we don't traditionally have this in the last path component externally. return [self fixPathToBeFilePath:myCurrentDirectory]; } - (NSString *)rootDirectory { return @"/"; } - (void)createDirectory:(NSString *)dirPath { NSAssert(dirPath && ![dirPath isEqualToString:@""], @"no directory specified"); if (![dirPath hasSuffix:@"/"]) dirPath = [dirPath stringByAppendingString:@"/"]; //Trailing slash indicates it's a directory. if ([[dirPath componentsSeparatedByString:@"/"] count] < 3) { // we are creating a bucket, so remove the trailing / dirPath = [dirPath substringToIndex:[dirPath length] - 1]; } CKHTTPRequest *req = [[CKHTTPRequest alloc] initWithMethod:@"PUT" uri:[dirPath encodeLegallyForS3]]; CKConnectionCommand *cmd = [CKConnectionCommand command:req awaitState:CKConnectionIdleState sentState:CKConnectionCreateDirectoryState dependant:nil userInfo:nil]; [req release]; [self queueCommand:cmd]; } - (void)createDirectory:(NSString *)dirPath permissions:(unsigned long)permissions { //we don't support setting permissions [self createDirectory:dirPath]; } - (void)rename:(NSString *)fromPath to:(NSString *)toPath { NSAssert(fromPath && ![fromPath isEqualToString:@""], @"fromPath is nil!"); NSAssert(toPath && ![toPath isEqualToString:@""], @"toPath is nil!"); /* IMPORTANT NOTES ABOUT RENAMING/MOVING ON S3: Renaming (Moving) in the sense that we have in FTP/SFTP/WebDAV is not possible with Amazon S3 at the moment. This current implementation is a temporary workaround until a RENAME or MOVE command is implemented into the API by Amazon. What we're doing here is really copying the fromPath to the toPath (with the COPY command), and then deleting fromPath. Worth noting, if you're intending on renaming a directory, you must call -recursivelyRenameS3Directory:to: which is implemented and handled by StreamBasedConnection. You need to do this because renaming a directory in the fashion this method implements will not bring the directory's children over with it. You have been warned! */ CKHTTPRequest *copyRequest = [CKHTTPRequest requestWithMethod:@"PUT" uri:[toPath encodeLegallyForS3]]; [copyRequest setHeader:[fromPath encodeLegallyForS3] forKey:@"x-amz-copy-source"]; CKConnectionCommand *copyCommand = [CKConnectionCommand command:copyRequest awaitState:CKConnectionIdleState sentState:CKConnectionRenameFromState dependant:nil userInfo:nil]; CKHTTPRequest *deleteRequest = [CKHTTPRequest requestWithMethod:@"DELETE" uri:[fromPath encodeLegallyForS3]]; CKConnectionCommand *deleteCommand = [CKConnectionCommand command:deleteRequest awaitState:CKConnectionRenameToState sentState:CKConnectionAwaitingRenameState dependant:copyCommand userInfo:nil]; [self queueRename:fromPath]; [self queueRename:toPath]; [self queueCommand:copyCommand]; [self queueCommand:deleteCommand]; } - (void)deleteFile:(NSString *)path { NSAssert(path && ![path isEqualToString:@""], @"path is nil!"); CKHTTPRequest *req = [[[CKHTTPRequest alloc] initWithMethod:@"DELETE" uri:[[self fixPathToBeFilePath:path] encodeLegallyForS3]] autorelease]; CKConnectionCommand *cmd = [CKConnectionCommand command:req awaitState:CKConnectionIdleState sentState:CKConnectionDeleteFileState dependant:nil userInfo:nil]; [self queueDeletion:path]; [self queueCommand:cmd]; } - (void)deleteDirectory:(NSString *)dirPath { NSAssert(dirPath && ![dirPath isEqualToString:@""], @"dirPath is nil!"); CKHTTPRequest *req = [[[CKHTTPRequest alloc] initWithMethod:@"DELETE" uri:[[self fixPathToBeDirectoryPath:dirPath] encodeLegallyForS3]] autorelease]; CKConnectionCommand *cmd = [CKConnectionCommand command:req awaitState:CKConnectionIdleState sentState:CKConnectionDeleteDirectoryState dependant:nil userInfo:nil]; [self queueDeletion:dirPath]; [self queueCommand:cmd]; } - (CKTransferRecord *)uploadFile:(NSString *)localPath toFile:(NSString *)remotePath checkRemoteExistence:(BOOL)flag delegate:(id)delegate { CKTransferRecord *rec = [CKTransferRecord recordWithName:remotePath size:[[NSFileManager defaultManager] sizeOfPath:localPath]]; CKHTTPPutRequest *req = [CKHTTPPutRequest putRequestWithContentsOfFile:localPath uri:[[self fixPathToBeFilePath:remotePath] encodeLegallyForS3]]; [req setHeader:@"public-read" forKey:@"x-amz-acl"]; CKConnectionCommand *cmd = [CKConnectionCommand command:req awaitState:CKConnectionIdleState sentState:CKConnectionUploadingFileState dependant:nil userInfo:nil]; CKInternalTransferRecord *upload = [CKInternalTransferRecord recordWithLocal:localPath data:nil offset:0 remote:remotePath delegate:(delegate) ? delegate : rec userInfo:nil]; [rec setUpload:YES]; [self queueUpload:upload]; [self queueCommand:cmd]; return rec; } - (void)uploadFromData:(NSData *)data toFile:(NSString *)remotePath { remotePath = [self fixPathToBeFilePath:remotePath]; CKHTTPPutRequest *req = [CKHTTPPutRequest putRequestWithData:data filename:[remotePath lastPathComponent] uri:[remotePath encodeLegallyForS3]]; CKConnectionCommand *cmd = [CKConnectionCommand command:req awaitState:CKConnectionIdleState sentState:CKConnectionUploadingFileState dependant:nil userInfo:nil]; CKInternalTransferRecord *upload = [CKInternalTransferRecord recordWithLocal:nil data:data offset:0 remote:remotePath delegate:nil userInfo:nil]; [self queueUpload:upload]; [self queueCommand:cmd]; } - (CKTransferRecord *)downloadFile:(NSString *)remotePath toDirectory:(NSString *)dirPath overwrite:(BOOL)flag delegate:(id)delegate { NSString *fixedRemotePath = [self fixPathToBeFilePath:remotePath]; NSString *localPath = [dirPath stringByAppendingPathComponent:[fixedRemotePath lastPathComponent]]; if (!flag && [[NSFileManager defaultManager] fileExistsAtPath:localPath]) { NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: LocalizedStringInConnectionKitBundle(@"Local File already exists", @"FTP download error"), NSLocalizedDescriptionKey, remotePath, NSFilePathErrorKey, nil]; NSError *error = [NSError errorWithDomain:S3ErrorDomain code:S3DownloadFileExists userInfo:userInfo]; [[self client] connectionDidReceiveError:error]; return nil; } CKTransferRecord *record = [CKTransferRecord recordWithName:fixedRemotePath size:0]; CKInternalTransferRecord *download = [CKInternalTransferRecord recordWithLocal:localPath data:nil offset:0 remote:fixedRemotePath delegate:(delegate) ? delegate : record userInfo:record]; [record setProperty:fixedRemotePath forKey:CKQueueDownloadRemoteFileKey]; [record setProperty:localPath forKey:CKQueueDownloadDestinationFileKey]; [record setProperty:[NSNumber numberWithInt:0] forKey:CKQueueDownloadTransferPercentReceived]; [self queueDownload:download]; CKHTTPFileDownloadRequest *r = [CKHTTPFileDownloadRequest downloadRemotePath:fixedRemotePath to:dirPath]; CKConnectionCommand *cmd = [CKConnectionCommand command:r awaitState:CKConnectionIdleState sentState:CKConnectionDownloadingFileState dependant:nil userInfo:nil]; [self queueCommand:cmd]; return record; } - (void)s3DirectoryContents:(NSString *)dir { NSString *theDir = dir != nil ? dir : myCurrentDirectory; NSString *bucketName = [theDir firstPathComponent]; NSString *prefixString = @""; if ([bucketName length] > 1) { NSString *subpath = [theDir substringFromIndex:[bucketName length] + 2]; if ([subpath length] > 0) prefixString = [NSString stringWithFormat:@"?prefix=%@", subpath]; } NSString *uri = [NSString stringWithFormat:@"/%@%@", bucketName, prefixString]; CKHTTPRequest *r = [[CKHTTPRequest alloc] initWithMethod:@"GET" uri:[uri encodeLegallyForS3]]; [myCurrentRequest autorelease]; myCurrentRequest = r; [self sendCommand:r]; } - (void)directoryContents { NSInvocation *inv = [NSInvocation invocationWithSelector:@selector(s3DirectoryContents:) target:self arguments:[NSArray array]]; CKConnectionCommand *cmd = [CKConnectionCommand command:inv awaitState:CKConnectionIdleState sentState:CKConnectionAwaitingDirectoryContentsState dependant:nil userInfo:nil]; [self queueCommand:cmd]; } - (void)contentsOfDirectory:(NSString *)dirPath { NSAssert(dirPath && ![dirPath isEqualToString:@""], @"no dirPath"); NSArray *cachedContents = [self cachedContentsWithDirectory:dirPath]; if (cachedContents) { [[self client] connectionDidReceiveContents:cachedContents ofDirectory:[self standardizePath:dirPath] error:nil]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"CKDoesNotRefreshCachedListings"]) { return; } } NSInvocation *inv = [NSInvocation invocationWithSelector:@selector(s3DirectoryContents:) target:self arguments:[NSArray arrayWithObject:[self fixPathToBeDirectoryPath:dirPath]]]; CKConnectionCommand *cmd = [CKConnectionCommand command:inv awaitState:CKConnectionIdleState sentState:CKConnectionAwaitingDirectoryContentsState dependant:nil userInfo:nil]; [self queueCommand:cmd]; } #pragma mark - #pragma mark Authentication - (void)connect { if (_isConnecting || [self isConnected]) return; _isConnecting = YES; // Request authentication before connecting _currentAuthenticationChallenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:nil proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:self]; [[self client] connectionDidReceiveAuthenticationChallenge:_currentAuthenticationChallenge]; } /* CKHTTPConnection implements the -cancel and -continueWithCredential methods for us in a perfectly * decent manner, so don't bother overriding them. */ - (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if (challenge != _currentAuthenticationChallenge) return; [_currentAuthenticationChallenge release]; _currentAuthenticationChallenge = nil; _credential = [credential retain]; // Continue on with connecting [super connect]; } @end ================================================ FILE: ConnectionKit/CKTransferProgressCell.h ================================================ /* Copyright (c) 2006, Greg Hulands All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Greg Hulands nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import /* To set the name and progress, set the object value with a dictionary and have the keys progress and name */ @interface CKTransferProgressCell : NSCell { NSInteger myProgress; BOOL _finished; } @end ================================================ FILE: ConnectionKit/CKTransferProgressCell.m ================================================ /* Copyright (c) 2006, Greg Hulands All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Greg Hulands nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "CKTransferProgressCell.h" static NSColor *sProgressColor = nil; static NSImage *sErrorImage = nil; static NSImage *sFinishedImage = nil; static NSMutableParagraphStyle *sStyle = nil; NSSize CKLimitMaxWidthHeight(NSSize ofSize, CGFloat toMaxDimension); @implementation CKTransferProgressCell + (void)initialize { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; sProgressColor = [[NSColor colorForControlTint:NSDefaultControlTint] retain]; NSBundle *b = [NSBundle bundleForClass:[self class]]; NSString *path = [b pathForResource:@"error" ofType:@"png"]; sErrorImage = [[NSImage alloc] initWithContentsOfFile:path]; path = [b pathForResource:@"finished" ofType:@"png"]; sFinishedImage = [[NSImage alloc] initWithContentsOfFile:path]; sStyle = [[NSMutableParagraphStyle alloc] init]; [sStyle setLineBreakMode:NSLineBreakByTruncatingTail]; [pool release]; } - (void)setObjectValue:(id)value { if ([value isKindOfClass:[NSDictionary class]]) { myProgress = [[value objectForKey:@"progress"] intValue]; _finished = [[value objectForKey:@"finished"] boolValue]; if (_finished) myProgress = 100; NSError *error = [value objectForKey:@"error"]; if (error && !(error.code == NSURLErrorCancelled && [error.domain isEqualToString:NSURLErrorDomain])) myProgress = -1; [super setObjectValue:[value objectForKey:@"name"]]; } else if ([value isKindOfClass:[NSNumber class]]) { myProgress = [value integerValue]; } } #define PADDING 5 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { NSRect imageRect = NSMakeRect(NSMinX(cellFrame), NSMinY(cellFrame), NSHeight(cellFrame), NSHeight(cellFrame)); imageRect = NSOffsetRect(imageRect, PADDING, 0); // from omni NSMutableAttributedString *label = [[NSMutableAttributedString alloc] initWithAttributedString:[self attributedStringValue]]; NSRange labelRange = NSMakeRange(0, [label length]); if ([NSColor respondsToSelector:@selector(alternateSelectedControlColor)]) { NSColor *highlightColor = [self highlightColorWithFrame:cellFrame inView:controlView]; BOOL highlighted = [self isHighlighted]; if (highlighted && [highlightColor isEqual:[NSColor alternateSelectedControlColor]]) { // add the alternate text color attribute. [label addAttribute:NSForegroundColorAttributeName value:[NSColor alternateSelectedControlTextColor] range:labelRange]; } } [label addAttribute:NSParagraphStyleAttributeName value:sStyle range:labelRange]; NSSize labelSize = [label size]; NSRect labelRect = NSMakeRect(NSMaxX(imageRect) + PADDING, NSMidY(cellFrame) - (labelSize.height / 2), NSWidth(cellFrame) - NSWidth(imageRect) - PADDING, labelSize.height); [label drawInRect:labelRect]; [label release]; // draw the image or progress pie if (myProgress < 0) { NSSize s = CKLimitMaxWidthHeight([sErrorImage size], NSHeight(cellFrame)); NSRect centered = NSMakeRect(NSMidX(imageRect) - (s.width / 2), NSMidY(imageRect) - (s.height / 2), s.width, s.height); [sErrorImage drawInRect:centered fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:YES hints:nil]; } else if (myProgress >= 0 && !_finished) { NSAffineTransform *flip = nil; if ([controlView isFlipped]) { [[NSGraphicsContext currentContext] saveGraphicsState]; flip = [NSAffineTransform transform]; [flip translateXBy:0 yBy:NSMaxY(imageRect)]; [flip scaleXBy:1 yBy:-1]; [flip concat]; imageRect.origin.y = 0; } NSBezierPath *circle = [NSBezierPath bezierPathWithOvalInRect:imageRect]; NSPoint cp = NSMakePoint(NSMidX(imageRect), NSMidY(imageRect)); NSBezierPath *pie = [NSBezierPath bezierPath]; CGFloat degrees = (myProgress / 100.0) * 360.0; [pie moveToPoint:cp]; [pie lineToPoint:NSMakePoint(NSMidX(imageRect), NSMaxY(imageRect))]; int i; CGFloat radius = floor(NSMaxY(imageRect) - NSMidY(imageRect)); CGFloat x, y; for (i = 0; i <= floor(degrees); i++) { CGFloat rad = i * (M_PI / 180.0); x = sinf(rad) * radius; y = cosf(rad) * radius; [pie lineToPoint:NSMakePoint(cp.x + x, cp.y + y)]; } [pie lineToPoint:cp]; [pie closePath]; [[NSColor whiteColor] set]; [circle fill]; [[sProgressColor colorWithAlphaComponent:0.5] set]; [pie fill]; [sProgressColor set]; [pie setLineWidth:1.0]; [pie stroke]; [sProgressColor set]; [circle setLineWidth:1.0]; [circle stroke]; if ([controlView isFlipped]) { [flip invert]; [flip concat]; [[NSGraphicsContext currentContext] restoreGraphicsState]; } } else { // we are finished NSSize s = CKLimitMaxWidthHeight([sFinishedImage size], NSHeight(cellFrame)); NSRect centered = NSMakeRect(NSMidX(imageRect) - (s.width / 2), NSMidY(imageRect) - (s.height / 2), s.width, s.height); [sFinishedImage drawInRect:centered fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:YES hints:nil]; } } @end NSSize CKLimitMaxWidthHeight(NSSize ofSize, CGFloat toMaxDimension) { CGFloat max = fmax(ofSize.width, ofSize.height); if (max <= toMaxDimension) return ofSize; if (ofSize.width >= ofSize.height) { ofSize.width = toMaxDimension; ofSize.height *= toMaxDimension / max; } else { ofSize.height = toMaxDimension; ofSize.width *= toMaxDimension / max; } return ofSize; } ================================================ FILE: ConnectionKit/CKTransferRecord.h ================================================ /* Copyright (c) 2006, Greg Hulands All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Greg Hulands nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import @class CK2FileOperation; @interface CKTransferRecord : NSObject { NSString *_name; CK2FileOperation *_operation; int64_t _size; NSTimeInterval _lastTransferTime; NSTimeInterval _transferStartTime; NSTimeInterval _lastDirectorySpeedUpdate; CGFloat _speed; NSMutableArray *_contents; BOOL _contentsComplete; CKTransferRecord *_parent; //not retained NSMutableDictionary *_properties; void *_observationInfo; } - (NSString *)name; - (void)setName:(NSString *)name; @property(readonly) int64_t size; - (CGFloat)speed; - (void)setSpeed:(CGFloat)speed; // TODO: Switch to CGFloat - (BOOL)isFinished; - (NSError *)error; - (CKTransferRecord *)parent; + (instancetype)recordWithName:(NSString *)name uploadOperation:(CK2FileOperation *)operation; - (id)initWithName:(NSString *)name uploadOperation:(CK2FileOperation *)operation; @property(nonatomic, retain, readonly) CK2FileOperation *uploadOperation; - (BOOL)isDirectory; - (unsigned long long)transferred; /** Between 0 and 100. */ @property(readonly) CGFloat progress; - (NSDictionary *)nameWithProgressAndFileSize; #pragma mark Contents - (void)addContent:(CKTransferRecord *)record; - (NSArray *)contents; /** Whether there is the possiblity of any more content being added. Used to know whether directories should be drawn with a tick mark or not, once their contents have finished uploading. If contents are not yet complete, once all descendant uploads have finished, a pie chart is still drawn, in case new contents arrive. */ @property(nonatomic, readonly) BOOL contentsAreComplete; /** Marks the receiver as not expecting any more records to be added to `contents`. The receiver recurses down and marks all its contents as being complete to so you can mark off whole sections of the hierarchy. See `contentsAreComplete` for details. */ - (void)markContentsAsComplete; #pragma mark - (CKTransferRecord *)root; - (NSString *)path; - (void)setProperty:(id)property forKey:(NSString *)key __attribute((nonnull(2))); - (id)propertyForKey:(NSString *)key; /* backward compatibility with NSDictionary */ - (void)setObject:(id)object forKey:(id)key; - (id)objectForKey:(id)key; // Helper methods for working with the recursive data structure + (CKTransferRecord *)rootRecordWithPath:(NSString *)path; // If the path is absolute, searches from root of tree, otherwise searches from receiver - (CKTransferRecord *)recordForPath:(NSString *)path; - (BOOL)problemsTransferringCountingErrors:(NSInteger *)outErrors successes:(NSInteger *)outSuccesses; @end extern NSString *CKTransferRecordTransferDidBeginNotification; extern NSString *CKTransferRecordTransferDidFinishNotification; #pragma mark - @interface NSObject (CKConnectionTransferDelegate) - (void)transferDidBegin:(CKTransferRecord *)transfer; - (void)transfer:(CKTransferRecord *)transfer transferredDataOfLength:(unsigned long long)length; - (void)transfer:(CKTransferRecord *)transfer receivedError:(NSError *)error; - (void)transferDidFinish:(CKTransferRecord *)transfer error:(NSError *)error; @end #pragma mark - @interface CKTransferRecord (Private) - (void)setSpeed:(double)bps; - (void)setUpload:(BOOL)flag; - (BOOL)isLeaf; @end ================================================ FILE: ConnectionKit/CKTransferRecord.m ================================================ /* Copyright (c) 2006, Greg Hulands All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Greg Hulands nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "CKTransferRecord.h" #import "CK2FileOperation.h" #import // for NSColor NSString *CKTransferRecordTransferDidBeginNotification = @"CKTransferRecordTransferDidBeginNotification"; NSString *CKTransferRecordTransferDidFinishNotification = @"CKTransferRecordTransferDidFinishNotification"; @interface CKTransferRecord () @property(nonatomic, readwrite) BOOL contentsAreComplete; @end @implementation CKTransferRecord - (NSString *)name { return _name; } - (void)setName:(NSString *)name { if (_name != name) { [self willChangeValueForKey:@"name"]; name = [name copy]; [_name release]; _name = name; [self didChangeValueForKey:@"name"]; } } @synthesize uploadOperation = _operation; - (BOOL)isFinished; { CK2FileOperation *operation = self.uploadOperation; if (operation) return (operation.state == CK2FileOperationStateCompleted); // Can't be finished until all contents are present if (!self.contentsAreComplete) return NO; for (CKTransferRecord *aRecord in self.contents) { if (!aRecord.isFinished) return NO; } return YES; } - (NSError *)error { CK2FileOperation *operation = self.uploadOperation; if (operation) return operation.error; NSError *result = nil; for (CKTransferRecord *aRecord in self.contents) { result = aRecord.error; if (result) break; } return result; } - (CKTransferRecord *)parent { return _parent; } + (instancetype)recordWithName:(NSString *)name uploadOperation:(CK2FileOperation *)operation; { return [[[CKTransferRecord alloc] initWithName:name uploadOperation:operation] autorelease]; } - (id)initWithName:(NSString *)name uploadOperation:(CK2FileOperation *)operation; { if ((self = [super init])) { _name = [name copy]; _operation = [operation retain]; _contents = [[NSMutableArray array] retain]; _properties = [[NSMutableDictionary dictionary] retain]; // Cache initial size estimate. Don't want it to change if request needs retransmitting _size = operation.countOfBytesExpectedToWrite; } return self; } - (void)dealloc { [_name release]; [_operation release]; [_contents makeObjectsPerformSelector:@selector(setParent:) withObject:nil]; [_contents release]; [_properties release]; [super dealloc]; } - (int64_t)size { // Calculate our size including our children int64_t result = _size; for (CKTransferRecord *aRecord in self.contents) { result += [aRecord size]; } return result; } - (unsigned long long)transferred { int64_t result = self.uploadOperation.countOfBytesWritten; for (CKTransferRecord *aRecord in _contents) // -contents is too slow as copies the internal storage { result += aRecord.transferred; } return result; } - (CGFloat)speed { if ([self isDirectory]) { if (_transferStartTime == 0.0) { _transferStartTime = [NSDate timeIntervalSinceReferenceDate]; } NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; if (_lastDirectorySpeedUpdate == 0.0 || now - _lastDirectorySpeedUpdate >= 1.0) { _lastDirectorySpeedUpdate = now; NSTimeInterval elapsedTime = now - _transferStartTime; [self willChangeValueForKey:@"speed"]; if (elapsedTime == 0.0) { //If we don't catch this, we are effectively dividing by zero below. This would leave _speed as NaN. _speed = 0.0; } else { unsigned long long transferred = [self transferred]; _speed = transferred / elapsedTime; } [self didChangeValueForKey:@"speed"]; } } return _speed; } - (void)setSpeed:(CGFloat)speed { if (speed != _speed) { [self willChangeValueForKey:@"speed"]; _speed = speed; [self didChangeValueForKey:@"speed"]; } } - (CGFloat)progress { CK2FileOperation *op = self.uploadOperation; __block int64_t totalWritten = op.countOfBytesWritten; __block int64_t totalExpected = op.countOfBytesExpectedToWrite; if (totalWritten > totalExpected) totalExpected = totalWritten; [self enumerateTransferRecordsRecursively:YES usingBlock:^(CKTransferRecord *record) { CK2FileOperation *op = record.uploadOperation; int64_t written = op.countOfBytesWritten; int64_t expected = op.countOfBytesExpectedToWrite; if (written > expected) expected = written; totalWritten += written; totalExpected += expected; }]; if (!totalExpected) return 0; return (100 * totalWritten) / totalExpected; } - (void)enumerateTransferRecordsRecursively:(BOOL)recursive usingBlock:(void (^)(CKTransferRecord *record))block; { [self.contents enumerateObjectsUsingBlock:^(CKTransferRecord *record, NSUInteger idx, BOOL *stop) { block(record); if (recursive) [record enumerateTransferRecordsRecursively:recursive usingBlock:block]; }]; } - (BOOL)problemsTransferringCountingErrors:(NSInteger *)outErrors successes:(NSInteger *)outSuccesses { if ([self isLeaf]) { if (self.error) { (*outErrors)++; } else { (*outSuccesses)++; } } else { // check children for errors NSEnumerator *e = [[self contents] objectEnumerator]; CKTransferRecord *cur; while ((cur = [e nextObject])) { (void) [cur problemsTransferringCountingErrors:outErrors successes:outSuccesses]; } } return (*outErrors > 0); // return if there were any problems } - (void)setParent:(CKTransferRecord *)parent { _parent = parent; } - (BOOL)isDirectory { return [_contents count] > 0; } #pragma mark KVO - (void)willChangeValueForKey:(NSString *)key { //We override this because we need to call the same on the record's parents to update any bindings on them as well. This traverses all the way up the parental hierarchy. [super willChangeValueForKey:key]; if ([self parent]) [[self parent] willChangeValueForKey:key]; } - (void)didChangeValueForKey:(NSString *)key { //We override this because we need to call the same on the record's parents to update any bindings on them as well. This traverses all the way up the parental hierarchy. [super didChangeValueForKey:key]; if ([self parent]) [[self parent] didChangeValueForKey:key]; } - (void *)observationInfo; { return _observationInfo; } - (void)setObservationInfo:(void *)observationInfo; { _observationInfo = observationInfo; } #pragma mark - (CKTransferRecord *)root { if (_parent) { return [_parent root]; } return self; } - (NSString *)path { if ([self parent]) { return [[_parent path] stringByAppendingPathComponent:[self name]]; // Old code was @"%@/%@" but it broke if _parent was just / } else { return [self name]; } } #pragma mark Contents - (void)addContent:(CKTransferRecord *)record { NSParameterAssert(record); NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:[_contents count]]; [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@"contents"]; {{ [_contents addObject:record]; [record setParent:self]; }} [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@"contents"]; } - (NSArray *)contents { return [[_contents copy] autorelease]; } @synthesize contentsAreComplete = _contentsComplete; - (void)markContentsAsComplete; { self.contentsAreComplete = YES; [self.contents makeObjectsPerformSelector:_cmd]; // recurse down } #pragma mark - (void)appendToDescription:(NSMutableString *)str indentation:(unsigned)indent { NSInteger i; for (i = 0; i < indent; i++) { [str appendString:@"\t"]; } [str appendFormat:@"\t%@", _name]; if ([self isDirectory]) { [str appendString:@"/"]; } [str appendFormat:@"\t(%lld of %lld bytes - %li%%)\n", [self transferred], [self size], (long) [self progress]]; NSEnumerator *e = [[self contents] objectEnumerator]; CKTransferRecord *cur; while ((cur = [e nextObject])) { [cur appendToDescription:str indentation:indent+1]; } } - (NSString *)description { NSMutableString *str = [NSMutableString stringWithString:@"\n"]; [self appendToDescription:str indentation:0]; return str; } - (void)setProperty:(id)property forKey:(NSString *)key { [_properties setObject:property forKey:key]; } - (id)propertyForKey:(NSString *)key { return [_properties objectForKey:key]; } // keep NSDictionary accessor compatible so we can move over internal use of this class - (void)setObject:(id)object forKey:(id)key { [self setProperty:object forKey:key]; } - (id)objectForKey:(id)key { return [self propertyForKey:key]; } #pragma mark - #pragma mark Connection Transfer Delegate - (void)transferDidBegin:(CKTransferRecord *)transfer { _lastTransferTime = [NSDate timeIntervalSinceReferenceDate]; [[NSNotificationCenter defaultCenter] postNotificationName:CKTransferRecordTransferDidBeginNotification object:self]; } - (void)transfer:(CKTransferRecord *)transfer transferredDataOfLength:(unsigned long long)length { NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; NSTimeInterval difference = now - _lastTransferTime; if (difference > 2.0 || self.transferred == self.size) { [self willChangeValueForKey:@"speed"]; if (self.transferred == self.size) { [self setSpeed:0.0]; } else { [self setSpeed:((double)self.transferred) / difference]; } _lastTransferTime = now; [self didChangeValueForKey:@"speed"]; } } - (void)transfer:(CKTransferRecord *)transfer receivedError:(NSError *)error { //If we get _any_ error while we're uploading, we're "finished" albeit with an error. Handle it as such. [self transferDidFinish:transfer error:error]; } - (void)transferDidFinish:(CKTransferRecord *)transfer error:(NSError *)error { _lastTransferTime = [NSDate timeIntervalSinceReferenceDate]; [[NSNotificationCenter defaultCenter] postNotificationName:CKTransferRecordTransferDidFinishNotification object:self]; //If parent is finished, they need notifications too. CKTransferRecord *parent = [self parent]; if (parent && [parent transferred] == [parent size]) [parent transferDidFinish:parent error:error]; } #pragma mark - #pragma mark Recursive File Transfer Methods + (CKTransferRecord *)rootRecordWithPath:(NSString *)path { CKTransferRecord *result = [CKTransferRecord recordWithName:@"" uploadOperation:nil]; NSArray *pathComponents = [path pathComponents]; if ([pathComponents count] > 0) { [result setName:[[path pathComponents] objectAtIndex:0]]; // -firstPathComponent ignores the root dir for absolute paths CKTransferRecord *thisNode, *subNode = result; for (NSUInteger i = 1; i < [pathComponents count]; i++) { thisNode = [CKTransferRecord recordWithName:[pathComponents objectAtIndex:i] uploadOperation:nil]; [subNode addContent:thisNode]; subNode = thisNode; } } return result; } - (CKTransferRecord *)recordForPath:(NSString *)path; { NSParameterAssert(path); if ([path length] == 0) return self; if ([path isAbsolutePath]) { path = [path substringFromIndex:1]; // should make it relative, if not we'll go round again return [[self root] recordForPath:path]; } NSArray *components = [path pathComponents]; NSString *name = [components objectAtIndex:0]; for (CKTransferRecord *aRecord in [self contents]) { if ([[aRecord name] isEqualToString:name]) { NSString *newPath = [NSString pathWithComponents: [components subarrayWithRange:NSMakeRange(1, [components count]-1)]]; return [aRecord recordForPath:newPath]; } } return nil; } #pragma mark - #pragma mark NSTreeController support - (BOOL)isLeaf { return [_contents count] == 0; } - (NSDictionary *)nameWithProgress { return [NSDictionary dictionaryWithObjectsAndKeys: @(self.progress), @"progress", self.name, @"name", @(self.isFinished && !self.error), @"finished", self.error, @"error", nil]; } + (NSSet *)keyPathsForValuesAffectingNameWithProgress; { return [NSSet setWithObject:@"progress"]; } - (void)setNameWithProgress:(id)notused { ; // just for KVO bindings } /* The same as -nameWithProgress, but also includes the file size in brackets if appropriate */ - (NSDictionary *)nameWithProgressAndFileSize { NSDictionary *result = [self nameWithProgress]; // Directories should not display their size info if (self.uploadOperation && [[self contents] count] == 0) { // Calculate the size of the transfer in a user-friendly manner NSString *fileSize = [self.class formattedFileSize:(double)[self size]]; NSString *unattributedDescription = [[NSString alloc] initWithFormat:@"%@ (%@)", [self name], fileSize]; NSDictionary *attributes = [NSDictionary dictionaryWithObject:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]] forKey:NSFontAttributeName]; NSMutableAttributedString *description = [[NSMutableAttributedString alloc] initWithString:unattributedDescription attributes:attributes]; [unattributedDescription release]; // Make the size info in grey [description addAttribute:NSForegroundColorAttributeName value:[NSColor grayColor] range:NSMakeRange([[self name] length] + 1, [fileSize length] + 2)]; NSMutableDictionary *mutable = [result mutableCopy]; [mutable setObject:description forKey:@"name"]; result = [mutable autorelease]; [description release]; } return result; } + (NSSet *)keyPathsForValuesAffectingNameWithProgressAndFileSize { return [NSSet setWithObjects:@"progress", @"name", @"size", nil]; } + (NSString *)formattedFileSize:(double)size { if (size == 0) return [NSString stringWithFormat:@"0 %@", LocalizedStringInConnectionKitBundle(@"bytes", @"filesize: bytes")]; NSString *suffix[] = { LocalizedStringInConnectionKitBundle(@"bytes", @"filesize: bytes"), LocalizedStringInConnectionKitBundle(@"KB", @"filesize: kilobytes"), LocalizedStringInConnectionKitBundle(@"MB", @"filesize: megabytes"), LocalizedStringInConnectionKitBundle(@"GB", @"filesize: gigabytes"), LocalizedStringInConnectionKitBundle(@"TB", @"filesize: terabytes"), LocalizedStringInConnectionKitBundle(@"PB", @"filesize: petabytes"), LocalizedStringInConnectionKitBundle(@"EB", @"filesize: exabytes") }; int power = floor(log(size) / log(1024)); if (power > 1) { return [NSString stringWithFormat:@"%01.02lf %@", size / pow(1024, power), suffix[power]]; } else { return [NSString stringWithFormat:@"%01.0lf %@", size / pow(1024, power), suffix[power]]; } } @end ================================================ FILE: ConnectionKit/CKUploader.h ================================================ // // CKUploader.h // Connection // // Created by Mike Abdullah on 14/11/2011. // Copyright (c) 2011 Karelia Software. All rights reserved. // #import #import "CK2FileManager.h" #import "CKTransferRecord.h" enum { CKUploadingDeleteExistingFileFirst = 1 << 0, CKUploadingDryRun = 1 << 1, /** Normally we rely on servers to respect the opening permissions we ask for. But some servers choose not to (e.g. a handful of SFTP setups). Or some server types (FTP, mainly) have no concept of opening permissions, so just do whatever is the OS default. In those cases, we need to followup an upload with setting permissions explicitly. `CKUploadingSetFilePermissionsAfterWriting` does just that. */ CKUploadingSetFilePermissionsAfterWriting = 1 << 2, }; typedef NSUInteger CKUploadingOptions; @protocol CKUploaderDelegate; @interface CKUploader : NSObject { @private NSURLRequest *_request; CKUploadingOptions _options; CK2FileManager *_fileManager; NSMutableArray *_queue; NSMutableDictionary *_recordsByOperation; CKTransferRecord *_rootRecord; CKTransferRecord *_baseRecord; BOOL _invalidated; BOOL _suspended; id _delegate; } /** File permissions are supplied by curl_curl_newFilePermissions. Supply a non-`nil` value if you want something different, or override `-posixPermissionsForPath:isDirectory:` */ + (CKUploader *)uploaderWithRequest:(NSURLRequest *)request options:(CKUploadingOptions)options delegate:(id )delegate; @property (nonatomic, copy, readonly) NSURLRequest *baseRequest; @property (nonatomic, assign, readonly) CKUploadingOptions options; @property (nonatomic, retain, readonly) id delegate; // retained until invalidated /** @param url Must not contain any `.` or `..` path components; the uploader will choke on those at present. */ - (CKTransferRecord *)uploadToURL:(NSURL *)url fromFile:(NSURL *)fileURL; /** @param url Must not contain any `.` or `..` path components; the uploader will choke on those at present. */ - (CKTransferRecord *)uploadToURL:(NSURL *)url fromData:(NSData *)data; - (void)removeItemAtURL:(NSURL *)url completionHandler:(void (^)(NSError *))handler __attribute((nonnull(1))); /** The underlying `CK2FileOperation`s that are in the queue. */ - (NSArray *)operations; @property (nonatomic, retain, readonly) CKTransferRecord *rootTransferRecord; @property (nonatomic, retain, readonly) CKTransferRecord *baseTransferRecord; - (void)finishOperationsAndInvalidate; // will disconnect once all files are uploaded - (void)invalidateAndCancel; // bails out as quickly as possible #pragma mark Suspending Operations @property (nonatomic, getter=isSuspended) BOOL suspended; #pragma mark Permissions // The permissions given to uploaded files - (NSNumber *)posixPermissionsForPath:(NSString *)path isDirectory:(BOOL)directory; @end @protocol CKUploaderDelegate - (void)uploader:(CKUploader *)uploader didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential *))completionHandler; @optional - (void)uploader:(CKUploader *)uploader didAddTransferRecord:(CKTransferRecord *)record; @required - (void)uploader:(CKUploader *)uploader didBeginUploadToPath:(NSString *)path; - (void)uploader:(CKUploader *)uploader appendString:(NSString *)string toTranscript:(CK2TranscriptType)transcript; @optional - (void)uploader:(CKUploader *)uploader didBeginRemovingItemAtURL:(NSURL *)url; - (void)uploader:(CKUploader *)uploader transferRecord:(CKTransferRecord *)record didWriteBodyData:(int64_t)bytesSent totalBytesWritten:(int64_t)totalBytesSent totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToSend; - (void)uploader:(CKUploader *)uploader transferRecord:(CKTransferRecord *)record didCompleteWithError:(NSError *)error; - (void)uploaderDidBecomeInvalid:(CKUploader *)uploader; - (void)uploader:(CKUploader *)uploader didFailWithError:(NSError *)error; // never called any more @end ================================================ FILE: ConnectionKit/CKUploader.m ================================================ // // CKUploader.m // Connection // // Created by Mike Abdullah on 14/11/2011. // Copyright (c) 2011 Karelia Software. All rights reserved. // #import "CKUploader.h" #import "CK2FileOperation.h" #import @implementation CKUploader #pragma mark Lifecycle - (id)initWithRequest:(NSURLRequest *)request options:(CKUploadingOptions)options delegate:(id)delegate; { if (self = [self init]) { _request = [request copy]; _options = options; _delegate = [delegate retain]; _suspended = NO; if (!(_options & CKUploadingDryRun)) { // Marshall all callbacks onto main queue, primarily for historical reasons, but also serialisation _fileManager = [CK2FileManager fileManagerWithDelegate:self delegateQueue:[NSOperationQueue mainQueue]]; [_fileManager retain]; } // Keep alive until invalidated. Hopefully one day CK2FileManager will do this for us. [self retain]; _queue = [[NSMutableArray alloc] init]; _recordsByOperation = [[NSMutableDictionary alloc] init]; _rootRecord = [[CKTransferRecord rootRecordWithPath:[[request URL] path]] retain]; _baseRecord = [_rootRecord retain]; } return self; } + (CKUploader *)uploaderWithRequest:(NSURLRequest *)request options:(CKUploadingOptions)options delegate:(id)delegate; { NSParameterAssert(request); return [[[self alloc] initWithRequest:request options:options delegate:delegate] autorelease]; } - (void)didBecomeInvalid; { id delegate = self.delegate; if ([delegate respondsToSelector:@selector(uploaderDidBecomeInvalid:)]) { [self.delegate uploaderDidBecomeInvalid:self]; } [_delegate release]; _delegate = nil; [self release]; // balance out the retain during -init… } - (void)dealloc { NSAssert(_queue.count == 0, @"%@ is being deallocated while there are still queued operations", self); [_fileManager setDelegate:nil]; [_request release]; [_fileManager release]; [_rootRecord release]; [_baseRecord release]; [_recordsByOperation release]; [super dealloc]; } #pragma mark Properties @synthesize delegate = _delegate; @synthesize baseRequest = _request; @synthesize options = _options; @synthesize rootTransferRecord = _rootRecord; @synthesize baseTransferRecord = _baseRecord; - (NSNumber *)posixPermissionsForPath:(NSString *)path isDirectory:(BOOL)directory; { NSNumber *result = (directory ? self.baseRequest.curl_newDirectoryPermissions : self.baseRequest.curl_newFilePermissions); return result; } #pragma mark Publishing - (void)removeItemAtURL:(NSURL *)url completionHandler:(void (^)(NSError *))handler; { [self removeItemAtURL:url transferRecord:nil completionHandler:handler]; } - (void)removeItemAtURL:(NSURL *)url transferRecord:(CKTransferRecord *)record completionHandler:(void (^)(NSError *))handler; { CK2FileOperation *op = [_fileManager removeOperationWithURL:url completionHandler:handler]; [self addOperation:op transferRecord:record]; } - (CKTransferRecord *)uploadToURL:(NSURL *)url fromData:(NSData *)data; { NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: [self posixPermissionsForPath:[_fileManager.class pathOfURL:url] isDirectory:NO], NSFilePosixPermissions, nil]; CK2FileOperation *op = [_fileManager createFileOperationWithURL:url fromData:data withIntermediateDirectories:YES openingAttributes:attributes completionHandler:NULL]; return [self uploadUsingOperation:op attributes:attributes]; } - (CKTransferRecord *)uploadToURL:(NSURL *)url fromFile:(NSURL *)fileURL; { NSNumber *size; if (![fileURL getResourceValue:&size forKey:NSURLFileSizeKey error:NULL]) size = nil; NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: [self posixPermissionsForPath:[_fileManager.class pathOfURL:url] isDirectory:NO], NSFilePosixPermissions, nil]; CK2FileOperation *op = [_fileManager createFileOperationWithURL:url fromFile:fileURL withIntermediateDirectories:YES openingAttributes:attributes completionHandler:NULL]; return [self uploadUsingOperation:op attributes:attributes]; } static void *sOperationStateObservationContext = &sOperationStateObservationContext; - (CKTransferRecord *)uploadUsingOperation:(CK2FileOperation *)operation attributes:(NSDictionary *)attributes; { NSParameterAssert(operation); // Create transfer record CKTransferRecord *result = [self makeTransferRecordForOperation:operation]; // Delete first if requested if (_options & CKUploadingDeleteExistingFileFirst) { [self removeItemAtURL:operation.originalURL transferRecord:nil // don't want failure to be reported completionHandler:NULL]; } // Enqueue upload [self addOperation:operation transferRecord:result]; // Notify delegate [self didAddTransferRecord:result]; // Set permissions after if requested if (self.options & CKUploadingSetFilePermissionsAfterWriting) { CK2FileOperation *op = [_fileManager setAttributesOperationWithURL:operation.originalURL attributes:attributes completionHandler:NULL]; // nothing really to do; don't care if fails [self addOperation:op transferRecord:nil]; // ignore failures } return result; } - (void)didAddTransferRecord:(CKTransferRecord *)record; { id delegate = self.delegate; if ([delegate respondsToSelector:@selector(uploader:didAddTransferRecord:)]) { [delegate uploader:self didAddTransferRecord:record]; } } - (void)finishOperationsAndInvalidate; { if (_invalidated) return; _invalidated = YES; // Now transfer records have all their content [self.rootTransferRecord markContentsAsComplete]; if (!_queue.count) { // Slightly delay delivery so it's similar to if there were operations // in the queue [_fileManager.delegateQueue addOperationWithBlock:^{ [self didBecomeInvalid]; }]; } } - (void)invalidateAndCancel; { [self.operations makeObjectsPerformSelector:@selector(cancel)]; [self finishOperationsAndInvalidate]; } #pragma mark Queue - (NSArray *)operations; { return [[_queue copy] autorelease]; } - (CK2FileOperation *)currentOperation; { return [_queue firstObject]; } - (void)addOperation:(CK2FileOperation *)operation transferRecord:(CKTransferRecord *)record; { NSAssert([NSThread isMainThread], @"-addOperation: is only safe to call on the main thread"); // No more operations can go on once finishing up if (_invalidated) [NSException raise:NSInvalidArgumentException format:@"%@ has been invalidated", self]; // Note the transfer record this op corresponds to if (record) [_recordsByOperation setObject:record forKey:operation]; // Watch for it to complete [operation addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew context:sOperationStateObservationContext]; // Add to the queue [_queue addObject:operation]; if (_queue.count == 1) { [self startNextOperationIfNotSuspended]; } } - (void)removeOperationAndStartNextIfAppropriate:(CK2FileOperation *)operation; { NSParameterAssert(operation); NSAssert([NSThread isMainThread], @"-%@ is only safe to call on the main thread", NSStringFromSelector(_cmd)); // We assume the operation is only in the queue the once, and most likely near the front NSUInteger index = [_queue indexOfObject:operation]; if (index != NSNotFound) [_queue removeObjectAtIndex:index]; // If was the current op, time to start the next if (index == 0) [self startNextOperationIfNotSuspended]; } - (void)startNextOperationIfNotSuspended; { if (self.suspended) return; if (_queue.count) { // We don't actually know what state the operation is in at this point. Normally, it should // be suspended, waiting for us to start it. Ideally, nothing outside of CKUploader should // start the operation itself (similar contract as to NSOperationQueue). // But if operations are being bulk-canceled (e.g. `invalidateAndCancel`), many of them may // have moved on from the suspended state to be cancelling, or completed. We trust that // we'll receive a KVO notification for each operation as it finishes cancelling (i.e. completes) // and remove it from the queue in due course, until there's nothing left and we become // invalid. CK2FileOperation *operation = [_queue objectAtIndex:0]; [operation resume]; } else if (_invalidated) { [self didBecomeInvalid]; } } - (void)operation:(CK2FileOperation *)operation didFinish:(NSError *)error; { NSAssert([NSThread isMainThread], @"Operation broke threading contract"); NSParameterAssert(operation); // Tell the record & delegate it's finished CKTransferRecord *record = [_recordsByOperation objectForKey:operation]; [record transferDidFinish:record error:error]; id delegate = self.delegate; if (record && [delegate respondsToSelector:@selector(uploader:transferRecord:didCompleteWithError:)]) { [delegate uploader:self transferRecord:record didCompleteWithError:error]; } [self removeOperationAndStartNextIfAppropriate:operation]; } #pragma mark Transfer Records - (CKTransferRecord *)makeTransferRecordForOperation:(CK2FileOperation *)operation; { NSURL *url = operation.originalURL; NSString *path = [CK2FileManager pathOfURL:url]; CKTransferRecord *result = [CKTransferRecord recordWithName:path.lastPathComponent uploadOperation:operation]; #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7 NSURL *parentURL = [url URLByDeletingLastPathComponent]; #else // 10.6's -URLByDeletingLastPathComponent can't handle double slashes should it encounter them NSURL *parentURL = [CK2FileManager URLWithPath:[path stringByDeletingLastPathComponent] isDirectory:YES hostURL:url]; #endif CKTransferRecord *parent = [self directoryTransferRecordWithURL:parentURL]; [parent addContent:result]; return result; } - (CKTransferRecord *)directoryTransferRecordWithURL:(NSURL *)url; { NSParameterAssert(url); NSAssert([NSThread isMainThread], @"CKUploader can only be used on main thread"); NSString *path = [CK2FileManager pathOfURL:url]; if ([path isEqualToString:@"/"] || path.length == 0) // the root for absolute and relative paths { return [self rootTransferRecord]; } // Recursively find a record we do have! NSURL *parentDirectoryURL = [url URLByDeletingLastPathComponent]; // need a sanity check to hunt down URLs being passed in like ftp://example.com//./ NSAssert(parentDirectoryURL.absoluteString.length < url.absoluteString.length, @"URLByDeletingLastPathComponent for %@ gives a longer result: %@", url.absoluteString, parentDirectoryURL.absoluteString); CKTransferRecord *parent = [self directoryTransferRecordWithURL:parentDirectoryURL]; // Create the record if it hasn't been already CKTransferRecord *result = nil; for (CKTransferRecord *aRecord in [parent contents]) { if ([[aRecord name] isEqualToString:[path lastPathComponent]]) { result = aRecord; break; } } if (!result) { result = [CKTransferRecord recordWithName:[path lastPathComponent] uploadOperation:nil]; [parent addContent:result]; [self didAddTransferRecord:result]; } return result; } #pragma mark Suspending Operations @synthesize suspended = _suspended; - (void)setSuspended:(BOOL)suspended; { if (suspended == _suspended) return; _suspended = suspended; if (!suspended) { CK2FileOperation *firstOp = _queue.firstObject; if (firstOp.state == CK2FileOperationStateSuspended) { [self startNextOperationIfNotSuspended]; } } } #pragma mark KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; { if (context == sOperationStateObservationContext) { CK2FileOperation *op = object; CK2FileOperationState state = [[change objectForKey:NSKeyValueChangeNewKey] integerValue]; if (state == CK2FileOperationStateCompleted) { [op removeObserver:self forKeyPath:keyPath]; [self operation:op didFinish:op.error]; } else if (state == CK2FileOperationStateRunning) { NSAssert([NSThread isMainThread], @"Only want to notify delegate on the main thread"); CKTransferRecord *record = [_recordsByOperation objectForKey:op]; [record transferDidBegin:record]; if (record) { [self.delegate uploader:self didBeginUploadToPath:record.path]; } else { // Assume record-less operations are for deleting items id delegate = self.delegate; if ([delegate respondsToSelector:@selector(uploader:didBeginRemovingItemAtURL:)]) { [delegate uploader:self didBeginRemovingItemAtURL:op.originalURL]; } } } } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } #pragma mark CK2FileManager Delegate - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLRequest *))completionHandler; { // Apply any customisations // Only allow SSL security to be *up*graded NSURLRequest *base = self.baseRequest; NSMutableURLRequest *customized = [request mutableCopy]; [base.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString *aField, NSString *aValue, BOOL *stop) { if (![customized valueForHTTPHeaderField:aField]) { [customized setValue:aValue forHTTPHeaderField:aField]; } }]; curl_usessl level = base.curl_desiredSSLLevel; if (level > customized.curl_desiredSSLLevel) [customized curl_setDesiredSSLLevel:level]; completionHandler(customized); [customized release]; } - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential *))completionHandler; { // Hand off to the delegate for auth id delegate = [self delegate]; if (delegate) { [delegate uploader:self didReceiveChallenge:challenge completionHandler:completionHandler]; } else { completionHandler(CK2AuthChallengePerformDefaultHandling, nil); } } - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation didWriteBodyData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesSent totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToSend; { CKTransferRecord *record = [_recordsByOperation objectForKey:operation]; NSAssert(record, @"Unknown operation"); [record transfer:record transferredDataOfLength:bytesWritten]; if ([self.delegate respondsToSelector:@selector(uploader:transferRecord:didWriteBodyData:totalBytesWritten:totalBytesExpectedToWrite:)]) { [self.delegate uploader:self transferRecord:record didWriteBodyData:bytesWritten totalBytesWritten:totalBytesSent totalBytesExpectedToWrite:totalBytesExpectedToSend]; } } - (void)fileManager:(CK2FileManager *)manager appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; { [[self delegate] uploader:self appendString:info toTranscript:transcript]; } @end ================================================ FILE: ConnectionKit/ConnectionKit.h ================================================ /* Copyright (c) 2006, Greg Hulands All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Greg Hulands nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import #import #import // Legacy #import #import #import ================================================ FILE: ConnectionKit/ConnectionTest.h ================================================ // // ConnectionTest.h // Marvel // // Created by Dan Wood on 11/29/04. // Copyright (c) 2004 Biophony, LLC. All rights reserved. // #import @interface ConnectionTest : NSObject { NSMutableDictionary *myCallbackDictionary; unsigned long myUniqueNumber; NSThread *myMainThread; // not retained. Just for diagnostics. NSString *myCurrentDirectory; } - (NSMutableDictionary *)callbackDictionary; - (void)setCallbackDictionary:(NSMutableDictionary *)aCallbackDictionary; - (NSString *)currentDirectory; - (void)setCurrentDirectory:(NSString *)aCurrentDirectory; @end ================================================ FILE: ConnectionKit/ConnectionTest.m ================================================ // // ConnectionTest.m // Marvel // // Created by Dan Wood on 11/29/04. // Copyright (c) 2004 Biophony, LLC. All rights reserved. // #import "ConnectionTest.h" #import "FTPConnection.h" #import "FileConnection.h" /* AVAILABLE MACROS UKPass() Pass always UKFail() Fail always UKTrue(condition) Pass if condition is true UKFalse(condition) Pass if condition is false UKNil(ref) Pass if ref is nil UKNotNil(ref) Pass if ref is not nil UKIntsEqual(a, b) Pass if a == b UKIntsNotEqual(a, b) Pass if a != b UKFloatsEqual(a, b, d) Pass if a == b UKFloatsNotEqual(a, b, d) Pass if a != b UKObjectsEqual(a, b) Pass if a isEqualTo: b UKObjectsNotEqual(a, b) Pass if NOT a isEqualTo: b UKObjectsSame(a, b) Pass if a (address) == b (address) UKObjectsNotSame(a, b) Pass if a (address) != b (address) UKStringsEqual(a, b) Pass if a isEqualToString: b UKStringsNotEqual(a, b) Pass if a NOT isEqualToString: b UKStringContains(a, b) Pass if a contains b UKStringDoesNotContain(a, b) Pass if a does NOT contain b UKRaisesException(exp) Pass if exp raises an exception UKDoesNotRaiseException(exp) Pass if exp does NOT raise an exception UKRaisesExceptionNamed(exp, b) Pass if exp raises an exception named b UKRaisesExceptionClass(exp, b) Pass if exp does NOT raise an exception named b */ @implementation ConnectionTest - (id)init { if (self = [super init]) { myUniqueNumber = (unsigned long) [NSDate timeIntervalSinceReferenceDate]; [self setCallbackDictionary:[NSMutableDictionary dictionary]]; } return self; } - (void)dealloc { [self setCallbackDictionary:nil]; [super dealloc]; } - (NSString *)currentDirectory { return myCurrentDirectory; } - (void)setCurrentDirectory:(NSString *)aCurrentDirectory { [aCurrentDirectory retain]; [myCurrentDirectory release]; myCurrentDirectory = aCurrentDirectory; } /* For all the connection methods, run a standard suite of tests. Test the following methods: (Need to hook up a delegate to self, somehow put it into a state of what is expected, so we'll know if it's called. If we expect it to be called and it's not, we can have it set an ivar and we'll check.) What will be tricky will be that this is async. We may need to queue up a bunch of operations, and then wait somehow until they have completed. Maybe make a runloop? Making connection changing to directory (good/bad) getting directory (as expected?) making directory (good/bad) making directory with certain permissions setting permissions (try a couple of times to make sure they actually change) (success/fail) rename delete delete directory upload file (some known file, like /etc/hosts, or make a new /tmp file with UUID) .... to directory resume upload file at offset (supported for all?) download file resumeDownload file cancel transfer get directory contents */ #pragma mark - #pragma mark Callbacks /*! These callbacks work by adding their method name to the dictionary so we can tell what has been run. Temporarily scrunched to one line per method, to make it easy to count and sort. */ - (void)connection:(id )con didChangeToDirectory:(NSString *)dirPath { [self setCurrentDirectory:dirPath]; NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:dirPath forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didConnectToHost:(NSString *)host { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:host forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didCreateDirectory:(NSString *)dirPath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:dirPath forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didDeleteDirectory:(NSString *)dirPath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:dirPath forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didDeleteFile:(NSString *)path { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:path forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didDisconnectFromHost:(NSString *)host { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:host forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didReceiveContents:(NSArray *)contents ofDirectory:(NSString *)dirPath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:contents forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didReceiveError:(NSError *)error { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:error forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didRename:(NSString *)fromPath to:(NSString *)toPath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSString stringWithFormat:@"%@ -> %@",fromPath,toPath] forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didSetPermissionsForFile:(NSString *)path { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:path forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con download:(NSString *)path progressedTo:(NSNumber *)percent { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSString stringWithFormat:@"%@: %@%%",path,percent] forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con download:(NSString *)path receivedDataOfLength:(int)length { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSString stringWithFormat:@"%@: %d bytes",path,length] forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con downloadDidBegin:(NSString *)remotePath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:remotePath forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con downloadDidFinish:(NSString *)remotePath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:remotePath forKey:NSStringFromSelector(_cmd)]; } - (NSString *)connection:(id )con needsAccountForUsername:(NSString *)username { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:username forKey:NSStringFromSelector(_cmd)]; return @"foo"; } - (void)connection:(id )con upload:(NSString *)remotePath progressedTo:(NSNumber *)percent { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSString stringWithFormat:@"%@: %@%%",remotePath,percent] forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con upload:(NSString *)remotePath sentDataOfLength:(int)length { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSString stringWithFormat:@"%@: %d bytes",remotePath,length] forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con uploadDidBegin:(NSString *)remotePath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:remotePath forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con uploadDidFinish:(NSString *)remotePath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:remotePath forKey:NSStringFromSelector(_cmd)]; } - (void)connectionDidCancelTransfer:(id )con { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSNumber numberWithBool:YES] forKey:NSStringFromSelector(_cmd)]; } - (void)connectionDidSendBadPassword:(id )con { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSNumber numberWithBool:YES] forKey:NSStringFromSelector(_cmd)]; } #pragma mark - #pragma mark Main Test Suite /*! Do a full test suite */ - (void)runTestSuiteWithConnection:(id )aConn expectingFailure:(BOOL) inExpectConnectionFailure { [aConn setDelegate:self]; BOOL done = NO; NSString *initialDirectory = nil; NSDate *dropDeadDate = [NSDate dateWithTimeIntervalSinceNow:120.0]; [aConn connect]; while (!done && NSOrderedAscending == [((NSDate *)[NSDate date]) compare:(NSDate *)dropDeadDate]) { if ([((NSObject *)aConn) isKindOfClass:[FTPConnection class]]) { NSLog(@"========= TEST: top of loop, state = %d, queue= %@", [((FTPConnection *)aConn) state], [((FTPConnection *)aConn) queueDescription]); } if (0 == [myCallbackDictionary count]) // We don't have any callbacks from the previous iteration { NSLog(@"========= TEST: will run NSRunLoop"); BOOL found = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:30.0]]; //[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:20.0]]; //[[NSRunLoop currentRunLoop] run]; NSLog(@"========= TEST: did run NSRunLoop, found = %d dict = %@", found, [[myCallbackDictionary allKeys] description]); if (!found) { done = true; // nothing found after waiting this long, so we're done. break; } } id value; NSDictionary *lastCallbackDictionary = [[myCallbackDictionary copy] autorelease]; [myCallbackDictionary removeAllObjects]; // clean out the original dict for the next pass if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:didReceiveError:"]) ) { NSLog(@"========= TEST: Error message: %@", value); UKTrue (inExpectConnectionFailure); // pass if we did expect an error! done = YES; // we're done anyhow. } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connectionDidSendBadPassword:"]) ) { UKTrue (inExpectConnectionFailure); // pass if we did expect an error! done = YES; // we're done anyhow. } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:didConnectToHost:"]) ) { NSLog(@"========= TEST: Connected to host: %@", value); UKFalse (inExpectConnectionFailure); // pass if we did not expect an error // NEXT TASK: MAKE A NEW DIRECTORY [aConn createDirectory:[NSString stringWithFormat:@"d%ld", myUniqueNumber]]; } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:didCreateDirectory:"]) ) { NSLog(@"========= TEST: Created directory called %@", [NSString stringWithFormat:@"d%ld", myUniqueNumber]); UKPass(); // Got our directory // NEXT TASK: Get the contents of the current directory [aConn directoryContents]; } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:didReceiveContents:ofDirectory:"]) ) { NSLog(@"========= TEST: Got directory contents %@", [value description]); UKPass(); // Got our directory ... REALY OUGHT TO VERIFY THAT OUR FILE IS IN THERE. // NEXT TASK: Change to that new directory [aConn changeToDirectory:[NSString stringWithFormat:@"d%ld", myUniqueNumber]]; } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:didChangeToDirectory:"]) ) { if (nil == initialDirectory) { initialDirectory = [self currentDirectory]; // this will depend on what connection we have UKPass(); // Got our directory ... REALY OUGHT TO VERIFY THAT OUR FILE IS IN THERE. } else // second change; make sure we are within initial directory { NSString *expectedNewDirectory = [initialDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"d%ld", myUniqueNumber]]; UKStringsEqual([self currentDirectory], expectedNewDirectory); } NSLog(@"========= TEST: Changed to directory %@", [value description]); // NEXT TASK: Upload a file [aConn uploadFile:@"/etc/hosts"]; } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:uploadDidFinish:"]) ) { NSLog(@"========= TEST: Done uploading %@", [value description]); UKPass(); // Got our directory ... REALY OUGHT TO VERIFY THAT OUR FILE IS IN THERE. done = YES; // done for now ... we don't have any more tests. } /* changing to directory (good/bad) getting directory (as expected?) making directory (good/bad) making directory with certain permissions setting permissions (try a couple of times to make sure they actually change) (success/fail) rename delete delete directory upload file (some known file, like /etc/hosts, or make a new /tmp file with UUID) .... to directory resume upload file at offset (supported for all?) download file resumeDownload file cancel transfer get directory contents */ } [aConn disconnect]; NSLog(@"========= TEST: Disconnected."); if (! (NSOrderedAscending == [((NSDate *)[NSDate date]) compare:dropDeadDate]) ) { UKFail(); // Ran out of time! } } #pragma mark - #pragma mark File tests /*! Test a file connection. Since there is no connection, we can't test for a bad file connection */ - (void)testFileConnection { FileConnection *fc = [FileConnection connection]; [self runTestSuiteWithConnection:fc expectingFailure:NO]; } #pragma mark - #pragma mark FTP tests - (void)testRealFTPConnection { NSDictionary *environment = [[NSProcessInfo processInfo] environment]; NSString *password = [environment objectForKey:@"biophonyTestPassword"]; FTPConnection *c = [FTPConnection connectionToHost:@"test.biophony.com" port:nil username:@"test" password:password]; [self runTestSuiteWithConnection:c expectingFailure:NO]; } - (void) testQuotesScan { FTPConnection *c = [FTPConnection connectionToHost:@"bogus" port:nil username:@"test" password:@"nothing"]; UKNil([c scanBetweenQuotes:@"hey there we're the monkeys"]); UKNil([c scanBetweenQuotes:@"This has one \" quote mark"]); UKStringsEqual([c scanBetweenQuotes:@"empty \"\" quoted string"], @""); UKStringsEqual([c scanBetweenQuotes:@"257 \"/somethingee\" created"], @"/somethingee"); UKStringsEqual([c scanBetweenQuotes:@"257 \"/he said \"\"yo\"\" to me\" created"], @"/he said \"yo\" to me"); } /* - (void)testBadAccountFTPConnection { FTPConnection *c = [FTPConnection connectionToHost:@"test.biophony.com" port:nil username:@"somebodyelse" password:@"mypassword"]; [self runTestSuiteWithConnection:c expectingFailure:YES]; } - (void)testConnectionRefusedFTPConnection { FTPConnection *c = [FTPConnection connectionToHost:@"vorlon.karelia.com" port:nil username:@"somebodyelse" password:@"mypassword"]; [self runTestSuiteWithConnection:c expectingFailure:YES]; } - (void)testHangingFTPConnection { FTPConnection *c = [FTPConnection connectionToHost:@"ftp.zocalo.net" port:nil username:@"somebodyelse" password:@"mypassword"]; [self runTestSuiteWithConnection:c expectingFailure:YES]; } - (void)testNoHostFTPConnection { FTPConnection *c = [FTPConnection connectionToHost:@"fdjskalrejwfje.com" port:nil username:@"somebodyelse" password:@"mypassword"]; [self runTestSuiteWithConnection:c expectingFailure:YES]; } */ #pragma mark - #pragma mark Private Support - (NSMutableDictionary *)callbackDictionary { return myCallbackDictionary; } - (void)setCallbackDictionary:(NSMutableDictionary *)aCallbackDictionary { [aCallbackDictionary retain]; [myCallbackDictionary release]; myCallbackDictionary = aCallbackDictionary; } @end ================================================ FILE: ConnectionKit/Connection_Prefix.pch ================================================ #define LocalizedStringInConnectionKitBundle(key, comment) \ [[NSBundle bundleForClass:[self class]] localizedStringForKey:(key) value:@"" table:nil] #ifndef CK2WebDAVLog #define CK2WebDAVLog(...) #endif ================================================ FILE: ConnectionKit/NSImage+CK2OpenPanel.h ================================================ // // NSImage+NSImage_CK2OpenPanel.h // Connection // // Created by Paul Kim on 1/27/13. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import @interface NSImage (CK2OpenPanel) - (NSImage *)ck2_imageWithBadgeImage:(NSImage *)image; @end ================================================ FILE: ConnectionKit/NSImage+CK2OpenPanel.m ================================================ // // NSImage+NSImage_CK2OpenPanel.m // Connection // // Created by Paul Kim on 1/27/13. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "NSImage+CK2OpenPanel.h" @class CK2BlockImageRep; @interface CK2BlockImageRep : NSImageRep { void (^_drawBlock)(CK2BlockImageRep *); } @property (readwrite, copy) void (^drawBlock)(CK2BlockImageRep *rep); + (id)imageRepWithDrawBlock:(void (^)(CK2BlockImageRep *))block; - (id)initWithDrawBlock:(void (^)(CK2BlockImageRep *))block; @end @implementation CK2BlockImageRep @synthesize drawBlock = _drawBlock; + (id)imageRepWithDrawBlock:(void (^)(CK2BlockImageRep *))block { return [[[[self class] alloc] initWithDrawBlock:block] autorelease]; } - (id)initWithDrawBlock:(void (^)(CK2BlockImageRep *))block { if ((self = [super init]) != nil) { [self setDrawBlock:block]; } return self; } #pragma mark NSCopying method - (id)copyWithZone:(NSZone *)zone { CK2BlockImageRep *copy; copy = [super copyWithZone:zone]; // NSImageRep uses NSCopyObject so we have to force a copy here (which actually // just retains the object in this case). copy->_drawBlock = [_drawBlock copy]; return copy; } - (void)dealloc { [self setDrawBlock:nil]; [super dealloc]; } #pragma mark NSImageRep methods - (BOOL)draw { if (_drawBlock != NULL) { _drawBlock(self); return YES; } return NO; } @end @implementation NSImage (CK2OpenPanel) + (id)ck2_imageWithSize:(NSSize)size usingDrawBlock:(void (^)(CK2BlockImageRep *))block { return [[[[self class] alloc] initCK2WithSize:size usingDrawBlock:block] autorelease]; } // Using the CK2 prefix for -init methods confuses the static analyzer so it's buried within the method name here - (id)initCK2WithSize:(NSSize)size usingDrawBlock:(void (^)(CK2BlockImageRep *))block { if ((self = [self initWithSize:size]) != nil) { CK2BlockImageRep *rep; rep = [CK2BlockImageRep imageRepWithDrawBlock:block]; [rep setSize:size]; [self addRepresentation:rep]; } return self; } - (NSImage *)ck2_imageWithBadgeImage:(NSImage *)badgeImage { NSImage *image; image = [[self copy] autorelease]; return [NSImage ck2_imageWithSize:[self size] usingDrawBlock: ^(CK2BlockImageRep *blockRep) { NSRect rect; [image drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; rect.origin = NSZeroPoint; rect.size = [blockRep size]; [badgeImage drawInRect:rect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; }]; } @end ================================================ FILE: ConnectionKit/NSURL+CK2OpenPanel.h ================================================ // // NSURL+CKRemote.h // ConnectionKit // // Created by Paul Kim on 12/15/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import @interface NSURL (CK2OpenPanel) + (NSURL *)ck2_loadingURL; + (NSURL *)ck2_errorURL; + (NSURL *)ck2_errorURLWithMessage:(NSString *)message; /** @return A comparator to compare the last component of URLs alphabetically. */ + (NSComparator)ck2_displayComparator; - (BOOL)ck2_isPlaceholder; - (NSString *)ck2_displayName; - (NSImage *)ck2_icon; - (NSNumber *)ck2_size; - (NSDate *)ck2_dateModified; - (NSString *)ck2_kind; - (BOOL)ck2_isDirectory; - (BOOL)ck2_isPackage; - (BOOL)ck2_isHidden; - (NSURL *)ck2_root; - (NSURL *)ck2_parentURL; /** Will return YES if receiver and given url are the same. @return Whether the receiver represents an ancestory directory of the given url. */ - (BOOL)ck2_isAncestorOfURL:(NSURL *)url; - (NSURL *)ck2_URLByDeletingTrailingSlash; @end ================================================ FILE: ConnectionKit/NSURL+CK2OpenPanel.m ================================================ // // NSURL+CKRemote.m // ConnectionKit // // Created by Paul Kim on 12/15/12. // Copyright (c) 2012 Paul Kim. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list // of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // Neither the name of Karelia Software nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior // written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "NSURL+CK2OpenPanel.h" #import "CK2FileManager.h" #import "NSImage+CK2OpenPanel.h" #import @interface CK2PlaceholderURL : NSURL @end @implementation NSURL (CK2OpenPanel) + (NSURL *)ck2_loadingURL { return [[[CK2PlaceholderURL alloc] initWithString:[NSLocalizedStringFromTableInBundle(@"Loading…", nil, [NSBundle bundleForClass:[self class]], @"Loading placeholder")stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] autorelease]; } + (NSURL *)ck2_errorURL { return [[[CK2PlaceholderURL alloc] initWithString:[NSLocalizedStringFromTableInBundle(@"Error", nil, [NSBundle bundleForClass:[self class]], @"Error placedholer")stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] autorelease]; } + (NSURL *)ck2_errorURLWithMessage:(NSString *)message { return [[[CK2PlaceholderURL alloc] initWithString:[message stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] autorelease]; } + (NSComparator)ck2_displayComparator { return [[^NSComparisonResult(id obj1, id obj2) { return [[obj1 lastPathComponent] caseInsensitiveCompare:[obj2 lastPathComponent]]; } copy] autorelease]; } - (BOOL)ck2_isPlaceholder { return NO; } - (NSString *)ck2_displayName { id value; NSError *error; if ([self getResourceValue:&value forKey:NSURLLocalizedNameKey error:&error]) { return value; } else { NSLog(@"Error getting name for URL %@: %@", [self absoluteString], error); } return @""; } - (NSImage *)ck2_icon { id value; NSError *error; if ([self getResourceValue:&value forKey:NSURLEffectiveIconKey error:&error]) { NSImage *image; NSURL *actualURL; image = nil; actualURL = [self ck2_destinationURL]; if (value == nil) { // Value may not be set (i.e. we created this URL instead of having it returned from CK2FileManager). We // provide some default images in this case. if ([actualURL ck2_isDirectory] && ![actualURL ck2_isPackage]) { image = [NSImage imageNamed:NSImageNameFolder]; } else { image = [[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode(kGenericDocumentIcon)]; } } else if ([value isKindOfClass:[NSImage class]]) { image = value; } else { NSLog(@"Received unexpected type for icon: %@", [value class]); return nil; } if (![self isEqual:actualURL]) { image = [image ck2_imageWithBadgeImage:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAliasBadgeIcon)]]; } return image; } else { NSLog(@"Error getting icon for URL %@: %@", [self absoluteString], error); } return nil; } - (NSDate *)ck2_dateModified { id value; NSError *error; if ([self getResourceValue:&value forKey:NSURLContentModificationDateKey error:&error]) { return value; } else { NSLog(@"Error getting date modified for URL %@: %@", [self absoluteString], error); } return nil; } - (NSString *)ck2_kind { if ([self isFileURL]) { id value; NSError *error; if ([self getResourceValue:&value forKey:NSURLLocalizedTypeDescriptionKey error:&error]) { return value; } else { NSLog(@"Error getting kind for URL %@: %@", [self absoluteString], error); } } else { NSString *type; OSStatus status; CFStringRef kindString; if ([self ck2_isDirectory] && ![self ck2_isPackage]) { return @"Folder"; } else if ([self ck2_isSymbolicLink]) { return @"Alias"; } type = [self pathExtension]; status = LSCopyKindStringForTypeInfo(kLSUnknownType, kLSUnknownCreator, (CFStringRef)type, &kindString); if (status == noErr) { return [(NSString *)kindString autorelease]; } else { NSLog(@"Error getting kind for URL %@: %s", [self absoluteString], GetMacOSStatusCommentString(status)); } } return @""; } - (NSNumber *)ck2_size { id value; NSError *error; if ([self getResourceValue:&value forKey:NSURLFileSizeKey error:&error]) { return value; } else { NSLog(@"Error getting size for URL %@: %@", [self absoluteString], error); } return nil; } - (BOOL)ck2_isDirectory { id value; NSError *error; NSURL *actualURL; actualURL = [self ck2_destinationURL]; if ([actualURL getResourceValue:&value forKey:NSURLIsDirectoryKey error:&error]) { if (value == nil) { // Info is not filled out. We will default to YES to force a directory listing return YES; } return [value boolValue]; } else { NSLog(@"Error getting isDirectory for URL %@: %@", [self absoluteString], error); } return NO; } - (BOOL)ck2_isPackage { id value; NSError *error; if ([self getResourceValue:&value forKey:NSURLIsPackageKey error:&error]) { return [value boolValue]; } else { NSLog(@"Error getting isPackage for URL %@: %@", [self absoluteString], error); } return NO; } - (BOOL)ck2_isHidden { id value; NSError *error; if ([self getResourceValue:&value forKey:NSURLIsHiddenKey error:&error]) { return [value boolValue]; } else { NSLog(@"Error getting isHidden for URL %@: %@", [self absoluteString], error); } return NO; } - (BOOL)ck2_isSymbolicLink { id value; NSError *error; error = nil; if ([self getResourceValue:&value forKey:NSURLIsSymbolicLinkKey error:&error]) { return [value boolValue]; } else { NSLog(@"Error determining if symbolic link for url %@: %@", self, error); } return NO; } - (NSURL *)ck2_destinationURL { id value; NSError *error; error = nil; // Not sure if CK2URLSymbolicLinkDestinationKey implies this but doing it just to be safe if ([self ck2_isSymbolicLink]) { if ([self getResourceValue:&value forKey:CK2URLSymbolicLinkDestinationKey error:&error]) { return value; } else { NSLog(@"Error getting destination link for url %@: %@", self, error); } } return self; } - (NSURL *)ck2_root { return [[CK2FileManager URLWithPath:@"/" isDirectory:YES hostURL:self] absoluteURL]; } - (NSURL *)ck2_parentURL { id value; NSError *error; if ([self getResourceValue:&value forKey:NSURLParentDirectoryURLKey error:&error]) { if ((value == nil) || [value isKindOfClass:[NSURL class]]) { return value; } else { NSLog(@"Parent of URL %@ is not an URL type: %@", self, [value class]); } } else { NSLog(@"Error getting parent URL for URL %@: %@", self, error); } return nil; } - (BOOL)ck2_isAncestorOfURL:(NSURL *)url { NSURL *tempURL; tempURL = url; while (![tempURL isEqual:self]) { if (tempURL == nil) { return NO; } tempURL = [tempURL ck2_parentURL]; } return YES; } - (NSURL *)ck2_URLByDeletingTrailingSlash { NSString *path; NSUInteger startIndex, endIndex; path = [self path]; startIndex = 0; endIndex = [path length]; if ([path hasPrefix:@"//"]) { startIndex++; } if ([path hasSuffix:@"/"]) { endIndex--; } path = [path substringWithRange:NSMakeRange(startIndex, endIndex - startIndex)]; // Quite the rigamarole just to get an URL without the trailing slash return [[CK2FileManager URLWithPath:path relativeToURL:[self ck2_root]] absoluteURL]; } @end @implementation CK2PlaceholderURL - (BOOL)ck2_isPlaceholder { return YES; } - (NSString *)ck2_displayName { return [[self absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; } - (NSImage *)ck2_icon { return nil; } - (NSDate *)ck2_dateModified { return nil; } - (NSString *)ck2_kind { return @""; } - (NSString *)sizeString { return @""; } - (BOOL)ck2_isDirectory { return NO; } - (BOOL)ck2_canHazChildren { return NO; } - (BOOL)isEqual:(id)object { return self == object; } - (NSUInteger)hash { return (NSUInteger)self; } - (id)copyWithZone:(NSZone *)zone { return [self retain]; } @end ================================================ FILE: ConnectionKit/en.lproj/CK2FilePreview.xib ================================================ 1060 11G63 2844 1138.51 569.00 com.apple.InterfaceBuilder.CocoaPlugin 2844 NSCustomObject NSCustomView NSDateFormatter NSImageCell NSImageView NSNumberFormatter NSTextField NSTextFieldCell com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion CK2BrowserPreviewController FirstResponder NSApplication 18 290 {{69, 16}, {128, 17}} _NS:1535 YES 69206081 -1874851840 Today, 3:30 PM LucidaGrande 11 3100 M/d/yy h:mm a NO _NS:1535 6 System controlColor 3 MC42NjY2NjY2NjY3AA 6 System controlTextColor 3 MAA 292 {{15, 52}, {49, 17}} _NS:1535 YES 605028416 71336960 Kind _NS:1535 6 System disabledControlTextColor 3 MC4zMzMzMzMzMzMzAA 290 {{69, 52}, {125, 17}} _NS:1535 YES 69206081 272631808 Application _NS:1535 290 {{69, 34}, {128, 17}} _NS:1535 YES 69206081 -1874851840 0 B -∞ +∞ #.# #.# NaN 0 0 YES NO 1 AAAAAAAAAAAAAAAAAAAAAA 3 YES YES YES . , NO NO YES _NS:1535 292 {{13, 34}, {51, 17}} _NS:1535 YES 605028416 71336960 Size _NS:1535 292 {{13, 19}, {51, 14}} _NS:1535 YES 605028416 71336960 Modified _NS:1535 292 {{13, 70}, {51, 17}} _NS:1535 YES 605028416 71336960 Name _NS:1535 290 {{69, 70}, {125, 17}} _NS:1535 YES 69206017 272629760 Name _NS:1535 282 Apple PDF pasteboard type Apple PICT pasteboard type Apple PNG pasteboard type NSFilenamesPboardType NeXT Encapsulated PostScript v1.2 pasteboard type NeXT TIFF v4.0 pasteboard type {{12, 149}, {186, 194}} _NS:9 YES 134217728 33554432 NSImage NSApplicationIcon _NS:9 0 3 0 NO YES {210, 363} CK2BrowserPreviewView view 55 _iconView 56 _nameField 57 _sizeField 59 _kindField 58 _dateModifiedField 60 _iconView 64 _nameField 65 _nameLabel 100 _dateModifiedLabel 103 _sizeLabel 102 _sizeField 69 _kindField 68 _kindLabel 101 _dateModifiedField 70 0 -2 File's Owner -1 First Responder -3 Application 1 2 3 40 41 38 39 46 47 44 45 51 52 49 50 42 43 53 54 71 104 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin CK2FileSizeFormatter com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin 104 CK2BrowserPreviewController NSViewController IBProjectSource ./Classes/CK2BrowserPreviewController.h CK2BrowserPreviewView NSView NSTextField NSTextField NSImageView NSTextField NSTextField NSTextField NSTextField NSTextField NSTextField _dateModifiedField NSTextField _dateModifiedLabel NSTextField _iconView NSImageView _kindField NSTextField _kindLabel NSTextField _nameField NSTextField _nameLabel NSTextField _sizeField NSTextField _sizeLabel NSTextField IBProjectSource ./Classes/CK2BrowserPreviewView.h CK2FileSizeFormatter NSNumberFormatter IBProjectSource ./Classes/CK2FileSizeFormatter.h 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx YES 3 NSApplicationIcon {128, 128} ================================================ FILE: ConnectionKit/en.lproj/CK2NewFolderWindow.xib ================================================ 1060 11G63 2844 1138.51 569.00 com.apple.InterfaceBuilder.CocoaPlugin 2844 NSButton NSButtonCell NSCustomObject NSProgressIndicator NSTextField NSTextFieldCell NSView NSWindowTemplate com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion CK2NewFolderWindowController FirstResponder NSApplication 1 2 {{196, 240}, {286, 137}} 813173760 New Folder NSWindow 256 268 {{17, 100}, {132, 17}} _NS:1535 YES 68157504 272630784 Name of new folder: LucidaGrande 13 1044 _NS:1535 6 System controlColor 3 MC42NjY2NjY2NjY3AA 6 System controlTextColor 3 MAA 266 {{20, 70}, {246, 22}} _NS:9 YES -1804599231 272630784 untitled folder _NS:9 YES 6 System textBackgroundColor 3 MQA 6 System textColor 289 {{191, 12}, {81, 32}} _NS:9 YES 67108864 134217728 Create _NS:9 -2038284288 129 DQ 200 25 289 {{109, 12}, {82, 32}} _NS:9 YES 67108864 134217728 Cancel _NS:9 -2038284288 129 Gw 200 25 -2147483380 {{17, 48}, {252, 14}} _NS:1535 YES 68157504 138544128 This name is already taken. LucidaGrande 11 3100 _NS:1535 268 {{250, 100}, {16, 16}} _NS:945 28938 100 {286, 137} {{0, 0}, {1920, 1178}} {10000000000000, 10000000000000} NO window 3 _okButton 25 ok: 26 cancel: 27 _nameField 28 _statusField 31 _progressIndicator 33 delegate 4 delegate 24 0 -2 File's Owner -1 First Responder -3 Application 1 2 5 6 11 12 18 19 20 21 29 30 32 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{357, 418}, {480, 270}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin 33 CK2NewFolderWindowController NSWindowController id id cancel: id ok: id NSTextField NSButton NSProgressIndicator NSTextField _nameField NSTextField _okButton NSButton _progressIndicator NSProgressIndicator _statusField NSTextField IBProjectSource ./Classes/CK2NewFolderWindowController.h 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx YES 3 ================================================ FILE: ConnectionKit/en.lproj/CK2OpenPanel.xib ================================================ 1060 12F45 4510 1187.40 626.00 com.apple.InterfaceBuilder.CocoaPlugin 4510 NSBox NSBrowser NSButton NSButtonCell NSCollectionView NSCollectionViewItem NSCustomObject NSCustomView NSDateFormatter NSImageCell NSImageView NSMenu NSMenuItem NSNumberFormatter NSOutlineView NSPopUpButton NSPopUpButtonCell NSProgressIndicator NSScrollView NSScroller NSSegmentedCell NSSegmentedControl NSTabView NSTabViewItem NSTableColumn NSTableHeaderView NSTextField NSTextFieldCell NSView NSViewController com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion CK2OpenPanelController FirstResponder NSApplication 292 266 268 Apple PDF pasteboard type Apple PICT pasteboard type Apple PNG pasteboard type NSFilenamesPboardType NeXT Encapsulated PostScript v1.2 pasteboard type NeXT TIFF v4.0 pasteboard type {{20, 17}, {48, 48}} _NS:9 YES 134217728 33554432 NSImage NSNetwork _NS:9 0 3 0 NO NO YES 266 {{73, 29}, {426, 22}} _NS:1535 YES 68157504 272630784 localhost LucidaGrande 16 16 _NS:1535 6 System controlColor 3 MC42NjY2NjY2NjY3AA 6 System controlTextColor 3 MAA NO 10 {{-1, -1}, {518, 5}} _NS:9 {0, 0} 67108864 0 Box LucidaGrande 13 1044 6 System textBackgroundColor 3 MQA 3 MCAwLjgwMDAwMDAxMTkAA 3 2 0 NO {{-1, 418}, {516, 85}} _NS:9 NSView 266 265 {{478, 9}, {27, 25}} _NS:9 YES 67108864 134217728 Refresh _NS:9 105136128 163 NSImage NSRefreshTemplate 200 25 NO -2147483383 {{485, 12}, {16, 16}} _NS:945 28938 100 268 {{233, 8}, {205, 26}} _NS:9 YES -2076180416 2048 _NS:9 112869376 129 400 75 1048576 2147483647 1 NSImage NSMenuCheckmark NSImage NSMenuMixedState _popUpItemAction: YES OtherViews 1048576 2147483647 _popUpItemAction: 1048576 2147483647 _popUpItemAction: -1 1 YES YES 2 NO 268 {{124, 8}, {104, 25}} _NS:9 YES 67108864 0 _NS:9 32 NSImage NSIconViewTemplate 0 32 NSImage NSListViewTemplate 1 0 32 NSImage NSColumnViewTemplate YES 0 2 2 NO 268 {{80, 8}, {38, 25}} _NS:9 YES 67108864 0 _NS:9 32 NSImage NSHomeTemplate 2 -1 2 2 NO 268 {{21, 8}, {53, 25}} _NS:9 YES 67108864 0 _NS:9 23 NSImage NSGoLeftTemplate 0 23 NSImage NSGoRightTemplate 1 0 1 2 2 NO {{-1, 378}, {517, 42}} _NS:9 NSView 290 289 {{416, 6}, {82, 32}} _NS:9 YES 67108864 134217728 Open _NS:9 -2038284288 129 DQ 200 25 NO 289 {{332, 6}, {82, 32}} _NS:9 YES 67108864 134217728 Cancel _NS:9 -2038284288 129 Gw 200 25 NO 268 {{19, 6}, {111, 32}} _NS:9 YES 67108864 134217728 New Folder _NS:9 -2038284288 268435585 N 200 25 NO {{-1, -1}, {516, 48}} _NS:9 NSView 274 34 {{0, -1}, {518, 5}} _NS:9 {0, 0} 67108864 0 Box 3 MCAwLjgwMDAwMDAxMTkAA 3 2 0 NO 274 {{0, 1}, {516, 332}} _NS:9 icon 274 274 2322 4370 {515, 331} _NS:80 {0, 0} {0, 0} 0 0 6 System controlBackgroundColor YES -1 0 {{1, 1}, {515, 331}} _NS:78 4 -2147483392 {{499, 1}, {15, 332}} _NS:82 NO _doScroller: 1 0.89655172824859619 -2147483392 {{-100, -100}, {498, 15}} _NS:91 NO 1 _doScroller: 0.63157892227172852 {517, 333} _NS:76 133650 0.25 4 1 {516, 332} _NS:11 Icon View list 274 274 2322 4352 {612, 315} _NS:13 YES NO YES 256 {612, 17} _NS:16 -2147483392 {{224, 0}, {16, 17}} _NS:18 Name 250 16 1000 75497536 2048 Name 3 MC4zMzMzMzI5ODU2AA 6 System headerTextColor 337641536 2048 Text Cell LucidaGrande 12 16 3 YES Date Modified 175 40 1000 75497536 2048 Date Modified 337641536 -2147481600 Text Cell d MMM y HH:mm NO 3 YES Size 75 10 3.4028234663852886e+38 75497536 67110912 Size 6 System headerColor 337641536 67110912 -∞ +∞ #,##0.# #,##0.# NaN 0 0 YES NO 1 AAAAAAAAAAAAAAAAAAAAAA 3 YES YES YES . , YES NO YES 3 YES Kind 100 10 3.4028234663852886e+38 75497536 2048 Kind 337641536 2048 Text Cell 3 YES 3 2 6 System gridColor 3 MC41AA 17 -757071872 CK2OpenPanelOutlineState 4 15 0 YES 0 1 NO YES {{1, 17}, {515, 315}} {{78, 0}, {515, 315}} _NS:11 4 -2147483392 {{224, 17}, {15, 102}} _NS:58 NO _doScroller: 0.95253164556962022 256 {{1, 316}, {515, 16}} _NS:60 YES NO 1 _doScroller: 0.80412371134020622 0.84150326797385622 2304 {{1, 0}, {515, 17}} {{78, 0}, {515, 17}} _NS:15 4 {517, 333} _NS:9 133810 QSAAAEEgAABBmAAAQZgAAA 0.25 4 1 {516, 332} _NS:28 List View column 274 4370 {517, 333} _NS:9 YES NO YES 67108928 2048 IA / 210 2 2 CK2OpenPanelBrowserState 262 YES 1208041472 17 {516, 332} Column View blank 256 18 274 {516, 334} _NS:11 {516, 334} _NS:9 {0, 0} 67108864 0 LucidaGrande 11 3100 3 MCAwLjgwMDAwMDAxMTkAA 0 4 0 NO 3 MCAwAA 3 MQA 2 {516, 334} 6 YES YES 10 {{0, 331}, {516, 5}} _NS:9 {0, 0} 67108864 0 Box 3 MCAwLjgwMDAwMDAxMTkAA 3 2 0 NO {{-1, 46}, {517, 334}} _NS:9 NSView {515, 501} _NS:21 266 {496, 14} _NS:1535 YES 67108928 138545152 Label _NS:1535 NO 268 {125, 122} CK2IconItemView 34 274 {{54, 3}, {69, 108}} _NS:11 {177, 114} _NS:9 {53, 2} 67108864 0 3 MCAwLjgwMDAwMDAxMTkAA 1 0 0 NO NO _topView 317 view 321 _listViewController 452 _browserController 453 _iconViewController 460 _hostField 320 changeHistory: 377 _historyButtons 376 _homeButton 410 home: 412 _viewPicker 311 _pathControl 420 pathControlItemSelected: 421 _progressIndicator 316 _newFolderButton 381 _cancelButton 373 cancel: 375 ok: 374 _okButton 372 _topSection 490 _buttonSection 491 _bottomSection 492 _accessoryContainer 493 _tabView 331 _middleSection 495 _messageLabel 496 newFolder: 497 refresh: 501 _refreshButton 502 delegate 349 takeSelectedTabViewItemFromSender: 304 initialFirstResponder 360 initialFirstResponder 478 initialFirstResponder 479 itemPrototype 398 _target 472 view 473 delegate 428 _controller 424 _browser 425 view 426 itemSelected: 469 dataSource 447 delegate 446 _controller 448 view 449 itemSelected: 468 _outlineView 450 _controller 459 view 457 _iconView 456 _item 474 0 -2 File's Owner -1 First Responder -3 Application 256 397 423 Column View Controller 444 List View Controller 455 Icon View Controller 470 483 Message Label 484 485 Accessory Box 486 Top Section 262 260 301 261 300 487 Button Bar 266 271 408 409 267 270 414 415 416 419 418 417 259 489 Bottom Section 378 379 370 371 368 369 494 Middle Section 264 475 Tab View Item - Blank 279 278 277 292 293 296 295 282 429 433 432 431 280 422 476 477 499 500 294 430 437 436 435 434 443 441 442 439 440 438 353 354 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin CK2IconView com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin CK2IconViewItem com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin CK2PathControl com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin CK2OpenPanelColumnViewController com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin CK2FileSizeFormatter com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin CK2FileCell com.apple.InterfaceBuilder.CocoaPlugin CK2OpenPanelListViewController com.apple.InterfaceBuilder.CocoaPlugin CK2OpenPanelIconViewController com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin CK2FileCell NSTextFieldCell IBProjectSource ./Classes/CK2FileCell.h CK2FileSizeFormatter NSNumberFormatter IBProjectSource ./Classes/CK2FileSizeFormatter.h CK2IconItemView NSView _item CK2IconViewItem _item _item CK2IconViewItem IBProjectSource ./Classes/CK2IconItemView.h CK2IconView NSCollectionView IBProjectSource ./Classes/CK2IconView.h CK2IconViewItem NSCollectionViewItem _target id _target _target id IBProjectSource ./Classes/CK2IconViewItem.h CK2OpenPanel NSPanel id id cancel: id ok: id IBProjectSource ./Classes/CK2OpenPanel.h CK2OpenPanelColumnViewController CK2OpenPanelViewController _browser NSBrowser _browser _browser NSBrowser IBProjectSource ./Classes/CK2OpenPanelColumnViewController.h CK2OpenPanelController NSViewController id id id id id id id id id back: id cancel: id changeHistory: id forward: id home: id newFolder: id ok: id pathControlItemSelected: id refresh: id NSBox NSView CK2OpenPanelColumnViewController NSView NSButton NSSegmentedControl NSSegmentedControl NSTextField CK2OpenPanelIconViewController CK2OpenPanelListViewController NSTextField NSView NSButton NSButton CK2OpenPanel CK2PathControl NSProgressIndicator NSButton NSTabView NSView NSSegmentedControl _accessoryContainer NSBox _bottomSection NSView _browserController CK2OpenPanelColumnViewController _buttonSection NSView _cancelButton NSButton _historyButtons NSSegmentedControl _homeButton NSSegmentedControl _hostField NSTextField _iconViewController CK2OpenPanelIconViewController _listViewController CK2OpenPanelListViewController _messageLabel NSTextField _middleSection NSView _newFolderButton NSButton _okButton NSButton _openPanel CK2OpenPanel _pathControl CK2PathControl _progressIndicator NSProgressIndicator _refreshButton NSButton _tabView NSTabView _topSection NSView _viewPicker NSSegmentedControl IBProjectSource ./Classes/CK2OpenPanelController.h CK2OpenPanelIconViewController CK2OpenPanelViewController id id goToSelectedItem: id itemDoubleClicked: id _iconView CK2IconView _iconView _iconView CK2IconView IBProjectSource ./Classes/CK2OpenPanelIconViewController.h CK2OpenPanelListViewController CK2OpenPanelViewController id id goToSelectedItem: id itemDoubleClicked: id _outlineView NSOutlineView _outlineView _outlineView NSOutlineView IBProjectSource ./Classes/CK2OpenPanelListViewController.h CK2OpenPanelViewController NSViewController id id id goToSelectedItem: id itemDoubleClicked: id itemSelected: id _controller CK2OpenPanelController _controller _controller CK2OpenPanelController IBProjectSource ./Classes/CK2OpenPanelViewController.h CK2PathControl NSPopUpButton IBProjectSource ./Classes/CK2PathControl.h 0 IBCocoaFramework YES com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 YES 3 {13, 10} {9, 9} {9, 9} {14, 13} {11, 10} {11, 10} {11, 11} {10, 3} {32, 32} {10, 12} ================================================ FILE: ConnectionKit/en.lproj/CK2PathFieldWindow.xib ================================================ 1060 11G63 3084 1138.51 569.00 com.apple.InterfaceBuilder.CocoaPlugin 3084 NSButton NSButtonCell NSCustomObject NSTextField NSTextFieldCell NSView NSWindowTemplate com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion CK2PathFieldWindowController FirstResponder NSApplication 1 2 {{196, 240}, {430, 127}} 1618478080 Go to folder NSWindow 256 268 {{17, 90}, {109, 17}} _NS:1535 YES 68157504 272630784 Go to the folder: LucidaGrande 13 1044 _NS:1535 6 System controlColor 3 MC42NjY2NjY2NjY3AA 6 System controlTextColor 3 MAA 268 {{20, 60}, {390, 22}} _NS:9 YES -1804599231 272630784 _NS:9 YES 6 System textBackgroundColor 3 MQA 6 System textColor 268 {{324, 12}, {92, 32}} _NS:9 YES 67108864 134217728 Go _NS:9 -2038284288 129 DQ 200 25 268 {{232, 12}, {92, 32}} _NS:9 YES 67108864 134217728 Cancel _NS:9 -2038284288 129 Gw 200 25 {430, 127} {{0, 0}, {1920, 1178}} {10000000000000, 10000000000000} YES window 3 _field 70 _goButton 71 go: 72 cancel: 73 delegate 4 delegate 74 performClick: 75 0 -2 File's Owner -1 First Responder -3 Application 1 2 5 6 11 12 16 17 20 21 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{357, 418}, {480, 270}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin 75 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx YES 3 ================================================ FILE: ConnectionKit/main.m ================================================ // // main.m // FTPConnection // // Created by Greg Hulands on 3/12/04. // Copyright __MyCompanyName__ 2004. All rights reserved. // #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **) argv); } ================================================ FILE: ConnectionKit 2.graffle ================================================ ActiveLayerIndex 0 ApplicationVersion com.omnigroup.OmniGraffle 129.18 AutoAdjust CanvasColor w 1 CanvasOrigin {0, 0} CanvasScale 1 ColumnAlign 1 ColumnSpacing 36 CreationDate 2012-12-16 20:51:20 +0000 Creator Mike DisplayScale 1 cm = 1 cm FileType flat GraphDocumentVersion 5 GraphicsList Class LineGraphic Head ID 31 ID 33 Points {366.2634, 303.48264} {384.2247, 310.21814} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 3 Class LineGraphic Head ID 31 ID 32 Points {373.7384, 275.28067} {384.30875, 284.08932} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 2 Class LineGraphic ID 30 Points {368.43951, 275.34518} {421.32031, 339.00391} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 2 Class LineGraphic Head ID 27 ID 28 Points {396.50226, 275.12875} {497.60785, 311.23788} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 2 Bounds {{498.07874, 274.96057}, {113.38577, 113.38593}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 27 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 NSFileManager} Bounds {{328.00006, 331.65356}, {56.692913, 56.692902}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 26 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 libcares} Class LineGraphic Head ID 23 ID 24 Points {157.92131, 332.15356} {157.92136, 360.00006} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 18 Bounds {{101.22847, 331.65359}, {113.38577, 56.692902}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 23 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 CFNetwork} Bounds {{271.30713, 360}, {56.692913, 28.346457}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 12 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 libcrypto} Bounds {{214.61424, 360}, {56.692913, 28.346457}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 10 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 libssl} Bounds {{214.61427, 331.65353}, {113.38577, 28.346457}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 5 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 libssh2} Bounds {{101.22841, 274.9606}, {113.38577, 28.346457}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 14 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 DAVKit} Bounds {{214.61426, 274.9606}, {226.77147, 28.346457}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 3 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 CURLHandle} Bounds {{101.22841, 303.3071}, {113.38577, 28.346466}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 18 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 NSURLConnection} Bounds {{101.22845, 246.61412}, {510.23602, 28.346457}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 2 Shape Rectangle Style fill GradientColor w 0.666667 MiddleColor w 0.333333 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 ConnectionKit 2} Bounds {{214.61424, 303.30707}, {170.07864, 28.346457}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 4 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 libcurl} Class LineGraphic Head ID 3 ID 6 Points {341.8197, 275.31412} {328, 289.13382} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 2 Class LineGraphic Head ID 4 ID 7 Points {313.47324, 303.66058} {299.65356, 317.48029} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 3 Class LineGraphic Head ID 5 ID 8 Points {285.12683, 332.00705} {271.30716, 345.82675} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 4 Class LineGraphic Head ID 10 ID 21 Points {256.78043, 360.35349} {242.96069, 374.17322} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 5 Class LineGraphic Head ID 12 ID 20 Points {285.83389, 360.35349} {299.65359, 374.17322} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 5 Class LineGraphic Head ID 14 ID 17 Points {256.63904, 275.03128} {215.10916, 280.96411} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 2 Class LineGraphic Head ID 18 ID 19 Points {157.9213, 303.80704} {157.9213, 317.48035} Style stroke Color a 0 b 0 g 0 r 0 HeadArrow 0 TailArrow 0 Tail ID 14 Bounds {{384.69287, 274.9606}, {113.38577, 113.38593}} Class ShapedGraphic FontInfo Color w 0 Font Helvetica NSKern 0.0 Size 12 ID 31 Shape Rectangle Style fill GradientColor w 0.666667 Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 \expnd0\expndtw0\kerning0 GCD} GridInfo GridSpacing 14.17322826385498 MajorGridSpacing 10 GuidesLocked NO GuidesVisible YES HPages 1 ImageCounter 1 IsPalette NO KeepToScale Layers Lock NO Name Layer 1 Print YES View YES LayoutInfo HierarchicalOrientation 0 LinksVisible NO MagnetsVisible NO MasterSheet Master 1 MasterSheets ActiveLayerIndex 0 AutoAdjust CanvasColor w 1 CanvasOrigin {0, 0} CanvasScale 1 ColumnAlign 1 ColumnSpacing 36 DisplayScale 1 cm = 1 cm GraphicsList GridInfo GridSpacing 14.17322826385498 MajorGridSpacing 10 HPages 1 IsPalette NO KeepToScale Layers Lock NO Name Layer 1 Print YES View YES LayoutInfo Orientation 2 OutlineStyle Basic RowAlign 1 RowSpacing 36 SheetTitle Master 1 UniqueID 1 VPages 1 ModificationDate 2012-12-16 21:26:48 +0000 Modifier Mike NotesVisible NO Orientation 2 OriginVisible NO OutlineStyle Basic PageBreaks YES PrintInfo NSBottomMargin coded BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG NSLeftMargin coded BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG NSOrientation int 1 NSPaperSize size {842, 595} NSRightMargin coded BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG NSTopMargin coded BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG ReadOnly NO RowAlign 1 RowSpacing 36 SheetTitle Canvas 1 SmartAlignmentGuidesActive YES SmartDistanceGuidesActive YES UniqueID 1 UseEntirePage VPages 1 WindowInfo CurrentSheet 0 DrawerOpen DrawerTab Outline DrawerWidth 209 Frame {{228, 202}, {857, 676}} VisibleRegion {{-29, -9}, {842, 578}} Zoom 1 ================================================ FILE: ConnectionKit.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ConnectionKit.xcworkspace/xcshareddata/ConnectionKit.xccheckout ================================================ IDESourceControlProjectFavoriteDictionaryKey IDESourceControlProjectIdentifier 8FBC02BE-89A6-4E66-A86A-2F30763D8732 IDESourceControlProjectName ConnectionKit IDESourceControlProjectOriginsDictionary 196ECB9D-DC69-481A-BC41-10BC9371E262 https://github.com/karelia/libssh2_sftp-Cocoa-wrapper.git 30181585-7835-4792-AA71-78979C5DC7E4 https://github.com/bagder/c-ares.git 3D0A7752-D915-4708-BED7-84F5752E91D0 https://github.com/karelia/curl.git 55929339-0664-4A1B-8A28-A403FA1A187E https://github.com/karelia/ConnectionKit.git 84801443-6FFF-4DB0-B4CE-0DA862E773E6 https://github.com/karelia/CurlHandle.git 966871D2-8C9C-44C2-BE6E-5FE45C4F558E https://github.com/karelia/DAVKit.git A83B07BE-C454-4AD2-884C-BC9377EDC056 https://github.com/boredzo/iso-8601-date-formatter.git EEF81E40-A006-4B24-AC4D-3B4A9499A316 https://github.com/karelia/iso-8601-date-formatter.git F057D5F2-EC7E-4CBA-9D9D-03960D2DC437 https://github.com/karelia/MockServer.git IDESourceControlProjectPath ConnectionKit.xcworkspace IDESourceControlProjectRelativeInstallPathDictionary 196ECB9D-DC69-481A-BC41-10BC9371E262 ../CurlHandle/SFTP 30181585-7835-4792-AA71-78979C5DC7E4 ../CurlHandle/c-ares 3D0A7752-D915-4708-BED7-84F5752E91D0 ../CurlHandle/curl 55929339-0664-4A1B-8A28-A403FA1A187E .. 84801443-6FFF-4DB0-B4CE-0DA862E773E6 ../CurlHandle 966871D2-8C9C-44C2-BE6E-5FE45C4F558E ../DAVKit A83B07BE-C454-4AD2-884C-BC9377EDC056 ../DAVKit/iso-8601-date-formatter EEF81E40-A006-4B24-AC4D-3B4A9499A316 ../DAVKit/Sources/iso-8601-date-formatter F057D5F2-EC7E-4CBA-9D9D-03960D2DC437 ../CurlHandle/CURLHandleSource/Tests/MockServer IDESourceControlProjectURL https://github.com/karelia/ConnectionKit.git IDESourceControlProjectVersion 110 IDESourceControlProjectWCCIdentifier 55929339-0664-4A1B-8A28-A403FA1A187E IDESourceControlProjectWCConfigurations IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey 30181585-7835-4792-AA71-78979C5DC7E4 IDESourceControlWCCName c-ares IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey 55929339-0664-4A1B-8A28-A403FA1A187E IDESourceControlWCCName ConnectionKit IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey 3D0A7752-D915-4708-BED7-84F5752E91D0 IDESourceControlWCCName curl IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey 84801443-6FFF-4DB0-B4CE-0DA862E773E6 IDESourceControlWCCName CurlHandle IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey 966871D2-8C9C-44C2-BE6E-5FE45C4F558E IDESourceControlWCCName DAVKit IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey A83B07BE-C454-4AD2-884C-BC9377EDC056 IDESourceControlWCCName iso-8601-date-formatter IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey EEF81E40-A006-4B24-AC4D-3B4A9499A316 IDESourceControlWCCName iso-8601-date-formatter IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey F057D5F2-EC7E-4CBA-9D9D-03960D2DC437 IDESourceControlWCCName MockServer IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey F057D5F2-EC7E-4CBA-9D9D-03960D2DC437 IDESourceControlWCCName MockServer IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey 196ECB9D-DC69-481A-BC41-10BC9371E262 IDESourceControlWCCName SFTP ================================================ FILE: ConnectionKitUI/ConnectionKitUI-Info.plist ================================================ CFBundleDevelopmentRegion English CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 NSPrincipalClass ================================================ FILE: ConnectionKitUI/ConnectionKitUI-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #ifdef __OBJC__ #import #endif ================================================ FILE: ConnectionKitUI/ConnectionKitUI.h ================================================ // // ConnectionKitUI.h // ConnectionKitUI // // Created by Mike on 08/07/2013. // // #import #import ================================================ FILE: ConnectionKitUI/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: Documentation/Standard Errors.md ================================================ Protocols have wildly differing implementations, and those implementation can return wildly different errors in a given situation. To some extent this is an impossible problem to solve, since it's unrealistic for us to provide a universal translation of all potential errors into some known set. However, it is useful to be able to test for a few common errors, in specific situations, such as: - authentication has failed - we tried to make a directory but it was already there - we tried to delete a directory but it wasn't there - we tried to delete a file but it wasn't there It's also helpful to wrap up all the other errors in some more generic catch-alls: - something went wrong with something that reads/lists files - something went wrong with writes/modifies/creates/destroys files As a result, CK2Protocol defines some standard methods that protocol implementations can use to wrap up their own errors: - (NSError*)standardCouldntWriteErrorWithUnderlyingError:(NSError*)error; - (NSError*)standardCouldntReadErrorWithUnderlyingError:(NSError*)error; - (NSError*)standardFileNotFoundErrorWithUnderlyingError:(NSError*)error; - (NSError*)standardAuthenticationErrorWithUnderlyingError:(NSError*)error; These methods will return, respectively: standardCouldntWriteErrorWithUnderlyingError: NSCocoaErrorDomain - NSFileWriteUnknownError standardCouldntReadErrorWithUnderlyingError: NSCocoaErrorDomain - NSFileNoSuchFileError standardFileNotFoundErrorWithUnderlyingError: NSCocoaErrorDomain - NSURLErrorNoPermissionsToReadFile standardAuthenticationErrorWithUnderlyingError: NSURLErrorDomain NSURLErrorUserAuthenticationRequired Protocol implementations should ensure that they use these errors in the following situations: Use standardAuthenticationErrorWithUnderlyingError to report any authentication problems (obviously). Use standardFileNotFoundErrorWithUnderlyingError if the protocol implementation is sure that the underlying problem is caused by the file being absent. For example when deleting a directory or file that doesn't exist - assuming that the underlying protocol error is specific enough to know that this is the reason for the failure. Use standardCouldntWriteErrorWithUnderlyingError when performing a createFile/createDirectory/removeFile/removeDirectory/setAttributes, if the protocol implementation's underlying error was too vague or definitely wasn't a file-not-found error. Use standardCouldntReadErrorWithUnderlyingError when performing a enumating operation, if the protocol implementation's underlying error was too vague or definitely wasn't a file-not-found error. One of the side benefits of this plan is that it allows us to write some unit tests that work generically across all supported protocols, since they now have some (vaguely) predictable errors to check against in the situations where the test is deliberately engineering an error situation. Hopefully, though, this will also allow client code that uses multiple protocols to do something vaguely sensible with the errors that it gets back. ================================================ FILE: Example/Connection-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.yourcompany.Connection CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType FMWK CFBundleSignature ???? CFBundleVersion 1.0 ================================================ FILE: Example/ConnectionApp.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 42; objects = { /* Begin PBXAggregateTarget section */ 7983E7E80B0C4F2200F5078E /* Build All */ = { isa = PBXAggregateTarget; buildConfigurationList = 7983E7F00B0C4F6F00F5078E /* Build configuration list for PBXAggregateTarget "Build All" */; buildPhases = ( 7983E7EE0B0C4F5100F5078E /* ShellScript */, ); dependencies = ( 7983E7EA0B0C4F4200F5078E /* PBXTargetDependency */, 7983E7EC0B0C4F4600F5078E /* PBXTargetDependency */, ); name = "Build All"; productName = "Build All"; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 09D6603A09FD37CD0000BA00 /* Connection.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD12609F702BE00172CDD /* Connection.framework */; }; 790FF0D70ACCE4F2004B4021 /* ktlogviewer_main.m in Sources */ = {isa = PBXBuildFile; fileRef = 790FF0D60ACCE4F2004B4021 /* ktlogviewer_main.m */; }; 790FF0DD0ACCE8EA004B4021 /* KTLogViewer.nib in Resources */ = {isa = PBXBuildFile; fileRef = 790FF0DB0ACCE8EA004B4021 /* KTLogViewer.nib */; }; 790FF0E40ACCE966004B4021 /* KTLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = 790FF0E30ACCE966004B4021 /* KTLogController.m */; }; 790FF23E0ACCEB46004B4021 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97324FDCFA39411CA2CEA /* AppKit.framework */; }; 790FF23F0ACCEB46004B4021 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97325FDCFA39411CA2CEA /* Foundation.framework */; }; 792E57860C8E2C2F00276AA2 /* symlink_folder.tif in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8F209F7076200172CDD /* symlink_folder.tif */; }; 792E57890C8E2C3300276AA2 /* symlink_file.tif in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8F109F7076200172CDD /* symlink_file.tif */; }; 794EFDD20A5CD6CA00913151 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 794EFDD10A5CD6CA00913151 /* libcrypto.dylib */; }; 794EFDD50A5CD6F900913151 /* libssl.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 794EFDD40A5CD6F900913151 /* libssl.dylib */; }; 795A010A0B116832006905FA /* edit.png in Resources */ = {isa = PBXBuildFile; fileRef = 795A01090B116832006905FA /* edit.png */; }; 795FF5020B0C018100EA9292 /* DropletMain.m in Sources */ = {isa = PBXBuildFile; fileRef = 795FF5010B0C018100EA9292 /* DropletMain.m */; }; 795FF5070B0C048700EA9292 /* Droplet.nib in Resources */ = {isa = PBXBuildFile; fileRef = 795FF5050B0C048700EA9292 /* Droplet.nib */; }; 795FF50E0B0C05F400EA9292 /* DropletController.m in Sources */ = {isa = PBXBuildFile; fileRef = 795FF50C0B0C05F400EA9292 /* DropletController.m */; }; 796DB30109F8BB1D0065897B /* SecurityInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 796DB2F609F8BB1D0065897B /* SecurityInterface.framework */; }; 797CC8C709F861770063FF9B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 797CC8C509F861770063FF9B /* Localizable.strings */; }; 798300070B0D4A2600F5078E /* DropletLauncher.nib in Resources */ = {isa = PBXBuildFile; fileRef = 798300050B0D4A2600F5078E /* DropletLauncher.nib */; }; 7983E0950B0C1A8500F5078E /* DropletOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7983E0940B0C1A8500F5078E /* DropletOutlineView.m */; }; 7983E2310B0C244700F5078E /* Connection.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD12609F702BE00172CDD /* Connection.framework */; }; 7983E24B0B0C245100F5078E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 7983E6CC0B0C4D8800F5078E /* DropletIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 7983E6CB0B0C4D8800F5078E /* DropletIcon.icns */; }; 7983E6DE0B0C4E0000F5078E /* DropletLauncher.m in Sources */ = {isa = PBXBuildFile; fileRef = 7983E6730B0C41E300F5078E /* DropletLauncher.m */; }; 7983E7000B0C4E2800F5078E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 7983E7050B0C4E4E00F5078E /* DropletLauncher in Resources */ = {isa = PBXBuildFile; fileRef = 7983E6D20B0C4DF000F5078E /* DropletLauncher */; }; 7983EDF90B0C802C00F5078E /* DropletIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 7983E6CB0B0C4D8800F5078E /* DropletIcon.icns */; }; 7983F3EE0B0D1B8B00F5078E /* DropletPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7983F3ED0B0D1B8B00F5078E /* DropletPanel.m */; }; 79CFD8D709F706F500172CDD /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CFD8CE09F706F500172CDD /* Controller.m */; }; 79CFD8D809F706F500172CDD /* FileTransfer.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CFD8D009F706F500172CDD /* FileTransfer.m */; }; 79CFD8D909F706F500172CDD /* InputDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CFD8D209F706F500172CDD /* InputDialog.m */; }; 79CFD8DA09F706F500172CDD /* PermissionsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CFD8D409F706F500172CDD /* PermissionsController.m */; }; 79CFD8DB09F706F500172CDD /* ProgressCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CFD8D609F706F500172CDD /* ProgressCell.m */; }; 79CFD8E009F7071400172CDD /* InputDialog.nib in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8DC09F7071400172CDD /* InputDialog.nib */; }; 79CFD8E109F7071400172CDD /* Permissions.nib in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8DE09F7071400172CDD /* Permissions.nib */; }; 79CFD8F409F7076200172CDD /* connect.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8E809F7076200172CDD /* connect.tiff */; }; 79CFD8F509F7076200172CDD /* console.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8E909F7076200172CDD /* console.tiff */; }; 79CFD8F609F7076200172CDD /* delete.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8EA09F7076200172CDD /* delete.tiff */; }; 79CFD8F709F7076200172CDD /* download.tif in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8EB09F7076200172CDD /* download.tif */; }; 79CFD8F909F7076200172CDD /* home.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8ED09F7076200172CDD /* home.tiff */; }; 79CFD8FA09F7076200172CDD /* icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8EE09F7076200172CDD /* icon.icns */; }; 79CFD8FB09F7076200172CDD /* lock.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8EF09F7076200172CDD /* lock.tiff */; }; 79CFD8FC09F7076200172CDD /* redo.tif in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8F009F7076200172CDD /* redo.tif */; }; 79CFD8FD09F7076200172CDD /* symlink_file.tif in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8F109F7076200172CDD /* symlink_file.tif */; }; 79CFD8FE09F7076200172CDD /* symlink_folder.tif in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8F209F7076200172CDD /* symlink_folder.tif */; }; 79CFD8FF09F7076200172CDD /* upload.tif in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD8F309F7076200172CDD /* upload.tif */; }; 79CFD92F09F7080B00172CDD /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD92D09F7080B00172CDD /* libz.dylib */; }; 79CFD93709F7084000172CDD /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD93609F7084000172CDD /* Security.framework */; }; 79CFD96B09F70A5C00172CDD /* Connection.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 79CFD12609F702BE00172CDD /* Connection.framework */; }; 79CFD97009F70A7E00172CDD /* Connection.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD12609F702BE00172CDD /* Connection.framework */; }; 79CFD97C09F70AA700172CDD /* network_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD97A09F70AA700172CDD /* network_icon.png */; }; 79CFD97D09F70AA700172CDD /* Stop.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 79CFD97B09F70AA700172CDD /* Stop.tiff */; }; 79F563FF0AC8C5F2006AD3E2 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CFD93609F7084000172CDD /* Security.framework */; }; 79F9533D09FDC3A80041E345 /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79F9532B09FDC3A80041E345 /* ApplicationServices.framework */; }; 79FB807209F74185006E7D11 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79FB807109F74185006E7D11 /* Carbon.framework */; }; 79FB808A09F741D6006E7D11 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79FB807109F74185006E7D11 /* Carbon.framework */; }; 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; }; 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; CE4F78E90B9C8F530083B946 /* Logs.icns in Resources */ = {isa = PBXBuildFile; fileRef = CE4F78E80B9C8F530083B946 /* Logs.icns */; }; D3053AAD0DE60D6B008646CE /* libicucore.A.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D3053AAC0DE60D6B008646CE /* libicucore.A.dylib */; }; D3EEAE380D41B42700D43A7A /* LeopardFolder.tiff in Resources */ = {isa = PBXBuildFile; fileRef = D3F1D3020D3BFF72008F6C35 /* LeopardFolder.tiff */; }; D3EEAE390D41B42B00D43A7A /* AquaFolder.png in Resources */ = {isa = PBXBuildFile; fileRef = D3F1D2FF0D3BFF6A008F6C35 /* AquaFolder.png */; }; D3F1D3000D3BFF6A008F6C35 /* AquaFolder.png in Resources */ = {isa = PBXBuildFile; fileRef = D3F1D2FF0D3BFF6A008F6C35 /* AquaFolder.png */; }; D3F1D3030D3BFF72008F6C35 /* LeopardFolder.tiff in Resources */ = {isa = PBXBuildFile; fileRef = D3F1D3020D3BFF72008F6C35 /* LeopardFolder.tiff */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 09D6604409FD37DD0000BA00 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; proxyType = 1; remoteGlobalIDString = 8D1107260486CEB800E47090; remoteInfo = Connection; }; 09D6604609FD37DD0000BA00 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; proxyType = 1; remoteGlobalIDString = 79CFD12509F702BE00172CDD; remoteInfo = Framework; }; 795FF4FB0B0C014B00EA9292 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; proxyType = 1; remoteGlobalIDString = 79CFD12509F702BE00172CDD; remoteInfo = Framework; }; 7983E7030B0C4E3300F5078E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; proxyType = 1; remoteGlobalIDString = 7983E6D10B0C4DF000F5078E; remoteInfo = DropletLauncher; }; 7983E7E90B0C4F4200F5078E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; proxyType = 1; remoteGlobalIDString = 79CFD12509F702BE00172CDD; remoteInfo = Framework; }; 7983E7EB0B0C4F4600F5078E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; proxyType = 1; remoteGlobalIDString = 795FF4F00B0C013200EA9292; remoteInfo = DropletHelper; }; 7983E8360B0C5B9800F5078E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; proxyType = 1; remoteGlobalIDString = 7983E7E80B0C4F2200F5078E; remoteInfo = "Build All"; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 79CFD96909F70A4D00172CDD /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 79CFD96B09F70A5C00172CDD /* Connection.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 089C165DFE840E0CC02AAC07 /* en */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; lineEnding = 0; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 09D6601D09FD37990000BA00 /* UnitTest.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTest.octest; sourceTree = BUILT_PRODUCTS_DIR; }; 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 29B97319FDCFA39411CA2CEA /* en */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = en; path = en.lproj/MainMenu.nib; sourceTree = ""; }; 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 32CA4F630368D1EE00C91783 /* Connection_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Connection_Prefix.pch; sourceTree = ""; }; 3C2D8EC10A63FF31008FE1B0 /* zh_TW */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; lineEnding = 0; name = zh_TW; path = zh_TW.lproj/Localizable.strings; sourceTree = ""; }; 3C2D8EC20A63FF3B008FE1B0 /* fr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; lineEnding = 0; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 3C2D8EC30A63FF41008FE1B0 /* da */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; lineEnding = 0; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; 790FF0CC0ACCE4B7004B4021 /* KTLog Viewer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "KTLog Viewer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 790FF0CE0ACCE4B7004B4021 /* KTLog Viewer-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "KTLog Viewer-Info.plist"; sourceTree = ""; }; 790FF0D60ACCE4F2004B4021 /* ktlogviewer_main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ktlogviewer_main.m; sourceTree = ""; }; 790FF0DC0ACCE8EA004B4021 /* en */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = en; path = en.lproj/KTLogViewer.nib; sourceTree = ""; }; 790FF0E20ACCE966004B4021 /* KTLogController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = KTLogController.h; sourceTree = ""; }; 790FF0E30ACCE966004B4021 /* KTLogController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = KTLogController.m; sourceTree = ""; }; 794EFDD10A5CD6CA00913151 /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = /usr/lib/libcrypto.dylib; sourceTree = ""; }; 794EFDD40A5CD6F900913151 /* libssl.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssl.dylib; path = /usr/lib/libssl.dylib; sourceTree = ""; }; 795A01090B116832006905FA /* edit.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = edit.png; sourceTree = ""; }; 795FF4F10B0C013200EA9292 /* DropletHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DropletHelper.app; sourceTree = BUILT_PRODUCTS_DIR; }; 795FF4F30B0C013200EA9292 /* DropletHelper-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "DropletHelper-Info.plist"; sourceTree = ""; }; 795FF5010B0C018100EA9292 /* DropletMain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DropletMain.m; sourceTree = ""; }; 795FF5060B0C048700EA9292 /* en */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = en; path = en.lproj/Droplet.nib; sourceTree = ""; }; 795FF50C0B0C05F400EA9292 /* DropletController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = DropletController.m; sourceTree = ""; }; 795FF50D0B0C05F400EA9292 /* DropletController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DropletController.h; sourceTree = ""; }; 796DB2F609F8BB1D0065897B /* SecurityInterface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SecurityInterface.framework; path = /System/Library/Frameworks/SecurityInterface.framework; sourceTree = ""; }; 797CC8C609F861770063FF9B /* en */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; lineEnding = 0; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 798300060B0D4A2600F5078E /* en */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = en; path = en.lproj/DropletLauncher.nib; sourceTree = ""; }; 7983E0930B0C1A8500F5078E /* DropletOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropletOutlineView.h; sourceTree = ""; }; 7983E0940B0C1A8500F5078E /* DropletOutlineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DropletOutlineView.m; sourceTree = ""; }; 7983E6730B0C41E300F5078E /* DropletLauncher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DropletLauncher.m; sourceTree = ""; }; 7983E6CB0B0C4D8800F5078E /* DropletIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = DropletIcon.icns; sourceTree = ""; }; 7983E6D20B0C4DF000F5078E /* DropletLauncher */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = DropletLauncher; sourceTree = BUILT_PRODUCTS_DIR; }; 7983F3EC0B0D1B8B00F5078E /* DropletPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropletPanel.h; sourceTree = ""; }; 7983F3ED0B0D1B8B00F5078E /* DropletPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DropletPanel.m; sourceTree = ""; }; 79CFD12609F702BE00172CDD /* Connection.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Connection.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 79CFD12709F702BE00172CDD /* Framework-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Framework-Info.plist"; sourceTree = ""; }; 79CFD8CD09F706F500172CDD /* Controller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Controller.h; sourceTree = SOURCE_ROOT; }; 79CFD8CE09F706F500172CDD /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Controller.m; sourceTree = SOURCE_ROOT; }; 79CFD8CF09F706F500172CDD /* FileTransfer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileTransfer.h; sourceTree = SOURCE_ROOT; }; 79CFD8D009F706F500172CDD /* FileTransfer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileTransfer.m; sourceTree = SOURCE_ROOT; }; 79CFD8D109F706F500172CDD /* InputDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputDialog.h; sourceTree = SOURCE_ROOT; }; 79CFD8D209F706F500172CDD /* InputDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InputDialog.m; sourceTree = SOURCE_ROOT; }; 79CFD8D309F706F500172CDD /* PermissionsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PermissionsController.h; sourceTree = SOURCE_ROOT; }; 79CFD8D409F706F500172CDD /* PermissionsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PermissionsController.m; sourceTree = SOURCE_ROOT; }; 79CFD8D509F706F500172CDD /* ProgressCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProgressCell.h; sourceTree = SOURCE_ROOT; }; 79CFD8D609F706F500172CDD /* ProgressCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProgressCell.m; sourceTree = SOURCE_ROOT; }; 79CFD8DD09F7071400172CDD /* en */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = en; path = en.lproj/InputDialog.nib; sourceTree = ""; }; 79CFD8DF09F7071400172CDD /* en */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = en; path = en.lproj/Permissions.nib; sourceTree = ""; }; 79CFD8E809F7076200172CDD /* connect.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = connect.tiff; sourceTree = SOURCE_ROOT; }; 79CFD8E909F7076200172CDD /* console.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = console.tiff; sourceTree = SOURCE_ROOT; }; 79CFD8EA09F7076200172CDD /* delete.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = delete.tiff; sourceTree = SOURCE_ROOT; }; 79CFD8EB09F7076200172CDD /* download.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = download.tif; sourceTree = SOURCE_ROOT; }; 79CFD8ED09F7076200172CDD /* home.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = home.tiff; sourceTree = SOURCE_ROOT; }; 79CFD8EE09F7076200172CDD /* icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = icon.icns; sourceTree = SOURCE_ROOT; }; 79CFD8EF09F7076200172CDD /* lock.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = lock.tiff; sourceTree = SOURCE_ROOT; }; 79CFD8F009F7076200172CDD /* redo.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = redo.tif; sourceTree = SOURCE_ROOT; }; 79CFD8F109F7076200172CDD /* symlink_file.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = symlink_file.tif; sourceTree = SOURCE_ROOT; }; 79CFD8F209F7076200172CDD /* symlink_folder.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = symlink_folder.tif; sourceTree = SOURCE_ROOT; }; 79CFD8F309F7076200172CDD /* upload.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = upload.tif; sourceTree = SOURCE_ROOT; }; 79CFD92C09F7080B00172CDD /* libcurl.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcurl.dylib; path = /usr/lib/libcurl.dylib; sourceTree = ""; }; 79CFD92D09F7080B00172CDD /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = /usr/lib/libz.dylib; sourceTree = ""; }; 79CFD93609F7084000172CDD /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = ""; }; 79CFD97A09F70AA700172CDD /* network_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = network_icon.png; sourceTree = ""; }; 79CFD97B09F70AA700172CDD /* Stop.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Stop.tiff; sourceTree = ""; }; 79F9532B09FDC3A80041E345 /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = /System/Library/Frameworks/ApplicationServices.framework; sourceTree = ""; }; 79FB807109F74185006E7D11 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; 8D1107320486CEB800E47090 /* Connection.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Connection.app; sourceTree = BUILT_PRODUCTS_DIR; }; CE1AD3DF0A7E845A0083C01E /* it */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; lineEnding = 0; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; CE4F78E80B9C8F530083B946 /* Logs.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Logs.icns; sourceTree = ""; }; CE55A6620AD194740091C8AE /* zh_CN */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; lineEnding = 0; name = zh_CN; path = zh_CN.lproj/Localizable.strings; sourceTree = ""; }; CE9FC9550DF70F32002330B2 /* da */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = ""; }; CE9FC95B0DF70F3D002330B2 /* de */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; CE9FC95C0DF70F44002330B2 /* fr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; CE9FC95D0DF70F49002330B2 /* it */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; CE9FC95E0DF70F50002330B2 /* ja */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = ""; }; CE9FC9610DF70F56002330B2 /* zh_CN */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = zh_CN; path = zh_CN.lproj/InfoPlist.strings; sourceTree = ""; }; CE9FC9620DF70F5C002330B2 /* zh_TW */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = zh_TW; path = zh_TW.lproj/InfoPlist.strings; sourceTree = ""; }; CEA9AFD30A64224100855897 /* ja */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; lineEnding = 0; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; CEB563850A7AB7070081179A /* de */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; lineEnding = 0; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; CEF894CF0BD72A45002886AA /* da */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = da; path = da.lproj/DropletLauncher.nib; sourceTree = ""; }; CEF894E90BD72A55002886AA /* de */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = de; path = de.lproj/DropletLauncher.nib; sourceTree = ""; }; CEF894EE0BD72A5C002886AA /* fr */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = fr; path = fr.lproj/DropletLauncher.nib; sourceTree = ""; }; CEF894F30BD72A62002886AA /* it */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = it; path = it.lproj/DropletLauncher.nib; sourceTree = ""; }; CEF894F80BD72A6A002886AA /* ja */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = ja; path = ja.lproj/DropletLauncher.nib; sourceTree = ""; }; CEF894FD0BD72A70002886AA /* zh_CN */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = zh_CN; path = zh_CN.lproj/DropletLauncher.nib; sourceTree = ""; }; CEF895020BD72A76002886AA /* zh_TW */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = zh_TW; path = zh_TW.lproj/DropletLauncher.nib; sourceTree = ""; }; D3053AAC0DE60D6B008646CE /* libicucore.A.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.A.dylib; path = /usr/lib/libicucore.A.dylib; sourceTree = ""; }; D3F1D2FF0D3BFF6A008F6C35 /* AquaFolder.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AquaFolder.png; sourceTree = ""; }; D3F1D3020D3BFF72008F6C35 /* LeopardFolder.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = LeopardFolder.tiff; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 09D6601A09FD37990000BA00 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 09D6603A09FD37CD0000BA00 /* Connection.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 790FF0CA0ACCE4B7004B4021 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 790FF23E0ACCEB46004B4021 /* AppKit.framework in Frameworks */, 790FF23F0ACCEB46004B4021 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 795FF4EF0B0C013200EA9292 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 7983E24B0B0C245100F5078E /* Cocoa.framework in Frameworks */, 7983E2310B0C244700F5078E /* Connection.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 7983E6D00B0C4DF000F5078E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 7983E7000B0C4E2800F5078E /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 79CFD12409F702BE00172CDD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 79CFD92F09F7080B00172CDD /* libz.dylib in Frameworks */, 79CFD93709F7084000172CDD /* Security.framework in Frameworks */, 79FB807209F74185006E7D11 /* Carbon.framework in Frameworks */, 796DB30109F8BB1D0065897B /* SecurityInterface.framework in Frameworks */, 79F9533D09FDC3A80041E345 /* ApplicationServices.framework in Frameworks */, 794EFDD20A5CD6CA00913151 /* libcrypto.dylib in Frameworks */, 794EFDD50A5CD6F900913151 /* libssl.dylib in Frameworks */, D3053AAD0DE60D6B008646CE /* libicucore.A.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 8D11072E0486CEB800E47090 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 79FB808A09F741D6006E7D11 /* Carbon.framework in Frameworks */, 79F563FF0AC8C5F2006AD3E2 /* Security.framework in Frameworks */, 79CFD97009F70A7E00172CDD /* Connection.framework in Frameworks */, 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { isa = PBXGroup; children = ( D3053AAC0DE60D6B008646CE /* libicucore.A.dylib */, 794EFDD40A5CD6F900913151 /* libssl.dylib */, 794EFDD10A5CD6CA00913151 /* libcrypto.dylib */, 79F9532B09FDC3A80041E345 /* ApplicationServices.framework */, 796DB2F609F8BB1D0065897B /* SecurityInterface.framework */, 79FB807109F74185006E7D11 /* Carbon.framework */, 79CFD93609F7084000172CDD /* Security.framework */, 79CFD92C09F7080B00172CDD /* libcurl.dylib */, 79CFD92D09F7080B00172CDD /* libz.dylib */, 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, ); name = "Linked Frameworks"; sourceTree = ""; }; 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { isa = PBXGroup; children = ( 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, 29B97325FDCFA39411CA2CEA /* Foundation.framework */, ); name = "Other Frameworks"; sourceTree = ""; }; 19C28FACFE9D520D11CA2CBB /* Products */ = { isa = PBXGroup; children = ( 8D1107320486CEB800E47090 /* Connection.app */, 79CFD12609F702BE00172CDD /* Connection.framework */, 09D6601D09FD37990000BA00 /* UnitTest.octest */, 790FF0CC0ACCE4B7004B4021 /* KTLog Viewer.app */, 795FF4F10B0C013200EA9292 /* DropletHelper.app */, 7983E6D20B0C4DF000F5078E /* DropletLauncher */, ); name = Products; sourceTree = ""; }; 29B97314FDCFA39411CA2CEA /* Connection */ = { isa = PBXGroup; children = ( 790FF0C30ACCE483004B4021 /* KTLog Viewer */, 795FF4EA0B0C00FF00EA9292 /* Droplet */, 79CFD11709F7020500172CDD /* Test Application */, 29B97315FDCFA39411CA2CEA /* Other Sources */, 29B97317FDCFA39411CA2CEA /* Resources */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, ); name = Connection; sourceTree = ""; }; 29B97315FDCFA39411CA2CEA /* Other Sources */ = { isa = PBXGroup; children = ( 32CA4F630368D1EE00C91783 /* Connection_Prefix.pch */, 29B97316FDCFA39411CA2CEA /* main.m */, ); name = "Other Sources"; sourceTree = ""; }; 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( CE4F78E80B9C8F530083B946 /* Logs.icns */, 797CC8C509F861770063FF9B /* Localizable.strings */, 79CFD8E709F7072100172CDD /* Interfaces */, 79CFD8E409F7071E00172CDD /* Images */, 79CFD12709F702BE00172CDD /* Framework-Info.plist */, 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, ); name = Resources; sourceTree = ""; }; 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, ); name = Frameworks; sourceTree = ""; }; 790FF0C30ACCE483004B4021 /* KTLog Viewer */ = { isa = PBXGroup; children = ( 790FF0E20ACCE966004B4021 /* KTLogController.h */, 790FF0E30ACCE966004B4021 /* KTLogController.m */, 790FF0DB0ACCE8EA004B4021 /* KTLogViewer.nib */, 790FF0D60ACCE4F2004B4021 /* ktlogviewer_main.m */, 790FF0CE0ACCE4B7004B4021 /* KTLog Viewer-Info.plist */, ); name = "KTLog Viewer"; sourceTree = ""; }; 795FF4EA0B0C00FF00EA9292 /* Droplet */ = { isa = PBXGroup; children = ( 7983E6CB0B0C4D8800F5078E /* DropletIcon.icns */, 795FF50D0B0C05F400EA9292 /* DropletController.h */, 795FF50C0B0C05F400EA9292 /* DropletController.m */, 795FF5010B0C018100EA9292 /* DropletMain.m */, 795FF4F30B0C013200EA9292 /* DropletHelper-Info.plist */, 7983E0930B0C1A8500F5078E /* DropletOutlineView.h */, 7983E0940B0C1A8500F5078E /* DropletOutlineView.m */, 7983E6730B0C41E300F5078E /* DropletLauncher.m */, 798300050B0D4A2600F5078E /* DropletLauncher.nib */, 795FF5050B0C048700EA9292 /* Droplet.nib */, 7983F3EC0B0D1B8B00F5078E /* DropletPanel.h */, 7983F3ED0B0D1B8B00F5078E /* DropletPanel.m */, ); name = Droplet; sourceTree = ""; }; 79CFD11709F7020500172CDD /* Test Application */ = { isa = PBXGroup; children = ( 79CFD8CD09F706F500172CDD /* Controller.h */, 79CFD8CE09F706F500172CDD /* Controller.m */, 79CFD8CF09F706F500172CDD /* FileTransfer.h */, 79CFD8D009F706F500172CDD /* FileTransfer.m */, 79CFD8D109F706F500172CDD /* InputDialog.h */, 79CFD8D209F706F500172CDD /* InputDialog.m */, 79CFD8D309F706F500172CDD /* PermissionsController.h */, 79CFD8D409F706F500172CDD /* PermissionsController.m */, 79CFD8D509F706F500172CDD /* ProgressCell.h */, 79CFD8D609F706F500172CDD /* ProgressCell.m */, ); name = "Test Application"; sourceTree = ""; }; 79CFD8E409F7071E00172CDD /* Images */ = { isa = PBXGroup; children = ( D3F1D3020D3BFF72008F6C35 /* LeopardFolder.tiff */, D3F1D2FF0D3BFF6A008F6C35 /* AquaFolder.png */, 795A01090B116832006905FA /* edit.png */, 79CFD97A09F70AA700172CDD /* network_icon.png */, 79CFD97B09F70AA700172CDD /* Stop.tiff */, 79CFD8E809F7076200172CDD /* connect.tiff */, 79CFD8E909F7076200172CDD /* console.tiff */, 79CFD8EA09F7076200172CDD /* delete.tiff */, 79CFD8EB09F7076200172CDD /* download.tif */, 79CFD8ED09F7076200172CDD /* home.tiff */, 79CFD8EE09F7076200172CDD /* icon.icns */, 79CFD8EF09F7076200172CDD /* lock.tiff */, 79CFD8F009F7076200172CDD /* redo.tif */, 79CFD8F109F7076200172CDD /* symlink_file.tif */, 79CFD8F209F7076200172CDD /* symlink_folder.tif */, 79CFD8F309F7076200172CDD /* upload.tif */, ); name = Images; sourceTree = ""; }; 79CFD8E709F7072100172CDD /* Interfaces */ = { isa = PBXGroup; children = ( 29B97318FDCFA39411CA2CEA /* MainMenu.nib */, 79CFD8DC09F7071400172CDD /* InputDialog.nib */, 79CFD8DE09F7071400172CDD /* Permissions.nib */, ); name = Interfaces; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 79CFD12109F702BE00172CDD /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 09D6601C09FD37990000BA00 /* UnitTest */ = { isa = PBXNativeTarget; buildConfigurationList = 09D6602609FD379A0000BA00 /* Build configuration list for PBXNativeTarget "UnitTest" */; buildPhases = ( 09D6601809FD37990000BA00 /* Resources */, 09D6601909FD37990000BA00 /* Sources */, 09D6601A09FD37990000BA00 /* Frameworks */, 09D6601B09FD37990000BA00 /* ShellScript */, ); buildRules = ( ); dependencies = ( 09D6604509FD37DD0000BA00 /* PBXTargetDependency */, 09D6604709FD37DD0000BA00 /* PBXTargetDependency */, ); name = UnitTest; productName = UnitTest; productReference = 09D6601D09FD37990000BA00 /* UnitTest.octest */; productType = "com.apple.product-type.bundle"; }; 790FF0CB0ACCE4B7004B4021 /* KTLog Viewer */ = { isa = PBXNativeTarget; buildConfigurationList = 790FF0CF0ACCE4B8004B4021 /* Build configuration list for PBXNativeTarget "KTLog Viewer" */; buildPhases = ( 790FF0C80ACCE4B7004B4021 /* Resources */, 790FF0C90ACCE4B7004B4021 /* Sources */, 790FF0CA0ACCE4B7004B4021 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = "KTLog Viewer"; productName = "KTLog Viewer"; productReference = 790FF0CC0ACCE4B7004B4021 /* KTLog Viewer.app */; productType = "com.apple.product-type.application"; }; 795FF4F00B0C013200EA9292 /* DropletHelper */ = { isa = PBXNativeTarget; buildConfigurationList = 795FF4F70B0C013300EA9292 /* Build configuration list for PBXNativeTarget "DropletHelper" */; buildPhases = ( 795FF4ED0B0C013200EA9292 /* Resources */, 795FF4EE0B0C013200EA9292 /* Sources */, 795FF4EF0B0C013200EA9292 /* Frameworks */, 7983ED650B0C749800F5078E /* Reset Install Path */, ); buildRules = ( ); dependencies = ( 795FF4FC0B0C014B00EA9292 /* PBXTargetDependency */, ); name = DropletHelper; productName = DropletHelper; productReference = 795FF4F10B0C013200EA9292 /* DropletHelper.app */; productType = "com.apple.product-type.application"; }; 7983E6D10B0C4DF000F5078E /* DropletLauncher */ = { isa = PBXNativeTarget; buildConfigurationList = 7983E6E20B0C4E1E00F5078E /* Build configuration list for PBXNativeTarget "DropletLauncher" */; buildPhases = ( 7983E6CF0B0C4DF000F5078E /* Sources */, 7983E6D00B0C4DF000F5078E /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = DropletLauncher; productName = DropletLauncher; productReference = 7983E6D20B0C4DF000F5078E /* DropletLauncher */; productType = "com.apple.product-type.tool"; }; 79CFD12509F702BE00172CDD /* Framework */ = { isa = PBXNativeTarget; buildConfigurationList = 79CFD12809F702BF00172CDD /* Build configuration list for PBXNativeTarget "Framework" */; buildPhases = ( 797CBBEB09F854B60063FF9B /* Generate Localizable.strings */, 79CFD12309F702BE00172CDD /* Sources */, 79CFD12409F702BE00172CDD /* Frameworks */, 79CFD12109F702BE00172CDD /* Headers */, 79CFD12209F702BE00172CDD /* Resources */, 7931260F0B3684B2002FF80D /* Strip UKKQueue */, ); buildRules = ( ); dependencies = ( 7983E7040B0C4E3300F5078E /* PBXTargetDependency */, ); name = Framework; productName = Framework; productReference = 79CFD12609F702BE00172CDD /* Connection.framework */; productType = "com.apple.product-type.framework"; }; 8D1107260486CEB800E47090 /* Connection */ = { isa = PBXNativeTarget; buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Connection" */; buildPhases = ( 8D1107290486CEB800E47090 /* Resources */, 8D11072C0486CEB800E47090 /* Sources */, 8D11072E0486CEB800E47090 /* Frameworks */, 79CFD96909F70A4D00172CDD /* CopyFiles */, ); buildRules = ( ); dependencies = ( 7983E8370B0C5B9800F5078E /* PBXTargetDependency */, ); name = Connection; productInstallPath = "$(HOME)/Applications"; productName = Connection; productReference = 8D1107320486CEB800E47090 /* Connection.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "ConnectionApp" */; compatibilityVersion = "Xcode 2.4"; hasScannedForEncodings = 1; knownRegions = ( en, ja, fr, de, zh_TW, da, it, zh_CN, ); mainGroup = 29B97314FDCFA39411CA2CEA /* Connection */; projectDirPath = ""; projectRoot = ""; targets = ( 7983E7E80B0C4F2200F5078E /* Build All */, 8D1107260486CEB800E47090 /* Connection */, 79CFD12509F702BE00172CDD /* Framework */, 09D6601C09FD37990000BA00 /* UnitTest */, 790FF0CB0ACCE4B7004B4021 /* KTLog Viewer */, 795FF4F00B0C013200EA9292 /* DropletHelper */, 7983E6D10B0C4DF000F5078E /* DropletLauncher */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 09D6601809FD37990000BA00 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 790FF0C80ACCE4B7004B4021 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 790FF0DD0ACCE8EA004B4021 /* KTLogViewer.nib in Resources */, CE4F78E90B9C8F530083B946 /* Logs.icns in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 795FF4ED0B0C013200EA9292 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 7983EDF90B0C802C00F5078E /* DropletIcon.icns in Resources */, 795FF5070B0C048700EA9292 /* Droplet.nib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 79CFD12209F702BE00172CDD /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 7983E7050B0C4E4E00F5078E /* DropletLauncher in Resources */, 797CC8C709F861770063FF9B /* Localizable.strings in Resources */, 7983E6CC0B0C4D8800F5078E /* DropletIcon.icns in Resources */, 798300070B0D4A2600F5078E /* DropletLauncher.nib in Resources */, 792E57860C8E2C2F00276AA2 /* symlink_folder.tif in Resources */, 792E57890C8E2C3300276AA2 /* symlink_file.tif in Resources */, D3EEAE380D41B42700D43A7A /* LeopardFolder.tiff in Resources */, D3EEAE390D41B42B00D43A7A /* AquaFolder.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 8D1107290486CEB800E47090 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */, 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 79CFD8E009F7071400172CDD /* InputDialog.nib in Resources */, 79CFD8E109F7071400172CDD /* Permissions.nib in Resources */, 79CFD8F409F7076200172CDD /* connect.tiff in Resources */, 79CFD8F509F7076200172CDD /* console.tiff in Resources */, 79CFD8F609F7076200172CDD /* delete.tiff in Resources */, 79CFD8F709F7076200172CDD /* download.tif in Resources */, 79CFD8F909F7076200172CDD /* home.tiff in Resources */, 79CFD8FA09F7076200172CDD /* icon.icns in Resources */, 79CFD8FB09F7076200172CDD /* lock.tiff in Resources */, 79CFD8FC09F7076200172CDD /* redo.tif in Resources */, 79CFD8FD09F7076200172CDD /* symlink_file.tif in Resources */, 79CFD8FE09F7076200172CDD /* symlink_folder.tif in Resources */, 79CFD8FF09F7076200172CDD /* upload.tif in Resources */, 79CFD97C09F70AA700172CDD /* network_icon.png in Resources */, 79CFD97D09F70AA700172CDD /* Stop.tiff in Resources */, 795A010A0B116832006905FA /* edit.png in Resources */, D3F1D3000D3BFF6A008F6C35 /* AquaFolder.png in Resources */, D3F1D3030D3BFF72008F6C35 /* LeopardFolder.tiff in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 09D6601B09FD37990000BA00 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; }; 7931260F0B3684B2002FF80D /* Strip UKKQueue */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Strip UKKQueue"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "HAS_SYMBOLS=`nm ${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH} |grep .objc_class_name_UKKQueue |grep -w A`\necho $HAS_SYMBOLS\nif [ \"$HAS_SYMBOLS\" == \"\" ]\nthen\n\techo \"No need to strip symbols\"\nelse\n\tnmedit -R \"${SOURCE_ROOT}/UKKQueue_Symbols\" \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\nfi\n"; showEnvVarsInLog = 0; }; 797CBBEB09F854B60063FF9B /* Generate Localizable.strings */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; files = ( ); inputPaths = ( ); name = "Generate Localizable.strings"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; shellScript = "echo \"Generating Localizable.strings ...\"\n\ncd \"${SRCROOT}\"\ngenstrings -littleEndian -q -u -s LocalizedStringInConnectionKitBundle -o en.lproj *.m\n"; showEnvVarsInLog = 0; }; 7983E7EE0B0C4F5100F5078E /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "#!/bin/sh\n\n# Copy the DropletHelper application into the Resources folder of the framework\n\nLAUNCHER_PATH=$CONFIGURATION_BUILD_DIR/DropletHelper.app\nCK_FRAMEWORK_PATH=$CONFIGURATION_BUILD_DIR/Connection.framework/Versions/A/Resources\n\nrm -rf $CK_FRAMEWORK_PATH/DropletHelper.app\n\nmv $LAUNCHER_PATH $CK_FRAMEWORK_PATH\n"; }; 7983ED650B0C749800F5078E /* Reset Install Path */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Reset Install Path"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/Developer/usr/bin/install_name_tool -change @executable_path/../Frameworks/Connection.framework/Versions/A/Connection @executable_path/../../../../Connection $CONFIGURATION_BUILD_DIR/DropletHelper.app/Contents/MacOS/DropletHelper"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 09D6601909FD37990000BA00 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 790FF0C90ACCE4B7004B4021 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 790FF0D70ACCE4F2004B4021 /* ktlogviewer_main.m in Sources */, 790FF0E40ACCE966004B4021 /* KTLogController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 795FF4EE0B0C013200EA9292 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 795FF5020B0C018100EA9292 /* DropletMain.m in Sources */, 795FF50E0B0C05F400EA9292 /* DropletController.m in Sources */, 7983E0950B0C1A8500F5078E /* DropletOutlineView.m in Sources */, 7983F3EE0B0D1B8B00F5078E /* DropletPanel.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 7983E6CF0B0C4DF000F5078E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 7983E6DE0B0C4E0000F5078E /* DropletLauncher.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 79CFD12309F702BE00172CDD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 8D11072C0486CEB800E47090 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 8D11072D0486CEB800E47090 /* main.m in Sources */, 79CFD8D709F706F500172CDD /* Controller.m in Sources */, 79CFD8D809F706F500172CDD /* FileTransfer.m in Sources */, 79CFD8D909F706F500172CDD /* InputDialog.m in Sources */, 79CFD8DA09F706F500172CDD /* PermissionsController.m in Sources */, 79CFD8DB09F706F500172CDD /* ProgressCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 09D6604509FD37DD0000BA00 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 8D1107260486CEB800E47090 /* Connection */; targetProxy = 09D6604409FD37DD0000BA00 /* PBXContainerItemProxy */; }; 09D6604709FD37DD0000BA00 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 79CFD12509F702BE00172CDD /* Framework */; targetProxy = 09D6604609FD37DD0000BA00 /* PBXContainerItemProxy */; }; 795FF4FC0B0C014B00EA9292 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 79CFD12509F702BE00172CDD /* Framework */; targetProxy = 795FF4FB0B0C014B00EA9292 /* PBXContainerItemProxy */; }; 7983E7040B0C4E3300F5078E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 7983E6D10B0C4DF000F5078E /* DropletLauncher */; targetProxy = 7983E7030B0C4E3300F5078E /* PBXContainerItemProxy */; }; 7983E7EA0B0C4F4200F5078E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 79CFD12509F702BE00172CDD /* Framework */; targetProxy = 7983E7E90B0C4F4200F5078E /* PBXContainerItemProxy */; }; 7983E7EC0B0C4F4600F5078E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 795FF4F00B0C013200EA9292 /* DropletHelper */; targetProxy = 7983E7EB0B0C4F4600F5078E /* PBXContainerItemProxy */; }; 7983E8370B0C5B9800F5078E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 7983E7E80B0C4F2200F5078E /* Build All */; targetProxy = 7983E8360B0C5B9800F5078E /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 089C165DFE840E0CC02AAC07 /* en */, CE9FC9550DF70F32002330B2 /* da */, CE9FC95B0DF70F3D002330B2 /* de */, CE9FC95C0DF70F44002330B2 /* fr */, CE9FC95D0DF70F49002330B2 /* it */, CE9FC95E0DF70F50002330B2 /* ja */, CE9FC9610DF70F56002330B2 /* zh_CN */, CE9FC9620DF70F5C002330B2 /* zh_TW */, ); name = InfoPlist.strings; sourceTree = ""; }; 29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = { isa = PBXVariantGroup; children = ( 29B97319FDCFA39411CA2CEA /* en */, ); name = MainMenu.nib; sourceTree = ""; }; 790FF0DB0ACCE8EA004B4021 /* KTLogViewer.nib */ = { isa = PBXVariantGroup; children = ( 790FF0DC0ACCE8EA004B4021 /* en */, ); name = KTLogViewer.nib; sourceTree = ""; }; 795FF5050B0C048700EA9292 /* Droplet.nib */ = { isa = PBXVariantGroup; children = ( 795FF5060B0C048700EA9292 /* en */, ); name = Droplet.nib; sourceTree = ""; }; 797CC8C509F861770063FF9B /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( 797CC8C609F861770063FF9B /* en */, 3C2D8EC10A63FF31008FE1B0 /* zh_TW */, 3C2D8EC20A63FF3B008FE1B0 /* fr */, 3C2D8EC30A63FF41008FE1B0 /* da */, CEA9AFD30A64224100855897 /* ja */, CEB563850A7AB7070081179A /* de */, CE1AD3DF0A7E845A0083C01E /* it */, CE55A6620AD194740091C8AE /* zh_CN */, ); name = Localizable.strings; sourceTree = ""; }; 798300050B0D4A2600F5078E /* DropletLauncher.nib */ = { isa = PBXVariantGroup; children = ( 798300060B0D4A2600F5078E /* en */, CEF894CF0BD72A45002886AA /* da */, CEF894E90BD72A55002886AA /* de */, CEF894EE0BD72A5C002886AA /* fr */, CEF894F30BD72A62002886AA /* it */, CEF894F80BD72A6A002886AA /* ja */, CEF894FD0BD72A70002886AA /* zh_CN */, CEF895020BD72A76002886AA /* zh_TW */, ); name = DropletLauncher.nib; sourceTree = ""; }; 79CFD8DC09F7071400172CDD /* InputDialog.nib */ = { isa = PBXVariantGroup; children = ( 79CFD8DD09F7071400172CDD /* en */, ); name = InputDialog.nib; sourceTree = ""; }; 79CFD8DE09F7071400172CDD /* Permissions.nib */ = { isa = PBXVariantGroup; children = ( 79CFD8DF09F7071400172CDD /* en */, ); name = Permissions.nib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 09D6602709FD379A0000BA00 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(value)", "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", ); GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Cocoa.framework/Headers/Cocoa.h"; INFOPLIST_FILE = "UnitTest-Info.plist"; INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles"; OTHER_LDFLAGS = ( "-framework", Cocoa, "-framework", SenTestingKit, ); PREBINDING = NO; PRODUCT_NAME = UnitTest; WRAPPER_EXTENSION = octest; ZERO_LINK = NO; }; name = Debug; }; 09D6602809FD379A0000BA00 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; FRAMEWORK_SEARCH_PATHS = ( "$(value)", "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", ); GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Cocoa.framework/Headers/Cocoa.h"; INFOPLIST_FILE = "UnitTest-Info.plist"; INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles"; OTHER_LDFLAGS = ( "-framework", Cocoa, "-framework", SenTestingKit, ); PREBINDING = NO; PRODUCT_NAME = UnitTest; WRAPPER_EXTENSION = octest; ZERO_LINK = NO; }; name = Release; }; 790FF0D00ACCE4B8004B4021 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; INFOPLIST_FILE = "KTLog Viewer-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PREBINDING = NO; PRODUCT_NAME = "KTLog Viewer"; WRAPPER_EXTENSION = app; ZERO_LINK = YES; }; name = Debug; }; 790FF0D10ACCE4B8004B4021 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; INFOPLIST_FILE = "KTLog Viewer-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PREBINDING = NO; PRODUCT_NAME = "KTLog Viewer"; WRAPPER_EXTENSION = app; ZERO_LINK = NO; }; name = Release; }; 795FF4F80B0C013300EA9292 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; INFOPLIST_FILE = "DropletHelper-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PREBINDING = NO; PRODUCT_NAME = DropletHelper; WRAPPER_EXTENSION = app; ZERO_LINK = NO; }; name = Debug; }; 795FF4F90B0C013300EA9292 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; INFOPLIST_FILE = "DropletHelper-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PREBINDING = NO; PRODUCT_NAME = DropletHelper; WRAPPER_EXTENSION = app; ZERO_LINK = NO; }; name = Release; }; 7983E6E30B0C4E1E00F5078E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; INSTALL_PATH = "$(HOME)/bin"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PREBINDING = NO; PRODUCT_NAME = DropletLauncher; ZERO_LINK = YES; }; name = Debug; }; 7983E6E40B0C4E1E00F5078E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; INSTALL_PATH = "$(HOME)/bin"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PREBINDING = NO; PRODUCT_NAME = DropletLauncher; ZERO_LINK = NO; }; name = Release; }; 7983E7F10B0C4F6F00F5078E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; PRODUCT_NAME = "Build All"; }; name = Debug; }; 7983E7F20B0C4F6F00F5078E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; PRODUCT_NAME = "Build All"; ZERO_LINK = NO; }; name = Release; }; 79A8578D0B9267CA00D9C844 /* Plugin */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; INSTALL_PATH = "$(HOME)/bin"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PREBINDING = NO; PRODUCT_NAME = DropletLauncher; ZERO_LINK = NO; }; name = Plugin; }; 79A8578E0B9267CA00D9C844 /* Plugin */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; GCC_PFE_FILE_C_DIALECTS = "objective-c c++ objective-c++"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Connection_Prefix.pch; INFOPLIST_FILE = "Framework-Info.plist"; INSTALL_PATH = "@loader_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", ); LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)\""; OTHER_CFLAGS = "-falign-loops=16"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, "-seg1addr", 0xcf200000, "-header-pad_max_install_names", ); PREBINDING = NO; PRODUCT_NAME = Connection; ZERO_LINK = NO; }; name = Plugin; }; 79A8578F0B9267CA00D9C844 /* Plugin */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; INFOPLIST_FILE = "DropletHelper-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PREBINDING = NO; PRODUCT_NAME = DropletHelper; WRAPPER_EXTENSION = app; ZERO_LINK = NO; }; name = Plugin; }; 79A857900B9267CA00D9C844 /* Plugin */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; PRODUCT_NAME = "Build All"; ZERO_LINK = NO; }; name = Plugin; }; 79A857910B9267CA00D9C844 /* Plugin */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = ( ppc, i386, ); GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; PRODUCT_NAME = Connection; WRAPPER_EXTENSION = app; ZERO_LINK = NO; }; name = Plugin; }; 79A857920B9267CA00D9C844 /* Plugin */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; FRAMEWORK_SEARCH_PATHS = ( "$(value)", "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", ); GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Cocoa.framework/Headers/Cocoa.h"; INFOPLIST_FILE = "UnitTest-Info.plist"; INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles"; OTHER_LDFLAGS = ( "-framework", Cocoa, "-framework", SenTestingKit, ); PREBINDING = NO; PRODUCT_NAME = UnitTest; WRAPPER_EXTENSION = octest; ZERO_LINK = NO; }; name = Plugin; }; 79A857930B9267CA00D9C844 /* Plugin */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; INFOPLIST_FILE = "KTLog Viewer-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PREBINDING = NO; PRODUCT_NAME = "KTLog Viewer"; WRAPPER_EXTENSION = app; ZERO_LINK = NO; }; name = Plugin; }; 79A857940B9267CA00D9C844 /* Plugin */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = ( ppc, i386, ); GCC_INCREASE_PRECOMPILED_HEADER_SHARING = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; PREBINDING = NO; SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; }; name = Plugin; }; 79CFD12909F702BF00172CDD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_ENABLE_OBJC_GC = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PFE_FILE_C_DIALECTS = "objective-c c++ objective-c++"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Connection_Prefix.pch; INFOPLIST_FILE = "Framework-Info.plist"; INSTALL_PATH = "@executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", ); LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)\""; OTHER_CFLAGS = "-falign-loops=16"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, "-seg1addr", 0xcf200000, "-header-pad_max_install_names", ); PREBINDING = NO; PRODUCT_NAME = Connection; ZERO_LINK = NO; }; name = Debug; }; 79CFD12A09F702BF00172CDD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; DEPLOYMENT_POSTPROCESSING = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_ENABLE_OBJC_GC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; GCC_PFE_FILE_C_DIALECTS = "objective-c c++ objective-c++"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Connection_Prefix.pch; INFOPLIST_FILE = "Framework-Info.plist"; INSTALL_PATH = "@executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", ); LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)\""; OTHER_CFLAGS = "-falign-loops=16"; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, "-seg1addr", 0xcf200000, "-header-pad_max_install_names", ); PREBINDING = NO; PRODUCT_NAME = Connection; ZERO_LINK = NO; }; name = Release; }; C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ""; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; OTHER_LDFLAGS = ""; PRODUCT_NAME = Connection; WRAPPER_EXTENSION = app; ZERO_LINK = NO; }; name = Debug; }; C01FCF4C08A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = ( ppc, i386, ); GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; PRODUCT_NAME = Connection; WRAPPER_EXTENSION = app; ZERO_LINK = NO; }; name = Release; }; C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { GCC_INCREASE_PRECOMPILED_HEADER_SHARING = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; PREBINDING = NO; SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; }; name = Debug; }; C01FCF5008A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1)"; ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1 = "ppc i386"; GCC_INCREASE_PRECOMPILED_HEADER_SHARING = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; PREBINDING = NO; SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 09D6602609FD379A0000BA00 /* Build configuration list for PBXNativeTarget "UnitTest" */ = { isa = XCConfigurationList; buildConfigurations = ( 09D6602709FD379A0000BA00 /* Debug */, 09D6602809FD379A0000BA00 /* Release */, 79A857920B9267CA00D9C844 /* Plugin */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 790FF0CF0ACCE4B8004B4021 /* Build configuration list for PBXNativeTarget "KTLog Viewer" */ = { isa = XCConfigurationList; buildConfigurations = ( 790FF0D00ACCE4B8004B4021 /* Debug */, 790FF0D10ACCE4B8004B4021 /* Release */, 79A857930B9267CA00D9C844 /* Plugin */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 795FF4F70B0C013300EA9292 /* Build configuration list for PBXNativeTarget "DropletHelper" */ = { isa = XCConfigurationList; buildConfigurations = ( 795FF4F80B0C013300EA9292 /* Debug */, 795FF4F90B0C013300EA9292 /* Release */, 79A8578F0B9267CA00D9C844 /* Plugin */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 7983E6E20B0C4E1E00F5078E /* Build configuration list for PBXNativeTarget "DropletLauncher" */ = { isa = XCConfigurationList; buildConfigurations = ( 7983E6E30B0C4E1E00F5078E /* Debug */, 7983E6E40B0C4E1E00F5078E /* Release */, 79A8578D0B9267CA00D9C844 /* Plugin */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 7983E7F00B0C4F6F00F5078E /* Build configuration list for PBXAggregateTarget "Build All" */ = { isa = XCConfigurationList; buildConfigurations = ( 7983E7F10B0C4F6F00F5078E /* Debug */, 7983E7F20B0C4F6F00F5078E /* Release */, 79A857900B9267CA00D9C844 /* Plugin */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 79CFD12809F702BF00172CDD /* Build configuration list for PBXNativeTarget "Framework" */ = { isa = XCConfigurationList; buildConfigurations = ( 79CFD12909F702BF00172CDD /* Debug */, 79CFD12A09F702BF00172CDD /* Release */, 79A8578E0B9267CA00D9C844 /* Plugin */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Connection" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4B08A954540054247B /* Debug */, C01FCF4C08A954540054247B /* Release */, 79A857910B9267CA00D9C844 /* Plugin */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C01FCF4E08A954540054247B /* Build configuration list for PBXProject "ConnectionApp" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4F08A954540054247B /* Debug */, C01FCF5008A954540054247B /* Release */, 79A857940B9267CA00D9C844 /* Plugin */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; } ================================================ FILE: Example/ConnectionTest.h ================================================ // // ConnectionTest.h // Marvel // // Created by Dan Wood on 11/29/04. // Copyright (c) 2004 Biophony, LLC. All rights reserved. // #import @interface ConnectionTest : NSObject { NSMutableDictionary *myCallbackDictionary; unsigned long myUniqueNumber; NSThread *myMainThread; // not retained. Just for diagnostics. NSString *myCurrentDirectory; } - (NSMutableDictionary *)callbackDictionary; - (void)setCallbackDictionary:(NSMutableDictionary *)aCallbackDictionary; - (NSString *)currentDirectory; - (void)setCurrentDirectory:(NSString *)aCurrentDirectory; @end ================================================ FILE: Example/ConnectionTest.m ================================================ // // ConnectionTest.m // Marvel // // Created by Dan Wood on 11/29/04. // Copyright (c) 2004 Biophony, LLC. All rights reserved. // #import "ConnectionTest.h" #import "FTPConnection.h" #import "FileConnection.h" /* AVAILABLE MACROS UKPass() Pass always UKFail() Fail always UKTrue(condition) Pass if condition is true UKFalse(condition) Pass if condition is false UKNil(ref) Pass if ref is nil UKNotNil(ref) Pass if ref is not nil UKIntsEqual(a, b) Pass if a == b UKIntsNotEqual(a, b) Pass if a != b UKFloatsEqual(a, b, d) Pass if a == b UKFloatsNotEqual(a, b, d) Pass if a != b UKObjectsEqual(a, b) Pass if a isEqualTo: b UKObjectsNotEqual(a, b) Pass if NOT a isEqualTo: b UKObjectsSame(a, b) Pass if a (address) == b (address) UKObjectsNotSame(a, b) Pass if a (address) != b (address) UKStringsEqual(a, b) Pass if a isEqualToString: b UKStringsNotEqual(a, b) Pass if a NOT isEqualToString: b UKStringContains(a, b) Pass if a contains b UKStringDoesNotContain(a, b) Pass if a does NOT contain b UKRaisesException(exp) Pass if exp raises an exception UKDoesNotRaiseException(exp) Pass if exp does NOT raise an exception UKRaisesExceptionNamed(exp, b) Pass if exp raises an exception named b UKRaisesExceptionClass(exp, b) Pass if exp does NOT raise an exception named b */ @implementation ConnectionTest - (id)init { if (self = [super init]) { myUniqueNumber = (unsigned long) [NSDate timeIntervalSinceReferenceDate]; [self setCallbackDictionary:[NSMutableDictionary dictionary]]; } return self; } - (void)dealloc { [self setCallbackDictionary:nil]; [super dealloc]; } - (NSString *)currentDirectory { return myCurrentDirectory; } - (void)setCurrentDirectory:(NSString *)aCurrentDirectory { [aCurrentDirectory retain]; [myCurrentDirectory release]; myCurrentDirectory = aCurrentDirectory; } /* For all the connection methods, run a standard suite of tests. Test the following methods: (Need to hook up a delegate to self, somehow put it into a state of what is expected, so we'll know if it's called. If we expect it to be called and it's not, we can have it set an ivar and we'll check.) What will be tricky will be that this is async. We may need to queue up a bunch of operations, and then wait somehow until they have completed. Maybe make a runloop? Making connection changing to directory (good/bad) getting directory (as expected?) making directory (good/bad) making directory with certain permissions setting permissions (try a couple of times to make sure they actually change) (success/fail) rename delete delete directory upload file (some known file, like /etc/hosts, or make a new /tmp file with UUID) .... to directory resume upload file at offset (supported for all?) download file resumeDownload file cancel transfer get directory contents */ #pragma mark - #pragma mark Callbacks /*! These callbacks work by adding their method name to the dictionary so we can tell what has been run. Temporarily scrunched to one line per method, to make it easy to count and sort. */ - (void)connection:(id )con didChangeToDirectory:(NSString *)dirPath { [self setCurrentDirectory:dirPath]; NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:dirPath forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didConnectToHost:(NSString *)host { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:host forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didCreateDirectory:(NSString *)dirPath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:dirPath forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didDeleteDirectory:(NSString *)dirPath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:dirPath forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didDeleteFile:(NSString *)path { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:path forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didDisconnectFromHost:(NSString *)host { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:host forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didReceiveContents:(NSArray *)contents ofDirectory:(NSString *)dirPath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:contents forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didReceiveError:(NSError *)error { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:error forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didRename:(NSString *)fromPath to:(NSString *)toPath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSString stringWithFormat:@"%@ -> %@",fromPath,toPath] forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con didSetPermissionsForFile:(NSString *)path { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:path forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con download:(NSString *)path progressedTo:(NSNumber *)percent { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSString stringWithFormat:@"%@: %@%%",path,percent] forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con download:(NSString *)path receivedDataOfLength:(int)length { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSString stringWithFormat:@"%@: %d bytes",path,length] forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con downloadDidBegin:(NSString *)remotePath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:remotePath forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con downloadDidFinish:(NSString *)remotePath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:remotePath forKey:NSStringFromSelector(_cmd)]; } - (NSString *)connection:(id )con needsAccountForUsername:(NSString *)username { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:username forKey:NSStringFromSelector(_cmd)]; return @"foo"; } - (void)connection:(id )con upload:(NSString *)remotePath progressedTo:(NSNumber *)percent { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSString stringWithFormat:@"%@: %@%%",remotePath,percent] forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con upload:(NSString *)remotePath sentDataOfLength:(int)length { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSString stringWithFormat:@"%@: %d bytes",remotePath,length] forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con uploadDidBegin:(NSString *)remotePath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:remotePath forKey:NSStringFromSelector(_cmd)]; } - (void)connection:(id )con uploadDidFinish:(NSString *)remotePath { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:remotePath forKey:NSStringFromSelector(_cmd)]; } - (void)connectionDidCancelTransfer:(id )con { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSNumber numberWithBool:YES] forKey:NSStringFromSelector(_cmd)]; } - (void)connectionDidSendBadPassword:(id )con { NSLog(@"========= CALLBACK: %@", NSStringFromSelector(_cmd)); [myCallbackDictionary setObject:[NSNumber numberWithBool:YES] forKey:NSStringFromSelector(_cmd)]; } #pragma mark - #pragma mark Main Test Suite /*! Do a full test suite */ - (void)runTestSuiteWithConnection:(id )aConn expectingFailure:(BOOL) inExpectConnectionFailure { [aConn setDelegate:self]; BOOL done = NO; NSString *initialDirectory = nil; NSDate *dropDeadDate = [NSDate dateWithTimeIntervalSinceNow:120.0]; [aConn connect]; while (!done && NSOrderedAscending == [((NSDate *)[NSDate date]) compare:(NSDate *)dropDeadDate]) { if ([((NSObject *)aConn) isKindOfClass:[FTPConnection class]]) { NSLog(@"========= TEST: top of loop, state = %d, queue= %@", [((FTPConnection *)aConn) state], [((FTPConnection *)aConn) queueDescription]); } if (0 == [myCallbackDictionary count]) // We don't have any callbacks from the previous iteration { NSLog(@"========= TEST: will run NSRunLoop"); BOOL found = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:30.0]]; //[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:20.0]]; //[[NSRunLoop currentRunLoop] run]; NSLog(@"========= TEST: did run NSRunLoop, found = %d dict = %@", found, [[myCallbackDictionary allKeys] description]); if (!found) { done = true; // nothing found after waiting this long, so we're done. break; } } id value; NSDictionary *lastCallbackDictionary = [[myCallbackDictionary copy] autorelease]; [myCallbackDictionary removeAllObjects]; // clean out the original dict for the next pass if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:didReceiveError:"]) ) { NSLog(@"========= TEST: Error message: %@", value); UKTrue (inExpectConnectionFailure); // pass if we did expect an error! done = YES; // we're done anyhow. } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connectionDidSendBadPassword:"]) ) { UKTrue (inExpectConnectionFailure); // pass if we did expect an error! done = YES; // we're done anyhow. } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:didConnectToHost:"]) ) { NSLog(@"========= TEST: Connected to host: %@", value); UKFalse (inExpectConnectionFailure); // pass if we did not expect an error // NEXT TASK: MAKE A NEW DIRECTORY [aConn createDirectory:[NSString stringWithFormat:@"d%ld", myUniqueNumber]]; } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:didCreateDirectory:"]) ) { NSLog(@"========= TEST: Created directory called %@", [NSString stringWithFormat:@"d%ld", myUniqueNumber]); UKPass(); // Got our directory // NEXT TASK: Get the contents of the current directory [aConn directoryContents]; } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:didReceiveContents:ofDirectory:"]) ) { NSLog(@"========= TEST: Got directory contents %@", [value description]); UKPass(); // Got our directory ... REALY OUGHT TO VERIFY THAT OUR FILE IS IN THERE. // NEXT TASK: Change to that new directory [aConn changeToDirectory:[NSString stringWithFormat:@"d%ld", myUniqueNumber]]; } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:didChangeToDirectory:"]) ) { if (nil == initialDirectory) { initialDirectory = [self currentDirectory]; // this will depend on what connection we have UKPass(); // Got our directory ... REALY OUGHT TO VERIFY THAT OUR FILE IS IN THERE. } else // second change; make sure we are within initial directory { NSString *expectedNewDirectory = [initialDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"d%ld", myUniqueNumber]]; UKStringsEqual([self currentDirectory], expectedNewDirectory); } NSLog(@"========= TEST: Changed to directory %@", [value description]); // NEXT TASK: Upload a file [aConn uploadFile:@"/etc/hosts"]; } else if (nil != (value = [lastCallbackDictionary objectForKey:@"connection:uploadDidFinish:"]) ) { NSLog(@"========= TEST: Done uploading %@", [value description]); UKPass(); // Got our directory ... REALY OUGHT TO VERIFY THAT OUR FILE IS IN THERE. done = YES; // done for now ... we don't have any more tests. } /* changing to directory (good/bad) getting directory (as expected?) making directory (good/bad) making directory with certain permissions setting permissions (try a couple of times to make sure they actually change) (success/fail) rename delete delete directory upload file (some known file, like /etc/hosts, or make a new /tmp file with UUID) .... to directory resume upload file at offset (supported for all?) download file resumeDownload file cancel transfer get directory contents */ } [aConn disconnect]; NSLog(@"========= TEST: Disconnected."); if (! (NSOrderedAscending == [((NSDate *)[NSDate date]) compare:dropDeadDate]) ) { UKFail(); // Ran out of time! } } #pragma mark - #pragma mark File tests /*! Test a file connection. Since there is no connection, we can't test for a bad file connection */ - (void)testFileConnection { FileConnection *fc = [FileConnection connection]; [self runTestSuiteWithConnection:fc expectingFailure:NO]; } #pragma mark - #pragma mark FTP tests - (void)testRealFTPConnection { NSDictionary *environment = [[NSProcessInfo processInfo] environment]; NSString *password = [environment objectForKey:@"biophonyTestPassword"]; FTPConnection *c = [FTPConnection connectionToHost:@"test.biophony.com" port:nil username:@"test" password:password]; [self runTestSuiteWithConnection:c expectingFailure:NO]; } - (void) testQuotesScan { FTPConnection *c = [FTPConnection connectionToHost:@"bogus" port:nil username:@"test" password:@"nothing"]; UKNil([c scanBetweenQuotes:@"hey there we're the monkeys"]); UKNil([c scanBetweenQuotes:@"This has one \" quote mark"]); UKStringsEqual([c scanBetweenQuotes:@"empty \"\" quoted string"], @""); UKStringsEqual([c scanBetweenQuotes:@"257 \"/somethingee\" created"], @"/somethingee"); UKStringsEqual([c scanBetweenQuotes:@"257 \"/he said \"\"yo\"\" to me\" created"], @"/he said \"yo\" to me"); } /* - (void)testBadAccountFTPConnection { FTPConnection *c = [FTPConnection connectionToHost:@"test.biophony.com" port:nil username:@"somebodyelse" password:@"mypassword"]; [self runTestSuiteWithConnection:c expectingFailure:YES]; } - (void)testConnectionRefusedFTPConnection { FTPConnection *c = [FTPConnection connectionToHost:@"vorlon.karelia.com" port:nil username:@"somebodyelse" password:@"mypassword"]; [self runTestSuiteWithConnection:c expectingFailure:YES]; } - (void)testHangingFTPConnection { FTPConnection *c = [FTPConnection connectionToHost:@"ftp.zocalo.net" port:nil username:@"somebodyelse" password:@"mypassword"]; [self runTestSuiteWithConnection:c expectingFailure:YES]; } - (void)testNoHostFTPConnection { FTPConnection *c = [FTPConnection connectionToHost:@"fdjskalrejwfje.com" port:nil username:@"somebodyelse" password:@"mypassword"]; [self runTestSuiteWithConnection:c expectingFailure:YES]; } */ #pragma mark - #pragma mark Private Support - (NSMutableDictionary *)callbackDictionary { return myCallbackDictionary; } - (void)setCallbackDictionary:(NSMutableDictionary *)aCallbackDictionary { [aCallbackDictionary retain]; [myCallbackDictionary release]; myCallbackDictionary = aCallbackDictionary; } @end ================================================ FILE: Example/Connection_Prefix.pch ================================================ // For Mac OS X < 10.5. #ifndef NSINTEGER_DEFINED #define NSINTEGER_DEFINED #ifdef __LP64__ || NS_BUILD_32_LIKE_64 typedef long NSInteger; typedef unsigned long NSUInteger; #define NSIntegerMin LONG_MIN #define NSIntegerMax LONG_MAX #define NSUIntegerMax ULONG_MAX #else typedef int NSInteger; typedef unsigned int NSUInteger; #define NSIntegerMin INT_MIN #define NSIntegerMax INT_MAX #define NSUIntegerMax UINT_MAX #endif #endif // NSINTEGER_DEFINED #import "KTLog.h" ================================================ FILE: Example/Controller.h ================================================ /* Controller */ #import @class InputDialog; @protocol AbstractConnectionProtocol; @interface Controller : NSObject { IBOutlet NSButton *btnConnect; IBOutlet NSButton *btnDelete; IBOutlet NSButton *btnNewFolder; IBOutlet NSButton *btnPermissions; IBOutlet NSButton *btnRefresh; IBOutlet NSButton *btnStop; IBOutlet NSButton *cBtnCancel; IBOutlet NSButton *cBtnConnect; IBOutlet NSTextField *cHost; IBOutlet NSPanel *connectWindow; IBOutlet NSTextField *cPass; IBOutlet NSTextField *cPort; IBOutlet NSPopUpButton *cTypePopup; IBOutlet NSTextField *cURL; IBOutlet NSTextField *cUser; IBOutlet NSPopUpButton *localPopup; IBOutlet NSTableView *localTable; IBOutlet NSPopUpButton *remotePopup; IBOutlet NSTableView *remoteTable; IBOutlet NSTextField *status; IBOutlet NSTableView *transferTable; IBOutlet NSWindow *window; IBOutlet NSDrawer *logDrawer; IBOutlet NSTextView *log; IBOutlet NSOutlineView *savedHosts; IBOutlet NSButton *btnDisconnect; IBOutlet NSTextField *initialDirectory; IBOutlet NSTextView *fileCheckLog; IBOutlet NSButton *btnBrowseHost; IBOutlet NSButton *btnEdit; IBOutlet NSPopUpButton *oConMenu; id con; NSMutableArray *remoteFiles; NSMutableArray *localFiles; NSMutableArray *transfers; NSString *currentLocalPath; NSString *currentRemotePath; int downloadCounter; int uploadCounter; BOOL isConnected; NSMutableArray *_savedHosts; id selectedItem; InputDialog *check; } - (IBAction)cancelConnect:(id)sender; - (IBAction)connect:(id)sender; - (IBAction)deleteFile:(id)sender; - (IBAction)localFileSelected:(id)sender; - (IBAction)localPopupChanged:(id)sender; - (IBAction)newFolder:(id)sender; - (IBAction)permissions:(id)sender; - (IBAction)refresh:(id)sender; - (IBAction)remoteFileSelected:(id)sender; - (IBAction)remotePopupChanged:(id)sender; - (IBAction)showConnect:(id)sender; - (IBAction)stopTransfer:(id)sender; - (IBAction)transferSelected:(id)sender; @end ================================================ FILE: Example/Controller.m ================================================ #import "Controller.h" #import "ProgressCell.h" #import "InputDialog.h" #import "PermissionsController.h" #import "FileTransfer.h" #import static NSString *AutoSelect = @"Auto Select"; NSString *TransferTypeKey = @"Type"; NSString *TransferLocalFileKey = @"LocalFile"; NSString *TransferRemoteFileKey = @"RemoteFile"; NSString *TransferControllerKey = @"Controller"; NSString *TransferProgressKey = @"Progress"; int TransferTypeDown = 0; int TransferTypeUp = 1; NSString *cxRemoteFilePBoardType = @"cxRemoteFilePBoardType"; NSString *cxLocalFilePBoardType = @"cxLocalFilePBoardType"; //Storing to NSUser Defaults NSString *HostsKey = @"Hosts"; NSString *HostKey = @"Host"; NSString *PortKey = @"Port"; NSString *UsernameKey = @"Username"; NSString *ConnectionTypeKey = @"Connection"; NSString *URLKey = @"URL"; NSString *InitialDirectoryKey = @"InitialDirectory"; NSString *ProtocolKey = @"Protocol"; @interface Controller(PRivate) - (void)refreshLocal; - (void)refreshHosts; - (void)downloadFile:(NSString *)remote toFolder:(NSString *)local; - (void)uploadFile:(NSString *)local to:(NSString *)remote; @end @interface NSString (FileSizeFormatting) + (NSString *)formattedFileSizeWithBytes:(NSNumber *)filesize; @end @implementation NSString (FileSizeFormatting) + (NSString *)formattedFileSizeWithBytes:(NSNumber *)filesize { static NSString *suffix[] = { @"B", @"KB", @"MB", @"GB", @"TB", @"PB", @"EB" }; int i, c = 7; long size = [filesize longValue]; for (i = 0; i < c && size >= 1024; i++) { size = size / 1024; } return [NSString stringWithFormat:@"%ld %@", size, suffix[i]]; } @end @implementation Controller - (void)awakeFromNib { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSString *savedWindowRect = [ud objectForKey:[window frameAutosaveName]]; if (savedWindowRect) [window setFrame:NSRectFromString(savedWindowRect) display:YES]; BOOL showLog = [ud boolForKey:@"showLog"]; if (showLog) { [logDrawer open:self]; } remoteFiles = [[NSMutableArray array] retain]; localFiles = [[NSMutableArray array] retain]; transfers = [[NSMutableArray array] retain]; NSArray *conTypes = [AbstractConnection registeredConnectionTypes]; [cTypePopup removeAllItems]; [cTypePopup addItemWithTitle:AutoSelect]; [[cTypePopup menu] addItem:[NSMenuItem separatorItem]]; [cTypePopup addItemsWithTitles:conTypes]; [localTable setDataSource:self]; [remoteTable setDataSource:self]; [transferTable setDataSource:self]; CKTransferProgressCell *cell = [[CKTransferProgressCell alloc] init]; [[transferTable tableColumnWithIdentifier:@"progress"] setDataCell:cell]; [cell release]; currentLocalPath = [[NSString stringWithFormat:@"%@", NSHomeDirectory()] copy]; [self refreshLocal]; [remotePopup removeAllItems]; [remoteTable setDoubleAction:@selector(remoteDoubleClick:)]; [localTable setDoubleAction:@selector(localDoubleClick:)]; //drag and drop [localTable registerForDraggedTypes:[NSArray arrayWithObject:cxRemoteFilePBoardType]]; // [remoteTable registerForDraggedTypes:[NSArray arrayWithObjects:cxLocalFilePBoardType, NSFilenamesPboardType, nil]]; // [remoteTable setHidden:YES]; [cUser setStringValue:NSUserName()]; //Get saved hosts _savedHosts = [[NSMutableArray array] retain]; [_savedHosts addObject:[[[CKBonjourCategory alloc] init] autorelease]]; id hosts = [ud objectForKey:HostsKey]; if (hosts) { if ([hosts isKindOfClass:[NSArray class]]) { CKHostCategory *cat = [[CKHostCategory alloc] initWithName:NSLocalizedString(@"Saved Hosts", @"category name")]; NSEnumerator *e = [hosts objectEnumerator]; NSDictionary *cur; CKHost *h; while ((cur = [e nextObject])) { h = [[CKHost alloc] init]; [h setHost:[cur objectForKey:HostKey]]; [h setPort:[cur objectForKey:PortKey]]; [h setUsername:[cur objectForKey:UsernameKey]]; [h setInitialPath:[cur objectForKey:InitialDirectoryKey]]; if ([cur objectForKey:URLKey] && ![[cur objectForKey:URLKey] isEqualToString:@""]) { [h setURL:[NSURL URLWithString:[cur objectForKey:URLKey]]]; } [h setConnectionType:[cur objectForKey:ProtocolKey]]; [cat addHost:h]; [h release]; } [[ConnectionRegistry sharedRegistry] addCategory:cat]; [_savedHosts addObject:cat]; [cat release]; } } [ud removeObjectForKey:HostsKey]; [savedHosts setDataSource:[ConnectionRegistry sharedRegistry]]; [savedHosts setDelegate:self]; CKHostCell *acell = [[CKHostCell alloc] initImageCell:nil]; [[[savedHosts tableColumns] objectAtIndex:0] setDataCell:acell]; [acell release]; [self refreshHosts]; NSEnumerator *e = [[[ConnectionRegistry sharedRegistry] connections] objectEnumerator]; id cur; while ((cur = [e nextObject])) { if ([cur isKindOfClass:[CKHostCategory class]]) { [savedHosts expandItem:cur]; } } //have a timer to remove completed transfers [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(cleanTransferTable:) userInfo:nil repeats:YES]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registryChanged:) name:CKRegistryChangedNotification object:nil]; [oConMenu setMenu:[[ConnectionRegistry sharedRegistry] menu]]; //[self runAutomatedScript]; [[ConnectionRegistry sharedRegistry] handleFilterableOutlineView:savedHosts]; } - (void)hostnameChanged:(id)sender { [[savedHosts itemAtRow:[savedHosts selectedRow]] setHost:[sender stringValue]]; } - (void)portChanged:(id)sender { [[savedHosts itemAtRow:[savedHosts selectedRow]] setPort:[sender stringValue]]; } - (void)usernameChanged:(id)sender { [[savedHosts itemAtRow:[savedHosts selectedRow]] setUsername:[sender stringValue]]; } - (void)passwordChanged:(id)sender { [[savedHosts itemAtRow:[savedHosts selectedRow]] setPassword:[sender stringValue]]; } - (void)initialDirectoryChanged:(id)sender { [[savedHosts itemAtRow:[savedHosts selectedRow]] setInitialPath:[sender stringValue]]; } - (void)urlChanged:(id)sender { [[savedHosts itemAtRow:[savedHosts selectedRow]] setURL:[NSURL URLWithString:[sender stringValue]]]; } - (void)registryChanged:(NSNotification *)n { [savedHosts reloadData]; [oConMenu setMenu:[[ConnectionRegistry sharedRegistry] menu]]; } - (void)newCategory:(id)sender { id parent = [savedHosts itemAtRow:[savedHosts selectedRow]]; if ([parent isKindOfClass:[CKHost class]]) { parent = [parent category]; } CKHostCategory *cat = [[CKHostCategory alloc] initWithName:NSLocalizedString(@"New Category", @"new cat name")]; if (parent) { [parent addChildCategory:cat]; } else { [[ConnectionRegistry sharedRegistry] addCategory:cat]; } [cat release]; } - (void)newHost:(id)sender { id parent = [savedHosts itemAtRow:[savedHosts selectedRow]]; if ([parent isKindOfClass:[CKHost class]]) { parent = [parent category]; } CKHost *h = [[CKHost alloc] init]; if (parent) { [parent addHost:h]; } else { [[ConnectionRegistry sharedRegistry] addHost:h]; } [h release]; } - (void)checkForFile:(id)sender { if (!check) { check = [[InputDialog alloc] init]; [check setDialogTitle:@"Find File"]; } [check beginSheetModalForWindow:window delegate:self selector:@selector(fileCheck:receivedValue:)]; } - (void)fileCheck:(InputDialog *)input receivedValue:(NSString *)val { if (val) { [con checkExistenceOfPath:val]; } } - (void)connectionTypeChanged:(id)sender { [[savedHosts itemAtRow:[savedHosts selectedRow]] setConnectionType:[sender titleOfSelectedItem]]; } - (void)cleanTransferTable:(NSTimer *)timer { // NSMutableArray *completed = [NSMutableArray array]; // NSEnumerator *e = [transfers objectEnumerator]; // CKTransferRecord *cur; // // while (cur = [e nextObject]) { // if ([cur isCompleted]) // [completed addObject:cur]; // } // [transfers removeObjectsInArray:completed]; // [transferTable reloadData]; } - (void)disconnect:(id)sender { [con disconnect]; } - (void)refreshHosts { [savedHosts reloadData]; } - (void)savedHostsChanged:(id)sender { id selected = [savedHosts itemAtRow:[savedHosts selectedRow]]; if ([selected isKindOfClass:[CKHost class]]) { CKHost *host = selected; [cHost setStringValue:[host host]]; [cUser setStringValue:[host username]]; [cPort setStringValue:[host port]]; if ([host initialPath]) [initialDirectory setStringValue:[host initialPath]]; else [initialDirectory setStringValue:@""]; if ([host connectionType]) [cTypePopup selectItemWithTitle:[host connectionType]]; NSString *pass = [host password]; if (pass) [cPass setStringValue:pass]; else [cPass setStringValue:@""]; [connectWindow makeFirstResponder:cPass]; } } - (IBAction)cancelConnect:(id)sender { [connectWindow orderOut:self]; [NSApp endSheet:connectWindow]; } - (IBAction)connect:(id)sender { NSError *err = nil; if ([[cTypePopup titleOfSelectedItem] isEqualToString:AutoSelect]) { if ([[cURL stringValue] length] > 0) con = [[AbstractConnection connectionWithURL:[NSURL URLWithString:[cURL stringValue]] error:&err] retain]; else con = [[AbstractConnection connectionToHost:[cHost stringValue] port:[cPort stringValue] username:[cUser stringValue] password:[cPass stringValue] error:&err] retain]; } else { con = [[AbstractConnection connectionWithName:[cTypePopup titleOfSelectedItem] host:[cHost stringValue] port:[cPort stringValue] username:[cUser stringValue] password:[cPass stringValue] error:&err] retain]; } if (!con) { if (err) { [NSApp presentError:err]; } return; } NSTextStorage *textStorage = [log textStorage]; [textStorage setDelegate:self]; // get notified when text changes [con setTranscript:textStorage]; [[fileCheckLog textStorage] setDelegate:self]; [con setProperty:[fileCheckLog textStorage] forKey:@"RecursiveDirectoryDeletionTranscript"]; [con setProperty:[fileCheckLog textStorage] forKey:@"FileCheckingTranscript"]; [con setProperty:[fileCheckLog textStorage] forKey:@"RecursiveDownloadTranscript"]; [con setDelegate:self]; [self cancelConnect:sender]; if ([btnBrowseHost state] == NSOnState) { ConnectionOpenPanel *browse = [ConnectionOpenPanel connectionOpenPanel:con]; [browse setCanCreateDirectories:YES]; [browse setCanChooseDirectories:YES]; [browse setCanChooseFiles:YES]; [browse setAllowsMultipleSelection:YES]; [browse beginSheetForDirectory:[initialDirectory stringValue] file:nil modalForWindow:window modalDelegate:self didEndSelector:@selector(browse:returnCode:contextInfo:) contextInfo:nil]; [con release]; con = nil; //we are not responsible for it, the open connection panel will release it. } else { [status setStringValue:[NSString stringWithFormat:@"Connecting to: %@", [cHost stringValue]]]; [con connect]; } // id copy = [con copy]; // [copy setDelegate:self]; // [copy connect]; } - (void)browse:(ConnectionOpenPanel *)panel returnCode:(int)returnCode contextInfo:(id)ui { if (returnCode == NSOKButton) { NSRunAlertPanel (@"Files Selected", [[panel filenames] description], @"OK", nil, nil); } else { NSRunAlertPanel(@"Open Panel Cancelled",@"The panel was cancelled by the user",@"OK",nil,nil); } } - (IBAction)deleteFile:(id)sender { NSEnumerator *e = [remoteTable selectedRowEnumerator]; NSNumber *cur; while (cur = [e nextObject]) { int row = [cur intValue]; NSDictionary *d = [remoteFiles objectAtIndex:row]; if ([[d objectForKey:NSFileType] isEqualToString:NSFileTypeRegular] || [[d objectForKey:NSFileType] isEqualToString:NSFileTypeSymbolicLink]) { NSString *file = [[con currentDirectory] stringByAppendingPathComponent:[d objectForKey:cxFilenameKey]]; [con deleteFile:file]; } else { [con recursivelyDeleteDirectory:[[con currentDirectory] stringByAppendingPathComponent:[d objectForKey:cxFilenameKey]]]; } } } - (IBAction)localFileSelected:(id)sender { } - (void)localDoubleClick:(id)sender { int row = [sender selectedRow]; if (row >= 0 && row < [localFiles count]) { BOOL isDir; if ([[NSFileManager defaultManager] fileExistsAtPath:[localFiles objectAtIndex:row] isDirectory:&isDir] && isDir) { [currentLocalPath autorelease]; currentLocalPath = [[localFiles objectAtIndex:row] copy]; [self refreshLocal]; } else { NSString *file = [localFiles objectAtIndex:row]; [self uploadFile:file to:[[con currentDirectory] stringByAppendingPathComponent:[file lastPathComponent]]]; [transferTable reloadData]; } } } - (IBAction)localPopupChanged:(id)sender { NSString *str = [[sender selectedItem] representedObject]; [currentLocalPath autorelease]; if ([str length] > 1) currentLocalPath = [[str substringToIndex:[str length] - 1] copy]; else currentLocalPath = [str copy]; [self refreshLocal]; } - (IBAction)newFolder:(id)sender { InputDialog *input = [[InputDialog alloc] init]; [input setDialogTitle:@"Enter New Folder Name"]; [input beginSheetModalForWindow:window delegate:self selector:@selector(newFolderValue:)]; } - (IBAction)logConfig:(id)sender { [KTLogger configure:self]; } - (void)newFolderValue:(NSString *)val { if (val) { NSString *dir = [[con currentDirectory] stringByAppendingPathComponent:val]; [con createDirectory:dir]; [con contentsOfDirectory:[con currentDirectory]]; } } - (IBAction)permissions:(id)sender { NSMutableDictionary *file = [remoteFiles objectAtIndex:[remoteTable selectedRow]]; [[PermissionsController sharedPermissions] displayFile:file sheet:window connection:con]; } - (IBAction)refresh:(id)sender { [con contentsOfDirectory:[con currentDirectory]]; } - (IBAction)remoteFileSelected:(id)sender { int idx = [sender selectedRow]; NSDictionary *file = [remoteFiles objectAtIndex:idx]; if (idx >= 0 && idx < [remoteFiles count]) { [btnDelete setEnabled:YES]; [btnPermissions setEnabled:YES]; } else { [btnDelete setEnabled:NO]; [btnPermissions setEnabled:NO]; } if (![[file objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory]) { [btnEdit setEnabled:YES]; } else { [btnEdit setEnabled:NO]; } } - (void)remoteDoubleClick:(id)sender { int row = [sender selectedRow]; if (row >= 0 && row < [remoteFiles count]) { NSDictionary *attribs = [remoteFiles objectAtIndex:row]; if ([[attribs objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory]) { NSString *path = [[con currentDirectory] stringByAppendingPathComponent:[attribs objectForKey:cxFilenameKey]]; [con changeToDirectory:path]; [con contentsOfDirectory:path]; //[remoteFiles removeAllObjects]; return; } else if ([[attribs objectForKey:NSFileType] isEqualToString:NSFileTypeSymbolicLink]) { NSString *target = [attribs objectForKey:cxSymbolicLinkTargetKey]; if ([target characterAtIndex:[target length] - 1] == '/' || [target characterAtIndex:[target length] - 1] == '\\') { [con changeToDirectory:[attribs objectForKey:cxFilenameKey]]; [con contentsOfDirectory:[attribs objectForKey:cxFilenameKey]]; return; } } if ([[attribs objectForKey:NSFileType] isEqualToString:NSFileTypeRegular]) { [self downloadFile:[[con currentDirectory] stringByAppendingPathComponent:[attribs objectForKey:cxFilenameKey]] toFolder:currentLocalPath]; } else if ([[attribs objectForKey:NSFileType] isEqualToString:NSFileTypeSymbolicLink]) { NSString *target = [attribs objectForKey:cxSymbolicLinkTargetKey]; if ([target characterAtIndex:[target length] - 1] != '/' && [target characterAtIndex:[target length] - 1] != '\\') [self downloadFile:[[con currentDirectory] stringByAppendingPathComponent:[attribs objectForKey:cxFilenameKey]] toFolder: currentLocalPath]; } [transferTable reloadData]; } } - (IBAction)remotePopupChanged:(id)sender { NSString *path = [[sender selectedItem] representedObject]; [con changeToDirectory:path]; [con contentsOfDirectory:path]; } - (IBAction)showConnect:(id)sender { //[self savedHostsChanged:savedHosts]; [NSApp beginSheet:connectWindow modalForWindow:window modalDelegate:nil didEndSelector:nil contextInfo:nil]; } - (IBAction)stopTransfer:(id)sender { [con cancelTransfer]; } - (IBAction)transferSelected:(id)sender { int idx = [sender selectedRow]; if (idx >= 0 && idx < [transfers count]) { [btnStop setEnabled:YES]; } else { [btnStop setEnabled:NO]; } } static NSImage *_folder = nil; - (void)refreshRemoteUI { //create popup menu NSString *dir = [con currentDirectory]; NSArray *folders = [dir componentsSeparatedByString:@"/"]; if ([dir isEqualToString:@"/"]) folders = [folders subarrayWithRange:NSMakeRange(1, [folders count] - 1)]; NSEnumerator *e = [folders objectEnumerator]; NSMutableString *buildup = [NSMutableString string]; NSString *cur; NSMenu *menu = [[NSMenu alloc] initWithTitle:@"remote"]; if (!_folder) { _folder = [[[NSWorkspace sharedWorkspace] iconForFile:@"/tmp"] retain]; [_folder setSize:NSMakeSize(16,16)]; } while (cur = [e nextObject]) { NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:cur action:nil keyEquivalent:@""]; [buildup appendFormat:@"/%@", cur]; [item setRepresentedObject:[[buildup copy] autorelease]]; [item setImage:_folder]; [menu addItem:item]; } [remotePopup setMenu:menu]; [menu release]; [remotePopup selectItem:[remotePopup lastItem]]; [remoteTable reloadData]; } - (void)refreshLocal { [localPopup removeAllItems]; //refresh file list NSArray *dir = [[NSFileManager defaultManager] directoryContentsAtPath:currentLocalPath]; [localFiles removeAllObjects]; NSEnumerator *e = [dir objectEnumerator]; NSString *cur; while (cur = [e nextObject]) { if ([cur characterAtIndex:0] != '.') //filter hidden files [localFiles addObject:[NSString stringWithFormat:@"%@/%@", currentLocalPath, cur]]; } NSArray *pathComponents = [currentLocalPath componentsSeparatedByString:@"/"]; NSMenu *menu = [[NSMenu alloc] initWithTitle:@"local"]; e = [pathComponents objectEnumerator]; NSWorkspace *ws = [NSWorkspace sharedWorkspace]; NSMutableString *buildup = [NSMutableString string]; NSMenuItem *item; if ([pathComponents count] > 1) { while (cur = [e nextObject]) { item = [[NSMenuItem alloc] initWithTitle:cur action:nil keyEquivalent:@""]; [buildup appendFormat:@"%@/", cur]; [item setRepresentedObject:[[buildup copy] autorelease]]; NSImage *img = [ws iconForFile:buildup]; [img setSize:NSMakeSize(16,16)]; [item setImage:img]; [menu addItem:item]; [item release]; } } [localPopup setMenu:menu]; [menu release]; [localPopup selectItem:[localPopup lastItem]]; [localTable reloadData]; } - (void)printQueueDescription:(id)sender { if ([con isKindOfClass:[AbstractQueueConnection class]]) { NSLog(@"Queue Description:\n%@", [(AbstractQueueConnection *)con queueDescription]); } } - (void)editFile:(id)sender { unsigned idx = [remoteTable selectedRow]; NSDictionary *file = [remoteFiles objectAtIndex:idx]; NSString *remotePath = [[con currentDirectory] stringByAppendingPathComponent:[file objectForKey:cxFilenameKey]]; [con editFile:remotePath]; } - (void)searchChanged:(id)sender { [[ConnectionRegistry sharedRegistry] setFilterString:[sender stringValue]]; } #pragma mark - #pragma mark Connection Helper Methods + (NSString *)formattedSpeed:(long) spd { if (spd == 0) return @"0 B/s"; NSString *suffix[] = { @"B", @"KB", @"MB", @"GB", @"TB", @"PB", @"EB" }; int i, c = 7; long size = spd; for (i = 0; i < c && size >= 1024; i++) { size = size / 1024; } float rem = 0; if (i != 0) rem = (spd - (i * 1024)) / (i * 1024); NSString *ext = suffix[i]; return [NSString stringWithFormat:@"%4.2f %@/s", size+rem, ext]; } - (FileTransfer *)uploadForLocalFile:(NSString *)file { NSEnumerator *e = [transfers objectEnumerator]; FileTransfer *cur; while (cur = [e nextObject]) { if ([[cur localFile] isEqualToString:file] && [cur type] == UploadType ) return cur; } return nil; } - (FileTransfer *)downloadForLocalFile:(NSString *)file { NSEnumerator *e = [transfers objectEnumerator]; FileTransfer *cur; while (cur = [e nextObject]) { if ([[cur localFile] isEqualToString:file] && [cur type] == DownloadType) return cur; } return nil; } - (FileTransfer *)uploadForRemoteFile:(NSString *)file { NSEnumerator *e = [transfers objectEnumerator]; FileTransfer *cur; while (cur = [e nextObject]) { if ([[cur remoteFile] isEqualToString:file] && [cur type] == UploadType ) return cur; } return nil; } - (FileTransfer *)downloadForRemoteFile:(NSString *)file { NSEnumerator *e = [transfers objectEnumerator]; FileTransfer *cur; while (cur = [e nextObject]) { if ([[cur remoteFile] isEqualToString:file] && [cur type] == DownloadType) return cur; } return nil; } - (void)downloadFile:(NSString *)remote toFolder:(NSString *)local { CKTransferRecord *rec = [con downloadFile:remote toDirectory:local overwrite:YES delegate:nil]; [transfers addObject:rec]; } - (void)uploadFile:(NSString *)local to:(NSString *)remote { CKTransferRecord *rec = [con uploadFile:local toFile:remote checkRemoteExistence:NO delegate:nil]; [transfers addObject:rec]; } - (void)recursivelyUploadContentsAtPath:(NSString *)aFolderPath serverPath:(NSString *)aServerPath { NSFileManager *fileManager = [NSFileManager defaultManager]; NSEnumerator *directoryEnum = [[fileManager directoryContentsAtPath:aFolderPath] objectEnumerator]; NSString *nextFile = nil; [con createDirectory:aServerPath]; while (nextFile = [directoryEnum nextObject]) { NSString *fullLocalPath = [aFolderPath stringByAppendingPathComponent:nextFile]; NSString *fullServerPath = [aServerPath stringByAppendingPathComponent:nextFile]; BOOL isDir; if ([nextFile hasPrefix:@"."]) { continue; } if ([fileManager fileExistsAtPath:fullLocalPath isDirectory:&isDir] && isDir) { [self recursivelyUploadContentsAtPath:fullLocalPath serverPath:fullServerPath]; } else { [self uploadFile:fullLocalPath to:fullServerPath]; } } } - (void)uploadFolderContentsAtPath:(NSString *)aFolderPath { NSString *serverCurrentDirectory = [[con currentDirectory] stringByAppendingPathComponent:[aFolderPath lastPathComponent]]; [self recursivelyUploadContentsAtPath:aFolderPath serverPath:serverCurrentDirectory]; [transferTable reloadData]; } #pragma mark - #pragma mark Connection Delegate Methods - (BOOL)connection:(id )con authorizeConnectionToHost:(NSString *)host message:(NSString *)message; { if (NSRunAlertPanel(@"Authorize Connection?", @"%@\nHost: %@", @"Yes", @"No", nil, message, host) == NSOKButton) return YES; return NO; } - (void)connection:(AbstractConnection *)aConn didConnectToHost:(NSString *)host { isConnected = YES; [status setStringValue:[NSString stringWithFormat:@"Connected to: %@", host]]; [btnRefresh setEnabled:YES]; [remotePopup setHidden:NO]; [btnNewFolder setEnabled:YES]; [remoteTable setHidden:NO]; [btnDisconnect setEnabled:YES]; NSString *dir = [[initialDirectory stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (dir && [dir length] > 0) [con changeToDirectory:[initialDirectory stringValue]]; [con directoryContents]; } - (void)connection:(AbstractConnection *)aConn didDisconnectFromHost:(NSString *)host { isConnected = NO; [status setStringValue:[NSString stringWithFormat:@"Disconnected from: %@", host]]; [btnRefresh setEnabled:NO]; [btnDelete setEnabled:NO]; [btnNewFolder setEnabled:NO]; [btnPermissions setEnabled:NO]; [btnStop setEnabled:NO]; [btnDisconnect setEnabled:NO]; [remotePopup setHidden:YES]; [btnNewFolder setEnabled:NO]; [remoteTable setHidden:YES]; [con release]; con = nil; } - (void)connection:(AbstractConnection *)aConn didReceiveError:(NSError *)error { NSLog(@"%@: %@", NSStringFromSelector(_cmd), error); NSRunAlertPanel(@"Error",@"Connection returned an error: %@",@"OK",nil ,nil, [error localizedDescription]); } - (void)connectionDidSendBadPassword:(AbstractConnection *)aConn { NSRunAlertPanel(@"Bad Password",@"The Password you entered is no good. Please re-enter it and try again.",@"OK",nil, nil); [self showConnect:self]; } - (NSString *)connection:(AbstractConnection *)aConn needsAccountForUsername:(NSString *)username { [status setStringValue:[NSString stringWithFormat:@"Need Account for %@ not implemented", username]]; return nil; } - (void)connection:(AbstractConnection *)aConn didCreateDirectory:(NSString *)dirPath { [status setStringValue:[NSString stringWithFormat:@"Created Directory: %@", dirPath]]; } - (void)connection:(AbstractConnection *)aConn didSetPermissionsForFile:(NSString *)path { } - (void)connection:(AbstractConnection *)aConn didRenameFile:(NSString *)from to:(NSString *)toPath { } - (void)connection:(AbstractConnection *)aConn didDeleteFile:(NSString *)path { [con contentsOfDirectory:[con currentDirectory]]; } - (void)connection:(AbstractConnection *)aConn didDeleteDirectory:(NSString *)path { [con contentsOfDirectory:[con currentDirectory]]; } - (void)connection:(AbstractConnection *)aConn didReceiveContents:(NSArray *)contents ofDirectory:(NSString *)dirPath { NSLog(@"%@ %@", NSStringFromSelector(_cmd), dirPath); [remoteFiles removeAllObjects]; [remoteFiles addObjectsFromArray:[contents filteredArrayByRemovingHiddenFiles]]; [self refreshRemoteUI]; } - (void)connection:(id )con download:(NSString *)path receivedDataOfLength:(unsigned long long)length { [transferTable reloadData]; } - (void)connection:(id )con upload:(NSString *)remotePath sentDataOfLength:(unsigned long long)length { [transferTable reloadData]; } - (void)connection:(id )con uploadDidFinish:(NSString *)remotePath { [self refreshRemoteUI]; [transferTable reloadData]; } - (void)connection:(id )con downloadDidFinish:(NSString *)remotePath error:(NSError *)error { [self refreshLocal]; [transferTable reloadData]; } - (void)connection:(id )con checkedExistenceOfPath:(NSString *)path pathExists:(BOOL)exists { if (exists) { NSRunAlertPanel(@"File Exists", @"Found path: %@", @"OK", nil, nil, path); } else { NSRunAlertPanel(@"File Not Found", @"Could not find path: %@", @"OK", nil, nil, path); } } #pragma mark - #pragma mark NSTableView DataSource Methods - (int)numberOfRowsInTableView:(NSTableView *)aTable { if (aTable == remoteTable) return [remoteFiles count]; else if (aTable == localTable) return [localFiles count]; return 0; } static NSImage *folder = nil; static NSImage *upload = nil; static NSImage *download = nil; static NSImage *symFolder = nil; static NSImage *symFile = nil; NSString *IconKey = @"Icon"; - (id)tableView:(NSTableView *)aTable objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex { NSString *identifier = [aTableColumn identifier]; if (aTable == remoteTable) { NSMutableDictionary *row = [remoteFiles objectAtIndex:rowIndex]; if ([identifier isEqualToString:@"icon"]) { NSImage *img = [row objectForKey:IconKey]; if (!img) { if ([[row objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory]) { if (!folder) folder = [[[NSWorkspace sharedWorkspace] iconForFile:@"/tmp"] retain]; img = folder; } else if ([[row objectForKey:NSFileType] isEqualToString:NSFileTypeSymbolicLink]) { if (!symFolder || !symFile) { symFolder = [[NSImage imageNamed:@"symlink_folder.tif"] retain]; symFile = [[NSImage imageNamed:@"symlink_file.tif"] retain]; } NSString *target = [row objectForKey:cxSymbolicLinkTargetKey]; if ([target characterAtIndex:[target length] - 1] == '/' || [target characterAtIndex:[target length] - 1] == '\\') img = symFolder; else { NSImage *fileType = [[NSWorkspace sharedWorkspace] iconForFileType:[[row objectForKey:cxFilenameKey] pathExtension]]; NSImage *comp = [[NSImage alloc] initWithSize:NSMakeSize(16,16)]; [img setScalesWhenResized:YES]; [img setSize:NSMakeSize(16,16)]; [comp lockFocus]; [fileType drawInRect:NSMakeRect(0,0,16,16) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; [symFile drawInRect:NSMakeRect(0,0,16,16) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; [comp unlockFocus]; [comp autorelease]; img = comp; } } else { img = [[NSWorkspace sharedWorkspace] iconForFileType:[[row objectForKey:cxFilenameKey] pathExtension]]; } [img setSize:NSMakeSize(16,16)]; [row setObject:img forKey:IconKey]; } return img; } else if ([identifier isEqualToString:@"name"]) { return [row objectForKey:cxFilenameKey]; } else if ([identifier isEqualToString:@"size"]) { //if ([[row objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory]) // return nil; return [NSString formattedFileSizeWithBytes:[row objectForKey:NSFileSize]]; } else if ([identifier isEqualToString:@"modified"]) { return [row objectForKey:NSFileModificationDate]; } } else if (aTable == localTable) { NSString *file = [localFiles objectAtIndex:rowIndex]; NSFileManager *fm = [NSFileManager defaultManager]; NSDictionary *attribs = [fm fileAttributesAtPath:file traverseLink:YES]; if ([identifier isEqualToString:@"icon"]) { NSImage *img = [[NSWorkspace sharedWorkspace] iconForFile:file]; [img setSize:NSMakeSize(16,16)]; return img; } else if ([identifier isEqualToString:@"name"]) { return [file lastPathComponent]; } else if ([identifier isEqualToString:@"size"]) { BOOL isDir; if ([fm fileExistsAtPath:file isDirectory:&isDir] && isDir) return nil; return [NSString formattedFileSizeWithBytes:[attribs objectForKey:NSFileSize]]; } else if ([identifier isEqualToString:@"modified"]) { return [attribs objectForKey:NSFileModificationDate]; } } return nil; } - (NSView *) tableView:(NSTableView *) tableView viewForRow:(int) row { return [[[transfers objectAtIndex:row] objectForKey:TransferControllerKey] view]; } - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray *)rows toPasteboard:(NSPasteboard *)pboard { if (tableView == localTable) { NSMutableArray *files = [NSMutableArray array]; NSEnumerator *e = [rows objectEnumerator]; NSNumber *cur; NSFileManager *fm = [NSFileManager defaultManager]; while (cur = [e nextObject]) { NSString *name = [localFiles objectAtIndex:[cur intValue]]; NSMutableDictionary *file = [[fm fileAttributesAtPath:name traverseLink:NO] mutableCopy]; [file setObject:[name lastPathComponent] forKey:cxFilenameKey]; [files addObject:file]; [file release]; } [pboard declareTypes:[NSArray arrayWithObject:cxLocalFilePBoardType] owner:nil]; [pboard setPropertyList:files forType:cxLocalFilePBoardType]; // return YES; } else if (tableView == remoteTable) { NSMutableArray *f = [NSMutableArray array]; NSEnumerator *e = [rows objectEnumerator]; NSNumber *cur; while (cur = [e nextObject]) { NSMutableDictionary *file = [[remoteFiles objectAtIndex:[cur intValue]] mutableCopy]; [file removeObjectForKey:@"Icon"]; [f addObject:file]; [file release]; } [pboard declareTypes:[NSArray arrayWithObject:cxRemoteFilePBoardType] owner:nil]; [pboard setPropertyList:f forType:cxRemoteFilePBoardType]; return YES; } return NO; } - (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id )info row:(int)row dropOperation:(NSTableViewDropOperation)operation { NSPasteboard *pb = [info draggingPasteboard]; if (tableView == localTable) //do a download { NSArray *files = [pb propertyListForType:cxRemoteFilePBoardType]; NSEnumerator *e = [files objectEnumerator]; NSDictionary *file; while (file = [e nextObject]) { if ([[file objectForKey:NSFileType] isEqualToString:NSFileTypeRegular]) { [self downloadFile:[[con currentDirectory] stringByAppendingPathComponent:[file objectForKey:cxFilenameKey]] toFolder: currentLocalPath]; } else if ([[file objectForKey:NSFileType] isEqualToString:NSFileTypeSymbolicLink]) { NSString *target = [file objectForKey:cxSymbolicLinkTargetKey]; if ([target characterAtIndex:[target length] - 1] != '/' && [target characterAtIndex:[target length] - 1] != '\\') { [self downloadFile:[[con currentDirectory] stringByAppendingPathComponent:target] toFolder: currentLocalPath]; } else { CKTransferRecord *rec = [con recursivelyDownload:[[con currentDirectory] stringByAppendingPathComponent:target] to:currentLocalPath overwrite:YES]; [transfers addObject:rec]; } } else if ([[file objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory]) { CKTransferRecord *rec = [con recursivelyDownload:[[con currentDirectory] stringByAppendingPathComponent:[file objectForKey:cxFilenameKey]] to:currentLocalPath overwrite:YES]; [transfers addObject:rec]; } } [transferTable reloadData]; return YES; } else if (tableView == remoteTable) //do an upload { if ([[pb types] containsObject:cxLocalFilePBoardType]) { NSArray *files = [pb propertyListForType:cxLocalFilePBoardType]; NSEnumerator *e = [files objectEnumerator]; NSDictionary *cur; while (cur = [e nextObject]) { if ([[cur objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory]) { CKTransferRecord *rec = [con recursivelyUpload:[currentLocalPath stringByAppendingPathComponent:[cur objectForKey:cxFilenameKey]] to:[con currentDirectory]]; [transfers addObject:rec]; } else if ([[cur objectForKey:NSFileType] isEqualToString:NSFileTypeSymbolicLink]) { CKTransferRecord *rec = [con recursivelyUpload:[currentLocalPath stringByAppendingPathComponent:[cur objectForKey:cxSymbolicLinkTargetKey]] to:[con currentDirectory]]; [transfers addObject:rec]; } else { [self uploadFile:[currentLocalPath stringByAppendingPathComponent:[cur objectForKey:cxFilenameKey]] to:[[con currentDirectory] stringByAppendingPathComponent:[cur objectForKey:cxFilenameKey]]]; } } [transferTable reloadData]; return YES; } else if ([[pb types] containsObject:NSFilenamesPboardType]) { NSFileManager *fm = [NSFileManager defaultManager]; NSArray *files = [pb propertyListForType:NSFilenamesPboardType]; NSEnumerator *e = [files objectEnumerator]; NSString *cur; BOOL isDir; NSString *curRemoteDir = [[con currentDirectory] copy]; while (cur = [e nextObject]) { CKTransferRecord *root = [con recursivelyUpload:cur to:[con currentDirectory]]; } [curRemoteDir release]; [transferTable reloadData]; return YES; } } return NO; } - (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id )info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation { if (!isConnected) return NSDragOperationNone; if (tableView == localTable || tableView == remoteTable) return NSDragOperationCopy; return NSDragOperationNone; } #pragma mark - #pragma mark Outline View Data Source - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { if (item == nil) { return [transfers count]; } return [[item contents] count]; } - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item { if (item == nil) { return [transfers objectAtIndex:index]; } return [[item contents] objectAtIndex:index]; } - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { return [item isDirectory]; } - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { NSString *identifier = [tableColumn identifier]; CKTransferRecord *transfer = (CKTransferRecord *)item; if ([identifier isEqualToString:@"progress"]) { return [NSDictionary dictionaryWithObjectsAndKeys:[transfer progress], @"progress", [transfer name], @"name", nil]; } else if ([identifier isEqualToString:@"file"]) { return [transfer name]; } else if ([identifier isEqualToString:@"speed"]) { return [NSString formattedSpeed:[transfer speed]]; } if ([identifier isEqualToString:@"image"]) { if (![transfer isUpload]) { if (!download) download = [[NSImage imageNamed:@"download.tif"] retain]; return download; } else { if (!upload) upload = [[NSImage imageNamed:@"upload.tif"] retain]; return upload; } } else if ([identifier isEqualToString:@"icon"]) { NSImage *img = [[NSWorkspace sharedWorkspace] iconForFileType:[[transfer path] pathExtension]]; [img setSize:NSMakeSize(16,16)]; return img; } return nil; } #pragma mark - #pragma mark NSTextView Delegate Methods /*! Called as a delegate of the log's text storage, so we can update the scroll position */ - (void)textStorageDidProcessEditing:(NSNotification *)aNotification { if ([aNotification object] == [log textStorage]) [self performSelector:@selector(scrollToVisible:) withObject:log afterDelay:0.0]; else [self performSelector:@selector(scrollToVisible:) withObject:fileCheckLog afterDelay:0.0]; // Don't scroll now, do it in a moment. Doing it now causes error messgaes. } - (void) scrollToVisible:(id)whichLog { [whichLog scrollRangeToVisible:NSMakeRange([[whichLog textStorage] length], 0)]; } #pragma mark - #pragma mark NSApplication Delegate Methods - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { if ([con numberOfTransfers] > 0) { if (NSRunAlertPanel(@"Transfers in Progress", @"Are you sure you want to quit while there are still file transfers in progress?", @"Yes Quit", @"No", nil) != NSOKButton) return NSTerminateCancel; } NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; [ud setObject:NSStringFromRect([window frame]) forKey:[window frameAutosaveName]]; [ud setBool:[logDrawer state] == NSDrawerOpenState forKey:@"showLog"]; return NSTerminateNow; } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { return YES; } @end ================================================ FILE: Example/DropletController.h ================================================ /* DropletController */ #import #import @interface DropletController : NSObject { IBOutlet NSButton *oCancel; IBOutlet NSTextField *oPassword; IBOutlet NSPanel *oPasswordPanel; IBOutlet NSTextField *oPasswordText; IBOutlet NSProgressIndicator *oProgressBar; IBOutlet NSTextField *oStatus; IBOutlet NSPanel *oWindow; IBOutlet NSOutlineView *oFiles; IBOutlet NSButton *oToggleFiles; CKHost *myHost; id myConnection; NSArray *myFilesDropped; NSMutableArray *myTransfers; } - (IBAction)cancelPassword:(id)sender; - (IBAction)cancelUpload:(id)sender; - (IBAction)connectPassword:(id)sender; - (IBAction)toggleFiles:(id)sender; @end ================================================ FILE: Example/DropletController.m ================================================ #import "DropletController.h" @interface DropletController (Private) - (void)startUpload; @end static NSSize sFilesExpandedSize = {375, 400}; static NSSize sFilesCollapsedSize = {375, 105}; @implementation DropletController - (void)dealloc { [myConnection release]; [myFilesDropped release]; [myHost release]; [myTransfers release]; [super dealloc]; } - (void)awakeFromNib { NSString *str = [[NSUserDefaults standardUserDefaults] objectForKey:[oWindow frameAutosaveName]]; if (str) { [oWindow setFrame:NSRectFromString(str) display:NO]; } NSNumber *state = [[NSUserDefaults standardUserDefaults] objectForKey:@"DisplayFiles"]; if ([state boolValue]) { [oToggleFiles setState:NSOnState]; } myTransfers = [[NSMutableArray array] retain]; [oFiles setDelegate:self]; [oFiles setDataSource:self]; CKTransferProgressCell *cell = [[CKTransferProgressCell alloc] init]; [[[oFiles tableColumns] objectAtIndex:0] setDataCell:cell]; [cell release]; NSArray *args = [[NSProcessInfo processInfo] arguments]; if ([args count] < 2) { NSRunAlertPanel(NSLocalizedString(@"Bad Configuration", @"config"), NSLocalizedString(@"No configuration file specified", @"config"), NSLocalizedString(@"Quit", @"config"), nil, nil); [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; return; } NSString *configurationFile = [args objectAtIndex:1]; myFilesDropped = [[args subarrayWithRange:NSMakeRange(2,[args count] - 2)] retain]; if (![[NSFileManager defaultManager] fileExistsAtPath:configurationFile]) { NSRunAlertPanel(NSLocalizedString(@"Bad Configuration", @"config"), NSLocalizedString(@"Couldn't find configuration file specified\n %@", @"config"), NSLocalizedString(@"Quit", @"config"), nil, nil, configurationFile); [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; return; } myHost = [[NSKeyedUnarchiver unarchiveObjectWithFile:configurationFile] retain]; if (![myHost password]) { NSString *str = [NSString stringWithFormat:[oPasswordText stringValue], [myHost host], [myHost connectionType]]; [oPasswordText setStringValue:str]; [oPasswordPanel center]; [NSApp activateIgnoringOtherApps:YES]; [oPasswordPanel makeKeyAndOrderFront:self]; } else { [self startUpload]; } } - (void)startUpload { [oProgressBar setIndeterminate:YES]; [oProgressBar setUsesThreadedAnimation:YES]; [oStatus setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Connecting to %@", @"connection string"), [myHost host]]]; [NSApp activateIgnoringOtherApps:YES]; [oWindow makeKeyAndOrderFront:self]; [oProgressBar startAnimation:self]; myConnection = [[myHost connection] retain]; [myConnection setDelegate:self]; [myConnection connect]; // queue up the transfers NSEnumerator *e = [myFilesDropped objectEnumerator]; NSString *cur; while ((cur = [e nextObject])) { CKTransferRecord *root = [myConnection recursivelyUpload:cur to:[myHost initialPath]]; CKTransferRecord *record = [CKTransferRecord recursiveRecord:root forFullPath:[[myHost initialPath] stringByAppendingPathComponent:[cur lastPathComponent]]]; [myTransfers addObject:record]; } [oFiles reloadData]; // expand the root items e = [myTransfers objectEnumerator]; CKTransferRecord *rec; while ((rec = [e nextObject])) { [oFiles expandItem:rec expandChildren:YES]; } // disconnect [myConnection disconnect]; } - (IBAction)cancelPassword:(id)sender { [oPasswordPanel orderOut:self]; [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; } - (IBAction)cancelUpload:(id)sender { [oWindow orderOut:self]; [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; } - (IBAction)connectPassword:(id)sender { [myHost setPassword:[oPassword stringValue]]; [oPasswordPanel orderOut:self]; [self startUpload]; } - (IBAction)toggleFiles:(id)sender { NSRect frame = [oWindow frame]; NSRect contentRect = [oWindow contentRectForFrameRect:frame]; CGFloat titlebarHeight = NSHeight(frame) - NSHeight(contentRect); NSSize newSize = [sender state] == NSOnState ? sFilesExpandedSize : sFilesCollapsedSize; frame.origin.y -= newSize.height - contentRect.size.height; frame.size = newSize; frame.size.height += titlebarHeight; [oWindow setFrame:frame display:YES animate:YES]; [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:[sender state] == NSOnState] forKey:@"DisplayFiles"]; } - (void)notifyGrowlOfSuccessfulUpload { NSString *dropletName = [[NSProcessInfo processInfo] processName]; NSString *scriptSource = [NSString stringWithFormat:@"tell application \"System Events\"\n" "set growlIsRunning to count of (every process whose name is \"GrowlHelperApp\") > 0\n" "end tell\n" "if growlIsRunning\n" "tell application \"GrowlHelperApp\"\n" "set the allNotificationsList to {\"Upload Complete\"}\n" "set the enabledNotificationsList to {\"Upload Complete\"}\n" "register as application \"Connection Droplet\" all notifications allNotificationsList default notifications enabledNotificationsList icon of application \"%@\"\n" "notify with name \"Upload Complete\" title \"Upload Complete\" description \"The items have been uploaded successfully.\" application name \"Connection Droplet\"\n" "end tell\n" "end if\n", dropletName]; NSDictionary *errorDictionary; NSAppleEventDescriptor *returnDescriptor = nil; NSAppleScript *applescript = [[NSAppleScript alloc] initWithSource:scriptSource]; returnDescriptor = [applescript executeAndReturnError:&errorDictionary]; if (!returnDescriptor) { //error NSLog(@"Applescript Error for Droplet Growl Notification: %@", errorDictionary); } [applescript release]; } #pragma mark - #pragma mark NSWindow Delegate Methods - (BOOL)windowShouldClose:(id)sender { if (NSRunAlertPanel(NSLocalizedString(@"Stop Upload?", @"close window"), NSLocalizedString(@"Are you sure you want to stop the upload?", @"close window"), NSLocalizedString(@"Continue Upload", @"close window"), NSLocalizedString(@"Stop Upload", @"close window"), nil) != NSOKButton) { [myConnection setDelegate:nil]; [myConnection forceDisconnect]; return YES; } return NO; } - (void)windowWillClose:(NSNotification *)aNotification { [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; } - (void)windowDidMove:(NSNotification *)aNotification { [[NSUserDefaults standardUserDefaults] setObject:NSStringFromRect([oWindow frame]) forKey:[oWindow frameAutosaveName]]; } #pragma mark - #pragma mark Connection Delegate Methods - (BOOL)connection:(id )con authorizeConnectionToHost:(NSString *)host message:(NSString *)message { NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Authorize Connection?", @"authorise") defaultButton:NSLocalizedString(@"Authorize", @"authorise") alternateButton:NSLocalizedString(@"Cancel", @"authorise") otherButton:nil informativeTextWithFormat:NSLocalizedString(@"%@\nWhat would you like to do?", @"authorise"), message]; if ([alert runModal] == NSOKButton) { return YES; } return NO; } - (void)connection:(id )con didConnectToHost:(NSString *)host { [oStatus setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Connected to %@", @"connected message"), [myHost host]]]; [oProgressBar setIndeterminate:NO]; [oProgressBar setMinValue:0]; [oProgressBar setMaxValue:1.0]; [oProgressBar setDoubleValue:0.0]; } - (void)connection:(id )con didDisconnectFromHost:(NSString *)host { [oStatus setStringValue:NSLocalizedString(@"Disconnected", @"status")]; [self notifyGrowlOfSuccessfulUpload]; [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.5]; } - (void)connection:(id )con didReceiveError:(NSError *)error { if ([[error userInfo] objectForKey:ConnectionDirectoryExistsKey]) { return; } NSLog(@"%@", error); NSAlert *a = [NSAlert alertWithError:error]; [a runModal]; } //- (NSString *)connection:(id )con needsAccountForUsername:(NSString *)username //{ // //} - (CKTransferRecord *)recordWithPath:(NSString *)path { NSString *chompedPath = [path substringFromIndex:[[myHost initialPath] length]]; CKTransferRecord *record = nil; NSEnumerator *e = [myTransfers objectEnumerator]; CKTransferRecord *cur; while ((cur = [e nextObject])) { record = [CKTransferRecord recursiveRecord:cur forPath:chompedPath]; if (record) { return record; } } return nil; } - (void)connection:(id )con upload:(NSString *)remotePath progressedTo:(NSNumber *)aPercent { CKTransferRecord *record = [self recordWithPath:remotePath]; NSInteger oldValue = [[record progress] integerValue]; NSInteger percent = [aPercent integerValue]; if (oldValue == 1 && percent == 100) { NSInteger i; for (i = 1; i <= 10; i++) { [record setProgress:i*10]; if ([oToggleFiles state] == NSOnState) { [oFiles reloadData]; [oWindow display]; [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.0125]]; } } } else if (percent > 0) { [record setProgress:percent]; [oFiles reloadData]; } // update progress bar unsigned long long totalBytes = 0; unsigned long long totalTransferred = 0; NSEnumerator *e = [myTransfers objectEnumerator]; CKTransferRecord *cur; while ((cur = [e nextObject])) { totalBytes += [cur size]; totalTransferred += [cur transferred]; } [oProgressBar setDoubleValue:(totalTransferred * 1.0) / (totalBytes * 1.0)]; } - (void)connection:(id )con uploadDidBegin:(NSString *)remotePath { CKTransferRecord *record = [self recordWithPath:remotePath]; [record setProgress:1]; [oFiles reloadData]; [oStatus setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Uploading %@", @"status"), [record name]]]; } - (void)connection:(id )con uploadDidFinish:(NSString *)remotePath { CKTransferRecord *record = [self recordWithPath:remotePath]; [record setProgress:100]; [oFiles reloadData]; } - (void)connectionDidSendBadPassword:(id )con { NSLog(@"Bad Password"); } #pragma mark - #pragma mark Outline View Data Source Methods - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { if (item == nil) { return [myTransfers count]; } return [[item contents] count]; } - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { return [item isDirectory]; } - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { if (item == nil) { return [myTransfers objectAtIndex:index]; } return [[item contents] objectAtIndex:index]; } - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { return [NSDictionary dictionaryWithObjectsAndKeys:[item progress], @"progress", [item name], @"name", nil]; } #pragma mark - #pragma mark Outline View Delegate Methods - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item { return NO; } @end ================================================ FILE: Example/DropletHelper-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile DropletIcon CFBundleIdentifier com.connectionkit.DropletHelper CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType APPL CFBundleSignature ???? CFBundleVersion 1.0 LSUIElement 1 NSMainNibFile Droplet NSPrincipalClass NSApplication ================================================ FILE: Example/DropletLauncher.m ================================================ // // DropletLauncher.m // Connection // // Created by Greg Hulands on 16/11/06. // Copyright 2006 __MyCompanyName__. All rights reserved. // #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **) argv); } @interface DropletLauncherDelegate : NSObject { BOOL myHasFilesToUpload; } @end @implementation DropletLauncherDelegate - (id)init { if ((self != [super init])) { [self release]; return nil; } myHasFilesToUpload = NO; return self; } - (void)application:(NSApplication *)app openFiles:(NSArray *)files { myHasFilesToUpload = YES; NSString *dropletCreator = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CKApplication"]; NSString *applicationPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:dropletCreator]; NSString *dyldPath = [[applicationPath stringByAppendingPathComponent:@"Contents"] stringByAppendingPathComponent:@"Frameworks"]; NSString *ckFrameworkPath = [[dyldPath stringByAppendingPathComponent:@"Connection"] stringByAppendingPathExtension:@"framework"]; NSString *dropletPath = [[NSBundle bundleWithPath:ckFrameworkPath] pathForResource:@"DropletHelper" ofType:@"app"]; NSString *path = [[NSBundle bundleWithPath:dropletPath] executablePath]; if (!path) { NSRunAlertPanel(NSLocalizedString(@"Bad Droplet", @"error"), NSLocalizedString(@"This droplet is missing the original application that created it. You will need to reinstall the original application.", @"error"), NSLocalizedString(@"Quit", @"error"), nil, nil); [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0]; return; } NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:path]; NSMutableArray *args = [NSMutableArray arrayWithObject:[[NSBundle mainBundle] pathForResource:@"configuration" ofType:@"ckhost"]]; [args addObjectsFromArray:files]; [task setArguments:args]; [task launch]; [task release]; [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { if (!myHasFilesToUpload) { NSString *dropletCreator = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CKApplication"]; NSString *applicationPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:dropletCreator]; [[NSWorkspace sharedWorkspace] openFile:[[NSBundle mainBundle] pathForResource:@"configuration" ofType:@"ckhost"] withApplication:applicationPath]; [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; } } @end ================================================ FILE: Example/DropletMain.m ================================================ // // DropletMain.m // Connection // // Created by Greg Hulands on 16/11/06. // Copyright 2006 __MyCompanyName__. All rights reserved. // #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **) argv); } ================================================ FILE: Example/DropletOutlineView.h ================================================ // // DropletOutlineView.h // Connection // // Created by Greg Hulands on 16/11/06. // Copyright 2006 __MyCompanyName__. All rights reserved. // #import @interface DropletOutlineView : NSOutlineView { BOOL isReloading; } @end ================================================ FILE: Example/DropletOutlineView.m ================================================ // // DropletOutlineView.m // Connection // // Created by Greg Hulands on 16/11/06. // Copyright 2006 __MyCompanyName__. All rights reserved. // #import "DropletOutlineView.h" @implementation DropletOutlineView - (void)reloadData { if (isReloading) { [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.0]; } isReloading = YES; [super reloadData]; isReloading = NO; } @end ================================================ FILE: Example/DropletPanel.h ================================================ // // DropletPanel.h // Connection // // Created by Greg Hulands on 17/11/06. // Copyright 2006 __MyCompanyName__. All rights reserved. // #import @interface DropletPanel : NSPanel { } @end ================================================ FILE: Example/DropletPanel.m ================================================ // // DropletPanel.m // Connection // // Created by Greg Hulands on 17/11/06. // Copyright 2006 __MyCompanyName__. All rights reserved. // #import "DropletPanel.h" @implementation DropletPanel - (BOOL)hidesOnDeactivate { return NO; } @end ================================================ FILE: Example/FileTransfer.h ================================================ // // FileTransfer.h // FTPConnection // // Created by Greg Hulands on 24/11/05. // Copyright 2005 __MyCompanyName__. All rights reserved. // // This is just a wrapper around the notion of a file transfer #import typedef enum { UploadType = 0, DownloadType, DeleteType } TransferType; @interface FileTransfer : NSObject { TransferType _type; NSString *_local; NSString *_remote; NSNumber *_size; NSNumber *_percent; NSNumber *_transferred; BOOL _completed; } + (id)uploadFile:(NSString *)local to:(NSString *)remote; + (id)downloadFile:(NSString *)remote to:(NSString *)local; + (id)deleteFile:(NSString *)remote; - (id)initWithType:(TransferType)type localFile:(NSString *)local remoteFile:(NSString *)remote; - (void)setLocalFile:(NSString *)local; - (void)setRemoteFile:(NSString *)remote; - (void)setSize:(NSNumber *)size; - (void)setPercentTransferred:(NSNumber *)percent; - (void)setAmountTransferred:(NSNumber *)transferred; - (void)setCompleted:(BOOL)flag; - (void)setType:(TransferType)type; - (TransferType)type; - (BOOL)isCompleted; - (NSString *)localFile; - (NSString *)remoteFile; - (NSNumber *)size; - (NSNumber *)percentTransferred; - (NSNumber *)amountTransferred; @end ================================================ FILE: Example/FileTransfer.m ================================================ // // FileTransfer.m // FTPConnection // // Created by Greg Hulands on 24/11/05. // Copyright 2005 __MyCompanyName__. All rights reserved. // #import "FileTransfer.h" @implementation FileTransfer + (id)uploadFile:(NSString *)local to:(NSString *)remote { FileTransfer *transfer = [[[FileTransfer alloc] initWithType:UploadType localFile:local remoteFile:remote] autorelease]; [transfer setSize: [NSNumber numberWithUnsignedLongLong: [[[NSFileManager defaultManager] fileAttributesAtPath: local traverseLink: NO] fileSize]]]; return transfer; } + (id)downloadFile:(NSString *)remote to:(NSString *)local { return [[[FileTransfer alloc] initWithType:DownloadType localFile:local remoteFile:remote] autorelease]; } + (id)deleteFile:(NSString *)remote { return [[[FileTransfer alloc] initWithType: DeleteType localFile: nil remoteFile: remote] autorelease]; } - (id)initWithType:(TransferType)type localFile:(NSString *)local remoteFile:(NSString *)remote { [super init]; _type = type; _local = [local copy]; _remote = [remote copy]; _transferred = [NSNumber numberWithInt: 0]; return self; } - (void)dealloc { [_local release]; [_remote release]; [_size release]; [_percent release]; [_transferred release]; [super dealloc]; } - (void)setLocalFile:(NSString *)local { [_local autorelease]; _local = [local copy]; } - (void)setRemoteFile:(NSString *)remote { [_remote autorelease]; _remote = [remote copy]; } - (void)setSize:(NSNumber *)size { [_size autorelease]; _size = [size copy]; } - (void)setPercentTransferred:(NSNumber *)percent { [_percent autorelease]; _percent = [percent copy]; } - (void)setAmountTransferred:(NSNumber *)transferred { [_transferred autorelease]; _transferred = [transferred copy]; } - (NSString *)localFile { return _local; } - (NSString *)remoteFile { return _remote; } - (NSNumber *)size { return _size; } - (NSNumber *)percentTransferred { if (_completed) return [NSNumber numberWithInt:100]; return _percent; } - (NSNumber *)amountTransferred { return _transferred; } - (void)setCompleted:(BOOL)flag { _completed = flag; } - (BOOL)isCompleted { return _completed; } - (void)setType:(TransferType)type { _type = type; } - (TransferType)type { return _type; } - (NSString *)description { NSMutableString *d = [NSMutableString stringWithString:@"File Transfer\n"]; [d appendFormat:@"Local File: %@\n", _local]; [d appendFormat:@"Remote File: %@\n", _remote]; if (_type == DownloadType) [d appendString:@"is Download"]; else if (_type == UploadType) [d appendString:@"is Upload"]; else [d appendString:@"is Delete"]; [d appendFormat:@" transfered: %@\n", [self amountTransferred]]; return d; } @end ================================================ FILE: Example/Framework Debug.xcconfig ================================================ COPY_PHASE_STRIP = NO DYLIB_COMPATIBILITY_VERSION = 1 DYLIB_CURRENT_VERSION = 1 FRAMEWORK_VERSION = A GCC_DYNAMIC_NO_PIC = NO GCC_ENABLE_FIX_AND_CONTINUE = YES GCC_ENABLE_OBJC_EXCEPTIONS = YES GCC_GENERATE_DEBUGGING_SYMBOLS = YES GCC_MODEL_TUNING = G5 GCC_OPTIMIZATION_LEVEL = 0 GCC_PRECOMPILE_PREFIX_HEADER = YES GCC_PREFIX_HEADER = $(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h OTHER_LDFLAGS = -framework Foundation -framework AppKit PREBINDING = NO PRODUCT_NAME = iMediaBrowser SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk ZERO_LINK = YES ================================================ FILE: Example/Framework Release.xcconfig ================================================ ARCHS = ppc i386 COPY_PHASE_STRIP = YES DYLIB_COMPATIBILITY_VERSION = 1 DYLIB_CURRENT_VERSION = 1 FRAMEWORK_VERSION = A GCC_ENABLE_FIX_AND_CONTINUE = NO GCC_ENABLE_OBJC_EXCEPTIONS = YES GCC_GENERATE_DEBUGGING_SYMBOLS = NO GCC_MODEL_TUNING = G5 GCC_PRECOMPILE_PREFIX_HEADER = YES GCC_PREFIX_HEADER = $(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h OTHER_LDFLAGS = -framework Foundation -framework AppKit PREBINDING = NO SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk ZERO_LINK = NO ================================================ FILE: Example/Framework-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.dlsxtreme.connection CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType FMWK CFBundleSignature ???? CFBundleVersion 1.0 ================================================ FILE: Example/InputDialog.h ================================================ /* InputDialog */ #import @interface InputDialog : NSObject { IBOutlet id input; IBOutlet id panel; IBOutlet id title; id _del; SEL _sel; } - (id)init; - (IBAction)cancel:(id)sender; - (IBAction)ok:(id)sender; - (void)setDialogTitle:(NSString *)str; //must respond to input: receivedValue: - (void)beginSheetModalForWindow:(NSWindow *)win delegate:(id)delegate selector:(SEL)sel; @end ================================================ FILE: Example/InputDialog.m ================================================ #import "InputDialog.h" @implementation InputDialog - (id)init { [super init]; [NSBundle loadNibNamed:@"InputDialog" owner:self]; return self; } - (IBAction)cancel:(id)sender { [panel orderOut:self]; [NSApp endSheet:panel]; [_del performSelector:_sel withObject:nil]; } - (IBAction)ok:(id)sender { [panel orderOut:self]; [NSApp endSheet:panel]; [_del performSelector:_sel withObject:[input stringValue]]; } - (void)setDialogTitle:(NSString *)str { [title setStringValue:str]; } - (void)beginSheetModalForWindow:(NSWindow *)win delegate:(id)delegate selector:(SEL)sel { _del = delegate; _sel = sel; [NSApp beginSheet:panel modalForWindow:win modalDelegate:nil didEndSelector:nil contextInfo:nil]; } @end ================================================ FILE: Example/KTLog Viewer-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDocumentTypes CFBundleTypeExtensions ktlog CFBundleTypeName Log File CFBundleTypeRole Viewer LSTypeIsPackage NSPersistentStoreTypeKey Binary CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile Logs CFBundleIdentifier com.utr-software.KTLogViewer CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType APPL CFBundleSignature ktlV CFBundleVersion 1.0 NSMainNibFile KTLogViewer NSPrincipalClass NSApplication ================================================ FILE: Example/KTLogController.h ================================================ /* KTLogController */ #import @interface KTLogController : NSObject { IBOutlet NSTableView *oTable; IBOutlet NSWindow *oWindow; NSArray *myEntries; } @end ================================================ FILE: Example/KTLogController.m ================================================ #import "KTLogController.h" #import "KTLog.h" @interface KTLogLevelTransformer : NSValueTransformer { } @end @interface NSStringToAttributedString : NSValueTransformer { } @end @implementation KTLogController + (void)initialize { [NSValueTransformer setValueTransformer:[[[KTLogLevelTransformer alloc] init] autorelease] forName:@"KTLogLevelTransformer"]; [NSValueTransformer setValueTransformer:[[[NSStringToAttributedString alloc] init] autorelease] forName:@"NSStringToAttributedString"]; } - (void)awakeFromNib { NSString *log = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastOpened"]; if (log && [[NSFileManager defaultManager] fileExistsAtPath:log]) { [self willChangeValueForKey:@"entries"]; myEntries = [[KTLogger entriesWithLogFile:log] retain]; [self didChangeValueForKey:@"entries"]; } [[[oTable tableColumnWithIdentifier:@"f"] dataCell] setLineBreakMode: NSLineBreakByTruncatingHead]; } - (NSArray *)entries { return myEntries; } - (void)open:(id)sender { NSOpenPanel *op = [NSOpenPanel openPanel]; [op beginSheetForDirectory:nil file:nil types:[NSArray arrayWithObject:@"ktlog"] modalForWindow:oWindow modalDelegate:self didEndSelector:@selector(open:rc:ci:) contextInfo:nil]; } - (void)open:(NSOpenPanel *)op rc:(int)rc ci:(id)ci { if (rc == NSOKButton) { [myEntries autorelease]; [self willChangeValueForKey:@"entries"]; myEntries = [[KTLogger entriesWithLogFile:[op filename]] retain]; [self didChangeValueForKey:@"entries"]; [[NSUserDefaults standardUserDefaults] setObject:[op filename] forKey:@"LastOpened"]; [[NSUserDefaults standardUserDefaults] synchronize]; } } @end @implementation NSStringToAttributedString + (BOOL)allowsReverseTransformation { return NO; } - (id)transformedValue:(id)value { return [[[NSAttributedString alloc] initWithString:value] autorelease]; } @end @implementation KTLogLevelTransformer + (BOOL)allowsReverseTransformation { return NO; } - (id)transformedValue:(id)value { return [[[KTLogger sharedLogger] levelNames] objectAtIndex:[value intValue]]; } @end ================================================ FILE: Example/ONBSSLContext.h ================================================ // This code derives from Aaron Jacobs's OneButton Socket, which was // at the time of writing normally licensed under the terms of the // GNU General Public License. You can find the "standard" version // in the CVS repository of OneButton FTP (www.onebutton.org). // // The SPECIFIC INCARNATION of OneButton Socket upon which this // code is based was specially distributed to Greg Hulands on 2006-01-05 // under the terms of a modified BSD-style license rather than the GPL. // This does not indicate that any other version of OneButton Socket // is or will be distributed under any license but the GPL. /* * Copyright (c) 2005, Aaron Jacobs. * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above two paragraph * note about licensing of OneButton Socket, the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Aaron Jacobs nor the names of OneButton Socket or * OneButton FTP may be used to endorse or promote products derived from * this software without specific prior written permission from Aaron Jacobs. * * THIS SOFTWARE IS PROVIDED BY AARON JACOBS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL AARON JACOBS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ #import #import @class ONBSSLIdentity; @interface ONBSSLContext : NSObject { SSLContextRef ONB_sslContext; NSMutableData *ONB_inputData; NSMutableData *ONB_outputData; BOOL ONB_verifySSLCerts; ONBSSLIdentity *ONB_sslIdentity; BOOL ONB_sslServerMode; } // Should certificates be verified against known root certificates? Turn this off if // you have self-signed or unsigned certificates. The default is YES. Note that // not verifying certificates removes a significant layer of security from SSL. // Must be set before starting a handshake. - (BOOL)verifySSLCertificates; - (void)setVerifySSLCertificates:(BOOL)verifySSLCertificates; // The SSL identity that should be used in the SSL session. This is required for // SSL server mode. Note that at this time it seems as if only RSA certificates work. // Must be set before starting a handshake. - (ONBSSLIdentity *)sslIdentity; - (void)setSSLIdentity:(ONBSSLIdentity *)sslIdentity; // Should the socket operate in SSL server mode or client mode? The default is client // mode (NO). If you change this to YES, you must also call setSSLCertificates:. // Must be set before starting a handshake. - (BOOL)sslServerMode; - (void)setSSLServerMode:(BOOL)sslServerMode; // Perform a handshake. inputData should be filled with data read from the socket // and outputData should be empty. When the method is done, inputData will contain // any unused data and outputData will contain any data that needs to be written to // the socket. Returns 0 if it needs to be called back when more input data arrives // and 1 if the handshake has completed. Returns a negative error code on error. - (int)handshakeWithInputData:(NSMutableData *)inputData outputData:(NSMutableData *)outputData; // Encrypt data to be written to the socket. Data will be taken from inputData (which // should contain raw bytes from the socket) if any needs to be read in the process of // the encryption. - (NSData *)encryptData:(NSData *)data inputData:(NSMutableData *)inputData; // Decrypt data read from the socket. If not all of the data could be decrypted at // the moment, the unused data will be left in the data object. Data will be added // to outputData if any needs to be written in the process of the decryption. - (NSData *)decryptData:(NSMutableData *)data outputData:(NSMutableData *)outputData; @end ================================================ FILE: Example/ONBSSLContext.m ================================================ // This code derives from Aaron Jacobs's OneButton Socket, which was // at the time of writing normally licensed under the terms of the // GNU General Public License. You can find the "standard" version // in the CVS repository of OneButton FTP (www.onebutton.org). // // The SPECIFIC INCARNATION of OneButton Socket upon which this // code is based was specially distributed to Greg Hulands on 2006-01-05 // under the terms of a modified BSD-style license rather than the GPL. // This does not indicate that any other version of OneButton Socket // is or will be distributed under any license but the GPL. /* * Copyright (c) 2005, Aaron Jacobs. * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above two paragraph * note about licensing of OneButton Socket, the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Aaron Jacobs nor the names of OneButton Socket or * OneButton FTP may be used to endorse or promote products derived from * this software without specific prior written permission from Aaron Jacobs. * * THIS SOFTWARE IS PROVIDED BY AARON JACOBS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL AARON JACOBS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ #import "ONBSSLContext.h" #import "ONBSSLIdentity.h" @interface ONBSSLContext ( ONBSSLContextPrivateMethods ) - (OSStatus)ONB_handleSSLReadToData:(void *)data size:(size_t *)size; - (OSStatus)ONB_handleSSLWriteFromData:(const void *)data size:(size_t *)size; @end OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength) { return [(ONBSSLContext *)connection ONB_handleSSLReadToData:data size:dataLength]; } OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength) { return [(ONBSSLContext *)connection ONB_handleSSLWriteFromData:data size:dataLength]; } @implementation ONBSSLContext - (id)init { if (! (self = [super init])) return nil; [self setSSLIdentity:nil]; return self; } - (void)dealloc { [self setSSLIdentity:nil]; [super dealloc]; } - (int)handshakeWithInputData:(NSMutableData *)inputData outputData:(NSMutableData *)outputData { int ret; // If we haven't yet set up the SSL context, we should do so now. if (! ONB_sslContext) { if (ret = SSLNewContext((Boolean)[self sslServerMode], &ONB_sslContext)) { NSLog(@"Error creating new context"); return ret; } if (ret = SSLSetIOFuncs(ONB_sslContext, SSLReadFunction, SSLWriteFunction)) { NSLog(@"Error setting IO Functions"); return ret; } if (ret = SSLSetConnection(ONB_sslContext, self)) { NSLog(@"Error setting connection"); return ret; } if (ret = SSLSetEnableCertVerify(ONB_sslContext, (Boolean)[self verifySSLCertificates])) { NSLog(@"Error calling SSLSetEnableCertVerify"); return ret; } SecIdentityRef identity = [[self sslIdentity] identityRef]; if (identity || [self sslServerMode]) { CFArrayRef certificates = CFArrayCreate(kCFAllocatorDefault, (const void **)&identity, identity ? 1 : 0, NULL); ret = SSLSetCertificate(ONB_sslContext, certificates); CFRelease(certificates); if (ret) { NSLog(@"Error setting certificates: %d", ret); return ret; } else NSLog(@"Set up certificates"); } } ONB_inputData = inputData; ONB_outputData = outputData; ret = SSLHandshake(ONB_sslContext); if (ret == errSSLWouldBlock) return 0; if (! ret) return 1; return ret; } - (NSData *)encryptData:(NSData *)data inputData:(NSMutableData *)inputData { if ((! data) || (! [data length])) return [NSData data]; ONB_inputData = inputData; ONB_outputData = [NSMutableData dataWithCapacity:2*[data length]]; unsigned int totalLength = [data length]; unsigned int processed = 0; const void *buffer = [data bytes]; while (processed < totalLength) { size_t written = 0; int ret; if (ret = SSLWrite(ONB_sslContext, buffer + processed, totalLength - processed, &written)) return nil; processed += written; } return [NSData dataWithData:ONB_outputData]; } - (NSData *)decryptData:(NSMutableData *)data outputData:(NSMutableData *)outputData { if ((! data) || (! [data length])) return [NSData data]; ONB_inputData = data; ONB_outputData = outputData; NSMutableData *decryptedData = [NSMutableData dataWithCapacity:[data length]]; int ret = 0; while (! ret) { size_t read = 0; char buf[1024]; ret = SSLRead(ONB_sslContext, buf, 1024, &read); if (ret && (ret != errSSLWouldBlock) && (ret != errSSLClosedGraceful)) { NSLog(@"Error in SSLRead: %d", ret); return nil; } [decryptedData appendBytes:buf length:read]; } return [NSData dataWithData:decryptedData]; } - (BOOL)verifySSLCertificates { return ONB_verifySSLCerts; } - (void)setVerifySSLCertificates:(BOOL)verifySSLCertificates { ONB_verifySSLCerts = verifySSLCertificates; } - (ONBSSLIdentity *)sslIdentity { return ONB_sslIdentity; } - (BOOL)sslServerMode { return ONB_sslServerMode; } - (void)setSSLServerMode:(BOOL)sslServerMode { ONB_sslServerMode = sslServerMode; } - (void)setSSLIdentity:(ONBSSLIdentity *)sslIdentity { [ONB_sslIdentity autorelease]; ONB_sslIdentity = [sslIdentity retain]; } - (OSStatus)ONB_handleSSLWriteFromData:(const void *)data size:(size_t *)size { [ONB_outputData appendBytes:data length:*size]; return noErr; } - (OSStatus)ONB_handleSSLReadToData:(void *)data size:(size_t *)size { size_t askedSize = *size; *size = MIN(askedSize, [ONB_inputData length]); if (! *size) { return errSSLWouldBlock; } NSRange byteRange = NSMakeRange(0, *size); [ONB_inputData getBytes:data range:byteRange]; [ONB_inputData replaceBytesInRange:byteRange withBytes:NULL length:0]; if (askedSize > *size) return errSSLWouldBlock; return noErr; } @end ================================================ FILE: Example/ONBSSLIdentity.h ================================================ // This code derives from Aaron Jacobs's OneButton Socket, which was // at the time of writing normally licensed under the terms of the // GNU General Public License. You can find the "standard" version // in the CVS repository of OneButton FTP (www.onebutton.org). // // The SPECIFIC INCARNATION of OneButton Socket upon which this // code is based was specially distributed to Greg Hulands on 2006-01-05 // under the terms of a modified BSD-style license rather than the GPL. // This does not indicate that any other version of OneButton Socket // is or will be distributed under any license but the GPL. /* * Copyright (c) 2005, Aaron Jacobs. * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above two paragraph * note about licensing of OneButton Socket, the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Aaron Jacobs nor the names of OneButton Socket or * OneButton FTP may be used to endorse or promote products derived from * this software without specific prior written permission from Aaron Jacobs. * * THIS SOFTWARE IS PROVIDED BY AARON JACOBS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL AARON JACOBS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ #import #import @interface ONBSSLIdentity : NSObject { SecIdentityRef ONB_identity; } // Calls defaultSSLIdentityFromKeychain: with an argument of nil. + (ONBSSLIdentity *)defaultSSLIdentity; // Returns the first SSL identity from the given keychain (or the default keychain // if nil is given). Returns nil if no such identity could be found. You need only // specify the keychain's filename, e.g. "login.keychain". + (ONBSSLIdentity *)defaultSSLIdentityInKeychain:(NSString *)keychainName; // Designated initializer - (id)initWithIdentityRef:(SecIdentityRef)identityRef; // It is your responsibility to retain this if you need it. - (SecIdentityRef)identityRef; @end ================================================ FILE: Example/ONBSSLIdentity.m ================================================ // This code derives from Aaron Jacobs's OneButton Socket, which was // at the time of writing normally licensed under the terms of the // GNU General Public License. You can find the "standard" version // in the CVS repository of OneButton FTP (www.onebutton.org). // // The SPECIFIC INCARNATION of OneButton Socket upon which this // code is based was specially distributed to Greg Hulands on 2006-01-05 // under the terms of a modified BSD-style license rather than the GPL. // This does not indicate that any other version of OneButton Socket // is or will be distributed under any license but the GPL. /* * Copyright (c) 2005, Aaron Jacobs. * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above two paragraph * note about licensing of OneButton Socket, the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Aaron Jacobs nor the names of OneButton Socket or * OneButton FTP may be used to endorse or promote products derived from * this software without specific prior written permission from Aaron Jacobs. * * THIS SOFTWARE IS PROVIDED BY AARON JACOBS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL AARON JACOBS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ #import "ONBSSLIdentity.h" @implementation ONBSSLIdentity - (id)init { return [self initWithIdentityRef:nil]; } - (id)initWithIdentityRef:(SecIdentityRef)identityRef { if (! (self = [super init])) return nil; ONB_identity = identityRef; CFRetain(ONB_identity); return self; } - (void)dealloc { CFRelease(ONB_identity); [super dealloc]; } + (ONBSSLIdentity *)defaultSSLIdentity { return [self defaultSSLIdentityInKeychain:nil]; } + (ONBSSLIdentity *)defaultSSLIdentityInKeychain:(NSString *)keychainName { SecKeychainRef keychainRef = nil; // If no keychain name was specified, use the default keychain. if (keychainName) { NSString *keychainsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Keychains"]; NSString *fullPath = [keychainsDirectory stringByAppendingPathComponent:keychainName]; if (SecKeychainOpen([fullPath UTF8String], &keychainRef)) { NSLog(@"Unable to open keychain"); return nil; } } else if (SecKeychainCopyDefault(&keychainRef)) { NSLog(@"Unable to get default keychain"); return nil; } SecIdentitySearchRef searchRef = nil; if (SecIdentitySearchCreate(keychainRef, CSSM_KEYUSE_SIGN, &searchRef)) { NSLog(@"Unable to create keychain search"); CFRelease(keychainRef); return nil; } SecIdentityRef identityRef = nil; if (SecIdentitySearchCopyNext(searchRef, &identityRef)) { NSLog(@"Unable to get next search result"); CFRelease(keychainRef); CFRelease(searchRef); return nil; } ONBSSLIdentity *sslIdentity = [[[ONBSSLIdentity alloc] initWithIdentityRef:identityRef] autorelease]; CFRelease(keychainRef); CFRelease(searchRef); CFRelease(identityRef); return sslIdentity; } - (SecIdentityRef)identityRef { return ONB_identity; } @end ================================================ FILE: Example/PermissionsController.h ================================================ /* PermissionsController */ #import @interface PermissionsController : NSObject { IBOutlet id ge; IBOutlet id gr; IBOutlet id gw; IBOutlet id ue; IBOutlet id ur; IBOutlet id uw; IBOutlet id we; IBOutlet id window; IBOutlet id wr; IBOutlet id ww; BOOL _needsUpdating; id _con; } + (id)sharedPermissions; - (void)displayFile:(NSMutableDictionary *)file sheet:(NSWindow *)win connection:(id)con; - (IBAction)attribsChanged:(id)sender; - (IBAction)cancel:(id)sender; - (IBAction)save:(id)sender; @end ================================================ FILE: Example/PermissionsController.m ================================================ #import "PermissionsController.h" static PermissionsController *_shared = nil; @implementation PermissionsController - (id)init { [super init]; [NSBundle loadNibNamed:@"Permissions" owner:self]; return self; } + (id)sharedPermissions { if (!_shared) _shared = [[PermissionsController alloc] init]; return _shared; } - (void)displayFile:(NSMutableDictionary *)file sheet:(NSWindow *)win connection:(id)con { _con = con; NSNumber *posix = [file objectForKey:NSFilePosixPermissions]; NSLog(@"%@: %@", NSStringFromSelector(_cmd), posix); [NSApp beginSheet:window modalForWindow:win modalDelegate:nil didEndSelector:nil contextInfo:nil]; } - (IBAction)attribsChanged:(id)sender { _needsUpdating = YES; } - (IBAction)cancel:(id)sender { [window orderOut:self]; [NSApp endSheet:window]; } - (IBAction)save:(id)sender { if (_needsUpdating) { // [_con setPermissions: forFile:]; } [self cancel:self]; } @end ================================================ FILE: Example/ProgressCell.h ================================================ // // ProgressCell.h // FTPConnection // // Created by Greg Hulands on 3/12/04. // Copyright 2004 __MyCompanyName__. All rights reserved. // #import @interface ProgressCell : NSCell { } @end ================================================ FILE: Example/ProgressCell.m ================================================ // // ProgressCell.m // FTPConnection // // Created by Greg Hulands on 3/12/04. // Copyright 2004 __MyCompanyName__. All rights reserved. // #import "ProgressCell.h" @implementation ProgressCell - (id)initTextCell:(NSString *)txt { [super initTextCell:txt]; [self setObjectValue:[NSNumber numberWithInt:-1]]; return self; } - (void)setObjectValue:(id)obj { [super setObjectValue:obj]; } - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { CGFloat percent = [[self objectValue] intValue] / 100.0; if (percent < 0 || percent >= 1) { if (percent < 0) { NSString *str = nil; if (percent == -1.0) { str = [NSString stringWithString:@"Waiting..."]; } else { str = [NSString stringWithString:@"Cancelled..."]; } NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:nil]; NSSize size = [str sizeWithAttributes:attribs]; NSRect centered = NSMakeRect(NSMidX(cellFrame) - (size.width/2), NSMidY(cellFrame) - (size.height / 2), NSWidth(cellFrame), size.height); [str drawInRect:centered withAttributes:attribs]; } else { NSString *str = [NSString stringWithString:@"Finished"]; NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:nil]; NSSize size = [str sizeWithAttributes:attribs]; NSRect centered = NSMakeRect(NSMidX(cellFrame) - (size.width/2), NSMidY(cellFrame) - (size.height / 2), NSWidth(cellFrame), size.height); [str drawInRect:centered withAttributes:attribs]; } } else { [[NSColor colorWithCalibratedRed:164.0/256.0 green:106.0/256.0 blue:255.0/256.0 alpha:1.0] set]; NSRectFill(NSMakeRect(NSMinX(cellFrame),NSMinY(cellFrame),percent * NSWidth(cellFrame),NSHeight(cellFrame))); [[NSColor colorWithCalibratedRed:129.0/256.0 green:84.0/256.0 blue:201.0/256.0 alpha:1.0] set]; NSFrameRect(cellFrame); } } @end ================================================ FILE: Example/README ================================================ Notes on Using the Connection Framework Dependencies: ================================================ FILE: Example/ReleaseNotes.txt ================================================ Connection Framework version 3 Release Notes There have been quite a few changes to the API so take note when moving to this version. This is mainly in the arguments passed back in the upload/download of the delegate methods. What's New 1. FTP can now connect to hosts that have multiple DNS names. 2. FTP has been fully tested. 3. SFTP support has been added. 4. WebDav and .Mac support has been added. 5. File Connection. 6. New class heirachy. AbstractQueueConnection and StreamBasedConnection makes writing a new connection class very easy. If subclassing from StreamBasedConnection all background threading issues are handled for you and all you have to do is create a state machine for the sending and receiving of commands. If your connection is not state based, then it is suggested not to subclass AbstractConnection, but rather implement the protocol. 7. Sample application has had more of the delegate methods implemented and better upload/download support. All feedback is appreciated. ghulands@framedphotographics.com ================================================ FILE: Example/UKKQueue_Symbols ================================================ _UKFileWatcherRenameNotification _UKFileWatcherLinkCountChangeNotification _UKFileWatcherDeleteNotification _UKFileWatcherAccessRevocationNotification _UKFileWatcherWriteNotification _UKFileWatcherAttributeChangeNotification _UKFileWatcherSizeIncreaseNotification .objc_class_name_UKFNSubscribeFileWatcher _UKFileSubscriptionProc .objc_class_name_UKKQueue .objc_category_name_NSObject_UKMainThreadProxy .objc_class_name_UKMainThreadProxy ================================================ FILE: Example/channel.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #ifndef WIN32 #include #endif /* {{{ libssh2_channel_nextid * Determine the next channel ID we can use at our end */ unsigned long libssh2_channel_nextid(LIBSSH2_SESSION *session) { unsigned long id = session->next_channel; LIBSSH2_CHANNEL *channel; channel = session->channels.head; while (channel) { if (channel->local.id > id) { id = channel->local.id; } channel = channel->next; } /* This is a shortcut to avoid waiting for close packets on channels we've forgotten about, * This *could* be a problem if we request and close 4 billion or so channels in too rapid succession * for the remote end to respond, but the worst case scenario is that some data meant for another channel * Gets picked up by the new one.... Pretty unlikely all told... */ session->next_channel = id + 1; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Allocated new channel ID#%lu", id); #endif return id; } /* }}} */ /* {{{ libssh2_channel_locate * Locate a channel pointer by number */ LIBSSH2_CHANNEL *libssh2_channel_locate(LIBSSH2_SESSION *session, unsigned long channel_id) { LIBSSH2_CHANNEL *channel = session->channels.head; while (channel) { if (channel->local.id == channel_id) { return channel; } channel = channel->next; } return NULL; } /* }}} */ #define libssh2_channel_add(session, channel) \ { \ if ((session)->channels.tail) { \ (session)->channels.tail->next = (channel); \ (channel)->prev = (session)->channels.tail; \ } else { \ (session)->channels.head = (channel); \ (channel)->prev = NULL; \ } \ (channel)->next = NULL; \ (session)->channels.tail = (channel); \ (channel)->session = (session); \ } /* {{{ libssh2_channel_open_session * Establish a generic session channel */ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_open_ex(LIBSSH2_SESSION *session, const char *channel_type, unsigned int channel_type_len, unsigned int window_size, unsigned int packet_size, const char *message, unsigned int message_len) { unsigned char reply_codes[3] = { SSH_MSG_CHANNEL_OPEN_CONFIRMATION, SSH_MSG_CHANNEL_OPEN_FAILURE, 0 }; LIBSSH2_CHANNEL *channel = NULL; unsigned long local_channel = libssh2_channel_nextid(session); unsigned char *s, *packet = NULL; unsigned long packet_len = channel_type_len + message_len + 17; /* packet_type(1) + channel_type_len(4) + sender_channel(4) + window_size(4) + packet_size(4) */ unsigned char *data = NULL; unsigned long data_len; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Opening Channel - win %d pack %d", window_size, packet_size); #endif channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL)); if (!channel) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate space for channel data", 0); return NULL; } memset(channel, 0, sizeof(LIBSSH2_CHANNEL)); channel->channel_type_len = channel_type_len; channel->channel_type = LIBSSH2_ALLOC(session, channel_type_len); if (!channel->channel_type) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Failed allocating memory for channel type name", 0); LIBSSH2_FREE(session, channel); return NULL; } memcpy(channel->channel_type, channel_type, channel_type_len); /* REMEMBER: local as in locally sourced */ channel->local.id = local_channel; channel->remote.window_size = window_size; channel->remote.window_size_initial = window_size; channel->remote.packet_size = packet_size; libssh2_channel_add(session, channel); s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate temporary space for packet", 0); return NULL; } *(s++) = SSH_MSG_CHANNEL_OPEN; libssh2_htonu32(s, channel_type_len); s += 4; memcpy(s, channel_type, channel_type_len); s += channel_type_len; libssh2_htonu32(s, local_channel); s += 4; libssh2_htonu32(s, window_size); s += 4; libssh2_htonu32(s, packet_size); s += 4; if (message && message_len) { memcpy(s, message, message_len); s += message_len; } if (libssh2_packet_write(session, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel-open request", 0); goto channel_error; } if (libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, packet + 5 + channel_type_len, 4)) { goto channel_error; } if (data[0] == SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { channel->remote.id = libssh2_ntohu32(data + 5); channel->local.window_size = libssh2_ntohu32(data + 9); channel->local.window_size_initial = libssh2_ntohu32(data + 9); channel->local.packet_size = libssh2_ntohu32(data + 13); #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Connection Established - ID: %lu/%lu win: %lu/%lu pack: %lu/%lu", channel->local.id, channel->remote.id, channel->local.window_size, channel->remote.window_size, channel->local.packet_size, channel->remote.packet_size); #endif LIBSSH2_FREE(session, packet); LIBSSH2_FREE(session, data); return channel; } if (data[0] == SSH_MSG_CHANNEL_OPEN_FAILURE) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Channel open failure", 0); } channel_error: if (data) { LIBSSH2_FREE(session, data); } if (packet) { LIBSSH2_FREE(session, packet); } if (channel) { unsigned char channel_id[4]; LIBSSH2_FREE(session, channel->channel_type); if (channel->next) { channel->next->prev = channel->prev; } if (channel->prev) { channel->prev->next = channel->next; } if (session->channels.head == channel) { session->channels.head = channel->next; } if (session->channels.tail == channel) { session->channels.tail = channel->prev; } /* Clear out packets meant for this channel */ libssh2_htonu32(channel_id, channel->local.id); while ((libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_DATA, &data, &data_len, 1, channel_id, 4, 1) >= 0) || (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_EXTENDED_DATA, &data, &data_len, 1, channel_id, 4, 1) >= 0)) { LIBSSH2_FREE(session, data); } LIBSSH2_FREE(session, channel); } return NULL; } /* }}} */ /* {{{ libssh2_channel_direct_tcpip_ex * Tunnel TCP/IP connect through the SSH session to direct host/port */ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *session, char *host, int port, char *shost, int sport) { LIBSSH2_CHANNEL *channel; unsigned char *message, *s; unsigned long host_len = strlen(host), shost_len = strlen(shost); unsigned long message_len = host_len + shost_len + 16; /* host_len(4) + port(4) + shost_len(4) + sport(4) */ #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Requesting direct-tcpip session to from %s:%d to %s:%d", shost, sport, host, port); #endif s = message = LIBSSH2_ALLOC(session, message_len); if (!message) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for direct-tcpip connection", 0); return NULL; } libssh2_htonu32(s, host_len); s += 4; memcpy(s, host, host_len); s += host_len; libssh2_htonu32(s, port); s += 4; libssh2_htonu32(s, shost_len); s += 4; memcpy(s, shost, shost_len); s += shost_len; libssh2_htonu32(s, sport); s += 4; channel = libssh2_channel_open_ex(session, "direct-tcpip", sizeof("direct-tcpip") - 1, LIBSSH2_CHANNEL_WINDOW_DEFAULT, LIBSSH2_CHANNEL_PACKET_DEFAULT, (char *)message, message_len); LIBSSH2_FREE(session, message); return channel; } /* }}} */ /* {{{ libssh2_channel_forward_listen_ex * Bind a port on the remote host and listen for connections */ LIBSSH2_API LIBSSH2_LISTENER *libssh2_channel_forward_listen_ex(LIBSSH2_SESSION *session, char *host, int port, int *bound_port, int queue_maxsize) { unsigned char *packet, *s, *data, reply_codes[3] = { SSH_MSG_REQUEST_SUCCESS, SSH_MSG_REQUEST_FAILURE, 0 }; unsigned long data_len; unsigned long host_len = (host ? strlen(host) : (sizeof("0.0.0.0") - 1)); unsigned long packet_len = host_len + (sizeof("tcpip-forward") - 1) + 14; /* packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + port(4) */ #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Requesting tcpip-forward session for %s:%d", host, port); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memeory for setenv packet", 0); return NULL; } *(s++) = SSH_MSG_GLOBAL_REQUEST; libssh2_htonu32(s, sizeof("tcpip-forward") - 1); s += 4; memcpy(s, "tcpip-forward", sizeof("tcpip-forward") - 1); s += sizeof("tcpip-forward") - 1; *(s++) = 0xFF; /* want_reply */ libssh2_htonu32(s, host_len); s += 4; memcpy(s, host ? host : "0.0.0.0", host_len); s += host_len; libssh2_htonu32(s, port); s += 4; if (libssh2_packet_write(session, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send global-request packet for forward listen request", 0); LIBSSH2_FREE(session, packet); return NULL; } LIBSSH2_FREE(session, packet); if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) { return NULL; } if (data[0] == SSH_MSG_REQUEST_SUCCESS) { LIBSSH2_LISTENER *listener; listener = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_LISTENER)); if (!listener) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for listener queue", 0); LIBSSH2_FREE(session, data); return NULL; } memset(listener, 0, sizeof(LIBSSH2_LISTENER)); listener->session = session; listener->host = LIBSSH2_ALLOC(session, host_len + 1); if (!listener->host) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for listener queue", 0); LIBSSH2_FREE(session, listener); LIBSSH2_FREE(session, data); return NULL; } memcpy(listener->host, host ? host : "0.0.0.0", host_len); listener->host[host_len] = 0; if (data_len >= 5 && !port) { listener->port = libssh2_ntohu32(data + 1); #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Dynamic tcpip-forward port allocated: %d", listener->port); #endif } else { listener->port = port; } listener->queue_size = 0; listener->queue_maxsize = queue_maxsize; listener->next = session->listeners; listener->prev = NULL; if (session->listeners) { session->listeners->prev = listener; } session->listeners = listener; if (bound_port) { *bound_port = listener->port; } LIBSSH2_FREE(session, data); return listener; } if (data[0] == SSH_MSG_REQUEST_FAILURE) { LIBSSH2_FREE(session, data); libssh2_error(session, LIBSSH2_ERROR_REQUEST_DENIED, "Unable to complete request for forward-listen", 0); return NULL; } return NULL; } /* }}} */ /* {{{ libssh2_channel_forward_cancel * Stop listening on a remote port and free the listener * Toss out any pending (un-accept()ed) connections */ LIBSSH2_API int libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener) { LIBSSH2_SESSION *session = listener->session; LIBSSH2_CHANNEL *queued = listener->queue; unsigned char *packet, *s; unsigned long host_len = strlen(listener->host); unsigned long packet_len = host_len + 14 + sizeof("cancel-tcpip-forward") - 1; /* packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + port(4) */ #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Cancelling tcpip-forward session for %s:%d", listener->host, listener->port); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memeory for setenv packet", 0); return -1; } *(s++) = SSH_MSG_GLOBAL_REQUEST; libssh2_htonu32(s, sizeof("cancel-tcpip-forward") - 1); s += 4; memcpy(s, "cancel-tcpip-forward", sizeof("cancel-tcpip-forward") - 1); s += sizeof("cancel-tcpip-forward") - 1; *(s++) = 0x00; /* want_reply */ libssh2_htonu32(s, host_len); s += 4; memcpy(s, listener->host, host_len); s += host_len; libssh2_htonu32(s, listener->port); s += 4; if (libssh2_packet_write(session, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send global-request packet for forward listen request", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); while (queued) { LIBSSH2_CHANNEL *next = queued->next; libssh2_channel_free(queued); queued = next; } LIBSSH2_FREE(session, listener->host); if (listener->next) { listener->next->prev = listener->prev; } if (listener->prev) { listener->prev->next = listener->next; } else { session->listeners = listener->next; } LIBSSH2_FREE(session, listener); return 0; } /* }}} */ /* {{{ libssh2_channel_forward_accept * Accept a connection */ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener) { while (libssh2_packet_read(listener->session, 0) > 0); if (listener->queue) { LIBSSH2_SESSION *session = listener->session; LIBSSH2_CHANNEL *channel; channel = listener->queue; listener->queue = listener->queue->next; if (listener->queue) { listener->queue->prev = NULL; } channel->prev = NULL; channel->next = session->channels.head; session->channels.head = channel; if (channel->next) { channel->next->prev = channel; } else { session->channels.tail = channel; } listener->queue_size--; return channel; } return NULL; } /* }}} */ /* {{{ libssh2_channel_setenv_ex * Set an environment variable prior to requesting a shell/program/subsystem */ LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, char *varname, unsigned int varname_len, char *value, unsigned int value_len) { LIBSSH2_SESSION *session = channel->session; unsigned char *s, *packet, *data, reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }, local_channel[4]; unsigned long data_len; unsigned long packet_len = varname_len + value_len + 21; /* packet_type(1) + channel_id(4) + request_len(4) + request(3)"env" + want_reply(1) + varname_len(4) + value_len(4) */ #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Setting remote environment variable: %s=%s on channel %lu/%lu", varname, value, channel->local.id, channel->remote.id); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memeory for setenv packet", 0); return -1; } *(s++) = SSH_MSG_CHANNEL_REQUEST; libssh2_htonu32(s, channel->remote.id); s += 4; libssh2_htonu32(s, sizeof("env") - 1); s += 4; memcpy(s, "env", sizeof("env") - 1); s += sizeof("env") - 1; *(s++) = 0xFF; libssh2_htonu32(s, varname_len); s += 4; memcpy(s, varname, varname_len); s += varname_len; libssh2_htonu32(s, value_len); s += 4; memcpy(s, value, value_len); s += value_len; if (libssh2_packet_write(session, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel-request packet for setenv request", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); libssh2_htonu32(local_channel, channel->local.id); if (libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, local_channel, 4)) { return -1; } if (data[0] == SSH_MSG_CHANNEL_SUCCESS) { LIBSSH2_FREE(session, data); return 0; } LIBSSH2_FREE(session, data); libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel-setenv", 0); return -1; } /* }}} */ /* {{{ libssh2_channel_request_pty_ex * Duh... Request a PTY */ LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, char *term, unsigned int term_len, char *modes, unsigned int modes_len, int width, int height, int width_px, int height_px) { LIBSSH2_SESSION *session = channel->session; unsigned char *s, *packet, *data, reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }, local_channel[4]; unsigned long data_len; unsigned long packet_len = term_len + modes_len + 41; /* packet_type(1) + channel(4) + pty_req_len(4) + "pty_req"(7) + want_reply(1) + term_len(4) + width(4) + height(4) + width_px(4) + height_px(4) + modes_len(4) */ #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Allocating tty on channel %lu/%lu", channel->local.id, channel->remote.id); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for pty-request", 0); return -1; } *(s++) = SSH_MSG_CHANNEL_REQUEST; libssh2_htonu32(s, channel->remote.id); s += 4; libssh2_htonu32(s, sizeof("pty-req") - 1); s += 4; memcpy(s, "pty-req", sizeof("pty-req") - 1); s += sizeof("pty-req") - 1; *(s++) = 0xFF; libssh2_htonu32(s, term_len); s += 4; if (term) { memcpy(s, term, term_len); s += term_len; } libssh2_htonu32(s, width); s += 4; libssh2_htonu32(s, height); s += 4; libssh2_htonu32(s, width_px); s += 4; libssh2_htonu32(s, height_px); s += 4; libssh2_htonu32(s, modes_len); s += 4; if (modes) { memcpy(s, modes, modes_len); s += modes_len; } if (libssh2_packet_write(session, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send pty-request packet", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); libssh2_htonu32(local_channel, channel->local.id); if (libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, local_channel, 4)) { return -1; } if (data[0] == SSH_MSG_CHANNEL_SUCCESS) { LIBSSH2_FREE(session, data); return 0; } LIBSSH2_FREE(session, data); libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel request-pty", 0); return -1; } /* }}} */ /* Keep this an even number */ #define LIBSSH2_X11_RANDOM_COOKIE_LEN 32 /* {{{ libssh2_channel_x11_req_ex * Request X11 forwarding */ LIBSSH2_API int libssh2_channel_x11_req_ex(LIBSSH2_CHANNEL *channel, int single_connection, char *auth_proto, char *auth_cookie, int screen_number) { LIBSSH2_SESSION *session = channel->session; unsigned char *s, *packet, *data, reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }, local_channel[4]; unsigned long data_len; unsigned long proto_len = auth_proto ? strlen(auth_proto) : (sizeof("MIT-MAGIC-COOKIE-1") - 1); unsigned long cookie_len = auth_cookie ? strlen(auth_cookie) : LIBSSH2_X11_RANDOM_COOKIE_LEN; unsigned long packet_len = proto_len + cookie_len + 30; /* packet_type(1) + channel(4) + x11_req_len(4) + "x11-req"(7) + want_reply(1) + single_cnx(1) + proto_len(4) + cookie_len(4) + screen_num(4) */ #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Requesting x11-req for channel %lu/%lu: single=%d proto=%s cookie=%s screen=%d", channel->local.id, channel->remote.id, single_connection, auth_proto ? auth_proto : "MIT-MAGIC-COOKIE-1", auth_cookie ? auth_cookie : "", screen_number); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for pty-request", 0); return -1; } *(s++) = SSH_MSG_CHANNEL_REQUEST; libssh2_htonu32(s, channel->remote.id); s += 4; libssh2_htonu32(s, sizeof("x11-req") - 1); s += 4; memcpy(s, "x11-req", sizeof("x11-req") - 1); s += sizeof("x11-req") - 1; *(s++) = 0xFF; /* want_reply */ *(s++) = single_connection ? 0xFF : 0x00; libssh2_htonu32(s, proto_len); s += 4; memcpy(s, auth_proto ? auth_proto : "MIT-MAGIC-COOKIE-1", proto_len); s += proto_len; libssh2_htonu32(s, cookie_len); s += 4; if (auth_cookie) { memcpy(s, auth_cookie, cookie_len); } else { int i; unsigned char buffer[LIBSSH2_X11_RANDOM_COOKIE_LEN / 2]; libssh2_random(buffer, LIBSSH2_X11_RANDOM_COOKIE_LEN / 2); for (i = 0; i < (LIBSSH2_X11_RANDOM_COOKIE_LEN / 2); i++) { snprintf((char *)s + (i * 2), 2, "%02X", buffer[i]); } } s += cookie_len; libssh2_htonu32(s, screen_number); s += 4; if (libssh2_packet_write(session, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send x11-req packet", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); libssh2_htonu32(local_channel, channel->local.id); if (libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, local_channel, 4)) { return -1; } if (data[0] == SSH_MSG_CHANNEL_SUCCESS) { LIBSSH2_FREE(session, data); return 0; } LIBSSH2_FREE(session, data); libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel x11-req", 0); return -1; } /* }}} */ /* {{{ libssh2_channel_process_startup * Primitive for libssh2_channel_(shell|exec|subsystem) */ LIBSSH2_API int libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, const char *request, unsigned int request_len, const char *message, unsigned int message_len) { LIBSSH2_SESSION *session = channel->session; unsigned char *s, *packet, *data, reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }, local_channel[4]; unsigned long data_len; unsigned long packet_len = request_len + 10; /* packet_type(1) + channel(4) + request_len(4) + want_reply(1) */ if (message) { packet_len += message_len + 4; } #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "starting request(%s) on channel %lu/%lu, message=%s", request, channel->local.id, channel->remote.id, message); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for channel-process request", 0); return -1; } *(s++) = SSH_MSG_CHANNEL_REQUEST; libssh2_htonu32(s, channel->remote.id); s += 4; libssh2_htonu32(s, request_len); s += 4; memcpy(s, request, request_len); s += request_len; *(s++) = 0xFF; if (message) { libssh2_htonu32(s, message_len); s += 4; memcpy(s, message, message_len); s += message_len; } if (libssh2_packet_write(session, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel request", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); libssh2_htonu32(local_channel, channel->local.id); if (libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, local_channel, 4)) { return -1; } if (data[0] == SSH_MSG_CHANNEL_SUCCESS) { LIBSSH2_FREE(session, data); return 0; } LIBSSH2_FREE(session, data); libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel-process-startup", 0); return -1; } /* }}} */ /* {{{ libssh2_channel_set_blocking * Set a channel's blocking mode on or off, similar to a socket's fcntl(fd, F_SETFL, O_NONBLOCK); type command */ LIBSSH2_API void libssh2_channel_set_blocking(LIBSSH2_CHANNEL *channel, int blocking) { #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(channel->session, LIBSSH2_DBG_CONN, "Setting blocking mode on channel %lu/%lu to %d", channel->local.id, channel->remote.id, blocking); #endif channel->blocking = blocking; } /* }}} */ /* {{{ libssh2_channel_flush_ex * Flush data from one (or all) stream * Returns number of bytes flushed, or -1 on failure */ LIBSSH2_API int libssh2_channel_flush_ex(LIBSSH2_CHANNEL *channel, int streamid) { LIBSSH2_PACKET *packet = channel->session->packets.head; unsigned long refund_bytes = 0, flush_bytes = 0; while (packet) { LIBSSH2_PACKET *next = packet->next; unsigned char packet_type = packet->data[0]; if (((packet_type == SSH_MSG_CHANNEL_DATA) || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) && (libssh2_ntohu32(packet->data + 1) == channel->local.id)) { /* It's our channel at least */ unsigned long packet_stream_id = (packet_type == SSH_MSG_CHANNEL_DATA) ? 0 : libssh2_ntohu32(packet->data + 5); if ((streamid == LIBSSH2_CHANNEL_FLUSH_ALL) || ((packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA) && ((streamid == LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA) || (streamid == packet_stream_id))) || ((packet_type == SSH_MSG_CHANNEL_DATA) && (streamid == 0))) { int bytes_to_flush = packet->data_len - packet->data_head; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(channel->session, LIBSSH2_DBG_CONN, "Flushing %d bytes of data from stream %lu on channel %lu/%lu", bytes_to_flush, packet_stream_id, channel->local.id, channel->remote.id); #endif /* It's one of the streams we wanted to flush */ refund_bytes += packet->data_len - 13; flush_bytes += bytes_to_flush; LIBSSH2_FREE(channel->session, packet->data); if (packet->prev) { packet->prev->next = packet->next; } else { channel->session->packets.head = packet->next; } if (packet->next) { packet->next->prev = packet->prev; } else { channel->session->packets.tail = packet->prev; } LIBSSH2_FREE(channel->session, packet); } } packet = next; } if (refund_bytes) { libssh2_channel_receive_window_adjust(channel, refund_bytes, 0); } return flush_bytes; } /* }}} */ /* {{{ libssh2_channel_get_exit_status * Return the channel's program exit status */ LIBSSH2_API int libssh2_channel_get_exit_status(LIBSSH2_CHANNEL* channel) { return channel->exit_status; } /* }}} */ /* {{{ libssh2_channel_receive_window_adjust * Adjust the receive window for a channel by adjustment bytes * If the amount to be adjusted is less than LIBSSH2_CHANNEL_MINADJUST and force is 0 * The adjustment amount will be queued for a later packet * * Returns the new size of the receive window (as understood by remote end) */ LIBSSH2_API unsigned long libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL *channel, unsigned long adjustment, unsigned char force) { unsigned char adjust[9]; /* packet_type(1) + channel(4) + adjustment(4) */ if (!force && (adjustment + channel->adjust_queue < LIBSSH2_CHANNEL_MINADJUST)) { #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(channel->session, LIBSSH2_DBG_CONN, "Queing %lu bytes for receive window adjustment for channel %lu/%lu", adjustment, channel->local.id, channel->remote.id); #endif channel->adjust_queue += adjustment; return channel->remote.window_size; } if (!adjustment && !channel->adjust_queue) { return channel->remote.window_size; } adjustment += channel->adjust_queue; channel->adjust_queue = 0; /* Adjust the window based on the block we just freed */ adjust[0] = SSH_MSG_CHANNEL_WINDOW_ADJUST; libssh2_htonu32(adjust + 1, channel->remote.id); libssh2_htonu32(adjust + 5, adjustment); #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(channel->session, LIBSSH2_DBG_CONN, "Adjusting window %lu bytes for data flushed from channel %lu/%lu", adjustment, channel->local.id, channel->remote.id); #endif if (libssh2_packet_write(channel->session, adjust, 9)) { libssh2_error(channel->session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send transfer-window adjustment packet, deferring", 0); channel->adjust_queue = adjustment; } else { channel->remote.window_size += adjustment; } return channel->remote.window_size; } /* }}} */ /* {{{ libssh2_channel_handle_extended_data * How should extended data look to the calling app? * Keep it in separate channels[_read() _read_stdder()]? (NORMAL) * Merge the extended data to the standard data? [everything via _read()]? (MERGE) * Ignore it entirely [toss out packets as they come in]? (IGNORE) */ LIBSSH2_API void libssh2_channel_handle_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode) { #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(channel->session, LIBSSH2_DBG_CONN, "Setting channel %lu/%lu handle_extended_data mode to %d", channel->local.id, channel->remote.id, ignore_mode); #endif channel->remote.extended_data_ignore_mode = ignore_mode; if (ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) { libssh2_channel_flush_ex(channel, LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA); } } /* }}} */ /* {{{ libssh2_channel_read_ex * Read data from a channel */ LIBSSH2_API int libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, int stream_id, char *buf, size_t buflen) { LIBSSH2_SESSION *session = channel->session; int bytes_read = 0, blocking_read = 0; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Attempting to read %d bytes from channel %lu/%lu stream #%d", (int)buflen, channel->local.id, channel->remote.id, stream_id); #endif do { LIBSSH2_PACKET *packet; /* Process any waiting packets */ while (libssh2_packet_read(session, blocking_read) > 0) blocking_read = 0; packet = session->packets.head; while (packet && (bytes_read < buflen)) { /* In case packet gets destroyed during this iteration */ LIBSSH2_PACKET *next = packet->next; /* Either we asked for a specific extended data stream (and data was available), * or the standard stream (and data was available), * or the standard stream with extended_data_merge enabled and data was available */ if ((stream_id && (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) && (channel->local.id == libssh2_ntohu32(packet->data + 1)) && (stream_id == libssh2_ntohu32(packet->data + 5))) || (!stream_id && (packet->data[0] == SSH_MSG_CHANNEL_DATA) && (channel->local.id == libssh2_ntohu32(packet->data + 1))) || (!stream_id && (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) && (channel->local.id == libssh2_ntohu32(packet->data + 1)) && (channel->remote.extended_data_ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE))) { int want = buflen - bytes_read; int unlink_packet = 0; if (want >= (packet->data_len - packet->data_head)) { want = packet->data_len - packet->data_head; unlink_packet = 1; } #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Reading %d of buffered data from %lu/%lu/%d", want, channel->local.id, channel->remote.id, stream_id); #endif memcpy(buf + bytes_read, packet->data + packet->data_head, want); packet->data_head += want; bytes_read += want; if (unlink_packet) { if (packet->prev) { packet->prev->next = packet->next; } else { session->packets.head = packet->next; } if (packet->next) { packet->next->prev = packet->prev; } else { session->packets.tail = packet->prev; } LIBSSH2_FREE(session, packet->data); #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Unlinking empty packet buffer from channel %lu/%lu", channel->local.id, channel->remote.id); #endif libssh2_channel_receive_window_adjust(channel, packet->data_len - (stream_id ? 13 : 9), 0); LIBSSH2_FREE(session, packet); } } packet = next; } blocking_read = 1; } while (channel->blocking && (bytes_read == 0) && !channel->remote.close); if (channel->blocking && (bytes_read == 0)) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_CLOSED, "Remote end has closed this channel", 0); } return bytes_read; } /* }}} */ /* {{{ libssh2_channel_write_ex * Send data to a channel */ LIBSSH2_API int libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, const char *buf, size_t buflen) { LIBSSH2_SESSION *session = channel->session; unsigned char *packet; unsigned long packet_len, bufwrote = 0; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Writing %d bytes on channel %lu/%lu, stream #%d", (int)buflen, channel->local.id, channel->remote.id, stream_id); #endif if (channel->local.close) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_CLOSED, "We've already closed this channel", 0); return -1; } if (channel->local.eof) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_EOF_SENT, "EOF has already been sight, data might be ignored", 0); } if (!channel->blocking && (channel->local.window_size <= 0)) { /* Can't write anything */ return 0; } packet_len = buflen + (stream_id ? 13 : 9); /* packet_type(1) + channelno(4) [ + streamid(4) ] + buflen(4) */ packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocte space for data transmission packet", 0); return -1; } while (buflen > 0) { size_t bufwrite = buflen; unsigned char *s = packet; *(s++) = stream_id ? SSH_MSG_CHANNEL_EXTENDED_DATA : SSH_MSG_CHANNEL_DATA; libssh2_htonu32(s, channel->remote.id); s += 4; if (stream_id) { libssh2_htonu32(s, stream_id); s += 4; } /* twiddle our thumbs until there's window space available */ while (channel->local.window_size <= 0) { /* Don't worry -- This is never hit unless it's a blocking channel anyway */ if (libssh2_packet_read(session, 1) < 0) { /* Error occured, disconnect? */ return -1; } } /* Don't exceed the remote end's limits */ /* REMEMBER local means local as the SOURCE of the data */ if (bufwrite > channel->local.window_size) { #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Splitting write block due to %lu byte window_size on %lu/%lu/%d", channel->local.window_size, channel->local.id, channel->remote.id, stream_id); #endif bufwrite = channel->local.window_size; } if (bufwrite > channel->local.packet_size) { #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Splitting write block due to %lu byte packet_size on %lu/%lu/%d", channel->local.packet_size, channel->local.id, channel->remote.id, stream_id); #endif bufwrite = channel->local.packet_size; } libssh2_htonu32(s, bufwrite); s += 4; memcpy(s, buf, bufwrite); s += bufwrite; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Sending %d bytes on channel %lu/%lu, stream_id=%d", (int)bufwrite, channel->local.id, channel->remote.id, stream_id); #endif if (libssh2_packet_write(session, packet, s - packet)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel data", 0); LIBSSH2_FREE(session, packet); return -1; } /* Shrink local window size */ channel->local.window_size -= bufwrite; /* Adjust buf for next iteration */ buflen -= bufwrite; buf += bufwrite; bufwrote += bufwrite; if (!channel->blocking) { break; } } LIBSSH2_FREE(session, packet); return bufwrote; } /* }}} */ /* {{{ libssh2_channel_send_eof * Send EOF on channel */ LIBSSH2_API int libssh2_channel_send_eof(LIBSSH2_CHANNEL *channel) { LIBSSH2_SESSION *session = channel->session; unsigned char packet[5]; /* packet_type(1) + channelno(4) */ #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Sending EOF on channel %lu/%lu",channel->local.id, channel->remote.id); #endif packet[0] = SSH_MSG_CHANNEL_EOF; libssh2_htonu32(packet + 1, channel->remote.id); if (libssh2_packet_write(session, packet, 5)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send EOF on channel", 0); return -1; } channel->local.eof = 1; return 0; } /* }}} */ /* {{{ libssh2_channel_eof * Read channel's eof status */ LIBSSH2_API int libssh2_channel_eof(LIBSSH2_CHANNEL *channel) { LIBSSH2_SESSION *session = channel->session; LIBSSH2_PACKET *packet = session->packets.head; while (packet) { if (((packet->data[0] == SSH_MSG_CHANNEL_DATA) || (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) && (channel->local.id == libssh2_ntohu32(packet->data + 1))) { /* There's data waiting to be read yet, mask the EOF status */ return 0; } packet = packet->next; } return channel->remote.eof; } /* }}} */ /* {{{ libssh2_channel_close * Close a channel */ LIBSSH2_API int libssh2_channel_close(LIBSSH2_CHANNEL *channel) { LIBSSH2_SESSION *session = channel->session; unsigned char packet[5]; if (channel->local.close) { /* Already closed, act like we sent another close, even though we didn't... shhhhhh */ return 0; } #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Closing channel %lu/%lu", channel->local.id, channel->remote.id); #endif if (channel->close_cb) { LIBSSH2_CHANNEL_CLOSE(session, channel); } channel->local.close = 1; packet[0] = SSH_MSG_CHANNEL_CLOSE; libssh2_htonu32(packet + 1, channel->remote.id); if (libssh2_packet_write(session, packet, 5)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send close-channel request", 0); return -1; } /* TODO: Wait up to a timeout value for a CHANNEL_CLOSE to come back, to avoid the problem alluded to in channel_nextid */ return 0; } /* }}} */ /* {{{ libssh2_channel_wait_closed * Awaiting channel close after EOF */ LIBSSH2_API int libssh2_channel_wait_closed(LIBSSH2_CHANNEL *channel) { LIBSSH2_SESSION* session = channel->session; if (!libssh2_channel_eof(channel)) { libssh2_error(session, LIBSSH2_ERROR_INVAL, "libssh2_channel_wait_closed() invoked when channel is not in EOF state", 0); return -1; } #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Awaiting close of channel %lu/%lu", channel->local.id, channel->remote.id); #endif /* while channel is not closed, read more * packets from the network. * Either or channel will be closed * or network timeout will occur */ while (!channel->remote.close && libssh2_packet_read(session, 1) > 0) ; return 1; } /* }}} */ /* {{{ libssh2_channel_free * Make sure a channel is closed, then remove the channel from the session and free its resource(s) */ LIBSSH2_API int libssh2_channel_free(LIBSSH2_CHANNEL *channel) { LIBSSH2_SESSION *session = channel->session; unsigned char channel_id[4], *data; unsigned long data_len; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Freeing channel %lu/%lu resources", channel->local.id, channel->remote.id); #endif /* Allow channel freeing even when the socket has lost its connection */ if (!channel->local.close && (session->socket_state == LIBSSH2_SOCKET_CONNECTED) && libssh2_channel_close(channel)) { return -1; } /* channel->remote.close *might* not be set yet, Well... * We've sent the close packet, what more do you want? * Just let packet_add ignore it when it finally arrives */ /* Clear out packets meant for this channel */ libssh2_htonu32(channel_id, channel->local.id); while ((libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_DATA, &data, &data_len, 1, channel_id, 4, 1) >= 0) || (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_EXTENDED_DATA, &data, &data_len, 1, channel_id, 4, 1) >= 0)) { LIBSSH2_FREE(session, data); } /* free "channel_type" */ if (channel->channel_type) { LIBSSH2_FREE(session, channel->channel_type); } /* Unlink from channel brigade */ if (channel->prev) { channel->prev->next = channel->next; } else { session->channels.head = channel->next; } if (channel->next) { channel->next->prev = channel->prev; } else { session->channels.tail = channel->prev; } LIBSSH2_FREE(session, channel); return 0; } /* }}} */ /* {{{ libssh2_channel_window_read_ex * Check the status of the read window * Returns the number of bytes which the remote end may send without overflowing the window limit * read_avail (if passed) will be populated with the number of bytes actually available to be read * window_size_initial (if passed) will be populated with the window_size_initial as defined by the channel_open request */ LIBSSH2_API unsigned long libssh2_channel_window_read_ex(LIBSSH2_CHANNEL *channel, unsigned long *read_avail, unsigned long *window_size_initial) { if (window_size_initial) { *window_size_initial = channel->remote.window_size_initial; } if (read_avail) { unsigned long bytes_queued = 0; LIBSSH2_PACKET *packet = channel->session->packets.head; while (packet) { unsigned char packet_type = packet->data[0]; if (((packet_type == SSH_MSG_CHANNEL_DATA) || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) && (libssh2_ntohu32(packet->data + 1) == channel->local.id)) { bytes_queued += packet->data_len - packet->data_head; } packet = packet->next; } *read_avail = bytes_queued; } return channel->remote.window_size; } /* }}} */ /* {{{ libssh2_channel_window_write_ex * Check the status of the write window * Returns the number of bytes which may be safely writen on the channel without blocking * window_size_initial (if passed) will be populated with the size of the initial window as defined by the channel_open request */ LIBSSH2_API unsigned long libssh2_channel_window_write_ex(LIBSSH2_CHANNEL *channel, unsigned long *window_size_initial) { if (window_size_initial) { /* For locally initiated channels this is very often 0, so it's not *that* useful as information goes */ *window_size_initial = channel->local.window_size_initial; } return channel->local.window_size; } /* }}} */ ================================================ FILE: Example/comp.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #include /* ******** * none * ******** */ /* {{{ libssh2_comp_method_none_comp * Minimalist compression: Absolutely none */ static int libssh2_comp_method_none_comp(LIBSSH2_SESSION *session, int compress, unsigned char **dest, unsigned long *dest_len, unsigned long payload_limit, int *free_dest, const unsigned char *src, unsigned long src_len, void **abstract) { (void)session; (void)compress; (void)payload_limit; (void)abstract; *dest = (unsigned char *)src; *dest_len = src_len; *free_dest = 0; return 0; } /* }}} */ static LIBSSH2_COMP_METHOD libssh2_comp_method_none = { (char *)"none", NULL, libssh2_comp_method_none_comp, NULL }; #ifdef LIBSSH2_HAVE_ZLIB /* ******** * zlib * ******** */ /* {{{ Memory management wrappers * Yes, I realize we're doing a callback to a callback, * Deal... */ static voidpf libssh2_comp_method_zlib_alloc(voidpf opaque, uInt items, uInt size) { LIBSSH2_SESSION *session = (LIBSSH2_SESSION*)opaque; return (voidpf)LIBSSH2_ALLOC(session, items * size); } static void libssh2_comp_method_zlib_free(voidpf opaque, voidpf address) { LIBSSH2_SESSION *session = (LIBSSH2_SESSION*)opaque; LIBSSH2_FREE(session, address); } /* }}} */ /* {{{ libssh2_comp_method_zlib_init * All your bandwidth are belong to us (so save some) */ static int libssh2_comp_method_zlib_init(LIBSSH2_SESSION *session, int compress, void **abstract) { z_stream *strm; int status; strm = LIBSSH2_ALLOC(session, sizeof(z_stream)); if (!strm) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for zlib compression/decompression", 0); return -1; } memset(strm, 0, sizeof(z_stream)); strm->opaque = (voidpf)session; strm->zalloc = (alloc_func)libssh2_comp_method_zlib_alloc; strm->zfree = (free_func)libssh2_comp_method_zlib_free; if (compress) { /* deflate */ status = deflateInit(strm, Z_DEFAULT_COMPRESSION); } else { /* inflate */ status = inflateInit(strm); } if (status != Z_OK) { LIBSSH2_FREE(session, strm); return -1; } *abstract = strm; return 0; } /* }}} */ /* {{{ libssh2_comp_method_zlib_comp * zlib, a compression standard for all occasions */ static int libssh2_comp_method_zlib_comp(LIBSSH2_SESSION *session, int compress, unsigned char **dest, unsigned long *dest_len, unsigned long payload_limit, int *free_dest, const unsigned char *src, unsigned long src_len, void **abstract) { z_stream *strm = *abstract; /* A short-term alloc of a full data chunk is better than a series of reallocs */ char *out; int out_maxlen = compress ? (src_len + 4) : (2 * src_len); int limiter = 0; /* In practice they never come smaller than this */ if (out_maxlen < 25) { out_maxlen = 25; } if (out_maxlen > (int)payload_limit) { out_maxlen = payload_limit; } strm->next_in = (unsigned char *)src; strm->avail_in = src_len; strm->next_out = (unsigned char *)LIBSSH2_ALLOC(session, out_maxlen); out = (char *)strm->next_out; strm->avail_out = out_maxlen; if (!strm->next_out) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate compression/decompression buffer", 0); return -1; } while (strm->avail_in) { int status; if (compress) { status = deflate(strm, Z_PARTIAL_FLUSH); } else { status = inflate(strm, Z_PARTIAL_FLUSH); } if (status != Z_OK) { libssh2_error(session, LIBSSH2_ERROR_ZLIB, "compress/decompression failure", 0); LIBSSH2_FREE(session, out); return -1; } if (strm->avail_in) { unsigned long out_ofs = out_maxlen - strm->avail_out; out_maxlen += compress ? (strm->avail_in + 4) : (2 * strm->avail_in); if ((out_maxlen > (int)payload_limit) && !compress && limiter++) { libssh2_error(session, LIBSSH2_ERROR_ZLIB, "Excessive growth in decompression phase", 0); LIBSSH2_FREE(session, out); return -1; } out = LIBSSH2_REALLOC(session, out, out_maxlen); if (!out) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to expand compress/decompression buffer", 0); return -1; } strm->next_out = (unsigned char *)out + out_ofs; strm->avail_out += compress ? (strm->avail_in + 4) : (2 * strm->avail_in); } else while (!strm->avail_out) { /* Done with input, might be a byte or two in internal buffer during compress * Or potentially many bytes if it's a decompress */ int grow_size = compress ? 8 : 1024; if (out_maxlen >= (int)payload_limit) { libssh2_error(session, LIBSSH2_ERROR_ZLIB, "Excessive growth in decompression phase", 0); LIBSSH2_FREE(session, out); return -1; } if (grow_size > (int)(payload_limit - out_maxlen)) { grow_size = payload_limit - out_maxlen; } out_maxlen += grow_size; strm->avail_out = grow_size; out = LIBSSH2_REALLOC(session, out, out_maxlen); if (!out) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to expand final compress/decompress buffer", 0); return -1; } strm->next_out = (unsigned char *)out + out_maxlen - grow_size; if (compress) { status = deflate(strm, Z_PARTIAL_FLUSH); } else { status = inflate(strm, Z_PARTIAL_FLUSH); } if (status != Z_OK) { libssh2_error(session, LIBSSH2_ERROR_ZLIB, "compress/decompression failure", 0); LIBSSH2_FREE(session, out); return -1; } } } *dest = (unsigned char *)out; *dest_len = out_maxlen - strm->avail_out; *free_dest = 1; return 0; } /* }}} */ /* {{{ libssh2_comp_method_zlib_dtor * All done, no more compression for you */ static int libssh2_comp_method_zlib_dtor(LIBSSH2_SESSION *session, int compress, void **abstract) { z_stream *strm = *abstract; if (strm) { if (compress) { /* deflate */ deflateEnd(strm); } else { /* inflate */ inflateEnd(strm); } LIBSSH2_FREE(session, strm); } *abstract = NULL; return 0; } /* }}} */ static LIBSSH2_COMP_METHOD libssh2_comp_method_zlib = { (char *)"zlib", libssh2_comp_method_zlib_init, libssh2_comp_method_zlib_comp, libssh2_comp_method_zlib_dtor, }; #endif /* LIBSSH2_HAVE_ZLIB */ /* *********************** * Compression Methods * *********************** */ static LIBSSH2_COMP_METHOD *_libssh2_comp_methods[] = { &libssh2_comp_method_none, #ifdef LIBSSH2_HAVE_ZLIB &libssh2_comp_method_zlib, #endif /* LIBSSH2_HAVE_ZLIB */ NULL }; LIBSSH2_COMP_METHOD **libssh2_comp_methods(void) { return _libssh2_comp_methods; } ================================================ FILE: Example/crypt.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #if LIBSSH2_CRYPT_NONE /* {{{ libssh2_crypt_none_crypt * Minimalist cipher: VERY secure *wink* */ static int libssh2_crypt_none_crypt(LIBSSH2_SESSION *session, unsigned char *buf, void **abstract) { /* Do nothing to the data! */ return 0; } /* }}} */ static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_none = { "none", 8, /* blocksize (SSH2 defines minimum blocksize as 8) */ 0, /* iv_len */ 0, /* secret_len */ 0, /* flags */ NULL, libssh2_crypt_none_crypt, NULL }; #endif /* LIBSSH2_CRYPT_NONE */ struct crypt_ctx { int encrypt; _libssh2_cipher_type(algo); _libssh2_cipher_ctx h; }; static int init (LIBSSH2_SESSION *session, LIBSSH2_CRYPT_METHOD *method, unsigned char *iv, int *free_iv, unsigned char *secret, int *free_secret, int encrypt, void **abstract) { struct crypt_ctx *ctx = LIBSSH2_ALLOC(session, sizeof(struct crypt_ctx)); if (!ctx) { return -1; } ctx->encrypt = encrypt; ctx->algo = method->algo; if (_libssh2_cipher_init (&ctx->h, ctx->algo, iv, secret, encrypt)) { LIBSSH2_FREE (session, ctx); return -1; } *abstract = ctx; *free_iv = 1; *free_secret = 1; return 0; } static int crypt(LIBSSH2_SESSION *session, unsigned char *block, void **abstract) { struct crypt_ctx *cctx = *(struct crypt_ctx **)abstract; return _libssh2_cipher_crypt(&cctx->h, cctx->algo, cctx->encrypt, block); } static int dtor(LIBSSH2_SESSION *session, void **abstract) { struct crypt_ctx **cctx = (struct crypt_ctx **)abstract; if (cctx && *cctx) { _libssh2_cipher_dtor(&(*cctx)->h); LIBSSH2_FREE(session, *cctx); *abstract = NULL; } return 0; } #if LIBSSH2_AES static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes128_cbc = { "aes128-cbc", 16, /* blocksize */ 16, /* initial value length */ 16, /* secret length -- 16*8 == 128bit */ 0, /* flags */ &init, &crypt, &dtor, _libssh2_cipher_aes128 }; static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes192_cbc = { "aes192-cbc", 16, /* blocksize */ 16, /* initial value length */ 24, /* secret length -- 24*8 == 192bit */ 0, /* flags */ &init, &crypt, &dtor, _libssh2_cipher_aes192 }; static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes256_cbc = { "aes256-cbc", 16, /* blocksize */ 16, /* initial value length */ 32, /* secret length -- 32*8 == 256bit */ 0, /* flags */ &init, &crypt, &dtor, _libssh2_cipher_aes256 }; /* rijndael-cbc@lysator.liu.se == aes256-cbc */ static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_rijndael_cbc_lysator_liu_se = { "rijndael-cbc@lysator.liu.se", 16, /* blocksize */ 16, /* initial value length */ 32, /* secret length -- 32*8 == 256bit */ 0, /* flags */ &init, &crypt, &dtor, _libssh2_cipher_aes256 }; #endif /* LIBSSH2_AES */ #if LIBSSH2_BLOWFISH static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_blowfish_cbc = { "blowfish-cbc", 8, /* blocksize */ 8, /* initial value length */ 16, /* secret length */ 0, /* flags */ &init, &crypt, &dtor, _libssh2_cipher_blowfish }; #endif /* LIBSSH2_BLOWFISH */ #if LIBSSH2_RC4 static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_arcfour = { "arcfour", 8, /* blocksize */ 8, /* initial value length */ 16, /* secret length */ 0, /* flags */ &init, &crypt, &dtor, _libssh2_cipher_arcfour }; #endif /* LIBSSH2_RC4 */ #if LIBSSH2_CAST static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_cast128_cbc = { "cast128-cbc", 8, /* blocksize */ 8, /* initial value length */ 16, /* secret length */ 0, /* flags */ &init, &crypt, &dtor, _libssh2_cipher_cast5 }; #endif /* LIBSSH2_CAST */ #if LIBSSH2_3DES static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_3des_cbc = { "3des-cbc", 8, /* blocksize */ 8, /* initial value length */ 24, /* secret length */ 0, /* flags */ &init, &crypt, &dtor, _libssh2_cipher_3des }; #endif static LIBSSH2_CRYPT_METHOD *_libssh2_crypt_methods[] = { #if LIBSSH2_AES &libssh2_crypt_method_aes256_cbc, &libssh2_crypt_method_rijndael_cbc_lysator_liu_se, /* == aes256-cbc */ &libssh2_crypt_method_aes192_cbc, &libssh2_crypt_method_aes128_cbc, #endif /* LIBSSH2_AES */ #if LIBSSH2_BLOWFISH &libssh2_crypt_method_blowfish_cbc, #endif /* LIBSSH2_BLOWFISH */ #if LIBSSH2_RC4 &libssh2_crypt_method_arcfour, #endif /* LIBSSH2_RC4 */ #if LIBSSH2_CAST &libssh2_crypt_method_cast128_cbc, #endif /* LIBSSH2_CAST */ #if LIBSSH2_3DES &libssh2_crypt_method_3des_cbc, #endif /* LIBSSH2_DES */ #if LIBSSH2_CRYPT_NONE &libssh2_crypt_method_none, #endif NULL }; /* Expose to kex.c */ LIBSSH2_CRYPT_METHOD **libssh2_crypt_methods(void) { return _libssh2_crypt_methods; } ================================================ FILE: Example/da.lproj/DropletLauncher.nib/classes.nib ================================================ { IBClasses = ( { CLASS = DropletLauncherDelegate; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, { CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/da.lproj/DropletLauncher.nib/info.nib ================================================ IBDocumentLocation 702 98 356 240 0 0 1920 1178 IBEditorPositions 29 116 400 338 44 0 0 1920 1178 IBFramework Version 489.0 IBOldestOS 4 IBSystem Version 9C7010 ================================================ FILE: Example/da.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Example/da.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nHvad vil du gøre?"; /* ConnectionOpenPanel */ "A Connection Error Occurred" = "Der opstod en forbindelsesfejl"; /* FTP Abort */ "Action Aborted. Local Error" = "Handling afbrudt. Lokal fejl"; /* FTP no data stream types available */ "All data connection modes have been exhausted. Check with the server administrator." = "Alle dataforbindelsesmuligheder er udtømte. Spørg serveradministratoren."; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "An intermediate directory does not exist and needs to be created before the current directory" = "Et midlertidigt bibliotek findes ikke og skal oprettes før det aktuelle bibliotek"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "An unknown error occurred" = "Der opstod en ukendt fejl"; /* multiple connection joiner */ "and" = "og"; /* close window */ "Are you sure you want to stop the upload?" = "Er du sikker på du vil stoppe din upload?"; /* authorise */ "Authorize" = "Godkend"; /* authorise */ "Authorize Connection?" = "Godkend forbindelse?"; /* connection type */ "auto" = "auto"; /* config */ "Bad Configuration" = "Fejlkonfiguration"; /* error */ "Bad Droplet" = "Ugyldig Droplet"; /* Transfer Controller */ "Bad Password." = "Forkert adgangkode."; /* context menu tree controller action gear */ "Browse Packages" = "Gennemse pakker"; /* filesize: bytes */ "bytes" = "bytes"; /* authorise */ "Cancel" = "Annuller"; /* FTP upload error */ "Cannot Upload File. Storage quota on server exceeded" = "Kan ikke uploade arkiv. Opbevaringskvota på serveren er overskredet"; /* transfer controller connected message */ "Connected to %@" = "Forbundet til %@"; /* file transcript */ "Connected to File System\n" = "Forbundet til arkivsystem\n"; /* connection string */ "Connecting to %@" = "Forbinder til %@"; /* file transcript */ "Connecting…\n" = "Forbinder…\n"; /* close window */ "Continue Upload" = "Fortsæt upload"; /* file transcript */ "Copying %@ to %@\n" = "Kopierer %1$@ til %2$@\n"; /* FileConnection set permissions error */ "Could not change file permissions" = "Kunne ikke ændre arkivtilladelser"; /* FileConnection create directory error */ "Could not create directory" = "Kunne ikke oprette mappe"; /* config */ "Couldn't find configuration file specified\n %@" = "Kunne ikke finde det angivne konfigurationsarkiv\n %@"; /* file transcript */ "Create Directory %@ (%lo)\n" = "Opret mappe %1$@ (%2$lo)\n"; /* FTP Create directory error */ "Create directory operation failed" = "Opret mappe-handling mislykkedes"; /* Failed to open a data stream connection */ "Data Stream Timed Out" = "Datastrøm udløb"; /* outline view column header name outline view column context menu item */ "Date Modified" = "Ændringsdato"; /* file transcript */ "Deleting File %@\n" = "Sletter arkiv %@\n"; /* status */ "Disconnected" = "Ikke forbundet"; /* transfer controller */ "Disconnected from %@" = "Lukker forbindelse til %@"; /* unknown UTI name */ "Document" = "Dokument"; /* filesize: exabytes */ "EB" = "EB"; /* Directory Parsing Error */ "Error parsing directory listing" = "Fejl ved undersøgelse af mappeindhold"; /* FTP no data streams available */ "Exhausted all connection types to server. Please contact server administrator" = "Har udtømt alle forbindelsestyper til serveren. Kontakt venligst serveradministratoren"; /* Bad ftp command */ "Failed to change to directory" = "Kunne ikke skifte mappe"; /* couldn't delete the file MobileMe Directory Deletion Error WebDAV Directory Deletion Error */ "Failed to delete directory" = "Kunne ikke slette mappe"; /* error for deleting a directory */ "Failed to delete directory: %@" = "Kunne ikke slette mappen: %@"; /* couldn't delete the file MobileMe file deletion error WebDAV File Deletion Error */ "Failed to delete file" = "Kunne ikke slette arkiv"; /* error for deleting a file */ "Failed to delete file: %@" = "Kunne ikke slette arkiv: %@"; /* No MobileMe account or password */ "Failed to retrieve MobileMe account details" = "Kunne ikke hente information om MobileMe-konto"; /* FTP Upload error */ "Failed to set permissions for path %@" = "Kunne ikke indstille tilladelser for stien %@"; /* FileConnection copy data error */ "Failed to upload data" = "Kunne ikke oploade data"; /* error transferring */ "Failed to verify file transferred successfully" = "Kunne ikke bekræfte at arkivoverførsel lykkedes"; /* FTP file download error */ "File %@ does not exist on server" = "Arkiver %@ findes ikke på serveren"; /* FTP error */ "File / Directory does not exist" = "Arkiv / Mappe findes ikke"; /* FileConnection error */ "File Already Exists" = "Arkivet findes allerede"; /* FTP file in use */ "File in Use" = "Arkivet er i brug"; /* FTP Upload error */ "Filename not Allowed" = "Arkivnavn ikke tilladt"; /* directory kind */ "Folder" = "Mappe"; /* FTP service timed out */ "FTP service not available; Remote server has closed connection" = "FTP-service ikke tilgængelig. Serveren har lukket forbindelsen"; /* FTP no service */ "FTP Service Unavailable" = "FTP-service ikke tilgængelig"; /* filesize: gigabytes */ "GB" = "GB"; /* transfer controller */ "Hide Files" = "Skjul arkiver"; /* Couldn't open the port to the host */ "Host Unavailable" = "Vært ikke tilgængelig"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "Insufficient storage space available" = "Ikke nok plads tilgængelig"; /* FTP Error */ "Invalid Account name" = "Ugyldigt kontonavn"; /* Stream Error before opening */ "Is the service running on the server" = "Kører denne service på serveren"; /* filesize: kilobytes */ "KB" = "KB"; /* outline view column header name outline view column context menu item */ "Kind" = "Type"; /* FTP download error */ "Local File already exists" = "Lokalt arkiv findes allerede"; /* filesize: megabytes */ "MB" = "MB"; /* outline view column header name outline view column context menu item */ "Name" = "Navn"; /* new cat name */ "New Category" = "Ny kategori"; /* tree controller action gear */ "New Folder" = "Ny mappe"; /* config */ "No configuration file specified" = "Intet konfigurationsarkiv angivet"; /* failed to find a connection class */ "No connection available for requested connection type" = "Ingen forbindelse tilgængelig for den ønskede forbindelsestype"; /* failed to find a connection class */ "No connection available for requested port" = "ingen forbindelse tilgængelig på den ønskede port"; /* failed to find a connection class */ "No connection available for requested protocol" = "Ingen forbindelse tilgængelig for den ønskede protokol"; /* FTP Error */ "No Storage Space Available" = "Ingen ledig plads tilgængelig"; /* FTP Error */ "Not Logged In" = "Ikke logget ind"; /* sftp last error OK */ "OK" = "OK"; /* WebDAV Uploading Error MobileMe File Uploading Error */ "Parent Folder does not exist" = "Overliggende mappe findes ikke"; /* No comment provided by engineer. */ "Password was not accepted." = "Adgangskoden blev ikke godkendt."; /* filesize: petabytes */ "PB" = "PB"; /* mimic Finder naming conventions */ "Plain text document" = "Normalt tekstdokument"; /* config error */ "Quit" = "Slut"; /* tree controller action gear */ "Refresh" = "Opdater"; /* file transcript */ "Renaming %@ to %@\n" = "Omdøber %1$@ til %2$@\n"; /* FTP Error */ "Request Aborted. Page Type Unknown" = "Anmodning afbrudt. Ukendt sidetype"; /* abbreviation for seconds, e.g. 12 MB/s */ "s" = "s"; /* category name */ "Saved Hosts" = "Arkiverede værter"; /* transfer controller */ "Show Files" = "Vis arkiver"; /* context menu tree controller action gear */ "Show Hidden Files" = "Vis skjulte arkiver"; /* context menu tree controller action gear */ "Show Package Extensions" = "Vis pakke-endelser"; /* outline view column header name outline view column context menu item */ "Size" = "Str."; /* close window */ "Stop Upload" = "Stop upload"; /* close window */ "Stop Upload?" = "Stop upload?"; /* Error creating stream */ "Stream Unavailable" = "Stream ikke tilgængelig"; /* filesize: terabytes */ "TB" = "TB"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "The body of the request is not supported" = "Indholdet af anmodningen understøttes ikke"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "The directory already exists" = "Mappen findes allerede"; /* name of a host to connect to; in this case, the local file system rather than a remote server */ "the File System" = "Arkivsystemet"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "The server does not allow the creation of directories at the current location" = "Serveren tillader ikke oprettelse af mapper på dette sted"; /* error transferring */ "The transferred file has a size of 0." = "Det overførte arkivs størrelse er 0."; /* MobileMe Directory Contents Error */ "There is no MobileMe access to the directory" = "Der er ikke MobileMe-adgang til mappen"; /* No WebDAV access to the specified path */ "There is no WebDAV access to the directory" = "Der er ikke WebDAV-adgang til mappen"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "Denne droplet mangler det originale program der oprettede den. Du er nød til at geninstallere det originale program."; /* time out */ "Timed Out waiting for remote host." = "Time out under venten på vært."; /* Transfer Controller */ "Too many files had transfer problems" = "For mange arkiver havde overførselsproblemer"; /* FileConnection failed to copy file */ "Unable to store data in file" = "Kunne ikke opbevare data i arkivet"; /* WebDAV Error */ "Unknown Error Occurred" = "Der opstod en ukendt fejl"; /* status message */ "Uploading" = "Uploader"; /* status */ "Uploading %@" = "Uploader %@"; /* No username or password */ "Username and Password are required for FTP connections" = "Der kræves brugernavn og adgangskode til FTP-forbindelser"; /* No username or password */ "Username and Password are required for S3 connections" = "Der kræves brugernavn og adgangskode til S3-forbindelser"; /* No username or password */ "Username and Password are required for WebDAV connections" = "Der kræver brugernavn og adgangskode til WebDAV-forbindelser"; /* file transcript */ "Writing data to %@\n" = "Skriver data til %@\n"; /* Bonjour Error */ "You can not add a child collection to the Bonjour category." = "Du kan ikke tilføje en underliggende samling til Bonjour-kategorien."; /* Bonjour Error */ "You can not add a new server to the Bonjour category." = "Du kan ikke tilføje en ny server til Bonjour-kategorien."; /* FTP file upload error */ "You do not have access to write file %@" = "Du har ikke tilladelse til at skrive arkivet %@"; /* FTP Error */ "You need an Account to Upload Files" = "Du skal have en konto for et kunne uploade arkiver"; ================================================ FILE: Example/de.lproj/DropletLauncher.nib/classes.nib ================================================ { IBClasses = ( { CLASS = DropletLauncherDelegate; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, { CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/de.lproj/DropletLauncher.nib/info.nib ================================================ IBDocumentLocation 415 56 356 240 0 0 1280 778 IBEditorPositions 29 69 259 338 44 0 0 1280 778 IBFramework Version 489.0 IBSystem Version 9E17 IBUsesTextArchiving ================================================ FILE: Example/de.lproj/DropletLauncher.nib/keyedobjects.nib ================================================ $archiver NSKeyedArchiver $objects $null $class CF$UID 387 NSAccessibilityConnectors CF$UID 384 NSAccessibilityOidsKeys CF$UID 385 NSAccessibilityOidsValues CF$UID 386 NSClassesKeys CF$UID 279 NSClassesValues CF$UID 280 NSConnections CF$UID 9 NSFontManager CF$UID 0 NSFramework CF$UID 6 NSNamesKeys CF$UID 255 NSNamesValues CF$UID 256 NSNextOid 250 NSObjectsKeys CF$UID 176 NSObjectsValues CF$UID 254 NSOidsKeys CF$UID 281 NSOidsValues CF$UID 282 NSRoot CF$UID 2 NSVisibleWindows CF$UID 7 $class CF$UID 5 NSClassName CF$UID 3 $class CF$UID 4 NS.string NSApplication $classes NSMutableString NSString NSObject $classname NSMutableString $classes NSCustomObject NSObject $classname NSCustomObject $class CF$UID 4 NS.string IBCocoaFramework $class CF$UID 8 NS.objects $classes NSMutableSet NSSet NSObject $classname NSMutableSet $class CF$UID 175 NS.objects CF$UID 10 CF$UID 24 CF$UID 29 CF$UID 35 CF$UID 40 CF$UID 46 CF$UID 51 CF$UID 57 CF$UID 61 CF$UID 66 CF$UID 70 CF$UID 74 CF$UID 79 CF$UID 84 CF$UID 90 CF$UID 95 CF$UID 100 CF$UID 105 CF$UID 110 CF$UID 115 CF$UID 120 CF$UID 125 CF$UID 130 CF$UID 134 CF$UID 138 CF$UID 142 CF$UID 148 CF$UID 152 CF$UID 156 CF$UID 160 CF$UID 165 CF$UID 170 $class CF$UID 23 NSLabel CF$UID 22 NSSource CF$UID 11 $class CF$UID 21 NSKeyEquiv CF$UID 14 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 13 $class CF$UID 184 NSMenuItems CF$UID 181 NSName CF$UID 183 NSTitle CF$UID 179 Im Dock ablegen m $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 17 NSImage NSMenuCheckmark $classes NSCustomResource NSObject $classname NSCustomResource $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 20 NSMenuMixedState $classes NSMenuItem NSObject $classname NSMenuItem $class CF$UID 4 NS.string performMiniaturize: $classes NSNibControlConnector NSNibConnector NSObject $classname NSNibControlConnector $class CF$UID 23 NSLabel CF$UID 28 NSSource CF$UID 25 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 26 Alle nach vorne bringen $class CF$UID 4 NS.string arrangeInFront: $class CF$UID 23 NSLabel CF$UID 34 NSSource CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 33 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 32 $class CF$UID 184 NSMenuItems CF$UID 236 NSTitle CF$UID 235 Drucken… p $class CF$UID 4 NS.string print: $class CF$UID 23 NSLabel CF$UID 39 NSSource CF$UID 36 $class CF$UID 21 NSKeyEquiv CF$UID 38 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 37 Papierformat… P $class CF$UID 4 NS.string runPageLayout: $class CF$UID 23 NSLabel CF$UID 45 NSSource CF$UID 41 $class CF$UID 21 NSKeyEquiv CF$UID 44 NSKeyEquivModMask 1048576 NSMenu CF$UID 42 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 43 $class CF$UID 184 NSMenuItems CF$UID 229 NSTitle CF$UID 228 NewApplication Hilfe ? $class CF$UID 4 NS.string showHelp: $class CF$UID 23 NSLabel CF$UID 50 NSSource CF$UID 47 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 48 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 49 $class CF$UID 184 NSMenuItems CF$UID 212 NSName CF$UID 213 NSTitle CF$UID 210 Menü löschen $class CF$UID 4 NS.string clearRecentDocuments: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 56 NSSource CF$UID 52 $class CF$UID 21 NSKeyEquiv CF$UID 55 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 54 $class CF$UID 184 NSMenuItems CF$UID 188 NSName CF$UID 202 NSTitle CF$UID 186 NewApplication beenden q $class CF$UID 4 NS.string terminate: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 60 NSSource CF$UID 58 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 59 Über NewApplication $class CF$UID 4 NS.string orderFrontStandardAboutPanel: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 65 NSSource CF$UID 62 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1572864 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 63 Andere ausblenden h hideOtherApplications: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 69 NSSource CF$UID 67 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 68 NewApplication ausblenden hide: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 73 NSSource CF$UID 71 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 72 Alle einblenden unhideAllApplications: $class CF$UID 23 NSLabel CF$UID 78 NSSource CF$UID 75 $class CF$UID 21 NSKeyEquiv CF$UID 77 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 76 Schließen w performClose: $class CF$UID 23 NSLabel CF$UID 83 NSSource CF$UID 80 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 82 $class CF$UID 184 NSMenuItems CF$UID 218 NSTitle CF$UID 216 Während der Texteingabe prüfen toggleContinuousSpellChecking: $class CF$UID 23 NSLabel CF$UID 89 NSSource CF$UID 85 $class CF$UID 21 NSKeyEquiv CF$UID 88 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 87 $class CF$UID 184 NSMenuItems CF$UID 245 NSTitle CF$UID 244 Widerrufen z $class CF$UID 4 NS.string undo: $class CF$UID 23 NSLabel CF$UID 94 NSSource CF$UID 91 $class CF$UID 21 NSKeyEquiv CF$UID 93 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 92 Kopieren c $class CF$UID 4 NS.string copy: $class CF$UID 23 NSLabel CF$UID 99 NSSource CF$UID 96 $class CF$UID 21 NSKeyEquiv CF$UID 98 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 97 Rechtschreibprüfung ; $class CF$UID 4 NS.string checkSpelling: $class CF$UID 23 NSLabel CF$UID 104 NSSource CF$UID 101 $class CF$UID 21 NSKeyEquiv CF$UID 103 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 102 Einsetzen v $class CF$UID 4 NS.string paste: $class CF$UID 23 NSLabel CF$UID 109 NSSource CF$UID 106 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 108 $class CF$UID 184 NSMenuItems CF$UID 227 NSTitle CF$UID 226 Sprachausgabe stoppen stopSpeaking: $class CF$UID 23 NSLabel CF$UID 114 NSSource CF$UID 111 $class CF$UID 21 NSKeyEquiv CF$UID 113 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 112 Ausschneiden x $class CF$UID 4 NS.string cut: $class CF$UID 23 NSLabel CF$UID 119 NSSource CF$UID 116 $class CF$UID 21 NSKeyEquiv CF$UID 118 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 117 Rechtschreibung… : $class CF$UID 4 NS.string showGuessPanel: $class CF$UID 23 NSLabel CF$UID 124 NSSource CF$UID 121 $class CF$UID 21 NSKeyEquiv CF$UID 123 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 122 Wiederholen Z $class CF$UID 4 NS.string redo: $class CF$UID 23 NSLabel CF$UID 129 NSSource CF$UID 126 $class CF$UID 21 NSKeyEquiv CF$UID 128 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 127 Alle auswählen a $class CF$UID 4 NS.string selectAll: $class CF$UID 23 NSLabel CF$UID 133 NSSource CF$UID 131 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 132 Sprachausgabe starten startSpeaking: $class CF$UID 23 NSLabel CF$UID 137 NSSource CF$UID 135 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 136 Löschen delete: $class CF$UID 23 NSLabel CF$UID 141 NSSource CF$UID 139 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 140 Zoomen performZoom: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 143 $class CF$UID 21 NSKeyEquiv CF$UID 146 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 1 NSTitle CF$UID 145 $class CF$UID 184 NSMenuItems CF$UID 221 NSTitle CF$UID 220 Suchen … f performFindPanelAction: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 149 $class CF$UID 21 NSKeyEquiv CF$UID 151 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 2 NSTitle CF$UID 150 Weitersuchen (vorwärts) g $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 153 $class CF$UID 21 NSKeyEquiv CF$UID 155 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 3 NSTitle CF$UID 154 Weitersuchen (rückwärts) G $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 157 $class CF$UID 21 NSKeyEquiv CF$UID 159 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 7 NSTitle CF$UID 158 Auswahl suchen e $class CF$UID 23 NSLabel CF$UID 164 NSSource CF$UID 161 $class CF$UID 21 NSKeyEquiv CF$UID 163 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 162 Auswahl anzeigen j centerSelectionInVisibleArea: $class CF$UID 23 NSLabel CF$UID 169 NSSource CF$UID 166 $class CF$UID 21 NSKeyEquiv CF$UID 168 NSKeyEquivModMask 1572864 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 167 Einsetzen und Stil anpassen V pasteAsPlainText: $class CF$UID 174 NSDestination CF$UID 171 NSLabel CF$UID 173 NSSource CF$UID 2 $class CF$UID 5 NSClassName CF$UID 172 DropletLauncherDelegate delegate $classes NSNibOutletConnector NSNibConnector NSObject $classname NSNibOutletConnector $classes NSMutableArray NSArray NSObject $classname NSMutableArray $class CF$UID 253 NS.objects CF$UID 157 CF$UID 143 CF$UID 177 CF$UID 91 CF$UID 185 CF$UID 47 CF$UID 62 CF$UID 203 CF$UID 206 CF$UID 209 CF$UID 200 CF$UID 214 CF$UID 215 CF$UID 131 CF$UID 193 CF$UID 219 CF$UID 144 CF$UID 190 CF$UID 222 CF$UID 25 CF$UID 182 CF$UID 223 CF$UID 107 CF$UID 153 CF$UID 111 CF$UID 41 CF$UID 201 CF$UID 197 CF$UID 116 CF$UID 135 CF$UID 42 CF$UID 230 CF$UID 126 CF$UID 12 CF$UID 232 CF$UID 194 CF$UID 11 CF$UID 234 CF$UID 71 CF$UID 31 CF$UID 149 CF$UID 171 CF$UID 96 CF$UID 240 CF$UID 242 CF$UID 121 CF$UID 75 CF$UID 67 CF$UID 86 CF$UID 52 CF$UID 237 CF$UID 53 CF$UID 166 CF$UID 30 CF$UID 248 CF$UID 139 CF$UID 81 CF$UID 189 CF$UID 85 CF$UID 178 CF$UID 48 CF$UID 58 CF$UID 161 CF$UID 246 CF$UID 106 CF$UID 80 CF$UID 36 CF$UID 101 $class CF$UID 21 NSAction CF$UID 180 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 178 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 12 NSTitle CF$UID 179 $class CF$UID 184 NSMenuItems CF$UID 251 NSName CF$UID 252 NSTitle CF$UID 250 Fenster submenuAction: $class CF$UID 175 NS.objects CF$UID 11 CF$UID 139 CF$UID 182 CF$UID 25 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSWindowsMenu $classes NSMenu NSObject $classname NSMenu $class CF$UID 21 NSAction CF$UID 187 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 178 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 53 NSTitle CF$UID 186 NewApplication submenuAction: $class CF$UID 175 NS.objects CF$UID 58 CF$UID 189 CF$UID 190 CF$UID 193 CF$UID 194 CF$UID 200 CF$UID 67 CF$UID 62 CF$UID 71 CF$UID 201 CF$UID 52 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSKeyEquiv CF$UID 192 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 191 Einstellungen … , $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 196 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 197 NSTitle CF$UID 195 Dienste submenuAction: $class CF$UID 184 NSMenuItems CF$UID 198 NSName CF$UID 199 NSTitle CF$UID 195 $class CF$UID 175 NS.objects _NSServicesMenu $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSAppleMenu $class CF$UID 21 NSKeyEquiv CF$UID 205 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 204 Öffnen … o $class CF$UID 21 NSKeyEquiv CF$UID 208 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 207 Sichern s $class CF$UID 21 NSAction CF$UID 211 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 48 NSTitle CF$UID 210 Benutzte Dokumente submenuAction: $class CF$UID 175 NS.objects CF$UID 47 _NSRecentDocumentsMenu $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 217 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 81 NSTitle CF$UID 216 Rechtschreibung submenuAction: $class CF$UID 175 NS.objects CF$UID 116 CF$UID 96 CF$UID 80 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 Suchen $class CF$UID 175 NS.objects CF$UID 143 CF$UID 149 CF$UID 153 CF$UID 157 CF$UID 161 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSKeyEquiv CF$UID 225 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 224 Neu n Sprachausgabe $class CF$UID 175 NS.objects CF$UID 131 CF$UID 106 Hilfe $class CF$UID 175 NS.objects CF$UID 41 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 231 Zurücksetzen $class CF$UID 21 NSAction CF$UID 233 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 178 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 42 NSTitle CF$UID 228 submenuAction: $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 Ablage $class CF$UID 175 NS.objects CF$UID 223 CF$UID 203 CF$UID 209 CF$UID 222 CF$UID 75 CF$UID 206 CF$UID 237 CF$UID 230 CF$UID 219 CF$UID 36 CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 239 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 238 Sichern unter … S $class CF$UID 21 NSAction CF$UID 241 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 107 NSTitle CF$UID 226 submenuAction: $class CF$UID 21 NSAction CF$UID 243 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 178 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 31 NSTitle CF$UID 235 submenuAction: Bearbeiten $class CF$UID 175 NS.objects CF$UID 85 CF$UID 121 CF$UID 234 CF$UID 111 CF$UID 91 CF$UID 101 CF$UID 166 CF$UID 135 CF$UID 126 CF$UID 214 CF$UID 246 CF$UID 215 CF$UID 240 $class CF$UID 21 NSAction CF$UID 247 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 144 NSTitle CF$UID 220 submenuAction: $class CF$UID 21 NSAction CF$UID 249 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 178 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 86 NSTitle CF$UID 244 submenuAction: Droplet Starter $class CF$UID 175 NS.objects CF$UID 185 CF$UID 242 CF$UID 248 CF$UID 177 CF$UID 232 _NSMainMenu $classes NSArray NSObject $classname NSArray $class CF$UID 253 NS.objects CF$UID 144 CF$UID 144 CF$UID 178 CF$UID 86 CF$UID 178 CF$UID 48 CF$UID 53 CF$UID 31 CF$UID 31 CF$UID 31 CF$UID 53 CF$UID 86 CF$UID 86 CF$UID 107 CF$UID 53 CF$UID 31 CF$UID 246 CF$UID 53 CF$UID 31 CF$UID 12 CF$UID 12 CF$UID 31 CF$UID 240 CF$UID 144 CF$UID 86 CF$UID 42 CF$UID 53 CF$UID 194 CF$UID 81 CF$UID 86 CF$UID 232 CF$UID 31 CF$UID 86 CF$UID 177 CF$UID 178 CF$UID 53 CF$UID 12 CF$UID 86 CF$UID 53 CF$UID 242 CF$UID 144 CF$UID 2 CF$UID 81 CF$UID 86 CF$UID 178 CF$UID 86 CF$UID 31 CF$UID 53 CF$UID 248 CF$UID 53 CF$UID 31 CF$UID 185 CF$UID 86 CF$UID 31 CF$UID 178 CF$UID 12 CF$UID 215 CF$UID 53 CF$UID 86 CF$UID 2 CF$UID 209 CF$UID 53 CF$UID 144 CF$UID 86 CF$UID 107 CF$UID 81 CF$UID 31 CF$UID 86 $class CF$UID 253 NS.objects CF$UID 31 CF$UID 96 CF$UID 242 CF$UID 171 CF$UID 2 CF$UID 203 CF$UID 75 CF$UID 206 CF$UID 52 CF$UID 215 CF$UID 237 CF$UID 30 CF$UID 222 CF$UID 219 CF$UID 190 CF$UID 81 CF$UID 182 CF$UID 178 CF$UID 223 CF$UID 42 CF$UID 41 CF$UID 116 CF$UID 80 CF$UID 36 CF$UID 230 CF$UID 12 CF$UID 232 $class CF$UID 253 NS.objects CF$UID 257 CF$UID 258 CF$UID 257 CF$UID 172 CF$UID 259 CF$UID 257 CF$UID 260 CF$UID 261 CF$UID 262 CF$UID 258 CF$UID 263 CF$UID 264 CF$UID 265 CF$UID 266 CF$UID 267 CF$UID 268 CF$UID 257 CF$UID 269 CF$UID 270 CF$UID 271 CF$UID 257 CF$UID 272 CF$UID 273 CF$UID 274 CF$UID 275 CF$UID 276 CF$UID 278 $class CF$UID 4 NS.string NSMenuItem $class CF$UID 4 NS.string File's Owner $class CF$UID 4 NS.string 1 $class CF$UID 4 NS.string 3 1111 $class CF$UID 4 NS.string 8 $class CF$UID 4 NS.string 6 $class CF$UID 4 NS.string 7 $class CF$UID 4 NS.string 2 121 NSMenu $class CF$UID 4 NS.string MainMenu $class CF$UID 4 NS.string 9 $class CF$UID 4 NS.string 2 NSMenuItem1 NSMenuItem2 $class CF$UID 4 NS.string 5 $class CF$UID 4 NS.string 10 $class CF$UID 277 $classes NSNull NSObject $classname NSNull $class CF$UID 4 NS.string 1 $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects CF$UID 115 CF$UID 143 CF$UID 185 CF$UID 62 CF$UID 203 CF$UID 209 CF$UID 200 CF$UID 214 CF$UID 215 CF$UID 165 CF$UID 105 CF$UID 219 CF$UID 142 CF$UID 182 CF$UID 223 CF$UID 153 CF$UID 110 CF$UID 84 CF$UID 201 CF$UID 42 CF$UID 197 CF$UID 135 CF$UID 100 CF$UID 126 CF$UID 232 CF$UID 12 CF$UID 70 CF$UID 11 CF$UID 71 CF$UID 149 CF$UID 242 CF$UID 2 CF$UID 121 CF$UID 52 CF$UID 134 CF$UID 237 CF$UID 24 CF$UID 46 CF$UID 53 CF$UID 30 CF$UID 248 CF$UID 81 CF$UID 189 CF$UID 178 CF$UID 61 CF$UID 170 CF$UID 90 CF$UID 130 CF$UID 80 CF$UID 157 CF$UID 138 CF$UID 177 CF$UID 91 CF$UID 47 CF$UID 74 CF$UID 206 CF$UID 57 CF$UID 131 CF$UID 193 CF$UID 222 CF$UID 144 CF$UID 190 CF$UID 25 CF$UID 40 CF$UID 107 CF$UID 111 CF$UID 10 CF$UID 41 CF$UID 116 CF$UID 230 CF$UID 194 CF$UID 120 CF$UID 234 CF$UID 31 CF$UID 96 CF$UID 171 CF$UID 79 CF$UID 152 CF$UID 240 CF$UID 35 CF$UID 75 CF$UID 95 CF$UID 67 CF$UID 86 CF$UID 156 CF$UID 166 CF$UID 139 CF$UID 85 CF$UID 29 CF$UID 48 CF$UID 58 CF$UID 161 CF$UID 246 CF$UID 125 CF$UID 106 CF$UID 148 CF$UID 51 CF$UID 36 CF$UID 66 CF$UID 160 CF$UID 101 $class CF$UID 253 NS.objects CF$UID 283 CF$UID 284 CF$UID 285 CF$UID 286 CF$UID 287 CF$UID 288 CF$UID 289 CF$UID 290 CF$UID 291 CF$UID 292 CF$UID 293 CF$UID 294 CF$UID 295 CF$UID 296 CF$UID 297 CF$UID 298 CF$UID 299 CF$UID 300 CF$UID 301 CF$UID 302 CF$UID 303 CF$UID 304 CF$UID 305 CF$UID 306 CF$UID 307 CF$UID 308 CF$UID 309 CF$UID 310 CF$UID 311 CF$UID 312 CF$UID 313 CF$UID 314 CF$UID 315 CF$UID 316 CF$UID 317 CF$UID 318 CF$UID 319 CF$UID 320 CF$UID 321 CF$UID 322 CF$UID 323 CF$UID 324 CF$UID 325 CF$UID 326 CF$UID 327 CF$UID 328 CF$UID 329 CF$UID 330 CF$UID 331 CF$UID 332 CF$UID 333 CF$UID 334 CF$UID 335 CF$UID 336 CF$UID 337 CF$UID 338 CF$UID 339 CF$UID 340 CF$UID 341 CF$UID 342 CF$UID 343 CF$UID 344 CF$UID 345 CF$UID 346 CF$UID 347 CF$UID 348 CF$UID 349 CF$UID 350 CF$UID 351 CF$UID 352 CF$UID 353 CF$UID 354 CF$UID 355 CF$UID 356 CF$UID 357 CF$UID 358 CF$UID 359 CF$UID 360 CF$UID 361 CF$UID 362 CF$UID 363 CF$UID 364 CF$UID 365 CF$UID 366 CF$UID 367 CF$UID 368 CF$UID 369 CF$UID 370 CF$UID 371 CF$UID 372 CF$UID 373 CF$UID 374 CF$UID 375 CF$UID 376 CF$UID 377 CF$UID 378 CF$UID 379 CF$UID 380 CF$UID 381 CF$UID 382 CF$UID 383 230 209 56 145 72 124 144 214 216 247 227 74 241 92 82 213 228 223 149 106 130 202 226 198 103 24 153 23 150 208 83 1 215 136 235 80 39 127 57 78 217 200 236 29 146 249 224 233 219 221 240 19 197 126 193 75 142 196 143 79 220 129 5 122 212 199 37 111 204 112 131 231 206 81 201 248 222 243 211 87 73 225 134 205 244 246 239 207 86 125 58 210 218 232 195 242 139 77 152 245 203 $class CF$UID 175 NS.objects $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects $classes NSIBObjectData NSObject $classname NSIBObjectData $top IB.objectdata CF$UID 1 $version 100000 ================================================ FILE: Example/de.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Example/de.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nWas möchten Sie tun?"; /* ConnectionOpenPanel */ "A Connection Error Occurred" = "Ein Verbindungsfehler ist aufgetreten"; /* FTP Abort */ "Action Aborted. Local Error" = "Aktion abgebrochen. Lokaler Fehler"; /* FTP no data stream types available */ "All data connection modes have been exhausted. Check with the server administrator." = "Alle Datenverbindungstypen haben versagt. Fragen Sie Ihren Administrator"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An intermediate directory does not exist and needs to be created before the current directory" = "Ein Zwischenordner existiert nicht und muss vor dem jetzigen Ordner neu eingerichtet werden"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An unknown error occurred" = "Ein unbekannter Fehler ist aufgetreten"; /* multiple connection joiner */ "and" = "und"; /* close window */ "Are you sure you want to stop the upload?" = "Wollen Sie die Übertragung wirklich stoppen?"; /* authorise */ "Authorize" = "Autorisieren"; /* authorise */ "Authorize Connection?" = "Verbindung autorisieren?"; /* connection type */ "auto" = "auto"; /* config */ "Bad Configuration" = "Falsche Konfiguration"; /* error */ "Bad Droplet" = "Falsches Droplet"; /* Transfer Controller */ "Bad Password." = "Falsches Kennwort."; /* context menu tree controller action gear */ "Browse Packages" = "Pakete durchsuchen"; /* filesize: bytes */ "bytes" = "Byte"; /* authorise */ "Cancel" = "Abbrechen"; /* FTP upload error */ "Cannot Upload File. Storage quota on server exceeded" = "Kann Datei nicht hochladen. Speicherkontingent auf Server überschritten."; /* connected message transfer controller */ "Connected to %@" = "Verbunden mit %@"; /* file transcript */ "Connected to File System\n" = "Mit Dateisystem verbunden"; /* connection string */ "Connecting to %@" = "Mit %@ verbinden"; /* file transcript */ "Connecting…\n" = "Verbinde …\n"; /* close window */ "Continue Upload" = "Übertragung fortsetzen"; /* file transcript */ "Copying %@ to %@\n" = "Kopiere %1$@ nach %2$@\n"; /* FileConnection set permissions error */ "Could not change file permissions" = "Konnte Datei Zugriffsrechte nicht ändern"; /* FileConnection create directory error */ "Could not create directory" = "Konnte Verzeichnis nicht erstellen"; /* config */ "Couldn't find configuration file specified\n %@" = "Konnte angegebene Konfigurationsdatei nicht finden\n %@"; /* file transcript */ "Create Directory %@ (%lo)\n" = "Verzeichnis %1$@ (%2$lo) erstellen\n"; /* FTP Create directory error */ "Create directory operation failed" = "Die Verzeichniserstellung ist fehlgeschlagen"; /* Failed to open a data stream connection */ "Data Stream Timed Out" = "Zeitüberschreitung beim Aufbau der Datenverbindung "; /* outline view column header name outline view column context menu item */ "Date Modified" = "Änderungsdatum"; /* file transcript */ "Deleting File %@\n" = "Datei %@ löschen\n"; /* status */ "Disconnected" = "Getrennt"; /* transfer controller */ "Disconnected from %@" = "Getrennt von %@"; /* unknown UTI name */ "Document" = "Dokument"; /* filesize: exabytes */ "EB" = "EB"; /* Directory Parsing Error */ "Error parsing directory listing" = "Fehler beim Lesen der Verzeichnisstruktur"; /* FTP no data streams available */ "Exhausted all connection types to server. Please contact server administrator" = "Alle Verbindungsarten zum Server wurden versucht. Kontaktieren Sie den Serveradministrator."; /* Bad ftp command */ "Failed to change to directory" = "Verzeichnis konnte nicht gewechselt werden"; /* WebDAV Directory Deletion Error couldn't delete the file MobileMe Directory Deletion Error */ "Failed to delete directory" = "Verzeichnis konnte nicht gelöscht werden"; /* error for deleting a directory */ "Failed to delete directory: %@" = "Konnte Verzeichnis %@ nicht löschen"; /* WebDAV File Deletion Error couldn't delete the file MobileMe file deletion error */ "Failed to delete file" = "Datei konnte nicht gelöscht werden"; /* error for deleting a file */ "Failed to delete file: %@" = "Konnte Datei %@ nicht löschen"; /* No MobileMe account or password */ "Failed to retrieve MobileMe account details" = "Konnte MobileMe Account Daten nicht erhalten"; /* FTP Upload error */ "Failed to set permissions for path %@" = "Zugriffsrechte für den Pfad %@ konnten nicht gesetzt werden"; /* FileConnection copy data error */ "Failed to upload data" = "Daten konnte nicht hochgeladen werden"; /* error transferring */ "Failed to verify file transferred successfully" = "Überprüfung der Dateiübertragung fehlgeschlagen"; /* FTP file download error */ "File %@ does not exist on server" = "Datei %@ existiert auf dem Server nicht"; /* FTP error */ "File / Directory does not exist" = "Datei / Verzeichnis existiert nicht"; /* FileConnection error */ "File Already Exists" = "Datei existiert bereits"; /* FTP file in use */ "File in Use" = "Datei wird bereits benutzt"; /* FTP Upload error */ "Filename not Allowed" = "Dateiname nicht erlaubt"; /* directory kind */ "Folder" = "Ordner"; /* FTP service timed out */ "FTP service not available; Remote server has closed connection" = "FTP Dienst nicht verfügbar; Entfernter Rechner hat die Verbindung beendet"; /* FTP no service */ "FTP Service Unavailable" = "FTP Dienst nicht verfügbar"; /* filesize: gigabytes */ "GB" = "GB"; /* transfer controller */ "Hide Files" = "Dateien ausblenden"; /* Couldn't open the port to the host */ "Host Unavailable" = "Host nicht verfügbar"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "Insufficient storage space available" = "Unzureichender freier Speicher"; /* FTP Error */ "Invalid Account name" = "Ungültiger Kontoname"; /* Stream Error before opening */ "Is the service running on the server" = "Läuft der Dienst auf dem Server"; /* filesize: kilobytes */ "KB" = "KB"; /* outline view column header name outline view column context menu item */ "Kind" = "Art"; /* FTP download error */ "Local File already exists" = "Lokale Datei existiert bereits"; /* filesize: megabytes */ "MB" = "MB"; /* outline view column header name outline view column context menu item */ "Name" = "Name"; /* new cat name */ "New Category" = "Neue Kategorie"; /* tree controller action gear */ "New Folder" = "Neuer Ordner"; /* config */ "No configuration file specified" = "Keine Konfigurationsdatei angegeben"; /* failed to find a connection class */ "No connection available for requested connection type" = "Keine verfügbare Verbindung für gewählte Verbindungsart"; /* failed to find a connection class */ "No connection available for requested port" = "Keine verfügbare Verbindung für angeforderten Port"; /* failed to find a connection class */ "No connection available for requested protocol" = "Keine verfügbare Verbindung für angefordertes Protokoll"; /* FTP Error */ "No Storage Space Available" = "Kein verfügbarer Speicher"; /* FTP Error */ "Not Logged In" = "Nicht eingeloggt"; /* OK */ "OK" = "OK"; /* WebDAV Uploading Error MobileMe File Uploading Error */ "Parent Folder does not exist" = "Übergeordneter Ordner existiert nicht"; /* No comment provided by engineer. */ "Password was not accepted." = "Kennwort wurde abgelehnt."; /* filesize: petabytes */ "PB" = "PB"; /* mimic Finder naming conventions */ "Plain text document" = "Reines Textdokument"; /* config error */ "Quit" = "Beenden"; /* tree controller action gear */ "Refresh" = "Aktualisieren"; /* file transcript */ "Renaming %@ to %@\n" = "Umbennen von %1$@ zu %2$@\n"; /* FTP Error */ "Request Aborted. Page Type Unknown" = "Vorgang abgebrochen. Seitentyp unbekannt"; /* abbreviation for seconds, e.g. 12 MB/s */ "s" = "s"; /* category name */ "Saved Hosts" = "Gespeicherte Hosts"; /* transfer controller */ "Show Files" = "Dateien einblenden"; /* context menu tree controller action gear */ "Show Hidden Files" = "Ausgeblendete Dateien einblenden"; /* context menu tree controller action gear */ "Show Package Extensions" = "Paketerweiterungen einblenden"; /* outline view column header name outline view column context menu item */ "Size" = "Größe"; /* close window */ "Stop Upload" = "Übertragung stoppen"; /* close window */ "Stop Upload?" = "Übertragung stoppen?"; /* Error creating stream */ "Stream Unavailable" = "Stream nicht verfügbar"; /* filesize: terabytes */ "TB" = "TB"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The body of the request is not supported" = "Der Befehl wird nicht unterstützt"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The directory already exists" = "Das Verzeichnis existiert bereits"; /* name of a host to connect to; in this case, the local file system rather than a remote server */ "the File System" = "lokales Dateisystem"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The server does not allow the creation of directories at the current location" = "Der Server erlaubt kein Erstellen von Verzeichnissen am momentanen Ort"; /* error transferring */ "The transferred file has a size of 0." = "Die übertragenen Datei hat die Größe 0."; /* MobileMe Directory Contents Error */ "There is no MobileMe access to the directory" = "Kein MobileMe Zugang zum gewählten Verzeichnis"; /* No WebDAV access to the specified path */ "There is no WebDAV access to the directory" = "Es ist kein WebDAV Zugang zum Verzeichnis vorhanden"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "Diesem Droplet fehlt das Originalprogramm mit welchem es erstellt wurden. Bitte reinstallieren Sie das Originalprogramm."; /* time out */ "Timed Out waiting for remote host." = "Zeitüberschreitung beim Warten auf den entfernten Host."; /* Transfer Controller */ "Too many files had transfer problems" = "Zu viele Dateien hatten Übertragungsprobleme"; /* FileConnection failed to copy file */ "Unable to store data in file" = "Konnte Daten nicht in Datei sichern"; /* WebDAV Error */ "Unknown Error Occurred" = "Unbekannter Fehler aufgetreten"; /* status message */ "Uploading" = "Hochladen von"; /* status */ "Uploading %@" = "Hochladen von %@"; /* No username or password */ "Username and Password are required for FTP connections" = "Benutzername und Kennwort werden für FTP-Zugang benötigt"; /* No username or password */ "Username and Password are required for S3 connections" = "Benutzername und Kennwort werden für S3 Verbindungen benötigt"; /* No username or password */ "Username and Password are required for WebDAV connections" = "Benutzername und Kennwort werden für WebDAV Verbindungen benötigt"; /* file transcript */ "Writing data to %@\n" = "Schreibe Daten nach %@\n"; /* Bonjour Error */ "You can not add a child collection to the Bonjour category." = "Sie können keine untergeordnete Sammlung zu der Bonjour Kategorie hinzufügen."; /* Bonjour Error */ "You can not add a new server to the Bonjour category." = "Sie können keinen neuen Server zu der Bonjour Kategorie hinzufügen."; /* FTP file upload error */ "You do not have access to write file %@" = "Sie haben keinen Schreibzugang zur Datei %@"; /* FTP Error */ "You need an Account to Upload Files" = "Sie brauchen eine Konto, um Dateien hochzuladen"; ================================================ FILE: Example/en.lproj/Droplet.nib/classes.nib ================================================ { IBClasses = ( { ACTIONS = { cancelPassword = id; cancelUpload = id; connectPassword = id; toggleFiles = id; }; CLASS = DropletController; LANGUAGE = ObjC; OUTLETS = { oCancel = NSButton; oFiles = NSOutlineView; oPassword = NSTextField; oPasswordPanel = NSPanel; oPasswordText = NSTextField; oProgressBar = NSProgressIndicator; oStatus = NSTextField; oToggleFiles = NSButton; oWindow = NSPanel; }; SUPERCLASS = NSObject; }, { CLASS = DropletOutlineView; LANGUAGE = ObjC; SUPERCLASS = NSOutlineView; }, { CLASS = DropletPanel; LANGUAGE = ObjC; SUPERCLASS = NSPanel; }, { CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/en.lproj/Droplet.nib/info.nib ================================================ IBDocumentLocation 325 154 356 240 0 0 1920 1178 IBFramework Version 489.0 IBOldestOS 4 IBSystem Version 9C7010 ================================================ FILE: Example/en.lproj/DropletLauncher.nib/classes.nib ================================================ { IBClasses = ( { CLASS = DropletLauncherDelegate; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, { CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/en.lproj/DropletLauncher.nib/info.nib ================================================ IBDocumentLocation 702 98 356 240 0 0 1920 1178 IBEditorPositions 29 116 400 338 44 0 0 1920 1178 IBFramework Version 489.0 IBOldestOS 4 IBSystem Version 9C7010 ================================================ FILE: Example/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Example/en.lproj/InputDialog.nib/classes.nib ================================================ { IBClasses = ( { CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, { ACTIONS = { cancel = id; ok = id; }; CLASS = InputDialog; LANGUAGE = ObjC; OUTLETS = { input = id; panel = id; title = id; }; SUPERCLASS = NSObject; }, { CLASS = "java.lang.Object"; LANGUAGE = Java; } ); IBVersion = 1; } ================================================ FILE: Example/en.lproj/InputDialog.nib/info.nib ================================================ IBDocumentLocation 160 93 356 240 0 0 1920 1178 IBFramework Version 489.0 IBOldestOS 4 IBSystem Version 9C7010 ================================================ FILE: Example/en.lproj/KTLogViewer.nib/classes.nib ================================================ { IBClasses = ( { CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, { ACTIONS = { open = id; }; CLASS = KTLogController; LANGUAGE = ObjC; OUTLETS = { oTable = NSTableView; oWindow = NSWindow; }; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/en.lproj/KTLogViewer.nib/info.nib ================================================ IBDocumentLocation 117 25 356 240 0 0 1920 1178 IBEditorPositions 29 115 400 325 44 0 0 1920 1178 IBFramework Version 489.0 IBOldestOS 4 IBSystem Version 9C7010 ================================================ FILE: Example/en.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nWhat would you like to do?"; /* ConnectionOpenPanel */ "A Connection Error Occurred" = "A Connection Error Occurred"; /* FTP Abort */ "Action Aborted. Local Error" = "Action Aborted. Local Error"; /* FTP no data stream types available */ "All data connection modes have been exhausted. Check with the server administrator." = "All data connection modes have been exhausted. Check with the server administrator."; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An intermediate directory does not exist and needs to be created before the current directory" = "An intermediate directory does not exist and needs to be created before the current directory"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An unknown error occurred" = "An unknown error occurred"; /* multiple connection joiner */ "and" = "and"; /* close window */ "Are you sure you want to stop the upload?" = "Are you sure you want to stop the upload?"; /* authorise */ "Authorize" = "Authorize"; /* authorise */ "Authorize Connection?" = "Authorize Connection?"; /* connection type */ "auto" = "auto"; /* config */ "Bad Configuration" = "Bad Configuration"; /* error */ "Bad Droplet" = "Bad Droplet"; /* Transfer Controller */ "Bad Password." = "Bad Password."; /* context menu tree controller action gear */ "Browse Packages" = "Browse Packages"; /* filesize: bytes */ "bytes" = "bytes"; /* authorise */ "Cancel" = "Cancel"; /* FTP upload error */ "Cannot Upload File. Storage quota on server exceeded" = "Cannot Upload File. Storage quota on server exceeded"; /* connected message transfer controller */ "Connected to %@" = "Connected to %@"; /* file transcript */ "Connected to File System\n" = "Connected to File System\n"; /* connection string */ "Connecting to %@" = "Connecting to %@"; /* file transcript */ "Connecting…\n" = "Connecting…\n"; /* close window */ "Continue Upload" = "Continue Upload"; /* file transcript */ "Copying %@ to %@\n" = "Copying %1$@ to %2$@\n"; /* FileConnection set permissions error */ "Could not change file permissions" = "Could not change file permissions"; /* FileConnection create directory error */ "Could not create directory" = "Could not create directory"; /* config */ "Couldn't find configuration file specified\n %@" = "Couldn't find configuration file specified\n %@"; /* file transcript */ "Create Directory %@ (%lo)\n" = "Create Directory %1$@ (%2$lo)\n"; /* FTP Create directory error Create directory operation failed */ "Create directory operation failed" = "Create directory operation failed"; /* Failed to open a data stream connection */ "Data Stream Timed Out" = "Data Stream Timed Out"; /* outline view column header name outline view column context menu item */ "Date Modified" = "Date Modified"; /* file transcript */ "Deleting File %@\n" = "Deleting File %@\n"; /* status */ "Disconnected" = "Disconnected"; /* transfer controller */ "Disconnected from %@" = "Disconnected from %@"; /* unknown UTI name */ "Document" = "Document"; /* filesize: exabytes */ "EB" = "EB"; /* Directory Parsing Error */ "Error parsing directory listing" = "Error parsing directory listing"; /* FTP no data streams available */ "Exhausted all connection types to server. Please contact server administrator" = "Exhausted all connection types to server. Please contact server administrator"; /* Failed to change to directory Bad ftp command */ "Failed to change to directory" = "Failed to change to directory"; /* WebDAV Directory Deletion Error couldn't delete the file MobileMe Directory Deletion Error */ "Failed to delete directory" = "Failed to delete directory"; /* error for deleting a directory */ "Failed to delete directory: %@" = "Failed to delete directory: %@"; /* WebDAV File Deletion Error couldn't delete the file MobileMe file deletion error */ "Failed to delete file" = "Failed to delete file"; /* error for deleting a file */ "Failed to delete file: %@" = "Failed to delete file: %@"; /* Failed to download file. */ "Failed to download file." = "Failed to download file."; /* Failed to rename file. */ "Failed to rename file." = "Failed to rename file."; /* No MobileMe account or password */ "Failed to retrieve MobileMe account details" = "Failed to retrieve MobileMe account details"; /* FTP Upload error SFTP Upload error */ "Failed to set permissions for path %@" = "Failed to set permissions for path %@"; /* FileConnection copy data error */ "Failed to upload data" = "Failed to upload data"; /* Failed to upload file. */ "Failed to upload file." = "Failed to upload file."; /* error transferring */ "Failed to verify file transferred successfully" = "Failed to verify file transferred successfully"; /* FTP file download error */ "File %@ does not exist on server" = "File %@ does not exist on server"; /* FileConnection error */ "File Already Exists" = "File Already Exists"; /* FTP file in use */ "File in Use" = "File in Use"; /* directory kind */ "Folder" = "Folder"; /* FTP service timed out */ "FTP service not available; Remote server has closed connection" = "FTP service not available; Remote server has closed connection"; /* FTP no service */ "FTP Service Unavailable" = "FTP Service Unavailable"; /* filesize: gigabytes */ "GB" = "GB"; /* transfer controller */ "Hide Files" = "Hide Files"; /* Couldn't open the port to the host Host Unavailable */ "Host Unavailable" = "Host Unavailable"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "Insufficient storage space available" = "Insufficient storage space available"; /* FTP Error */ "Invalid Account name" = "Invalid Account name"; /* Stream Error before opening */ "Is the service running on the server" = "Is the service running on the server"; /* filesize: kilobytes */ "KB" = "KB"; /* outline view column header name outline view column context menu item */ "Kind" = "Kind"; /* FTP download error */ "Local File already exists" = "Local File already exists"; /* filesize: megabytes */ "MB" = "MB"; /* outline view column header name outline view column context menu item */ "Name" = "Name"; /* new cat name */ "New Category" = "New Category"; /* tree controller action gear */ "New Folder" = "New Folder"; /* config */ "No configuration file specified" = "No configuration file specified"; /* failed to find a connection class */ "No connection available for requested connection type" = "No connection available for requested connection type"; /* failed to find a connection class */ "No connection available for requested port" = "No connection available for requested port"; /* failed to find a connection class */ "No connection available for requested protocol" = "No connection available for requested protocol"; /* FTP Error */ "No Storage Space Available" = "No Storage Space Available"; /* No such file */ "No such file" = "No such file"; /* FTP Error */ "Not Logged In" = "Not Logged In"; /* OK */ "OK" = "OK"; /* WebDAV Uploading Error MobileMe File Uploading Error */ "Parent Folder does not exist" = "Parent Folder does not exist"; /* No comment provided by engineer. */ "Password was not accepted." = "Password was not accepted."; /* filesize: petabytes */ "PB" = "PB"; /* Permission Denied */ "Permission Denied" = "Permission Denied"; /* mimic Finder naming conventions */ "Plain text document" = "Plain text document"; /* config error */ "Quit" = "Quit"; /* tree controller action gear */ "Refresh" = "Refresh"; /* file transcript */ "Renaming %@ to %@\n" = "Renaming %1$@ to %2$@\n"; /* abbreviation for seconds, e.g. 12 MB/s */ "s" = "s"; /* category name */ "Saved Hosts" = "Saved Hosts"; /* transfer controller */ "Show Files" = "Show Files"; /* context menu tree controller action gear */ "Show Hidden Files" = "Show Hidden Files"; /* context menu tree controller action gear */ "Show Package Extensions" = "Show Package Extensions"; /* outline view column header name outline view column context menu item */ "Size" = "Size"; /* close window */ "Stop Upload" = "Stop Upload"; /* close window */ "Stop Upload?" = "Stop Upload?"; /* Error creating stream */ "Stream Unavailable" = "Stream Unavailable"; /* filesize: terabytes */ "TB" = "TB"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The body of the request is not supported" = "The body of the request is not supported"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The directory already exists" = "The directory already exists"; /* name of a host to connect to; in this case, the local file system rather than a remote server */ "the File System" = "the File System"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The server does not allow the creation of directories at the current location" = "The server does not allow the creation of directories at the current location"; /* error transferring */ "The transferred file has a size of 0." = "The transferred file has a size of 0."; /* MobileMe Directory Contents Error */ "There is no MobileMe access to the directory" = "There is no MobileMe access to the directory"; /* No WebDAV access to the specified path */ "There is no WebDAV access to the directory" = "There is no WebDAV access to the directory"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "This droplet is missing the original application that created it. You will need to reinstall the original application."; /* time out */ "Timed Out waiting for remote host." = "Timed Out waiting for remote host."; /* Transfer Controller */ "Too many files had transfer problems" = "Too many files had transfer problems"; /* FileConnection failed to copy file */ "Unable to store data in file" = "Unable to store data in file"; /* WebDAV Error */ "Unknown Error Occurred" = "Unknown Error Occurred"; /* status message */ "Uploading" = "Uploading"; /* status */ "Uploading %@" = "Uploading %@"; /* No username or password */ "Username and Password are required for FTP connections" = "Username and Password are required for FTP connections"; /* No username or password */ "Username and Password are required for S3 connections" = "Username and Password are required for S3 connections"; /* No username or password */ "Username and Password are required for WebDAV connections" = "Username and Password are required for WebDAV connections"; /* file transcript */ "Writing data to %@\n" = "Writing data to %@\n"; /* Bonjour Error */ "You can not add a child collection to the Bonjour category." = "You can not add a child collection to the Bonjour category."; /* Bonjour Error */ "You can not add a new server to the Bonjour category." = "You can not add a new server to the Bonjour category."; /* FTP file upload error */ "You do not have access to write file %@" = "You do not have access to write file %@"; /* FTP Error */ "You need an Account to Upload Files" = "You need an Account to Upload Files"; ================================================ FILE: Example/en.lproj/MainMenu.nib/classes.nib ================================================ { IBClasses = ( { ACTIONS = { cancelConnect = id; checkForFile = id; connect = id; connectionTypeChanged = id; deleteFile = id; disconnect = id; editFile = id; hostnameChanged = id; initialDirectoryChanged = id; localFileSelected = id; localPopupChanged = id; logConfig = id; newCategory = id; newFolder = id; newHost = id; passwordChanged = id; permissions = id; portChanged = id; printQueueDescription = id; refresh = id; remoteFileSelected = id; remotePopupChanged = id; saveHost = id; savedHostsChanged = id; searchChanged = id; showConnect = id; stopTransfer = id; transferSelected = id; urlChanged = id; usernameChanged = id; }; CLASS = Controller; LANGUAGE = ObjC; OUTLETS = { btnBrowseHost = NSButton; btnConnect = NSButton; btnDelete = NSButton; btnDisconnect = NSButton; btnEdit = NSButton; btnNewFolder = NSButton; btnPermissions = NSButton; btnRefresh = NSButton; btnStop = NSButton; cBtnCancel = NSButton; cBtnConnect = NSButton; cHost = NSTextField; cPass = NSTextField; cPort = NSTextField; cTypePopup = NSPopUpButton; cURL = NSTextField; cUser = NSTextField; connectWindow = NSPanel; fileCheckLog = NSTextView; initialDirectory = NSTextField; localBrowserBox = NSBox; localPopup = NSPopUpButton; localTable = NSTableView; log = NSTextView; logDrawer = NSDrawer; oConMenu = NSPopUpButton; remoteBrowserBox = NSBox; remotePopup = NSPopUpButton; remoteTable = NSTableView; savedHosts = id; status = NSTextField; transferTable = NSTableView; window = NSWindow; }; SUPERCLASS = NSObject; }, { CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/en.lproj/MainMenu.nib/info.nib ================================================ IBDocumentLocation 95 807 356 240 0 0 1920 1178 IBEditorPositions 29 1333 989 498 44 0 0 2560 1578 332 948 972 664 210 0 0 2560 1578 IBFramework Version 489.0 IBOldestOS 4 IBSystem Version 9C7010 ================================================ FILE: Example/en.lproj/Permissions.nib/classes.nib ================================================ { IBClasses = ( { CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, { ACTIONS = { attribsChanged = id; cancel = id; save = id; }; CLASS = PermissionsController; LANGUAGE = ObjC; OUTLETS = { ge = id; gr = id; gw = id; ue = id; ur = id; uw = id; we = id; window = id; wr = id; ww = id; }; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/en.lproj/Permissions.nib/info.nib ================================================ IBDocumentLocation 114 29 356 240 0 0 1920 1178 IBFramework Version 489.0 IBOldestOS 4 IBSystem Version 9C7010 ================================================ FILE: Example/fr.lproj/DropletLauncher.nib/classes.nib ================================================ { IBClasses = ( {CLASS = DropletLauncherDelegate; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/fr.lproj/DropletLauncher.nib/info.nib ================================================ IBDocumentLocation 415 79 356 240 0 0 1280 1002 IBEditorPositions 29 69 338 378 44 0 0 1280 1002 IBFramework Version 439.0 IBSystem Version 8S165 IBUsesTextArchiving ================================================ FILE: Example/fr.lproj/DropletLauncher.nib/keyedobjects.nib ================================================ $archiver NSKeyedArchiver $objects $null $class CF$UID 388 NSAccessibilityConnectors CF$UID 385 NSAccessibilityOidsKeys CF$UID 386 NSAccessibilityOidsValues CF$UID 387 NSClassesKeys CF$UID 280 NSClassesValues CF$UID 281 NSConnections CF$UID 9 NSFontManager CF$UID 0 NSFramework CF$UID 6 NSNamesKeys CF$UID 256 NSNamesValues CF$UID 257 NSNextOid 250 NSObjectsKeys CF$UID 176 NSObjectsValues CF$UID 255 NSOidsKeys CF$UID 282 NSOidsValues CF$UID 283 NSRoot CF$UID 2 NSVisibleWindows CF$UID 7 $class CF$UID 5 NSClassName CF$UID 3 $class CF$UID 4 NS.string NSApplication $classes NSMutableString NSString NSObject $classname NSMutableString $classes NSCustomObject NSObject $classname NSCustomObject $class CF$UID 4 NS.string IBCocoaFramework $class CF$UID 8 NS.objects $classes NSMutableSet NSSet NSObject $classname NSMutableSet $class CF$UID 175 NS.objects CF$UID 10 CF$UID 24 CF$UID 29 CF$UID 35 CF$UID 40 CF$UID 46 CF$UID 51 CF$UID 57 CF$UID 61 CF$UID 66 CF$UID 70 CF$UID 74 CF$UID 79 CF$UID 84 CF$UID 90 CF$UID 95 CF$UID 100 CF$UID 105 CF$UID 110 CF$UID 115 CF$UID 120 CF$UID 125 CF$UID 130 CF$UID 134 CF$UID 138 CF$UID 142 CF$UID 148 CF$UID 152 CF$UID 156 CF$UID 160 CF$UID 165 CF$UID 170 $class CF$UID 23 NSLabel CF$UID 22 NSSource CF$UID 11 $class CF$UID 21 NSKeyEquiv CF$UID 14 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 13 $class CF$UID 191 NSMenuItems CF$UID 244 NSName CF$UID 246 NSTitle CF$UID 242 Placer dans le Dock m $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 17 NSImage NSMenuCheckmark $classes NSCustomResource %NSCustomResource NSObject $classname NSCustomResource $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 20 NSMenuMixedState $classes NSMenuItem NSObject $classname NSMenuItem $class CF$UID 4 NS.string performMiniaturize: $classes NSNibControlConnector NSNibConnector NSObject $classname NSNibControlConnector $class CF$UID 23 NSLabel CF$UID 28 NSSource CF$UID 25 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 26 Tout ramener au premier plan $class CF$UID 4 NS.string arrangeInFront: $class CF$UID 23 NSLabel CF$UID 34 NSSource CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 33 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 32 $class CF$UID 191 NSMenuItems CF$UID 199 NSTitle CF$UID 197 Imprimer… p $class CF$UID 4 NS.string print: $class CF$UID 23 NSLabel CF$UID 39 NSSource CF$UID 36 $class CF$UID 21 NSKeyEquiv CF$UID 38 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 37 Format d’impression… P $class CF$UID 4 NS.string runPageLayout: $class CF$UID 23 NSLabel CF$UID 45 NSSource CF$UID 41 $class CF$UID 21 NSKeyEquiv CF$UID 44 NSKeyEquivModMask 1048576 NSMenu CF$UID 42 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 43 $class CF$UID 191 NSMenuItems CF$UID 252 NSTitle CF$UID 250 Aide NewApplication ? $class CF$UID 4 NS.string showHelp: $class CF$UID 23 NSLabel CF$UID 50 NSSource CF$UID 47 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 48 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 49 $class CF$UID 191 NSMenuItems CF$UID 209 NSName CF$UID 210 NSTitle CF$UID 207 Effacer le menu $class CF$UID 4 NS.string clearRecentDocuments: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 56 NSSource CF$UID 52 $class CF$UID 21 NSKeyEquiv CF$UID 55 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 54 $class CF$UID 191 NSMenuItems CF$UID 178 NSName CF$UID 194 NSTitle CF$UID 177 Quitter NewApplication q $class CF$UID 4 NS.string terminate: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 60 NSSource CF$UID 58 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 59 À propos de NewApplication $class CF$UID 4 NS.string orderFrontStandardAboutPanel: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 65 NSSource CF$UID 62 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1572864 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 63 Masquer les autres h hideOtherApplications: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 69 NSSource CF$UID 67 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 68 Masquer NewApplication hide: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 73 NSSource CF$UID 71 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 72 Tout afficher unhideAllApplications: $class CF$UID 23 NSLabel CF$UID 78 NSSource CF$UID 75 $class CF$UID 21 NSKeyEquiv CF$UID 77 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 76 Fermer w performClose: $class CF$UID 23 NSLabel CF$UID 83 NSSource CF$UID 80 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 82 $class CF$UID 191 NSMenuItems CF$UID 238 NSTitle CF$UID 236 Vérifier l’orthographe lors de la frappe toggleContinuousSpellChecking: $class CF$UID 23 NSLabel CF$UID 89 NSSource CF$UID 85 $class CF$UID 21 NSKeyEquiv CF$UID 88 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 87 $class CF$UID 191 NSMenuItems CF$UID 228 NSTitle CF$UID 226 Annuler z $class CF$UID 4 NS.string undo: $class CF$UID 23 NSLabel CF$UID 94 NSSource CF$UID 91 $class CF$UID 21 NSKeyEquiv CF$UID 93 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 92 Copier c $class CF$UID 4 NS.string copy: $class CF$UID 23 NSLabel CF$UID 99 NSSource CF$UID 96 $class CF$UID 21 NSKeyEquiv CF$UID 98 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 97 Vérifier l’orthographe ; $class CF$UID 4 NS.string checkSpelling: $class CF$UID 23 NSLabel CF$UID 104 NSSource CF$UID 101 $class CF$UID 21 NSKeyEquiv CF$UID 103 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 102 Coller v $class CF$UID 4 NS.string paste: $class CF$UID 23 NSLabel CF$UID 109 NSSource CF$UID 106 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 108 $class CF$UID 191 NSMenuItems CF$UID 222 NSTitle CF$UID 221 Arrêter la lecture stopSpeaking: $class CF$UID 23 NSLabel CF$UID 114 NSSource CF$UID 111 $class CF$UID 21 NSKeyEquiv CF$UID 113 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 112 Couper x $class CF$UID 4 NS.string cut: $class CF$UID 23 NSLabel CF$UID 119 NSSource CF$UID 116 $class CF$UID 21 NSKeyEquiv CF$UID 118 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 117 Orthographe… : $class CF$UID 4 NS.string showGuessPanel: $class CF$UID 23 NSLabel CF$UID 124 NSSource CF$UID 121 $class CF$UID 21 NSKeyEquiv CF$UID 123 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 122 Rétablir Z $class CF$UID 4 NS.string redo: $class CF$UID 23 NSLabel CF$UID 129 NSSource CF$UID 126 $class CF$UID 21 NSKeyEquiv CF$UID 128 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 127 Tout sélectionner a $class CF$UID 4 NS.string selectAll: $class CF$UID 23 NSLabel CF$UID 133 NSSource CF$UID 131 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 132 Commencer la lecture startSpeaking: $class CF$UID 23 NSLabel CF$UID 137 NSSource CF$UID 135 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 136 Supprimer delete: $class CF$UID 23 NSLabel CF$UID 141 NSSource CF$UID 139 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 140 Zoom performZoom: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 143 $class CF$UID 21 NSKeyEquiv CF$UID 146 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 1 NSTitle CF$UID 145 $class CF$UID 191 NSMenuItems CF$UID 234 NSTitle CF$UID 232 Rechercher… f performFindPanelAction: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 149 $class CF$UID 21 NSKeyEquiv CF$UID 151 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 2 NSTitle CF$UID 150 Rechercher le suivant g $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 153 $class CF$UID 21 NSKeyEquiv CF$UID 155 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 3 NSTitle CF$UID 154 Rechercher le précédent G $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 157 $class CF$UID 21 NSKeyEquiv CF$UID 159 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 7 NSTitle CF$UID 158 Utiliser la sélection pour trouver e $class CF$UID 23 NSLabel CF$UID 164 NSSource CF$UID 161 $class CF$UID 21 NSKeyEquiv CF$UID 163 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 162 Aller à la sélection j centerSelectionInVisibleArea: $class CF$UID 23 NSLabel CF$UID 169 NSSource CF$UID 166 $class CF$UID 21 NSKeyEquiv CF$UID 168 NSKeyEquivModMask 1572864 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 167 Coller le style et l’appliquer V pasteAsPlainText: $class CF$UID 174 NSDestination CF$UID 171 NSLabel CF$UID 173 NSSource CF$UID 2 $class CF$UID 5 NSClassName CF$UID 172 DropletLauncherDelegate delegate $classes NSNibOutletConnector NSNibConnector NSObject $classname NSNibOutletConnector $classes NSMutableArray NSArray NSObject $classname NSMutableArray $class CF$UID 254 NS.objects CF$UID 53 CF$UID 195 CF$UID 107 CF$UID 106 CF$UID 11 CF$UID 223 CF$UID 143 CF$UID 71 CF$UID 171 CF$UID 25 CF$UID 58 CF$UID 36 CF$UID 157 CF$UID 218 CF$UID 111 CF$UID 80 CF$UID 192 CF$UID 225 CF$UID 153 CF$UID 179 CF$UID 187 CF$UID 220 CF$UID 241 CF$UID 161 CF$UID 180 CF$UID 215 CF$UID 139 CF$UID 193 CF$UID 235 CF$UID 131 CF$UID 230 CF$UID 47 CF$UID 85 CF$UID 196 CF$UID 200 CF$UID 245 CF$UID 211 CF$UID 144 CF$UID 249 CF$UID 62 CF$UID 116 CF$UID 12 CF$UID 121 CF$UID 239 CF$UID 75 CF$UID 30 CF$UID 81 CF$UID 91 CF$UID 86 CF$UID 212 CF$UID 96 CF$UID 41 CF$UID 166 CF$UID 229 CF$UID 67 CF$UID 231 CF$UID 135 CF$UID 31 CF$UID 42 CF$UID 126 CF$UID 203 CF$UID 101 CF$UID 184 CF$UID 52 CF$UID 48 CF$UID 183 CF$UID 206 CF$UID 149 NewApplication $class CF$UID 175 NS.objects CF$UID 58 CF$UID 179 CF$UID 180 CF$UID 183 CF$UID 184 CF$UID 192 CF$UID 67 CF$UID 62 CF$UID 71 CF$UID 193 CF$UID 52 $class CF$UID 21 NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSKeyEquiv CF$UID 182 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 181 Préférences… , $class CF$UID 21 NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 186 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 187 NSTitle CF$UID 185 Services submenuAction: $class CF$UID 191 NSMenuItems CF$UID 189 NSName CF$UID 190 NSTitle CF$UID 188 $class CF$UID 4 NS.string Services $class CF$UID 175 NS.objects _NSServicesMenu $classes NSMenu NSObject $classname NSMenu $class CF$UID 21 NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSAppleMenu $class CF$UID 21 NSAction CF$UID 198 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 196 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 31 NSTitle CF$UID 197 $class CF$UID 191 NSMenuItems CF$UID 248 NSName CF$UID 253 NSTitle CF$UID 247 Fichier submenuAction: $class CF$UID 175 NS.objects CF$UID 200 CF$UID 203 CF$UID 206 CF$UID 211 CF$UID 75 CF$UID 212 CF$UID 215 CF$UID 218 CF$UID 220 CF$UID 36 CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 202 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 201 Nouveau n $class CF$UID 21 NSKeyEquiv CF$UID 205 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 204 Ouvrir… o $class CF$UID 21 NSAction CF$UID 208 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 48 NSTitle CF$UID 207 Ouvrir récent submenuAction: $class CF$UID 175 NS.objects CF$UID 47 _NSRecentDocumentsMenu $class CF$UID 21 NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSKeyEquiv CF$UID 214 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 213 Enregistrer s $class CF$UID 21 NSKeyEquiv CF$UID 217 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 216 Enregistrer sous… S $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 219 Revenir $class CF$UID 21 NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 Parole $class CF$UID 175 NS.objects CF$UID 131 CF$UID 106 $class CF$UID 21 NSAction CF$UID 224 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 196 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 53 NSTitle CF$UID 177 submenuAction: $class CF$UID 21 NSAction CF$UID 227 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 196 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 86 NSTitle CF$UID 226 Édition submenuAction: $class CF$UID 175 NS.objects CF$UID 85 CF$UID 121 CF$UID 229 CF$UID 111 CF$UID 91 CF$UID 101 CF$UID 166 CF$UID 135 CF$UID 126 CF$UID 230 CF$UID 231 CF$UID 235 CF$UID 239 $class CF$UID 21 NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 233 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 144 NSTitle CF$UID 232 Rechercher submenuAction: $class CF$UID 175 NS.objects CF$UID 143 CF$UID 149 CF$UID 153 CF$UID 157 CF$UID 161 $class CF$UID 21 NSAction CF$UID 237 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 81 NSTitle CF$UID 236 Orthographe submenuAction: $class CF$UID 175 NS.objects CF$UID 116 CF$UID 96 CF$UID 80 $class CF$UID 21 NSAction CF$UID 240 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 107 NSTitle CF$UID 221 submenuAction: $class CF$UID 21 NSAction CF$UID 243 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 196 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 12 NSTitle CF$UID 242 Fenêtre submenuAction: $class CF$UID 175 NS.objects CF$UID 11 CF$UID 139 CF$UID 245 CF$UID 25 $class CF$UID 21 NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSWindowsMenu DropletLauncher $class CF$UID 175 NS.objects CF$UID 223 CF$UID 195 CF$UID 225 CF$UID 241 CF$UID 249 $class CF$UID 21 NSAction CF$UID 251 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 196 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 42 NSTitle CF$UID 250 Aide submenuAction: $class CF$UID 175 NS.objects CF$UID 41 _NSMainMenu $classes NSArray NSObject $classname NSArray $class CF$UID 254 NS.objects CF$UID 223 CF$UID 196 CF$UID 239 CF$UID 107 CF$UID 12 CF$UID 196 CF$UID 144 CF$UID 53 CF$UID 2 CF$UID 12 CF$UID 53 CF$UID 31 CF$UID 144 CF$UID 31 CF$UID 86 CF$UID 81 CF$UID 53 CF$UID 196 CF$UID 144 CF$UID 53 CF$UID 184 CF$UID 31 CF$UID 196 CF$UID 144 CF$UID 53 CF$UID 31 CF$UID 12 CF$UID 53 CF$UID 86 CF$UID 107 CF$UID 86 CF$UID 48 CF$UID 86 CF$UID 2 CF$UID 31 CF$UID 12 CF$UID 31 CF$UID 231 CF$UID 196 CF$UID 53 CF$UID 81 CF$UID 241 CF$UID 86 CF$UID 86 CF$UID 31 CF$UID 31 CF$UID 235 CF$UID 86 CF$UID 225 CF$UID 31 CF$UID 81 CF$UID 42 CF$UID 86 CF$UID 86 CF$UID 53 CF$UID 86 CF$UID 86 CF$UID 195 CF$UID 249 CF$UID 86 CF$UID 31 CF$UID 86 CF$UID 53 CF$UID 53 CF$UID 206 CF$UID 53 CF$UID 31 CF$UID 144 $class CF$UID 254 NS.objects CF$UID 36 CF$UID 211 CF$UID 203 CF$UID 80 CF$UID 195 CF$UID 81 CF$UID 116 CF$UID 2 CF$UID 235 CF$UID 196 CF$UID 75 CF$UID 245 CF$UID 96 CF$UID 171 CF$UID 42 CF$UID 249 CF$UID 12 CF$UID 200 CF$UID 30 CF$UID 52 CF$UID 220 CF$UID 41 CF$UID 215 CF$UID 212 CF$UID 180 CF$UID 218 CF$UID 31 $class CF$UID 254 NS.objects CF$UID 258 CF$UID 259 CF$UID 260 CF$UID 261 CF$UID 260 CF$UID 262 CF$UID 263 CF$UID 264 CF$UID 265 CF$UID 266 CF$UID 267 CF$UID 260 CF$UID 265 CF$UID 172 CF$UID 268 CF$UID 269 CF$UID 270 CF$UID 272 CF$UID 273 CF$UID 274 CF$UID 275 CF$UID 260 CF$UID 276 CF$UID 277 CF$UID 278 CF$UID 279 CF$UID 260 $class CF$UID 4 NS.string 5 $class CF$UID 4 NS.string 7 $class CF$UID 4 NS.string NSMenuItem2 NSMenu NSMenuItem1 $class CF$UID 4 NS.string File's Owner NSMenuItem $class CF$UID 4 NS.string MainMenu $class CF$UID 4 NS.string 1 $class CF$UID 4 NS.string 2 $class CF$UID 4 NS.string 1 $class CF$UID 271 $classes NSNull %NSNull NSObject $classname NSNull $class CF$UID 4 NS.string 9 $class CF$UID 4 NS.string 6 1111 $class CF$UID 4 NS.string 2 $class CF$UID 4 NS.string 8 $class CF$UID 4 NS.string 3 121 $class CF$UID 4 NS.string 10 $class CF$UID 254 NS.objects $class CF$UID 254 NS.objects $class CF$UID 254 NS.objects CF$UID 193 CF$UID 35 CF$UID 36 CF$UID 170 CF$UID 10 CF$UID 125 CF$UID 29 CF$UID 211 CF$UID 84 CF$UID 229 CF$UID 110 CF$UID 187 CF$UID 95 CF$UID 241 CF$UID 225 CF$UID 171 CF$UID 86 CF$UID 206 CF$UID 90 CF$UID 71 CF$UID 166 CF$UID 149 CF$UID 230 CF$UID 142 CF$UID 215 CF$UID 41 CF$UID 111 CF$UID 79 CF$UID 245 CF$UID 148 CF$UID 218 CF$UID 57 CF$UID 107 CF$UID 80 CF$UID 11 CF$UID 184 CF$UID 235 CF$UID 116 CF$UID 106 CF$UID 152 CF$UID 180 CF$UID 139 CF$UID 75 CF$UID 42 CF$UID 126 CF$UID 67 CF$UID 231 CF$UID 115 CF$UID 153 CF$UID 156 CF$UID 62 CF$UID 91 CF$UID 100 CF$UID 239 CF$UID 160 CF$UID 2 CF$UID 130 CF$UID 70 CF$UID 30 CF$UID 52 CF$UID 220 CF$UID 105 CF$UID 25 CF$UID 165 CF$UID 134 CF$UID 192 CF$UID 195 CF$UID 74 CF$UID 157 CF$UID 61 CF$UID 143 CF$UID 81 CF$UID 46 CF$UID 85 CF$UID 51 CF$UID 96 CF$UID 196 CF$UID 58 CF$UID 138 CF$UID 66 CF$UID 40 CF$UID 121 CF$UID 200 CF$UID 12 CF$UID 120 CF$UID 223 CF$UID 212 CF$UID 144 CF$UID 31 CF$UID 131 CF$UID 53 CF$UID 249 CF$UID 161 CF$UID 203 CF$UID 183 CF$UID 47 CF$UID 101 CF$UID 24 CF$UID 135 CF$UID 179 CF$UID 48 $class CF$UID 254 NS.objects CF$UID 284 CF$UID 285 CF$UID 286 CF$UID 287 CF$UID 288 CF$UID 289 CF$UID 290 CF$UID 291 CF$UID 292 CF$UID 293 CF$UID 294 CF$UID 295 CF$UID 296 CF$UID 297 CF$UID 298 CF$UID 299 CF$UID 300 CF$UID 301 CF$UID 302 CF$UID 303 CF$UID 304 CF$UID 305 CF$UID 306 CF$UID 307 CF$UID 308 CF$UID 309 CF$UID 310 CF$UID 311 CF$UID 312 CF$UID 313 CF$UID 314 CF$UID 315 CF$UID 316 CF$UID 317 CF$UID 318 CF$UID 319 CF$UID 320 CF$UID 321 CF$UID 322 CF$UID 323 CF$UID 324 CF$UID 325 CF$UID 326 CF$UID 327 CF$UID 328 CF$UID 329 CF$UID 330 CF$UID 331 CF$UID 332 CF$UID 333 CF$UID 334 CF$UID 335 CF$UID 336 CF$UID 337 CF$UID 338 CF$UID 339 CF$UID 340 CF$UID 341 CF$UID 342 CF$UID 343 CF$UID 344 CF$UID 345 CF$UID 346 CF$UID 347 CF$UID 348 CF$UID 349 CF$UID 350 CF$UID 351 CF$UID 352 CF$UID 353 CF$UID 354 CF$UID 355 CF$UID 356 CF$UID 357 CF$UID 358 CF$UID 359 CF$UID 360 CF$UID 361 CF$UID 362 CF$UID 363 CF$UID 364 CF$UID 365 CF$UID 366 CF$UID 367 CF$UID 368 CF$UID 369 CF$UID 370 CF$UID 371 CF$UID 372 CF$UID 373 CF$UID 374 CF$UID 375 CF$UID 376 CF$UID 377 CF$UID 378 CF$UID 379 CF$UID 380 CF$UID 381 CF$UID 382 CF$UID 383 CF$UID 384 149 87 77 249 37 232 86 79 223 206 228 130 225 19 217 248 205 124 224 150 246 208 214 241 80 111 199 222 92 242 112 142 212 219 23 131 216 204 195 243 129 239 73 106 198 134 218 230 213 244 145 197 226 211 245 1 233 153 78 136 74 227 5 247 235 144 83 193 221 146 209 200 127 207 139 201 29 58 240 152 122 215 82 24 231 56 75 220 81 196 57 103 210 72 143 126 203 39 202 236 125 $class CF$UID 175 NS.objects $class CF$UID 254 NS.objects $class CF$UID 254 NS.objects $classes NSIBObjectData NSObject $classname NSIBObjectData $top IB.objectdata CF$UID 1 $version 100000 ================================================ FILE: Example/fr.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Example/fr.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nQue désirez-vous faire ?"; /* ConnectionOpenPanel */ "A Connection Error Occurred" = "Une erreur de connexion s'est produite"; /* FTP Abort */ "Action Aborted. Local Error" = "Action interrompue. Erreur locale"; /* FTP no data stream types available */ "All data connection modes have been exhausted. Check with the server administrator." = "Tous les modes de connexion de données ont été épuisés. Vérifier avec l'administrateur du serveur."; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An intermediate directory does not exist and needs to be created before the current directory" = "Un répertoire intermédiaire n'existe pas et doit être créé avant le répertoire actuel"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An unknown error occurred" = "Une erreur inconnue s'est produite"; /* multiple connection joiner */ "and" = "et"; /* close window */ "Are you sure you want to stop the upload?" = "Êtes-vous sûr de vouloir arrêter le chargement ?"; /* authorise */ "Authorize" = "Authoriser"; /* authorise */ "Authorize Connection?" = "Autoriser la connexion ?"; /* connection type */ "auto" = "auto"; /* config */ "Bad Configuration" = "Mauvaise configuration"; /* error */ "Bad Droplet" = "Mauvaise 'droplet'"; /* Transfer Controller */ "Bad Password." = "Mauvais mot de passe."; /* context menu tree controller action gear */ "Browse Packages" = "Parcourir les paquets"; /* filesize: bytes */ "bytes" = "octets"; /* authorise */ "Cancel" = "Annuler"; /* FTP upload error */ "Cannot Upload File. Storage quota on server exceeded" = "Ne peut pas charger le fichier. Le quota de stockage sur le serveur est dépassé"; /* connected message transfer controller */ "Connected to %@" = "Connecté à %@"; /* file transcript */ "Connected to File System\n" = "Relié au Système de fichiers\n"; /* connection string */ "Connecting to %@" = "Se connecte à %@"; /* file transcript */ "Connecting…\n" = "En connexion…\n"; /* close window */ "Continue Upload" = "Continuer le chargement"; /* file transcript */ "Copying %@ to %@\n" = "Copie de %1$@ vers %2$@\n"; /* FileConnection set permissions error */ "Could not change file permissions" = "N'a pas pu modifier des autorisations de fichier"; /* FileConnection create directory error */ "Could not create directory" = "N'a pas pu créer le répertoire"; /* config */ "Couldn't find configuration file specified\n %@" = "N'a pu trouver le fichier de configuration indiqué\n %@ "; /* file transcript */ "Create Directory %@ (%lo)\n" = "Crération du répertoire %1$@ (%2$lo)\n"; /* FTP Create directory error */ "Create directory operation failed" = "L'opération de création du répertoire a échoué"; /* Failed to open a data stream connection */ "Data Stream Timed Out" = "Le flux de données est arrêté"; /* outline view column header name outline view column context menu item */ "Date Modified" = "Date de modification"; /* file transcript */ "Deleting File %@\n" = "Suppression du fichier %@\n"; /* status */ "Disconnected" = "Déconnecté"; /* transfer controller */ "Disconnected from %@" = "Déconnecté de %@"; /* unknown UTI name */ "Document" = "Document"; /* filesize: exabytes */ "EB" = "Eo"; /* Directory Parsing Error */ "Error parsing directory listing" = "Erreur en traitant le listage du répertoire"; /* FTP no data streams available */ "Exhausted all connection types to server. Please contact server administrator" = "Tous les types de connexion au serveur sont épuisés. Veuillez contacter l'administrateur de serveur"; /* Bad ftp command */ "Failed to change to directory" = "N'a pas modifié en répertoire"; /* WebDAV Directory Deletion Error couldn't delete the file MobileMe Directory Deletion Error */ "Failed to delete directory" = "N'a pas effacé le répertoire"; /* error for deleting a directory */ "Failed to delete directory: %@" = "N'a pas effacé le répertoire : %@"; /* WebDAV File Deletion Error couldn't delete the file MobileMe file deletion error */ "Failed to delete file" = "N'a pas effacé le fichier"; /* error for deleting a file */ "Failed to delete file: %@" = "N'a pas effacé le fichier : %@"; /* No MobileMe account or password */ "Failed to retrieve MobileMe account details" = "N'a pu obtenir les infos du compte MobileMe"; /* FTP Upload error */ "Failed to set permissions for path %@" = "N'a pu définir les permissions pour le chemin %@"; /* FileConnection copy data error */ "Failed to upload data" = "N'a pas chargé les données"; /* error transferring */ "Failed to verify file transferred successfully" = "N'a pu vérifié le fichier transféré avec succès"; /* FTP file download error */ "File %@ does not exist on server" = "Le fichier %@ n'existe pas sur le serveur"; /* FTP error */ "File / Directory does not exist" = "Le fichier/répertoire n'existe pas"; /* FileConnection error */ "File Already Exists" = "Le fichier existe déjà"; /* FTP file in use */ "File in Use" = "Le fichier est utilisé"; /* FTP Upload error */ "Filename not Allowed" = "Nom du fichier n'est pas permis"; /* directory kind */ "Folder" = "Dossier"; /* FTP service timed out */ "FTP service not available; Remote server has closed connection" = "Service FTP indisponible ; Le serveur à distance fermé la connexion"; /* FTP no service */ "FTP Service Unavailable" = "Service FTP indisponible"; /* filesize: gigabytes */ "GB" = "Go"; /* transfer controller */ "Hide Files" = "Masquer les fichiers"; /* Couldn't open the port to the host */ "Host Unavailable" = "Hôte indisponible"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "Insufficient storage space available" = "Espace de stockage insuffisant disponible"; /* FTP Error */ "Invalid Account name" = "Nom de compte invalide"; /* Stream Error before opening */ "Is the service running on the server" = "Est-ce que le service fonctionne sur le serveur"; /* filesize: kilobytes */ "KB" = "Ko"; /* outline view column header name outline view column context menu item */ "Kind" = "Type"; /* FTP download error */ "Local File already exists" = "Le fichier local existe déjà"; /* filesize: megabytes */ "MB" = "Mo"; /* outline view column header name outline view column context menu item */ "Name" = "Nom "; /* new cat name */ "New Category" = "Nouvelle catégorie"; /* tree controller action gear */ "New Folder" = "Nouveau dossier"; /* config */ "No configuration file specified" = "Aucun fichier de configuration indiqué"; /* failed to find a connection class */ "No connection available for requested connection type" = "Aucune connexion disponible pour le type de connexion demandé"; /* failed to find a connection class */ "No connection available for requested port" = "Aucune connexion disponible pour le port demandé"; /* failed to find a connection class */ "No connection available for requested protocol" = "Aucune connexion disponible pour le protocole demandé"; /* FTP Error */ "No Storage Space Available" = "Pas d'espace de stockage disponible"; /* FTP Error */ "Not Logged In" = "Pas connecté"; /* OK */ "OK" = "OK"; /* WebDAV Uploading Error MobileMe File Uploading Error */ "Parent Folder does not exist" = "Le dossier Parent n'existe pas"; /* No comment provided by engineer. */ "Password was not accepted." = "Le mot de passe fut refusé."; /* filesize: petabytes */ "PB" = "Po"; /* mimic Finder naming conventions */ "Plain text document" = "Document sans format"; /* config error */ "Quit" = "Quitter"; /* tree controller action gear */ "Refresh" = "Actualiser"; /* file transcript */ "Renaming %@ to %@\n" = "Renommage de %1$@ en %2$@\n"; /* FTP Error */ "Request Aborted. Page Type Unknown" = "Demande interrompue. Type inconnu de page"; /* abbreviation for seconds, e.g. 12 MB/s */ "s" = "s"; /* category name */ "Saved Hosts" = "Hôtes enregistrées"; /* transfer controller */ "Show Files" = "Afficher les fichiers"; /* context menu tree controller action gear */ "Show Hidden Files" = "Afficher les fichiers masqués"; /* context menu tree controller action gear */ "Show Package Extensions" = "Afficher les extensions des paquets"; /* outline view column header name outline view column context menu item */ "Size" = "Taille"; /* close window */ "Stop Upload" = "Arrêter le chargement"; /* close window */ "Stop Upload?" = "Arrêter le chargement ?"; /* Error creating stream */ "Stream Unavailable" = "Flux indisponible"; /* filesize: terabytes */ "TB" = "To"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The body of the request is not supported" = "Le corps de la demande n'est pas supporté"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The directory already exists" = "Le répertoire existe déjà"; /* name of a host to connect to; in this case, the local file system rather than a remote server */ "the File System" = "le système de fichier"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The server does not allow the creation of directories at the current location" = "Le serveur ne permet pas la création de répertoires à l'emplacement actuel"; /* error transferring */ "The transferred file has a size of 0." = "Le fichier transféré a une taille de 0."; /* MobileMe Directory Contents Error */ "There is no MobileMe access to the directory" = "Aucun accès MobileMe au répertoire"; /* No WebDAV access to the specified path */ "There is no WebDAV access to the directory" = "Aucun accès WebDAV au répertoire"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "Il manque à cette gouttelette l'application originale qui l'a créée. Vous devrez réinstaller l'application originale."; /* time out */ "Timed Out waiting for remote host." = "Manque de temps pour l'hôte distant."; /* Transfer Controller */ "Too many files had transfer problems" = "Trop de fichiers ont eu des problèmes de transfert"; /* FileConnection failed to copy file */ "Unable to store data in file" = "Incapable d'enregistrer des données dans le fichier"; /* WebDAV Error */ "Unknown Error Occurred" = "Une erreur inconnue s'est produite"; /* status message */ "Uploading" = "Téléversement"; /* status */ "Uploading %@" = "Chargement de %@"; /* No username or password */ "Username and Password are required for FTP connections" = "Un nom d'utilisateur et un mot de passe sont exigés pour des connexions FTP"; /* No username or password */ "Username and Password are required for S3 connections" = "Le nom utilisateur et le mot de passe sont exigés pour les connexions S3"; /* No username or password */ "Username and Password are required for WebDAV connections" = "Un nom d'utilisateur et un mot de passe sont exigés pour des connexions WebDAV"; /* file transcript */ "Writing data to %@\n" = "Écriture des données sur %@\n"; /* Bonjour Error */ "You can not add a child collection to the Bonjour category." = "Vous ne pouvez pas ajouter une collection enfant à la catégorie Bonjour."; /* Bonjour Error */ "You can not add a new server to the Bonjour category." = "Vous ne pouvez pas ajouter un nouveau serveur à la catégorie Bonjour."; /* FTP file upload error */ "You do not have access to write file %@" = "Vous n'avez pas l'accès pour écrire le fichier %@"; /* FTP Error */ "You need an Account to Upload Files" = "Vous avez besoin d'un compte pour télécharger des fichiers"; ================================================ FILE: Example/hostkey.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" /* Needed for struct iovec on some platforms */ #ifdef HAVE_SYS_UIO_H #include #endif #if LIBSSH2_RSA /* *********** * ssh-rsa * *********** */ static int libssh2_hostkey_method_ssh_rsa_dtor(LIBSSH2_SESSION *session, void **abstract); /* {{{ libssh2_hostkey_method_ssh_rsa_init * Initialize the server hostkey working area with e/n pair */ static int libssh2_hostkey_method_ssh_rsa_init(LIBSSH2_SESSION *session, unsigned char *hostkey_data, unsigned long hostkey_data_len, void **abstract) { libssh2_rsa_ctx *rsactx; unsigned char *s, *e, *n; unsigned long len, e_len, n_len; (void)hostkey_data_len; if (*abstract) { libssh2_hostkey_method_ssh_rsa_dtor(session, abstract); *abstract = NULL; } s = hostkey_data; len = libssh2_ntohu32(s); s += 4; if (len != 7 || strncmp((char *)s, "ssh-rsa", 7) != 0) { return -1; } s += 7; e_len = libssh2_ntohu32(s); s += 4; e = s; s += e_len; n_len = libssh2_ntohu32(s); s += 4; n = s; s += n_len; if (_libssh2_rsa_new (&rsactx, e, e_len, n, n_len, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0)) return -1; *abstract = rsactx; return 0; } /* }}} */ /* {{{ libssh2_hostkey_method_ssh_rsa_initPEM * Load a Private Key from a PEM file */ static int libssh2_hostkey_method_ssh_rsa_initPEM(LIBSSH2_SESSION *session, const char *privkeyfile, unsigned const char *passphrase, void **abstract) { libssh2_rsa_ctx *rsactx; FILE *fp; int ret; if (*abstract) { libssh2_hostkey_method_ssh_rsa_dtor(session, abstract); *abstract = NULL; } fp = fopen(privkeyfile, "r"); if (!fp) { return -1; } ret = _libssh2_rsa_new_private (&rsactx, session, fp, passphrase); fclose(fp); if (ret) { return -1; } *abstract = rsactx; return 0; } /* }}} */ /* {{{ libssh2_hostkey_method_ssh_rsa_sign * Verify signature created by remote */ static int libssh2_hostkey_method_ssh_rsa_sig_verify(LIBSSH2_SESSION *session, const unsigned char *sig, unsigned long sig_len, const unsigned char *m, unsigned long m_len, void **abstract) { libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx*)(*abstract); (void)session; /* Skip past keyname_len(4) + keyname(7){"ssh-rsa"} + signature_len(4) */ sig += 15; sig_len -= 15; return _libssh2_rsa_sha1_verify (rsactx, sig, sig_len, m, m_len); } /* }}} */ /* {{{ libssh2_hostkey_method_ssh_rsa_signv * Construct a signature from an array of vectors */ static int libssh2_hostkey_method_ssh_rsa_signv(LIBSSH2_SESSION *session, unsigned char **signature, unsigned long *signature_len, unsigned long veccount, const struct iovec datavec[], void **abstract) { libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx*)(*abstract); int ret; unsigned int i; unsigned char hash[SHA_DIGEST_LENGTH]; libssh2_sha1_ctx ctx; libssh2_sha1_init(&ctx); for(i = 0; i < veccount; i++) { libssh2_sha1_update(ctx, datavec[i].iov_base, datavec[i].iov_len); } libssh2_sha1_final(ctx, hash); ret = _libssh2_rsa_sha1_sign(session, rsactx, hash, SHA_DIGEST_LENGTH, signature, signature_len); if (ret) { return -1; } return 0; } /* }}} */ /* {{{ libssh2_hostkey_method_ssh_rsa_dtor * Shutdown the hostkey */ static int libssh2_hostkey_method_ssh_rsa_dtor(LIBSSH2_SESSION *session, void **abstract) { libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx*)(*abstract); (void)session; _libssh2_rsa_free(rsactx); *abstract = NULL; return 0; } /* }}} */ static LIBSSH2_HOSTKEY_METHOD libssh2_hostkey_method_ssh_rsa = { "ssh-rsa", MD5_DIGEST_LENGTH, libssh2_hostkey_method_ssh_rsa_init, libssh2_hostkey_method_ssh_rsa_initPEM, libssh2_hostkey_method_ssh_rsa_sig_verify, libssh2_hostkey_method_ssh_rsa_signv, NULL, /* encrypt */ libssh2_hostkey_method_ssh_rsa_dtor, }; #endif /* LIBSSH2_RSA */ #if LIBSSH2_DSA /* *********** * ssh-dss * *********** */ static int libssh2_hostkey_method_ssh_dss_dtor(LIBSSH2_SESSION *session, void **abstract); /* {{{ libssh2_hostkey_method_ssh_dss_init * Initialize the server hostkey working area with p/q/g/y set */ static int libssh2_hostkey_method_ssh_dss_init(LIBSSH2_SESSION *session, unsigned char *hostkey_data, unsigned long hostkey_data_len, void **abstract) { libssh2_dsa_ctx *dsactx; unsigned char *p, *q, *g, *y, *s; unsigned long p_len, q_len, g_len, y_len, len; (void)hostkey_data_len; if (*abstract) { libssh2_hostkey_method_ssh_dss_dtor(session, abstract); *abstract = NULL; } s = hostkey_data; len = libssh2_ntohu32(s); s += 4; if (len != 7 || strncmp((char *)s, "ssh-dss", 7) != 0) { return -1; } s += 7; p_len = libssh2_ntohu32(s); s += 4; p = s; s += p_len; q_len = libssh2_ntohu32(s); s += 4; q = s; s += q_len; g_len = libssh2_ntohu32(s); s += 4; g = s; s += g_len; y_len = libssh2_ntohu32(s); s += 4; y = s; s += y_len; _libssh2_dsa_new(&dsactx, p, p_len, q, q_len, g, g_len, y, y_len, NULL, 0); *abstract = dsactx; return 0; } /* }}} */ /* {{{ libssh2_hostkey_method_ssh_dss_initPEM * Load a Private Key from a PEM file */ static int libssh2_hostkey_method_ssh_dss_initPEM(LIBSSH2_SESSION *session, const char *privkeyfile, unsigned const char *passphrase, void **abstract) { libssh2_dsa_ctx *dsactx; FILE *fp; int ret; if (*abstract) { libssh2_hostkey_method_ssh_dss_dtor(session, abstract); *abstract = NULL; } fp = fopen(privkeyfile, "r"); if (!fp) { return -1; } ret = _libssh2_dsa_new_private (&dsactx, session, fp, passphrase); fclose(fp); if (ret) { return -1; } *abstract = dsactx; return 0; } /* }}} */ /* {{{ libssh2_hostkey_method_ssh_dss_sign * Verify signature created by remote */ static int libssh2_hostkey_method_ssh_dss_sig_verify(LIBSSH2_SESSION *session, const unsigned char *sig, unsigned long sig_len, const unsigned char *m, unsigned long m_len, void **abstract) { libssh2_dsa_ctx *dsactx = (libssh2_dsa_ctx*)(*abstract); /* Skip past keyname_len(4) + keyname(7){"ssh-dss"} + signature_len(4) */ sig += 15; sig_len -= 15; if (sig_len != 40) { libssh2_error(session, LIBSSH2_ERROR_PROTO, "Invalid DSS signature length", 0); return -1; } return _libssh2_dsa_sha1_verify(dsactx, sig, m, m_len); } /* }}} */ /* {{{ libssh2_hostkey_method_ssh_dss_signv * Construct a signature from an array of vectors */ static int libssh2_hostkey_method_ssh_dss_signv(LIBSSH2_SESSION *session, unsigned char **signature, unsigned long *signature_len, unsigned long veccount, const struct iovec datavec[], void **abstract) { libssh2_dsa_ctx *dsactx = (libssh2_dsa_ctx*)(*abstract); unsigned char hash[SHA_DIGEST_LENGTH]; libssh2_sha1_ctx ctx; unsigned int i; *signature = LIBSSH2_ALLOC(session, 2 * SHA_DIGEST_LENGTH); *signature_len = 2 * SHA_DIGEST_LENGTH; memset(*signature, 0, 2 * SHA_DIGEST_LENGTH); if (!(*signature)) { return -1; } libssh2_sha1_init(&ctx); for(i = 0; i < veccount; i++) { libssh2_sha1_update(ctx, datavec[i].iov_base, datavec[i].iov_len); } libssh2_sha1_final(ctx, hash); if (_libssh2_dsa_sha1_sign(dsactx, hash, SHA_DIGEST_LENGTH, *signature)) { LIBSSH2_FREE(session, *signature); return -1; } return 0; } /* }}} */ /* {{{ libssh2_hostkey_method_ssh_dss_dtor * Shutdown the hostkey method */ static int libssh2_hostkey_method_ssh_dss_dtor(LIBSSH2_SESSION *session, void **abstract) { libssh2_dsa_ctx *dsactx = (libssh2_dsa_ctx*)(*abstract); (void)session; _libssh2_dsa_free(dsactx); *abstract = NULL; return 0; } /* }}} */ static LIBSSH2_HOSTKEY_METHOD libssh2_hostkey_method_ssh_dss = { "ssh-dss", MD5_DIGEST_LENGTH, libssh2_hostkey_method_ssh_dss_init, libssh2_hostkey_method_ssh_dss_initPEM, libssh2_hostkey_method_ssh_dss_sig_verify, libssh2_hostkey_method_ssh_dss_signv, NULL, /* encrypt */ libssh2_hostkey_method_ssh_dss_dtor, }; #endif /* LIBSSH2_DSA */ static LIBSSH2_HOSTKEY_METHOD *_libssh2_hostkey_methods[] = { #if LIBSSH2_RSA &libssh2_hostkey_method_ssh_rsa, #endif /* LIBSSH2_RSA */ #if LIBSSH2_DSA &libssh2_hostkey_method_ssh_dss, #endif /* LIBSSH2_DSA */ NULL }; LIBSSH2_HOSTKEY_METHOD **libssh2_hostkey_methods(void) { return _libssh2_hostkey_methods; } /* {{{ libssh2_hostkey_hash * Returns hash signature * Returned buffer should NOT be freed * Length of buffer is determined by hash type * i.e. MD5 == 16, SHA1 == 20 */ LIBSSH2_API const char *libssh2_hostkey_hash(LIBSSH2_SESSION *session, int hash_type) { switch (hash_type) { #if LIBSSH2_MD5 case LIBSSH2_HOSTKEY_HASH_MD5: return (char *)session->server_hostkey_md5; break; #endif /* LIBSSH2_MD5 */ case LIBSSH2_HOSTKEY_HASH_SHA1: return (char *)session->server_hostkey_sha1; break; default: return NULL; } } /* }}} */ ================================================ FILE: Example/it.lproj/DropletLauncher.nib/classes.nib ================================================ { IBClasses = ( {CLASS = DropletLauncherDelegate; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/it.lproj/DropletLauncher.nib/info.nib ================================================ IBDocumentLocation 415 56 356 240 0 0 1280 778 IBEditorPositions 29 69 259 338 44 0 0 1280 778 IBFramework Version 437.0 IBSystem Version 8P135 IBUsesTextArchiving ================================================ FILE: Example/it.lproj/DropletLauncher.nib/keyedobjects.nib ================================================ $archiver NSKeyedArchiver $objects $null $class CF$UID 387 NSAccessibilityConnectors CF$UID 384 NSAccessibilityOidsKeys CF$UID 385 NSAccessibilityOidsValues CF$UID 386 NSClassesKeys CF$UID 279 NSClassesValues CF$UID 280 NSConnections CF$UID 9 NSFontManager CF$UID 0 NSFramework CF$UID 6 NSNamesKeys CF$UID 255 NSNamesValues CF$UID 256 NSNextOid 250 NSObjectsKeys CF$UID 176 NSObjectsValues CF$UID 254 NSOidsKeys CF$UID 281 NSOidsValues CF$UID 282 NSRoot CF$UID 2 NSVisibleWindows CF$UID 7 $class CF$UID 5 NSClassName CF$UID 3 $class CF$UID 4 NS.string NSApplication $classes NSMutableString NSString NSObject $classname NSMutableString $classes NSCustomObject NSObject $classname NSCustomObject $class CF$UID 4 NS.string IBCocoaFramework $class CF$UID 8 NS.objects $classes NSMutableSet NSSet NSObject $classname NSMutableSet $class CF$UID 175 NS.objects CF$UID 10 CF$UID 24 CF$UID 29 CF$UID 35 CF$UID 40 CF$UID 46 CF$UID 51 CF$UID 57 CF$UID 61 CF$UID 66 CF$UID 70 CF$UID 74 CF$UID 79 CF$UID 84 CF$UID 90 CF$UID 95 CF$UID 100 CF$UID 105 CF$UID 110 CF$UID 115 CF$UID 120 CF$UID 125 CF$UID 130 CF$UID 134 CF$UID 138 CF$UID 142 CF$UID 148 CF$UID 152 CF$UID 156 CF$UID 160 CF$UID 165 CF$UID 170 $class CF$UID 23 NSLabel CF$UID 22 NSSource CF$UID 11 $class CF$UID 21 NSKeyEquiv CF$UID 14 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 13 $class CF$UID 189 NSMenuItems CF$UID 187 NSName CF$UID 188 NSTitle CF$UID 185 Contrai m $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 17 NSImage NSMenuCheckmark $classes NSCustomResource NSObject $classname NSCustomResource $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 20 NSMenuMixedState $classes NSMenuItem NSObject $classname NSMenuItem $class CF$UID 4 NS.string performMiniaturize: $classes NSNibControlConnector NSNibConnector NSObject $classname NSNibControlConnector $class CF$UID 23 NSLabel CF$UID 28 NSSource CF$UID 25 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 26 Porta tutto in primo piano $class CF$UID 4 NS.string arrangeInFront: $class CF$UID 23 NSLabel CF$UID 34 NSSource CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 33 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 32 $class CF$UID 189 NSMenuItems CF$UID 192 NSTitle CF$UID 191 Stampa… p $class CF$UID 4 NS.string print: $class CF$UID 23 NSLabel CF$UID 39 NSSource CF$UID 36 $class CF$UID 21 NSKeyEquiv CF$UID 38 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 37 Formato di stampa… P $class CF$UID 4 NS.string runPageLayout: $class CF$UID 23 NSLabel CF$UID 45 NSSource CF$UID 41 $class CF$UID 21 NSKeyEquiv CF$UID 44 NSKeyEquivModMask 1048576 NSMenu CF$UID 42 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 43 $class CF$UID 189 NSMenuItems CF$UID 251 NSTitle CF$UID 249 Aiuto NewApplication ? $class CF$UID 4 NS.string showHelp: $class CF$UID 23 NSLabel CF$UID 50 NSSource CF$UID 47 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 48 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 49 $class CF$UID 189 NSMenuItems CF$UID 202 NSName CF$UID 203 NSTitle CF$UID 200 Cancella menu $class CF$UID 4 NS.string clearRecentDocuments: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 56 NSSource CF$UID 52 $class CF$UID 21 NSKeyEquiv CF$UID 55 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 54 $class CF$UID 189 NSMenuItems CF$UID 218 NSName CF$UID 228 NSTitle CF$UID 216 Esci da NewApplication q $class CF$UID 4 NS.string terminate: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 60 NSSource CF$UID 58 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 59 Info su NewApplication $class CF$UID 4 NS.string orderFrontStandardAboutPanel: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 65 NSSource CF$UID 62 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1572864 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 63 Nascondi altre h hideOtherApplications: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 69 NSSource CF$UID 67 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 68 Nascondi NewApplication hide: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 73 NSSource CF$UID 71 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 72 Mostra tutte unhideAllApplications: $class CF$UID 23 NSLabel CF$UID 78 NSSource CF$UID 75 $class CF$UID 21 NSKeyEquiv CF$UID 77 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 76 Chiudi w performClose: $class CF$UID 23 NSLabel CF$UID 83 NSSource CF$UID 80 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 82 $class CF$UID 189 NSMenuItems CF$UID 243 NSTitle CF$UID 241 Controlla ortografia mentre scrivo toggleContinuousSpellChecking: $class CF$UID 23 NSLabel CF$UID 89 NSSource CF$UID 85 $class CF$UID 21 NSKeyEquiv CF$UID 88 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 87 $class CF$UID 189 NSMenuItems CF$UID 234 NSTitle CF$UID 232 Annulla z $class CF$UID 4 NS.string undo: $class CF$UID 23 NSLabel CF$UID 94 NSSource CF$UID 91 $class CF$UID 21 NSKeyEquiv CF$UID 93 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 92 Copia c $class CF$UID 4 NS.string copy: $class CF$UID 23 NSLabel CF$UID 99 NSSource CF$UID 96 $class CF$UID 21 NSKeyEquiv CF$UID 98 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 97 Controlla ortografia ; $class CF$UID 4 NS.string checkSpelling: $class CF$UID 23 NSLabel CF$UID 104 NSSource CF$UID 101 $class CF$UID 21 NSKeyEquiv CF$UID 103 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 102 Incolla v $class CF$UID 4 NS.string paste: $class CF$UID 23 NSLabel CF$UID 109 NSSource CF$UID 106 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 108 $class CF$UID 189 NSMenuItems CF$UID 247 NSTitle CF$UID 245 Interrompi riproduzione stopSpeaking: $class CF$UID 23 NSLabel CF$UID 114 NSSource CF$UID 111 $class CF$UID 21 NSKeyEquiv CF$UID 113 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 112 Taglia x $class CF$UID 4 NS.string cut: $class CF$UID 23 NSLabel CF$UID 119 NSSource CF$UID 116 $class CF$UID 21 NSKeyEquiv CF$UID 118 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 117 Ortografia… : $class CF$UID 4 NS.string showGuessPanel: $class CF$UID 23 NSLabel CF$UID 124 NSSource CF$UID 121 $class CF$UID 21 NSKeyEquiv CF$UID 123 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 122 Ripristina Z $class CF$UID 4 NS.string redo: $class CF$UID 23 NSLabel CF$UID 129 NSSource CF$UID 126 $class CF$UID 21 NSKeyEquiv CF$UID 128 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 127 Seleziona tutto a $class CF$UID 4 NS.string selectAll: $class CF$UID 23 NSLabel CF$UID 133 NSSource CF$UID 131 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 132 Inizia riproduzione startSpeaking: $class CF$UID 23 NSLabel CF$UID 137 NSSource CF$UID 135 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 136 Cancella delete: $class CF$UID 23 NSLabel CF$UID 141 NSSource CF$UID 139 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 140 Ridimensiona performZoom: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 143 $class CF$UID 21 NSKeyEquiv CF$UID 146 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 1 NSTitle CF$UID 145 $class CF$UID 189 NSMenuItems CF$UID 239 NSTitle CF$UID 237 Cerca… f performFindPanelAction: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 149 $class CF$UID 21 NSKeyEquiv CF$UID 151 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 2 NSTitle CF$UID 150 Cerca successivo g $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 153 $class CF$UID 21 NSKeyEquiv CF$UID 155 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 3 NSTitle CF$UID 154 Cerca precedente G $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 157 $class CF$UID 21 NSKeyEquiv CF$UID 159 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 7 NSTitle CF$UID 158 Usa selezione per cercare e $class CF$UID 23 NSLabel CF$UID 164 NSSource CF$UID 161 $class CF$UID 21 NSKeyEquiv CF$UID 163 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 162 Vai fino alla selezione j centerSelectionInVisibleArea: $class CF$UID 23 NSLabel CF$UID 169 NSSource CF$UID 166 $class CF$UID 21 NSKeyEquiv CF$UID 168 NSKeyEquivModMask 1572864 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 167 Incolla e mantieni lo stile V pasteAsPlainText: $class CF$UID 174 NSDestination CF$UID 171 NSLabel CF$UID 173 NSSource CF$UID 2 $class CF$UID 5 NSClassName CF$UID 172 DropletLauncherDelegate delegate $classes NSNibOutletConnector NSNibConnector NSObject $classname NSNibOutletConnector $classes NSMutableArray NSArray NSObject $classname NSMutableArray $class CF$UID 253 NS.objects CF$UID 177 CF$UID 47 CF$UID 178 CF$UID 171 CF$UID 36 CF$UID 179 CF$UID 67 CF$UID 182 CF$UID 183 CF$UID 161 CF$UID 143 CF$UID 190 CF$UID 101 CF$UID 25 CF$UID 131 CF$UID 121 CF$UID 11 CF$UID 62 CF$UID 80 CF$UID 31 CF$UID 210 CF$UID 204 CF$UID 184 CF$UID 229 CF$UID 106 CF$UID 12 CF$UID 81 CF$UID 193 CF$UID 149 CF$UID 236 CF$UID 166 CF$UID 235 CF$UID 196 CF$UID 71 CF$UID 139 CF$UID 219 CF$UID 207 CF$UID 41 CF$UID 215 CF$UID 53 CF$UID 116 CF$UID 248 CF$UID 244 CF$UID 153 CF$UID 144 CF$UID 135 CF$UID 111 CF$UID 107 CF$UID 199 CF$UID 227 CF$UID 42 CF$UID 96 CF$UID 240 CF$UID 220 CF$UID 209 CF$UID 75 CF$UID 231 CF$UID 226 CF$UID 52 CF$UID 86 CF$UID 58 CF$UID 157 CF$UID 126 CF$UID 91 CF$UID 85 CF$UID 48 CF$UID 30 CF$UID 223 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSKeyEquiv CF$UID 181 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 180 Registra s $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 186 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 184 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 12 NSTitle CF$UID 185 $class CF$UID 189 NSMenuItems CF$UID 214 NSName CF$UID 252 NSTitle CF$UID 213 Finestra submenuAction: $class CF$UID 175 NS.objects CF$UID 11 CF$UID 139 CF$UID 178 CF$UID 25 _NSWindowsMenu $classes NSMenu NSObject $classname NSMenu $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 Archivio $class CF$UID 175 NS.objects CF$UID 193 CF$UID 196 CF$UID 199 CF$UID 182 CF$UID 75 CF$UID 179 CF$UID 204 CF$UID 207 CF$UID 209 CF$UID 36 CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 195 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 194 Nuovo n $class CF$UID 21 NSKeyEquiv CF$UID 198 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 197 Apri… o $class CF$UID 21 NSAction CF$UID 201 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 48 NSTitle CF$UID 200 Apri recente submenuAction: $class CF$UID 175 NS.objects CF$UID 47 _NSRecentDocumentsMenu $class CF$UID 21 NSKeyEquiv CF$UID 206 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 205 Registra col nome… S $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 208 Ripristina $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSKeyEquiv CF$UID 212 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 211 Preferenze… , DropletLauncher $class CF$UID 175 NS.objects CF$UID 215 CF$UID 229 CF$UID 231 CF$UID 183 CF$UID 248 $class CF$UID 21 NSAction CF$UID 217 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 184 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 53 NSTitle CF$UID 216 NewApplication submenuAction: $class CF$UID 175 NS.objects CF$UID 58 CF$UID 177 CF$UID 210 CF$UID 219 CF$UID 220 CF$UID 226 CF$UID 67 CF$UID 62 CF$UID 71 CF$UID 227 CF$UID 52 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 222 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 223 NSTitle CF$UID 221 Servizi submenuAction: $class CF$UID 189 NSMenuItems CF$UID 224 NSName CF$UID 225 NSTitle CF$UID 221 $class CF$UID 175 NS.objects _NSServicesMenu $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSAppleMenu $class CF$UID 21 NSAction CF$UID 230 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 184 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 31 NSTitle CF$UID 191 submenuAction: $class CF$UID 21 NSAction CF$UID 233 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 184 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 86 NSTitle CF$UID 232 Composizione submenuAction: $class CF$UID 175 NS.objects CF$UID 85 CF$UID 121 CF$UID 235 CF$UID 111 CF$UID 91 CF$UID 101 CF$UID 166 CF$UID 135 CF$UID 126 CF$UID 190 CF$UID 236 CF$UID 240 CF$UID 244 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 238 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 144 NSTitle CF$UID 237 Cerca submenuAction: $class CF$UID 175 NS.objects CF$UID 143 CF$UID 149 CF$UID 153 CF$UID 157 CF$UID 161 $class CF$UID 21 NSAction CF$UID 242 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 81 NSTitle CF$UID 241 Ortografia submenuAction: $class CF$UID 175 NS.objects CF$UID 116 CF$UID 96 CF$UID 80 $class CF$UID 21 NSAction CF$UID 246 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 107 NSTitle CF$UID 245 Voce submenuAction: $class CF$UID 175 NS.objects CF$UID 131 CF$UID 106 $class CF$UID 21 NSAction CF$UID 250 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 184 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 42 NSTitle CF$UID 249 Aiuto submenuAction: $class CF$UID 175 NS.objects CF$UID 41 _NSMainMenu $classes NSArray NSObject $classname NSArray $class CF$UID 253 NS.objects CF$UID 53 CF$UID 48 CF$UID 12 CF$UID 2 CF$UID 31 CF$UID 31 CF$UID 53 CF$UID 31 CF$UID 184 CF$UID 144 CF$UID 144 CF$UID 86 CF$UID 86 CF$UID 12 CF$UID 107 CF$UID 86 CF$UID 12 CF$UID 53 CF$UID 81 CF$UID 229 CF$UID 53 CF$UID 31 CF$UID 2 CF$UID 184 CF$UID 107 CF$UID 183 CF$UID 240 CF$UID 31 CF$UID 144 CF$UID 86 CF$UID 86 CF$UID 86 CF$UID 31 CF$UID 53 CF$UID 12 CF$UID 53 CF$UID 31 CF$UID 42 CF$UID 184 CF$UID 215 CF$UID 81 CF$UID 184 CF$UID 86 CF$UID 144 CF$UID 236 CF$UID 86 CF$UID 86 CF$UID 244 CF$UID 31 CF$UID 53 CF$UID 248 CF$UID 81 CF$UID 86 CF$UID 53 CF$UID 31 CF$UID 31 CF$UID 184 CF$UID 53 CF$UID 53 CF$UID 231 CF$UID 53 CF$UID 144 CF$UID 86 CF$UID 86 CF$UID 86 CF$UID 199 CF$UID 31 CF$UID 220 $class CF$UID 253 NS.objects CF$UID 182 CF$UID 240 CF$UID 41 CF$UID 171 CF$UID 204 CF$UID 36 CF$UID 229 CF$UID 96 CF$UID 31 CF$UID 184 CF$UID 42 CF$UID 179 CF$UID 12 CF$UID 248 CF$UID 80 CF$UID 196 CF$UID 209 CF$UID 30 CF$UID 178 CF$UID 52 CF$UID 75 CF$UID 116 CF$UID 2 CF$UID 193 CF$UID 207 CF$UID 210 CF$UID 81 $class CF$UID 253 NS.objects CF$UID 257 CF$UID 258 CF$UID 259 CF$UID 172 CF$UID 260 CF$UID 261 CF$UID 259 CF$UID 258 CF$UID 259 CF$UID 262 CF$UID 263 CF$UID 264 CF$UID 265 CF$UID 267 CF$UID 268 CF$UID 259 CF$UID 269 CF$UID 270 CF$UID 259 CF$UID 271 CF$UID 272 CF$UID 273 CF$UID 274 CF$UID 275 CF$UID 276 CF$UID 277 CF$UID 278 $class CF$UID 4 NS.string 7 NSMenuItem $class CF$UID 4 NS.string $class CF$UID 4 NS.string 8 $class CF$UID 4 NS.string 5 $class CF$UID 4 NS.string MainMenu $class CF$UID 4 NS.string 2 $class CF$UID 4 NS.string 3 $class CF$UID 266 $classes NSNull %NSNull NSObject $classname NSNull $class CF$UID 4 NS.string 1 NSMenuItem2 $class CF$UID 4 NS.string 2 $class CF$UID 4 NS.string 6 1111 $class CF$UID 4 NS.string 1 NSMenuItem1 $class CF$UID 4 NS.string File's Owner $class CF$UID 4 NS.string 9 $class CF$UID 4 NS.string 10 121 NSMenu $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects CF$UID 199 CF$UID 100 CF$UID 11 CF$UID 62 CF$UID 121 CF$UID 161 CF$UID 177 CF$UID 226 CF$UID 41 CF$UID 240 CF$UID 53 CF$UID 61 CF$UID 130 CF$UID 244 CF$UID 236 CF$UID 106 CF$UID 81 CF$UID 178 CF$UID 75 CF$UID 227 CF$UID 111 CF$UID 134 CF$UID 219 CF$UID 90 CF$UID 2 CF$UID 183 CF$UID 139 CF$UID 138 CF$UID 24 CF$UID 86 CF$UID 204 CF$UID 120 CF$UID 101 CF$UID 223 CF$UID 29 CF$UID 170 CF$UID 142 CF$UID 184 CF$UID 149 CF$UID 46 CF$UID 30 CF$UID 42 CF$UID 84 CF$UID 209 CF$UID 105 CF$UID 135 CF$UID 116 CF$UID 196 CF$UID 229 CF$UID 66 CF$UID 85 CF$UID 157 CF$UID 110 CF$UID 215 CF$UID 148 CF$UID 70 CF$UID 190 CF$UID 51 CF$UID 171 CF$UID 48 CF$UID 152 CF$UID 71 CF$UID 95 CF$UID 235 CF$UID 210 CF$UID 74 CF$UID 182 CF$UID 52 CF$UID 35 CF$UID 144 CF$UID 10 CF$UID 12 CF$UID 25 CF$UID 125 CF$UID 79 CF$UID 47 CF$UID 179 CF$UID 67 CF$UID 91 CF$UID 58 CF$UID 220 CF$UID 96 CF$UID 80 CF$UID 131 CF$UID 143 CF$UID 248 CF$UID 160 CF$UID 207 CF$UID 57 CF$UID 166 CF$UID 193 CF$UID 40 CF$UID 115 CF$UID 156 CF$UID 165 CF$UID 107 CF$UID 153 CF$UID 31 CF$UID 36 CF$UID 231 CF$UID 126 $class CF$UID 253 NS.objects CF$UID 283 CF$UID 284 CF$UID 285 CF$UID 286 CF$UID 287 CF$UID 288 CF$UID 289 CF$UID 290 CF$UID 291 CF$UID 292 CF$UID 293 CF$UID 294 CF$UID 295 CF$UID 296 CF$UID 297 CF$UID 298 CF$UID 299 CF$UID 300 CF$UID 301 CF$UID 302 CF$UID 303 CF$UID 304 CF$UID 305 CF$UID 306 CF$UID 307 CF$UID 308 CF$UID 309 CF$UID 310 CF$UID 311 CF$UID 312 CF$UID 313 CF$UID 314 CF$UID 315 CF$UID 316 CF$UID 317 CF$UID 318 CF$UID 319 CF$UID 320 CF$UID 321 CF$UID 322 CF$UID 323 CF$UID 324 CF$UID 325 CF$UID 326 CF$UID 327 CF$UID 328 CF$UID 329 CF$UID 330 CF$UID 331 CF$UID 332 CF$UID 333 CF$UID 334 CF$UID 335 CF$UID 336 CF$UID 337 CF$UID 338 CF$UID 339 CF$UID 340 CF$UID 341 CF$UID 342 CF$UID 343 CF$UID 344 CF$UID 345 CF$UID 346 CF$UID 347 CF$UID 348 CF$UID 349 CF$UID 350 CF$UID 351 CF$UID 352 CF$UID 353 CF$UID 354 CF$UID 355 CF$UID 356 CF$UID 357 CF$UID 358 CF$UID 359 CF$UID 360 CF$UID 361 CF$UID 362 CF$UID 363 CF$UID 364 CF$UID 365 CF$UID 366 CF$UID 367 CF$UID 368 CF$UID 369 CF$UID 370 CF$UID 371 CF$UID 372 CF$UID 373 CF$UID 374 CF$UID 375 CF$UID 376 CF$UID 377 CF$UID 378 CF$UID 379 CF$UID 380 CF$UID 381 CF$UID 382 CF$UID 383 124 226 23 145 215 210 236 144 111 216 57 146 233 211 218 195 200 92 73 149 199 235 143 224 1 19 239 240 39 205 80 231 203 130 86 249 241 29 208 127 78 106 223 74 227 202 204 72 83 152 207 221 228 56 242 153 214 139 248 125 243 150 225 206 129 193 79 136 87 220 37 24 5 232 222 126 75 134 197 58 131 201 219 196 209 103 245 112 142 246 82 122 230 244 247 212 213 81 77 217 198 $class CF$UID 175 NS.objects $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects $classes NSIBObjectData NSObject $classname NSIBObjectData $top IB.objectdata CF$UID 1 $version 100000 ================================================ FILE: Example/it.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Example/it.lproj/Localizable.strings ================================================ /* ssh host key changed */ "%@'s fingerprint does not match the one on record. If the server has changed its key, then this is to be expected. If not then you should check with the system administrator before proceeding.\nThe new fingerprint is %@.\nDo you wish to connect to the server?" = "La fingerprint di %1$@ non corrisponde a quella in archivio. Se il server ha cambiato la sua chiave questo è un comportamento normale. Diversamente, dovresti fare un controllo con l'amministratore del sistema prima di continuare.\n La nuova fingerprint è %2$@.\nVuoi collegarti al server?"; /* authorise */ "%@\nWhat would you like to do?" = "%@\nChe cosa ti piacerebbe fare?"; /* ConnectionOpenPanel */ "A Connection Error Occurred" = "Si è verificato un errore di connessione"; /* FTP Abort */ "Action Aborted. Local Error" = "Azione interrotta. Errore locale"; /* FTP no data stream types available */ "All data connection modes have been exhausted. Check with the server administrator." = "Tutte le possibili modalità di connessione sono state provate. Fai un controllo con l'amministratore del server."; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "An intermediate directory does not exist and needs to be created before the current directory" = "Non esiste una cartella intermedia e deve essere creata prima della cartella attuale."; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "An unknown error occurred" = "Si è verificato un errore sconosciuto"; /* multiple connection joiner */ "and" = "e"; /* close window */ "Are you sure you want to stop the upload?" = "Sei sicuro di voler interrompere il caricamento?"; /* failed pk authentication for ssh */ "Authentication by Public Key Failed" = "L'autenticazione con Chiave Pubblica è fallita"; /* failed authentication for ssh */ "Authentication Failed. Do you have permission to access this server via SFTP?" = "Autenticazione fallita. Sei sicuro di avere i permessi per accedere al server via SFTP?"; /* authorise */ "Authorize" = "Autorizza"; /* authorise */ "Authorize Connection?" = "Autorizzi la connessione?"; /* connection type */ "auto" = "auto"; /* config */ "Bad Configuration" = "Configurazione errata"; /* error */ "Bad Droplet" = "Droplet errato"; /* sftp last error */ "Bad Message" = "Messaggio errato"; /* Transfer Controller */ "Bad Password." = "Password errata."; /* filesize: bytes */ "bytes" = "bytes"; /* authorise */ "Cancel" = "Annulla"; /* FTP upload error */ "Cannot Upload File. Storage quota on server exceeded" = "Non posso caricare il file. Lo spazio assegnato sul server è esaurito"; /* transfer controller connected message */ "Connected to %@" = "Connesso a %@"; /* file transcript */ "Connected to File System\n" = "Connesso al File System\n"; /* connection string */ "Connecting to %@" = "Connessione a %@"; /* file transcript */ "Connecting…\n" = "Connessione…\n"; /* sftp last error */ "Connection Lost" = "Connessione perduta"; /* close window */ "Continue Upload" = "Continua caricamento"; /* file transcript */ "Copying %@ to %@\n" = "Sto copiando %1$@ di %2$@\n"; /* FileConnection set permissions error */ "Could not change file permissions" = "Non posso cambiare i permessi dei files"; /* FileConnection create directory error */ "Could not create directory" = "Non posso creare la cartella"; /* config */ "Couldn't find configuration file specified\n %@" = "Impossibile trovare il file di configurazione specificato\n %@"; /* file transcript */ "Create Directory %@ (%lo)\n" = "Crea cartella %1$@ (%2$lo)\n"; /* FTP Create directory error */ "Create directory operation failed" = "Operazione di creazione cartella fallita"; /* Failed to open a data stream connection */ "Data Stream Timed Out" = "Tempo scaduto per il flusso di dati"; /* file transcript */ "Deleting File %@\n" = "Cancellazione file %@\n"; /* sftp last error */ "Directory not empty" = "Cartella non vuota"; /* status */ "Disconnected" = "Disconnesso"; /* transfer controller */ "Disconnected from %@" = "Disconnesso da %@"; /* filesize: exabytes */ "EB" = "EB"; /* sftp last error */ "End of File" = "Fine del file"; /* sftp last error */ "Error INVAL" = "Errore INVAL"; /* Directory Parsing Error */ "Error parsing directory listing" = "Errore nell'analizzare l'elenco della cartella"; /* FTP no data streams available */ "Exhausted all connection types to server. Please contact server administrator" = "Tutte le possibili modalità di connessione sono state provate. Contatta l'amministratore del server."; /* Bad ftp command */ "Failed to change to directory" = "Impossibile cambiare la cartella"; /* sftp failure to create dir */ "Failed to create directory" = "Impossibile creare la cartella"; /* couldn't delete the file MobileMe Directory Deletion Error WebDAV Directory Deletion Error */ "Failed to delete directory" = "Impossibile cancellare la cartella"; /* error for deleting a directory */ "Failed to delete directory: %@" = "Impossibile cancellare la cartella: %@"; /* couldn't delete the file MobileMe file deletion error WebDAV File Deletion Error */ "Failed to delete file" = "Impossibile cancellare il file"; /* error for deleting a file */ "Failed to delete file: %@" = "Impossibile cancellare il file: %@"; /* failed to find the id_dsa.pub */ "Failed to find public key in %@. If you have a custom named key, please set the User Default key SSHPublicKey." = "Impossibile trovare una Chiave Pubblica in %@. Se hai una chiave nominale personale, imposta la chiave utente di default SSHPublicKey."; /* failed authentication for ssh */ "Failed to initialize SFTP Subsystem. Do you have permission to access this server via SFTP?" = "Impossibile inizializzare il sottosistema SFTP. Hai i permessi necessari per accedere al server via SFTP?"; /* No MobileMe account or password */ "Failed to retrieve MobileMe account details" = "Impossibile recuperare le informazioni dell'utenza MobileMe"; /* FileConnection copy data error */ "Failed to upload data" = "Impossibile caricare dati"; /* error transferring */ "Failed to verify file transferred successfully" = "Impossibile verificare l'esito positivo del trasferimento"; /* sftp error */ "Failed to write all data" = "Impossibile verificare tutti i dati"; /* sftp last error */ "Failure" = "Fallito"; /* FTP file download error */ "File %@ does not exist on server" = "Il file %@ non esiste sul server"; /* FTP error */ "File / Directory does not exist" = "Il file / Cartella non esiste"; /* FileConnection error */ "File Already Exists" = "Il file esiste già "; /* sftp last error */ "File already exists" = "Il file esiste già"; /* FTP file in use */ "File in Use" = "Il file è in uso"; /* FTP Upload error */ "Filename not Allowed" = "Il nome file non è consentito"; /* FTP service timed out */ "FTP service not available; Remote server has closed connection" = "Il servizio FTP non è disponibile; il server remoto ha chiuso la connessione"; /* FTP no service */ "FTP Service Unavailable" = "Il servizio FTP non è disponibile"; /* filesize: gigabytes */ "GB" = "GB"; /* transfer controller */ "Hide Files" = "Nascondi files"; /* Couldn't open the port to the host */ "Host Unavailable" = "L'Host non è disponibile"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "Insufficient storage space available" = "Lo spazio su disco non è sufficiente"; /* FTP Error */ "Invalid Account name" = "Il nome account non è valido"; /* sftp last error */ "Invalid filename" = "Il nome file non è valido"; /* sftp last error */ "Invalid Handle" = "L'handle non è valido"; /* Stream Error before opening */ "Is the service running on the server" = "Il servizio è attivo sul server"; /* filesize: kilobytes */ "Kilobytes" = "Kilobytes"; /* sftp last error */ "Link Loop" = "Collegameno ricorsivo"; /* FTP download error */ "Local File already exists" = "Il file locale esiste già"; /* sftp last error */ "Lock Conflict" = "Conflitto di Lock"; /* filesize: megabytes */ "MB" = "MB"; /* sftp last error */ "Method not Supported" = "Metodo non supportato"; /* new cat name */ "New Category" = "Nuova categoria"; /* config */ "No configuration file specified" = "Non è stato specificato nessun file di configurazione"; /* sftp last error */ "No Connection" = "Nessuna connessione"; /* failed to find a connection class */ "No connection available for requested connection type" = "Nessuna connessione è disponibile per il tipo di connessione richiesto"; /* failed to find a connection class */ "No connection available for requested port" = "Nessuna connessione è disponibile per la porta richiesta"; /* failed to find a connection class */ "No connection available for requested protocol" = "Nessuna connessione è disponibile per il protocollo richiesto"; /* sftp last error */ "No Media" = "Nessun media"; /* sftp last error */ "No space left on remote machine" = "Non c'è altro spazio disponibile sulla macchina remota"; /* FTP Error */ "No Storage Space Available" = "Non c'è più spazio disponibile"; /* sftp last error */ "No such file" = "Nessun file"; /* sftp last error */ "No such path" = "Nessun percorso"; /* sftp last error */ "Not a directory" = "Non è una cartella"; /* FTP Error */ "Not Logged In" = "Noi sei connesso"; /* sftp last error OK */ "OK" = "OK"; /* sftp last error */ "Operation Unsupported" = "L'operazione non è supportata"; /* WebDAV Uploading Error MobileMe File Uploading Error */ "Parent Folder does not exist" = "La cartella superiore non esiste"; /* No comment provided by engineer. */ "Password was not accepted." = "La password non è stata accettata."; /* filesize: petabytes */ "PB" = "PB"; /* sftp last error */ "Permission Denied" = "Permesso negato"; /* config error */ "Quit" = "Esci"; /* sftp last error */ "Quota exceeded" = "Quota superata"; /* file transcript */ "Renaming %@ to %@\n" = "Restanti %1$@ di %2$@\n"; /* FTP Error */ "Request Aborted. Page Type Unknown" = "La richiesta è stata interrotta. Tipo di pagina sconosciuto"; /* sftp last error */ "Request Denied" = "Richiesta negata"; /* abbreviation for seconds, e.g. 12 MB/s */ "s" = "sec"; /* category name */ "Saved Hosts" = "Host registrati"; /* No comment provided by engineer. */ "Secure Shell Connection Error" = "Errore di connessione Secure Shell"; /* transfer controller */ "Show Files" = "Mostra files"; /* close window */ "Stop Upload" = "Ferma caricamento"; /* close window */ "Stop Upload?" = "Interrompere caricamento?"; /* Error creating stream */ "Stream Unavailable" = "Stream non disponibile"; /* filesize: terabytes */ "TB" = "TB"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "The body of the request is not supported" = "Il corpo della richiesta non è supportato"; /* sftp bad directory */ "The directory (%@) does not exist" = "La cartella (%@) non esiste"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "The directory already exists" = "La cartella esiste già"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "The server does not allow the creation of directories at the current location" = "Il server non permette la creazione di directory nella posizione attuale"; /* No comment provided by engineer. */ "The SSH manager returned the following error message:\n\n%@" = "Il manager SSH ha riportato il seguente messaggio di errore:\n\n%@"; /* error transferring */ "The transferred file has a size of 0." = "Il file trasferito ha dimensione uguale a 0."; /* MobileMe Directory Contents Error */ "There is no MobileMe access to the directory" = "Non c'è un accesso MobileMe alla cartella"; /* No WebDAV access to the specified path */ "There is no WebDAV access to the directory" = "Non c'è un accesso WebDAV alla directory"; /* sftp error */ "There was an error with the SSH subsystem: " = "Si è verificato un errore con il sottosistema SSH:"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "Il droplet ha perduto la connessione con l'applicazione che l'ha creato. Dovrai reinstallare l'applicazione originale."; /* ssh authorise remote host fingerprint */ "This is the first time connecting to %@. It has a fingerprint of \n%@. Do you wish to connect?" = "Questa è la prima volta che ti colleghi a %1$@, che ha una fingerprint di \n%2$@. Vuoi connetterti?"; /* time out */ "Timed Out waiting for remote host." = "E' scaduto il tempo di attesa per l'host remoto."; /* Transfer Controller */ "Too many files had transfer problems" = "Problemi dovuti allo scaricamento di troppi files"; /* FileConnection failed to copy file */ "Unable to store data in file" = "Non posso salvare i dati nel file"; /* WebDAV Error */ "Unknown Error Occurred" = "Si è verificato un errore sconosciuto"; /* sftp last error */ "Unknown Principle" = "Principio sconosciuto"; /* status message */ "Uploading" = "Sto caricando"; /* status */ "Uploading %@" = "Sto caricando %@"; /* No username or password */ "Username and Password are required for FTP connections" = "Nome Utente e Password sono necessarie per le connessioni FTP"; /* No username or password */ "Username and Password are required for S3 connections" = "Nome Utente e Password sono necessarie per le connessioni S3"; /* No username or password */ "Username and Password are required for WebDAV connections" = "Nome Utente e Password sono necessarie per le connessioni WebDAV"; /* No username or password */ "Username is required for SFTP connections" = "Il Nome Utente è necessario per le connessioni SFTP"; /* sftp last error */ "Write Protected" = "Protetto da scrittura"; /* file transcript */ "Writing data to %@\n" = "Sto scrivendo i dati in %@\n"; /* Bonjour Error */ "You can not add a child category to the Bonjour category." = "Non puoi aggiungere una categoria secondaria alla categoria di Bonjour."; /* Bonjour Error */ "You can not add a new host to the Bonjour category." = "Non puoi aggiungere un nuovo host alla categoria di Bonjour."; /* FTP file upload error */ "You do not have access to write file %@" = "Non hai l'accesso per scrivere il file %@"; /* FTP Error */ "You need an Account to Upload Files" = "Hai bisogno di un account per caricare files"; ================================================ FILE: Example/ja.lproj/DropletLauncher.nib/classes.nib ================================================ { IBClasses = ( {CLASS = DropletLauncherDelegate; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/ja.lproj/DropletLauncher.nib/info.nib ================================================ IBDocumentLocation 415 56 356 240 0 0 1280 778 IBEditorPositions 29 69 259 338 44 0 0 1280 778 IBFramework Version 446.1 IBSystem Version 8P2137 IBUsesTextArchiving ================================================ FILE: Example/ja.lproj/DropletLauncher.nib/keyedobjects.nib ================================================ $archiver NSKeyedArchiver $objects $null $class CF$UID 387 NSAccessibilityConnectors CF$UID 384 NSAccessibilityOidsKeys CF$UID 385 NSAccessibilityOidsValues CF$UID 386 NSClassesKeys CF$UID 279 NSClassesValues CF$UID 280 NSConnections CF$UID 9 NSFontManager CF$UID 0 NSFramework CF$UID 6 NSNamesKeys CF$UID 255 NSNamesValues CF$UID 256 NSNextOid 250 NSObjectsKeys CF$UID 176 NSObjectsValues CF$UID 254 NSOidsKeys CF$UID 281 NSOidsValues CF$UID 282 NSRoot CF$UID 2 NSVisibleWindows CF$UID 7 $class CF$UID 5 NSClassName CF$UID 3 $class CF$UID 4 NS.string NSApplication $classes NSMutableString NSString NSObject $classname NSMutableString $classes NSCustomObject NSObject $classname NSCustomObject $class CF$UID 4 NS.string IBCocoaFramework $class CF$UID 8 NS.objects $classes NSMutableSet NSSet NSObject $classname NSMutableSet $class CF$UID 175 NS.objects CF$UID 10 CF$UID 24 CF$UID 29 CF$UID 35 CF$UID 40 CF$UID 46 CF$UID 51 CF$UID 57 CF$UID 61 CF$UID 66 CF$UID 70 CF$UID 74 CF$UID 79 CF$UID 84 CF$UID 90 CF$UID 95 CF$UID 100 CF$UID 105 CF$UID 110 CF$UID 115 CF$UID 120 CF$UID 125 CF$UID 130 CF$UID 134 CF$UID 138 CF$UID 142 CF$UID 148 CF$UID 152 CF$UID 156 CF$UID 160 CF$UID 165 CF$UID 170 $class CF$UID 23 NSLabel CF$UID 22 NSSource CF$UID 11 $class CF$UID 21 NSKeyEquiv CF$UID 14 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 13 $class CF$UID 181 NSMenuItems CF$UID 183 NSName CF$UID 185 NSTitle CF$UID 182 しまう m $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 17 NSImage NSMenuCheckmark $classes NSCustomResource NSObject $classname NSCustomResource $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 20 NSMenuMixedState $classes NSMenuItem NSObject $classname NSMenuItem $class CF$UID 4 NS.string performMiniaturize: $classes NSNibControlConnector NSNibConnector NSObject $classname NSNibControlConnector $class CF$UID 23 NSLabel CF$UID 28 NSSource CF$UID 25 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 26 すべてを手前に移動 $class CF$UID 4 NS.string arrangeInFront: $class CF$UID 23 NSLabel CF$UID 34 NSSource CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 33 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 32 $class CF$UID 181 NSMenuItems CF$UID 242 NSTitle CF$UID 240 プリント… p $class CF$UID 4 NS.string print: $class CF$UID 23 NSLabel CF$UID 39 NSSource CF$UID 36 $class CF$UID 21 NSKeyEquiv CF$UID 38 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 37 ページ設定… P $class CF$UID 4 NS.string runPageLayout: $class CF$UID 23 NSLabel CF$UID 45 NSSource CF$UID 41 $class CF$UID 21 NSKeyEquiv CF$UID 44 NSKeyEquivModMask 1048576 NSMenu CF$UID 42 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 43 $class CF$UID 181 NSMenuItems CF$UID 230 NSTitle CF$UID 229 新規アプリケーションヘルプ ? $class CF$UID 4 NS.string showHelp: $class CF$UID 23 NSLabel CF$UID 50 NSSource CF$UID 47 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 48 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 49 $class CF$UID 181 NSMenuItems CF$UID 179 NSName CF$UID 180 NSTitle CF$UID 178 メニューを消去 $class CF$UID 4 NS.string clearRecentDocuments: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 56 NSSource CF$UID 52 $class CF$UID 21 NSKeyEquiv CF$UID 55 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 54 $class CF$UID 181 NSMenuItems CF$UID 217 NSName CF$UID 228 NSTitle CF$UID 215 新規アプリケーションを終了 q $class CF$UID 4 NS.string terminate: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 60 NSSource CF$UID 58 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 59 新規アプリケーションについて $class CF$UID 4 NS.string orderFrontStandardAboutPanel: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 65 NSSource CF$UID 62 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1572864 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 63 ほかを隠す h hideOtherApplications: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 69 NSSource CF$UID 67 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 68 新規アプリケーションを隠す hide: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 73 NSSource CF$UID 71 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 72 すべてを表示 unhideAllApplications: $class CF$UID 23 NSLabel CF$UID 78 NSSource CF$UID 75 $class CF$UID 21 NSKeyEquiv CF$UID 77 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 76 閉じる w performClose: $class CF$UID 23 NSLabel CF$UID 83 NSSource CF$UID 80 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 82 $class CF$UID 181 NSMenuItems CF$UID 203 NSTitle CF$UID 201 入力中に自動スペルチェック toggleContinuousSpellChecking: $class CF$UID 23 NSLabel CF$UID 89 NSSource CF$UID 85 $class CF$UID 21 NSKeyEquiv CF$UID 88 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 87 $class CF$UID 181 NSMenuItems CF$UID 193 NSTitle CF$UID 192 取り消し z $class CF$UID 4 NS.string undo: $class CF$UID 23 NSLabel CF$UID 94 NSSource CF$UID 91 $class CF$UID 21 NSKeyEquiv CF$UID 93 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 92 コピー c $class CF$UID 4 NS.string copy: $class CF$UID 23 NSLabel CF$UID 99 NSSource CF$UID 96 $class CF$UID 21 NSKeyEquiv CF$UID 98 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 97 スペルチェック ; $class CF$UID 4 NS.string checkSpelling: $class CF$UID 23 NSLabel CF$UID 104 NSSource CF$UID 101 $class CF$UID 21 NSKeyEquiv CF$UID 103 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 102 ペースト v $class CF$UID 4 NS.string paste: $class CF$UID 23 NSLabel CF$UID 109 NSSource CF$UID 106 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 108 $class CF$UID 181 NSMenuItems CF$UID 207 NSTitle CF$UID 205 読み上げ中止 stopSpeaking: $class CF$UID 23 NSLabel CF$UID 114 NSSource CF$UID 111 $class CF$UID 21 NSKeyEquiv CF$UID 113 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 112 カット x $class CF$UID 4 NS.string cut: $class CF$UID 23 NSLabel CF$UID 119 NSSource CF$UID 116 $class CF$UID 21 NSKeyEquiv CF$UID 118 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 117 スペル… : $class CF$UID 4 NS.string showGuessPanel: $class CF$UID 23 NSLabel CF$UID 124 NSSource CF$UID 121 $class CF$UID 21 NSKeyEquiv CF$UID 123 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 122 やり直し Z $class CF$UID 4 NS.string redo: $class CF$UID 23 NSLabel CF$UID 129 NSSource CF$UID 126 $class CF$UID 21 NSKeyEquiv CF$UID 128 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 127 すべてを選択 a $class CF$UID 4 NS.string selectAll: $class CF$UID 23 NSLabel CF$UID 133 NSSource CF$UID 131 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 132 読み上げ開始 startSpeaking: $class CF$UID 23 NSLabel CF$UID 137 NSSource CF$UID 135 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 136 削除 delete: $class CF$UID 23 NSLabel CF$UID 141 NSSource CF$UID 139 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 140 拡大/縮小 performZoom: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 143 $class CF$UID 21 NSKeyEquiv CF$UID 146 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 1 NSTitle CF$UID 145 $class CF$UID 181 NSMenuItems CF$UID 199 NSTitle CF$UID 197 検索… f performFindPanelAction: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 149 $class CF$UID 21 NSKeyEquiv CF$UID 151 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 2 NSTitle CF$UID 150 次を検索 g $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 153 $class CF$UID 21 NSKeyEquiv CF$UID 155 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 3 NSTitle CF$UID 154 前を検索 G $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 157 $class CF$UID 21 NSKeyEquiv CF$UID 159 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 7 NSTitle CF$UID 158 選択部分で検索 e $class CF$UID 23 NSLabel CF$UID 164 NSSource CF$UID 161 $class CF$UID 21 NSKeyEquiv CF$UID 163 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 162 選択部分へジャンプ j centerSelectionInVisibleArea: $class CF$UID 23 NSLabel CF$UID 169 NSSource CF$UID 166 $class CF$UID 21 NSKeyEquiv CF$UID 168 NSKeyEquivModMask 1572864 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 167 スタイルを合わせてペースト V pasteAsPlainText: $class CF$UID 174 NSDestination CF$UID 171 NSLabel CF$UID 173 NSSource CF$UID 2 $class CF$UID 5 NSClassName CF$UID 172 DropletLauncherDelegate delegate $classes NSNibOutletConnector NSNibConnector NSObject $classname NSNibOutletConnector $classes NSMutableArray NSArray NSObject $classname NSMutableArray $class CF$UID 253 NS.objects CF$UID 106 CF$UID 177 CF$UID 67 CF$UID 48 CF$UID 171 CF$UID 75 CF$UID 12 CF$UID 153 CF$UID 85 CF$UID 186 CF$UID 116 CF$UID 91 CF$UID 126 CF$UID 96 CF$UID 189 CF$UID 36 CF$UID 184 CF$UID 86 CF$UID 62 CF$UID 208 CF$UID 41 CF$UID 157 CF$UID 204 CF$UID 211 CF$UID 213 CF$UID 214 CF$UID 195 CF$UID 42 CF$UID 25 CF$UID 219 CF$UID 121 CF$UID 52 CF$UID 139 CF$UID 30 CF$UID 231 CF$UID 101 CF$UID 234 CF$UID 161 CF$UID 143 CF$UID 47 CF$UID 80 CF$UID 71 CF$UID 194 CF$UID 187 CF$UID 196 CF$UID 166 CF$UID 131 CF$UID 220 CF$UID 53 CF$UID 31 CF$UID 248 CF$UID 227 CF$UID 81 CF$UID 149 CF$UID 246 CF$UID 58 CF$UID 200 CF$UID 250 CF$UID 111 CF$UID 226 CF$UID 11 CF$UID 144 CF$UID 135 CF$UID 223 CF$UID 218 CF$UID 239 CF$UID 243 CF$UID 107 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 最近使った項目を開く $class CF$UID 175 NS.objects CF$UID 47 _NSRecentDocumentsMenu $classes NSMenu NSObject $classname NSMenu ウインドウ $class CF$UID 175 NS.objects CF$UID 11 CF$UID 139 CF$UID 184 CF$UID 25 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSWindowsMenu $class CF$UID 21 NSAction CF$UID 188 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 187 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 12 NSTitle CF$UID 182 $class CF$UID 181 NSMenuItems CF$UID 238 NSName CF$UID 252 NSTitle CF$UID 237 submenuAction: $class CF$UID 21 NSKeyEquiv CF$UID 191 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 190 環境設定… , 編集 $class CF$UID 175 NS.objects CF$UID 85 CF$UID 121 CF$UID 194 CF$UID 111 CF$UID 91 CF$UID 101 CF$UID 166 CF$UID 135 CF$UID 126 CF$UID 195 CF$UID 196 CF$UID 200 CF$UID 204 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 198 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 144 NSTitle CF$UID 197 検索 submenuAction: $class CF$UID 175 NS.objects CF$UID 143 CF$UID 149 CF$UID 153 CF$UID 157 CF$UID 161 $class CF$UID 21 NSAction CF$UID 202 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 81 NSTitle CF$UID 201 スペル submenuAction: $class CF$UID 175 NS.objects CF$UID 116 CF$UID 96 CF$UID 80 $class CF$UID 21 NSAction CF$UID 206 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 107 NSTitle CF$UID 205 読み上げ submenuAction: $class CF$UID 175 NS.objects CF$UID 131 CF$UID 106 $class CF$UID 21 NSKeyEquiv CF$UID 210 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 209 保存 s $class CF$UID 21 NSAction CF$UID 212 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 187 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 86 NSTitle CF$UID 192 submenuAction: $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 216 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 187 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 53 NSTitle CF$UID 215 新規アプリケーション submenuAction: $class CF$UID 175 NS.objects CF$UID 58 CF$UID 218 CF$UID 189 CF$UID 219 CF$UID 220 CF$UID 226 CF$UID 67 CF$UID 62 CF$UID 71 CF$UID 227 CF$UID 52 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 222 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 223 NSTitle CF$UID 221 サービス submenuAction: $class CF$UID 181 NSMenuItems CF$UID 224 NSName CF$UID 225 NSTitle CF$UID 221 $class CF$UID 175 NS.objects _NSServicesMenu $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSAppleMenu ヘルプ $class CF$UID 175 NS.objects CF$UID 41 $class CF$UID 21 NSKeyEquiv CF$UID 233 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 232 別名で保存… S $class CF$UID 21 NSKeyEquiv CF$UID 236 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 235 開く… o ドロップレットランチャー $class CF$UID 175 NS.objects CF$UID 214 CF$UID 239 CF$UID 211 CF$UID 186 CF$UID 250 $class CF$UID 21 NSAction CF$UID 241 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 187 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 31 NSTitle CF$UID 240 ファイル submenuAction: $class CF$UID 175 NS.objects CF$UID 243 CF$UID 234 CF$UID 246 CF$UID 213 CF$UID 75 CF$UID 208 CF$UID 231 CF$UID 248 CF$UID 177 CF$UID 36 CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 245 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 244 新しいファイル n $class CF$UID 21 NSAction CF$UID 247 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 48 NSTitle CF$UID 178 submenuAction: $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 249 元に戻す $class CF$UID 21 NSAction CF$UID 251 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 187 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 42 NSTitle CF$UID 229 submenuAction: _NSMainMenu $classes NSArray NSObject $classname NSArray $class CF$UID 253 NS.objects CF$UID 107 CF$UID 31 CF$UID 53 CF$UID 246 CF$UID 2 CF$UID 31 CF$UID 186 CF$UID 144 CF$UID 86 CF$UID 187 CF$UID 81 CF$UID 86 CF$UID 86 CF$UID 81 CF$UID 53 CF$UID 31 CF$UID 12 CF$UID 211 CF$UID 53 CF$UID 31 CF$UID 42 CF$UID 144 CF$UID 86 CF$UID 187 CF$UID 31 CF$UID 187 CF$UID 86 CF$UID 250 CF$UID 12 CF$UID 53 CF$UID 86 CF$UID 53 CF$UID 12 CF$UID 31 CF$UID 31 CF$UID 86 CF$UID 31 CF$UID 144 CF$UID 144 CF$UID 48 CF$UID 81 CF$UID 53 CF$UID 86 CF$UID 2 CF$UID 86 CF$UID 86 CF$UID 107 CF$UID 53 CF$UID 214 CF$UID 239 CF$UID 31 CF$UID 53 CF$UID 200 CF$UID 144 CF$UID 31 CF$UID 53 CF$UID 86 CF$UID 187 CF$UID 86 CF$UID 53 CF$UID 12 CF$UID 196 CF$UID 86 CF$UID 220 CF$UID 53 CF$UID 187 CF$UID 31 CF$UID 204 $class CF$UID 253 NS.objects CF$UID 171 CF$UID 12 CF$UID 42 CF$UID 31 CF$UID 81 CF$UID 75 CF$UID 248 CF$UID 96 CF$UID 234 CF$UID 200 CF$UID 213 CF$UID 2 CF$UID 80 CF$UID 250 CF$UID 52 CF$UID 30 CF$UID 189 CF$UID 231 CF$UID 36 CF$UID 243 CF$UID 41 CF$UID 116 CF$UID 184 CF$UID 187 CF$UID 208 CF$UID 177 CF$UID 239 $class CF$UID 253 NS.objects CF$UID 172 CF$UID 257 CF$UID 259 CF$UID 260 CF$UID 261 CF$UID 262 CF$UID 263 CF$UID 264 CF$UID 260 CF$UID 264 CF$UID 265 CF$UID 266 CF$UID 267 CF$UID 268 CF$UID 269 CF$UID 270 CF$UID 271 CF$UID 272 CF$UID 273 CF$UID 274 CF$UID 260 CF$UID 275 CF$UID 260 CF$UID 276 CF$UID 277 CF$UID 278 CF$UID 260 $class CF$UID 258 $classes NSNull %NSNull NSObject $classname NSNull $class CF$UID 4 NS.string 2 $class CF$UID 4 NS.string NSMenu $class CF$UID 4 NS.string 1 $class CF$UID 4 NS.string 10 NSMenuItem $class CF$UID 4 NS.string 7 $class CF$UID 4 NS.string File's Owner NSMenuItem2 $class CF$UID 4 NS.string 1 1111 $class CF$UID 4 NS.string 6 121 $class CF$UID 4 NS.string 8 $class CF$UID 4 NS.string 5 $class CF$UID 4 NS.string 9 NSMenuItem1 $class CF$UID 4 NS.string MainMenu $class CF$UID 4 NS.string 3 $class CF$UID 4 NS.string 2 $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects CF$UID 61 CF$UID 48 CF$UID 177 CF$UID 218 CF$UID 131 CF$UID 161 CF$UID 115 CF$UID 213 CF$UID 66 CF$UID 106 CF$UID 135 CF$UID 31 CF$UID 200 CF$UID 194 CF$UID 148 CF$UID 70 CF$UID 219 CF$UID 204 CF$UID 214 CF$UID 100 CF$UID 46 CF$UID 12 CF$UID 47 CF$UID 101 CF$UID 152 CF$UID 195 CF$UID 74 CF$UID 62 CF$UID 130 CF$UID 250 CF$UID 223 CF$UID 91 CF$UID 187 CF$UID 57 CF$UID 156 CF$UID 79 CF$UID 208 CF$UID 86 CF$UID 134 CF$UID 171 CF$UID 144 CF$UID 226 CF$UID 90 CF$UID 227 CF$UID 149 CF$UID 160 CF$UID 80 CF$UID 138 CF$UID 116 CF$UID 107 CF$UID 246 CF$UID 248 CF$UID 120 CF$UID 139 CF$UID 165 CF$UID 157 CF$UID 142 CF$UID 11 CF$UID 35 CF$UID 166 CF$UID 30 CF$UID 25 CF$UID 40 CF$UID 105 CF$UID 36 CF$UID 189 CF$UID 75 CF$UID 243 CF$UID 10 CF$UID 41 CF$UID 110 CF$UID 126 CF$UID 67 CF$UID 84 CF$UID 58 CF$UID 111 CF$UID 2 CF$UID 220 CF$UID 186 CF$UID 96 CF$UID 239 CF$UID 95 CF$UID 85 CF$UID 196 CF$UID 143 CF$UID 71 CF$UID 184 CF$UID 211 CF$UID 121 CF$UID 125 CF$UID 170 CF$UID 51 CF$UID 234 CF$UID 231 CF$UID 52 CF$UID 153 CF$UID 24 CF$UID 81 CF$UID 42 CF$UID 53 CF$UID 29 $class CF$UID 253 NS.objects CF$UID 283 CF$UID 284 CF$UID 285 CF$UID 286 CF$UID 287 CF$UID 288 CF$UID 289 CF$UID 290 CF$UID 291 CF$UID 292 CF$UID 293 CF$UID 294 CF$UID 295 CF$UID 296 CF$UID 297 CF$UID 298 CF$UID 299 CF$UID 300 CF$UID 301 CF$UID 302 CF$UID 303 CF$UID 304 CF$UID 305 CF$UID 306 CF$UID 307 CF$UID 308 CF$UID 309 CF$UID 310 CF$UID 311 CF$UID 312 CF$UID 313 CF$UID 314 CF$UID 315 CF$UID 316 CF$UID 317 CF$UID 318 CF$UID 319 CF$UID 320 CF$UID 321 CF$UID 322 CF$UID 323 CF$UID 324 CF$UID 325 CF$UID 326 CF$UID 327 CF$UID 328 CF$UID 329 CF$UID 330 CF$UID 331 CF$UID 332 CF$UID 333 CF$UID 334 CF$UID 335 CF$UID 336 CF$UID 337 CF$UID 338 CF$UID 339 CF$UID 340 CF$UID 341 CF$UID 342 CF$UID 343 CF$UID 344 CF$UID 345 CF$UID 346 CF$UID 347 CF$UID 348 CF$UID 349 CF$UID 350 CF$UID 351 CF$UID 352 CF$UID 353 CF$UID 354 CF$UID 355 CF$UID 356 CF$UID 357 CF$UID 358 CF$UID 359 CF$UID 360 CF$UID 361 CF$UID 362 CF$UID 363 CF$UID 364 CF$UID 365 CF$UID 366 CF$UID 367 CF$UID 368 CF$UID 369 CF$UID 370 CF$UID 371 CF$UID 372 CF$UID 373 CF$UID 374 CF$UID 375 CF$UID 376 CF$UID 377 CF$UID 378 CF$UID 379 CF$UID 380 CF$UID 381 CF$UID 382 CF$UID 383 146 125 74 236 196 210 230 79 152 195 202 81 216 206 242 153 143 211 56 226 127 24 126 203 243 214 193 145 233 103 130 197 29 142 244 222 75 205 235 248 220 144 224 149 208 245 219 240 204 212 124 112 231 239 247 221 241 23 87 246 78 5 122 227 77 129 73 82 37 111 228 198 134 223 58 199 1 131 19 201 83 225 207 218 209 150 92 217 215 232 249 139 72 80 136 213 39 200 106 57 86 $class CF$UID 175 NS.objects $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects $classes NSIBObjectData NSObject $classname NSIBObjectData $top IB.objectdata CF$UID 1 $version 100000 ================================================ FILE: Example/ja.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Example/ja.lproj/Localizable.strings ================================================ /* ssh host key changed */ "%@'s fingerprint does not match the one on record. If the server has changed its key, then this is to be expected. If not then you should check with the system administrator before proceeding.\nThe new fingerprint is %@.\nDo you wish to connect to the server?" = "%1$@ の公開鍵が記録されているものと照合しません。サーバー側で鍵を変更した場合は、このようなエラーが起こります。もしそうでない場合は、まずシステム管理者に確認してください。\n新しい公開鍵は %2$@ です。\nサーバーに接続しますか?"; /* authorise */ "%@\nWhat would you like to do?" = "%@\nどうしますか?"; /* ConnectionOpenPanel */ "A Connection Error Occurred" = "接続エラーが起こりました"; /* FTP Abort */ "Action Aborted. Local Error" = "操作が取り消されました。ローカルエラー"; /* FTP no data stream types available */ "All data connection modes have been exhausted. Check with the server administrator." = "あらゆる接続モードでの接続に失敗しました。サーバ管理者にお問い合わせください。"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "An intermediate directory does not exist and needs to be created before the current directory" = "現在のディレクトリの上位にもう一つのディレクトリを作成する必要があります。"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "An unknown error occurred" = "予期せぬエラーが起こりました"; /* multiple connection joiner */ "and" = "と"; /* close window */ "Are you sure you want to stop the upload?" = "アップロードを中断してもよろしいですか?"; /* failed pk authentication for ssh */ "Authentication by Public Key Failed" = "公開鍵での認証に失敗しました"; /* failed authentication for ssh */ "Authentication Failed. Do you have permission to access this server via SFTP?" = "認証に失敗しました。このサーバに SFTP 接続する権限を持っていますか?"; /* authorise */ "Authorize" = "認証"; /* authorise */ "Authorize Connection?" = "接続認証しますか?"; /* connection type */ "auto" = "自動"; /* config */ "Bad Configuration" = "設定が悪い"; /* error */ "Bad Droplet" = "ドロップレットが悪い"; /* sftp last error */ "Bad Message" = "間違ったメッセージ"; /* Transfer Controller */ "Bad Password." = "パスワードが間違い"; /* filesize: bytes */ "bytes" = "バイト"; /* authorise */ "Cancel" = "キャンセル"; /* FTP upload error */ "Cannot Upload File. Storage quota on server exceeded" = "ファイルをアップロードできません。サーバの保存容量を超過しています。"; /* transfer controller connected message */ "Connected to %@" = "%@ に接続"; /* file transcript */ "Connected to File System\n" = "ファイルシステム\nに接続中"; /* connection string */ "Connecting to %@" = "%@ に接続中"; /* file transcript */ "Connecting…\n" = "n\に接続中…"; /* sftp last error */ "Connection Lost" = "接続が途絶えました"; /* close window */ "Continue Upload" = "アップロードを継続"; /* file transcript */ "Copying %@ to %@\n" = "%1$@ から %2$@へコピー\n"; /* FileConnection set permissions error */ "Could not change file permissions" = "ファイルのアクセス権を変更できませんでした"; /* FileConnection create directory error */ "Could not create directory" = "ディレクトリを作成できませんでした"; /* config */ "Couldn't find configuration file specified\n %@" = "指定した設定ファイルが見つかりませんでした\n %@"; /* file transcript */ "Create Directory %@ (%lo)\n" = "ディレクトリを作成 %1$@ (%2$lo)\n"; /* FTP Create directory error */ "Create directory operation failed" = "ディレクトリ作成の操作に失敗しました"; /* Failed to open a data stream connection */ "Data Stream Timed Out" = "データストリームのタイムアウト"; /* file transcript */ "Deleting File %@\n" = "ファイル削除 %@\n"; /* sftp last error */ "Directory not empty" = "ディレクトリが空ではありません"; /* status */ "Disconnected" = "切断"; /* transfer controller */ "Disconnected from %@" = "%@ から切断"; /* filesize: exabytes */ "EB" = "EB"; /* sftp last error */ "End of File" = "ファイルの終わり"; /* sftp last error */ "Error INVAL" = "INVAL エラー"; /* Directory Parsing Error */ "Error parsing directory listing" = "ディレクトリリストの解析エラー"; /* FTP no data streams available */ "Exhausted all connection types to server. Please contact server administrator" = "すべてのサーバ接続タイプでの接続に失敗しました。サーバ管理者に連絡してください。"; /* Bad ftp command */ "Failed to change to directory" = "ディレクトリへの移動に失敗しました"; /* sftp failure to create dir */ "Failed to create directory" = "ディレクトリの作成に失敗しました"; /* couldn't delete the file MobileMe Directory Deletion Error WebDAV Directory Deletion Error */ "Failed to delete directory" = "ディレクトリの削除に失敗しました"; /* error for deleting a directory */ "Failed to delete directory: %@" = "ディレクトリ削除に失敗:%@"; /* couldn't delete the file MobileMe file deletion error WebDAV File Deletion Error */ "Failed to delete file" = "ファイルの削除に失敗しました"; /* error for deleting a file */ "Failed to delete file: %@" = "ファイル削除に失敗:%@"; /* failed to find the id_dsa.pub */ "Failed to find public key in %@. If you have a custom named key, please set the User Default key SSHPublicKey." = "%@ で公開鍵を見つけることができませんでした。カスタマイズされた鍵をお持ちの場合は、ユーザデフォルト鍵を SSHPublicKey に設定してください。"; /* failed authentication for ssh */ "Failed to initialize SFTP Subsystem. Do you have permission to access this server via SFTP?" = "SFTP のサブシステムの初期化に失敗しました。このサーバへの SFTP でのアクセス権限を持っていますか?"; /* No MobileMe account or password */ "Failed to retrieve MobileMe account details" = "MobileMe アカウント詳細の取得に失敗しました"; /* FileConnection copy data error */ "Failed to upload data" = "データアップロードに失敗"; /* error transferring */ "Failed to verify file transferred successfully" = "ファイル転送成功の確認に失敗"; /* sftp error */ "Failed to write all data" = "データを全て書き込みに失敗"; /* sftp last error */ "Failure" = "失敗"; /* FTP file download error */ "File %@ does not exist on server" = "ファイル %@ はサーバに存在しません"; /* FTP error */ "File / Directory does not exist" = "ファイル/ディレクトリーがありません"; /* FileConnection error */ "File Already Exists" = "ファイルは既に存在します"; /* sftp last error */ "File already exists" = "ファイルはすでに存在します"; /* FTP file in use */ "File in Use" = "ファイルが使用中です"; /* FTP Upload error */ "Filename not Allowed" = "このファイル名は使用できません"; /* FTP service timed out */ "FTP service not available; Remote server has closed connection" = "FTP サービスがありません; リモートサーバーは接続を切断しました。"; /* FTP no service */ "FTP Service Unavailable" = "FTP サービスが使用できません"; /* filesize: gigabytes */ "GB" = "GB"; /* transfer controller */ "Hide Files" = "ファイルを隠す"; /* Couldn't open the port to the host */ "Host Unavailable" = "ホストが利用できません"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "Insufficient storage space available" = "保存スペースが十分ではありません"; /* FTP Error */ "Invalid Account name" = "アカウント名が無効です"; /* sftp last error */ "Invalid filename" = "ファイル名が無効です"; /* sftp last error */ "Invalid Handle" = "ハンドルが無効です"; /* Stream Error before opening */ "Is the service running on the server" = "サーバーは稼働していますか?"; /* filesize: kilobytes */ "Kilobytes" = "キロバイト"; /* sftp last error */ "Link Loop" = "リンクループ"; /* FTP download error */ "Local File already exists" = "このローカルファイルはすでに存在します"; /* sftp last error */ "Lock Conflict" = "ロックコンフリクト"; /* filesize: megabytes */ "MB" = "MB"; /* sftp last error */ "Method not Supported" = "サポートされていないメソッド"; /* new cat name */ "New Category" = "新規カテゴリー"; /* config */ "No configuration file specified" = "設定ファイルが指定されていません"; /* sftp last error */ "No Connection" = "コネクションがありません"; /* failed to find a connection class */ "No connection available for requested connection type" = "この接続タイプは有効ではありません"; /* failed to find a connection class */ "No connection available for requested port" = "このポートでの接続は有効ではありません"; /* failed to find a connection class */ "No connection available for requested protocol" = "このプロトコルでの接続は有効ではありません"; /* sftp last error */ "No Media" = "メディアがありません"; /* sftp last error */ "No space left on remote machine" = "リモートマシンにスペースがありません"; /* FTP Error */ "No Storage Space Available" = "保存スペースがありません"; /* sftp last error */ "No such file" = "そのようなファイルはありません"; /* sftp last error */ "No such path" = "そのようなパスはありません"; /* sftp last error */ "Not a directory" = "ディレクトリがありません"; /* FTP Error */ "Not Logged In" = "ログインしていません"; /* sftp last error OK */ "OK" = "OK"; /* sftp last error */ "Operation Unsupported" = "この動作は未サポートです"; /* WebDAV Uploading Error MobileMe File Uploading Error */ "Parent Folder does not exist" = "上位フォルダが存在しません"; /* No comment provided by engineer. */ "Password was not accepted." = "パスワードが承認されませんでした"; /* filesize: petabytes */ "PB" = "PB"; /* sftp last error */ "Permission Denied" = "認証失敗"; /* config error */ "Quit" = "終了"; /* sftp last error */ "Quota exceeded" = "割り当て超過"; /* file transcript */ "Renaming %@ to %@\n" = "%1$@ を %2$@\n に改名"; /* FTP Error */ "Request Aborted. Page Type Unknown" = "要求は中止されました。不明のページタイプ"; /* sftp last error */ "Request Denied" = "リクエスト却下"; /* abbreviation for seconds, e.g. 12 MB/s */ "s" = "s"; /* category name */ "Saved Hosts" = "保存されたホスト"; /* No comment provided by engineer. */ "Secure Shell Connection Error" = "セキュアシェル接続エラー"; /* transfer controller */ "Show Files" = "ファイルを表示"; /* close window */ "Stop Upload" = "アップロードを停止"; /* close window */ "Stop Upload?" = "アップロードを停止しますか?"; /* Error creating stream */ "Stream Unavailable" = "ストリームは利用できません"; /* filesize: terabytes */ "TB" = "TB"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "The body of the request is not supported" = "リクエストの本体はサポートされていません。"; /* sftp bad directory */ "The directory (%@) does not exist" = "ディレクトリ (%@) は存在しません"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "The directory already exists" = "このディレクトリはすでに存在します"; /* WebDAV Create Directory Error MobileMe Create Directory Error */ "The server does not allow the creation of directories at the current location" = "サーバはこの場所への新規ディレクトリの作成を許可していません"; /* No comment provided by engineer. */ "The SSH manager returned the following error message:\n\n%@" = "SSH マネージャーが以下のエラーを出しました:\n\n%@"; /* error transferring */ "The transferred file has a size of 0." = "転送されたファイルのサイズが0となっています"; /* MobileMe Directory Contents Error */ "There is no MobileMe access to the directory" = "このディレクトリへの MobileMe 接続はできません"; /* No WebDAV access to the specified path */ "There is no WebDAV access to the directory" = "このディレクトリへの WebDAV 接続はできません"; /* sftp error */ "There was an error with the SSH subsystem: " = "SSH サブシステムにエラーがあります:"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "ドロップレットを作成したときのアプリケーションがありません。そのアプリケーションを再インストールする必要があります。"; /* ssh authorise remote host fingerprint */ "This is the first time connecting to %@. It has a fingerprint of \n%@. Do you wish to connect?" = "%1$@ に接続するのは今回が初めてです。次の公開鍵が設定されています:\n%2$@. 接続しますか?"; /* time out */ "Timed Out waiting for remote host." = "リモートホストの待ち時間がタイムアウトしました。"; /* Transfer Controller */ "Too many files had transfer problems" = "ファイル転送エラーが多すぎました。"; /* FileConnection failed to copy file */ "Unable to store data in file" = "データをファイルに保存できません"; /* WebDAV Error */ "Unknown Error Occurred" = "予期せぬエラーが起こりました"; /* sftp last error */ "Unknown Principle" = "Principle 不明"; /* status message */ "Uploading" = "アップロード"; /* status */ "Uploading %@" = "%@ をアップロード中"; /* No username or password */ "Username and Password are required for FTP connections" = "FTP ではユーザ名とパスワードが必要です"; /* No username or password */ "Username and Password are required for S3 connections" = "S3 接続にはユーザー名とパスワードが必要です"; /* No username or password */ "Username and Password are required for WebDAV connections" = "WebDAV ではユーザ名とパスワードが必要です"; /* No username or password */ "Username is required for SFTP connections" = "SFTP 接続ではユーザ名が必要です"; /* sftp last error */ "Write Protected" = "書き込み不可"; /* file transcript */ "Writing data to %@\n" = "%@\nにデータを書き込み中"; /* Bonjour Error */ "You can not add a child category to the Bonjour category." = "Bonjour カテゴリ内にサブカテゴリを加えることはできません。"; /* Bonjour Error */ "You can not add a new host to the Bonjour category." = "Bonjour カテゴリに新しいホストを追加できます。"; /* FTP file upload error */ "You do not have access to write file %@" = "%@ というファイルへのアクセス権がありません"; /* FTP Error */ "You need an Account to Upload Files" = "アップロードをするためにアカウントが必要です"; ================================================ FILE: Example/kex.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" /* TODO: Switch this to an inline and handle alloc() failures */ /* Helper macro called from libssh2_kex_method_diffie_hellman_group1_sha1_key_exchange */ #define LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(value, reqlen, version) \ { \ libssh2_sha1_ctx hash; \ unsigned long len = 0; \ if (!(value)) { \ value = LIBSSH2_ALLOC(session, reqlen + SHA_DIGEST_LENGTH); \ } \ while (len < reqlen) { \ libssh2_sha1_init(&hash); \ libssh2_sha1_update(hash, k_value, k_value_len); \ libssh2_sha1_update(hash, h_sig_comp, SHA_DIGEST_LENGTH); \ if (len > 0) { \ libssh2_sha1_update(hash, value, len); \ } else { \ libssh2_sha1_update(hash, (version), 1); \ libssh2_sha1_update(hash, session->session_id, session->session_id_len); \ } \ libssh2_sha1_final(hash, (value) + len); \ len += SHA_DIGEST_LENGTH; \ } \ } /* {{{ libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange * Diffie Hellman Key Exchange, Group Agnostic */ static int libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange(LIBSSH2_SESSION *session, _libssh2_bn *g, _libssh2_bn *p, int group_order, unsigned char packet_type_init, unsigned char packet_type_reply, unsigned char *midhash, unsigned long midhash_len) { unsigned char *e_packet = NULL, *s_packet = NULL, *tmp, h_sig_comp[SHA_DIGEST_LENGTH], c; unsigned long e_packet_len, s_packet_len, tmp_len; int ret = 0; _libssh2_bn_ctx *ctx = _libssh2_bn_ctx_new(); _libssh2_bn *x = _libssh2_bn_init(); /* Random from client */ _libssh2_bn *e = _libssh2_bn_init(); /* g^x mod p */ _libssh2_bn *f = _libssh2_bn_init(); /* g^(Random from server) mod p */ _libssh2_bn *k = _libssh2_bn_init(); /* The shared secret: f^x mod p */ unsigned char *s, *f_value, *k_value = NULL, *h_sig; unsigned long f_value_len, k_value_len, h_sig_len; libssh2_sha1_ctx exchange_hash; /* Generate x and e */ _libssh2_bn_rand(x, group_order, 0, -1); _libssh2_bn_mod_exp(e, g, x, p, ctx); /* Send KEX init */ e_packet_len = _libssh2_bn_bytes(e) + 6; /* packet_type(1) + String Length(4) + leading 0(1) */ if (_libssh2_bn_bits(e) % 8) { /* Leading 00 not needed */ e_packet_len--; } e_packet = LIBSSH2_ALLOC(session, e_packet_len); if (!e_packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Out of memory error", 0); ret = -1; goto clean_exit; } e_packet[0] = packet_type_init; libssh2_htonu32(e_packet + 1, e_packet_len - 5); if (_libssh2_bn_bits(e) % 8) { _libssh2_bn_to_bin(e, e_packet + 5); } else { e_packet[5] = 0; _libssh2_bn_to_bin(e, e_packet + 6); } #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sending KEX packet %d", (int)packet_type_init); #endif if (libssh2_packet_write(session, e_packet, e_packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send KEX init message", 0); ret = -11; goto clean_exit; } if (session->burn_optimistic_kexinit) { /* The first KEX packet to come along will be the guess initially sent by the server * That guess turned out to be wrong so we need to silently ignore it */ int burn_type; #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Waiting for badly guessed KEX packet (to be ignored)"); #endif burn_type = libssh2_packet_burn(session); if (burn_type <= 0) { /* Failed to receive a packet */ ret = -1; goto clean_exit; } session->burn_optimistic_kexinit = 0; #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Burnt packet of type: %02x", (unsigned int)burn_type); #endif } /* Wait for KEX reply */ if (libssh2_packet_require(session, packet_type_reply, &s_packet, &s_packet_len)) { libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, "Timed out waiting for KEX reply", 0); ret = -1; goto clean_exit; } /* Parse KEXDH_REPLY */ s = s_packet + 1; session->server_hostkey_len = libssh2_ntohu32(s); s += 4; session->server_hostkey = LIBSSH2_ALLOC(session, session->server_hostkey_len); if (!session->server_hostkey) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for a copy of the host key", 0); ret = -1; goto clean_exit; } memcpy(session->server_hostkey, s, session->server_hostkey_len); s += session->server_hostkey_len; #if LIBSSH2_MD5 { libssh2_md5_ctx fingerprint_ctx; libssh2_md5_init(&fingerprint_ctx); libssh2_md5_update(fingerprint_ctx, session->server_hostkey, session->server_hostkey_len); libssh2_md5_final(fingerprint_ctx, session->server_hostkey_md5); } #ifdef LIBSSH2_DEBUG_KEX { char fingerprint[50], *fprint = fingerprint; int i; for(i = 0; i < 16; i++, fprint += 3) { snprintf(fprint, 4, "%02x:", session->server_hostkey_md5[i]); } *(--fprint) = '\0'; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Server's MD5 Fingerprint: %s", fingerprint); } #endif /* LIBSSH2_DEBUG_KEX */ #endif /* ! LIBSSH2_MD5 */ { libssh2_sha1_ctx fingerprint_ctx; libssh2_sha1_init(&fingerprint_ctx); libssh2_sha1_update (fingerprint_ctx, session->server_hostkey, session->server_hostkey_len); libssh2_sha1_final(fingerprint_ctx, session->server_hostkey_sha1); } #ifdef LIBSSH2_DEBUG_KEX { char fingerprint[64], *fprint = fingerprint; int i; for(i = 0; i < 20; i++, fprint += 3) { snprintf(fprint, 4, "%02x:", session->server_hostkey_sha1[i]); } *(--fprint) = '\0'; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Server's SHA1 Fingerprint: %s", fingerprint); } #endif /* LIBSSH2_DEBUG_KEX */ if (session->hostkey->init(session, session->server_hostkey, session->server_hostkey_len, &session->server_hostkey_abstract)) { libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unable to initialize hostkey importer", 0); ret = -1; goto clean_exit; } f_value_len = libssh2_ntohu32(s); s += 4; f_value = s; s += f_value_len; _libssh2_bn_from_bin(f, f_value_len, f_value); h_sig_len = libssh2_ntohu32(s); s += 4; h_sig = s; /* Compute the shared secret */ _libssh2_bn_mod_exp(k, f, x, p, ctx); k_value_len = _libssh2_bn_bytes(k) + 5; if (_libssh2_bn_bits(k) % 8) { /* don't need leading 00 */ k_value_len--; } k_value = LIBSSH2_ALLOC(session, k_value_len); if (!k_value) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate buffer for K", 0); ret = -1; goto clean_exit; } libssh2_htonu32(k_value, k_value_len - 4); if (_libssh2_bn_bits(k) % 8) { _libssh2_bn_to_bin(k, k_value + 4); } else { k_value[4] = 0; _libssh2_bn_to_bin(k, k_value + 5); } libssh2_sha1_init(&exchange_hash); if (session->local.banner) { libssh2_htonu32(h_sig_comp, strlen((char *)session->local.banner) - 2); libssh2_sha1_update(exchange_hash, h_sig_comp, 4); libssh2_sha1_update(exchange_hash, (char *)session->local.banner, strlen((char *)session->local.banner) - 2); } else { libssh2_htonu32(h_sig_comp, sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); libssh2_sha1_update(exchange_hash, h_sig_comp, 4); libssh2_sha1_update(exchange_hash, LIBSSH2_SSH_DEFAULT_BANNER, sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); } libssh2_htonu32(h_sig_comp, strlen((char *)session->remote.banner)); libssh2_sha1_update(exchange_hash, h_sig_comp, 4); libssh2_sha1_update(exchange_hash, session->remote.banner, strlen((char *)session->remote.banner)); libssh2_htonu32(h_sig_comp, session->local.kexinit_len); libssh2_sha1_update(exchange_hash, h_sig_comp, 4); libssh2_sha1_update(exchange_hash, session->local.kexinit, session->local.kexinit_len); libssh2_htonu32(h_sig_comp, session->remote.kexinit_len); libssh2_sha1_update(exchange_hash, h_sig_comp, 4); libssh2_sha1_update(exchange_hash, session->remote.kexinit, session->remote.kexinit_len); libssh2_htonu32(h_sig_comp, session->server_hostkey_len); libssh2_sha1_update(exchange_hash, h_sig_comp, 4); libssh2_sha1_update(exchange_hash, session->server_hostkey, session->server_hostkey_len); if (packet_type_init == SSH_MSG_KEX_DH_GEX_INIT) { /* diffie-hellman-group-exchange hashes additional fields */ #ifdef LIBSSH2_DH_GEX_NEW libssh2_htonu32(h_sig_comp, LIBSSH2_DH_GEX_MINGROUP); libssh2_htonu32(h_sig_comp + 4, LIBSSH2_DH_GEX_OPTGROUP); libssh2_htonu32(h_sig_comp + 8, LIBSSH2_DH_GEX_MAXGROUP); libssh2_sha1_update(exchange_hash, h_sig_comp, 12); #else libssh2_htonu32(h_sig_comp, LIBSSH2_DH_GEX_OPTGROUP); libssh2_sha1_update(exchange_hash, h_sig_comp, 4); #endif } if (midhash) { libssh2_sha1_update(exchange_hash, midhash, midhash_len); } libssh2_sha1_update(exchange_hash, e_packet + 1, e_packet_len - 1); libssh2_htonu32(h_sig_comp, f_value_len); libssh2_sha1_update(exchange_hash, h_sig_comp, 4); libssh2_sha1_update(exchange_hash, f_value, f_value_len); libssh2_sha1_update(exchange_hash, k_value, k_value_len); libssh2_sha1_final(exchange_hash, h_sig_comp); if (session->hostkey->sig_verify(session, h_sig, h_sig_len, h_sig_comp, 20, &session->server_hostkey_abstract)) { libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_SIGN, "Unable to verify hostkey signature", 0); ret = -1; goto clean_exit; } #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sending NEWKEYS message"); #endif c = SSH_MSG_NEWKEYS; if (libssh2_packet_write(session, &c, 1)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send NEWKEYS message", 0); ret = -1; goto clean_exit; } if (libssh2_packet_require(session, SSH_MSG_NEWKEYS, &tmp, &tmp_len)) { libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, "Timed out waiting for NEWKEYS", 0); ret = -1; goto clean_exit; } /* The first key exchange has been performed, switch to active crypt/comp/mac mode */ session->state |= LIBSSH2_STATE_NEWKEYS; #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Received NEWKEYS message"); #endif /* This will actually end up being just packet_type(1) for this packet type anyway */ LIBSSH2_FREE(session, tmp); if (!session->session_id) { session->session_id = LIBSSH2_ALLOC(session, SHA_DIGEST_LENGTH); if (!session->session_id) { ret = -1; goto clean_exit; } memcpy(session->session_id, h_sig_comp, SHA_DIGEST_LENGTH); session->session_id_len = SHA_DIGEST_LENGTH; #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "session_id calculated"); #endif } /* Cleanup any existing cipher */ if (session->local.crypt->dtor) { session->local.crypt->dtor(session, &session->local.crypt_abstract); } /* Calculate IV/Secret/Key for each direction */ if (session->local.crypt->init) { unsigned char *iv = NULL, *secret = NULL; int free_iv = 0, free_secret = 0; LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(iv, session->local.crypt->iv_len, "A"); LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(secret, session->local.crypt->secret_len, "C"); if (session->local.crypt->init(session, session->local.crypt, iv, &free_iv, secret, &free_secret, 1, &session->local.crypt_abstract)) { LIBSSH2_FREE(session, iv); LIBSSH2_FREE(session, secret); ret = -1; goto clean_exit; } if (free_iv) { memset(iv, 0, session->local.crypt->iv_len); LIBSSH2_FREE(session, iv); } if (free_secret) { memset(secret, 0, session->local.crypt->secret_len); LIBSSH2_FREE(session, secret); } } #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Client to Server IV and Key calculated"); #endif if (session->remote.crypt->dtor) { /* Cleanup any existing cipher */ session->remote.crypt->dtor(session, &session->remote.crypt_abstract); } if (session->remote.crypt->init) { unsigned char *iv = NULL, *secret = NULL; int free_iv = 0, free_secret = 0; LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(iv, session->remote.crypt->iv_len, "B"); LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(secret, session->remote.crypt->secret_len, "D"); if (session->remote.crypt->init(session, session->remote.crypt, iv, &free_iv, secret, &free_secret, 0, &session->remote.crypt_abstract)) { LIBSSH2_FREE(session, iv); LIBSSH2_FREE(session, secret); ret = -1; goto clean_exit; } if (free_iv) { memset(iv, 0, session->remote.crypt->iv_len); LIBSSH2_FREE(session, iv); } if (free_secret) { memset(secret, 0, session->remote.crypt->secret_len); LIBSSH2_FREE(session, secret); } } #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Server to Client IV and Key calculated"); #endif if (session->local.mac->dtor) { session->local.mac->dtor(session, &session->local.mac_abstract); } if (session->local.mac->init) { unsigned char *key = NULL; int free_key = 0; LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(key, session->local.mac->key_len, "E"); session->local.mac->init(session, key, &free_key, &session->local.mac_abstract); if (free_key) { memset(key, 0, session->local.mac->key_len); LIBSSH2_FREE(session, key); } } #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Client to Server HMAC Key calculated"); #endif if (session->remote.mac->dtor) { session->remote.mac->dtor(session, &session->remote.mac_abstract); } if (session->remote.mac->init) { unsigned char *key = NULL; int free_key = 0; LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(key, session->remote.mac->key_len, "F"); session->remote.mac->init(session, key, &free_key, &session->remote.mac_abstract); if (free_key) { memset(key, 0, session->remote.mac->key_len); LIBSSH2_FREE(session, key); } } #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Server to Client HMAC Key calculated"); #endif clean_exit: _libssh2_bn_free(x); _libssh2_bn_free(e); _libssh2_bn_free(f); _libssh2_bn_free(k); _libssh2_bn_ctx_free(ctx); if (e_packet) { LIBSSH2_FREE(session, e_packet); } if (s_packet) { LIBSSH2_FREE(session, s_packet); } if (k_value) { LIBSSH2_FREE(session, k_value); } if (session->server_hostkey) { LIBSSH2_FREE(session, session->server_hostkey); session->server_hostkey = NULL; } return ret; } /* }}} */ /* {{{ libssh2_kex_method_diffie_hellman_group1_sha1_key_exchange * Diffie-Hellman Group1 (Actually Group2) Key Exchange using SHA1 */ static int libssh2_kex_method_diffie_hellman_group1_sha1_key_exchange(LIBSSH2_SESSION *session) { unsigned char p_value[128] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; /* g == 2 */ _libssh2_bn *p = _libssh2_bn_init(); /* SSH2 defined value (p_value) */ _libssh2_bn *g = _libssh2_bn_init(); /* SSH2 defined value (2) */ int ret; /* Initialize P and G */ _libssh2_bn_set_word(g, 2); _libssh2_bn_from_bin(p, 128, p_value); #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Initiating Diffie-Hellman Group1 Key Exchange"); #endif ret = libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange(session, g, p, 128, SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, NULL, 0); _libssh2_bn_free(p); _libssh2_bn_free(g); return ret; } /* }}} */ /* {{{ libssh2_kex_method_diffie_hellman_group14_sha1_key_exchange * Diffie-Hellman Group14 Key Exchange using SHA1 */ static int libssh2_kex_method_diffie_hellman_group14_sha1_key_exchange(LIBSSH2_SESSION *session) { unsigned char p_value[256] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; /* g == 2 */ _libssh2_bn *p = _libssh2_bn_init(); /* SSH2 defined value (p_value) */ _libssh2_bn *g = _libssh2_bn_init(); /* SSH2 defined value (2) */ int ret; /* Initialize P and G */ _libssh2_bn_set_word(g, 2); _libssh2_bn_from_bin(p, 256, p_value); #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Initiating Diffie-Hellman Group14 Key Exchange"); #endif ret = libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange(session, g, p, 256, SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, NULL, 0); _libssh2_bn_free(p); _libssh2_bn_free(g); return ret; } /* }}} */ /* {{{ libssh2_kex_method_diffie_hellman_group_exchange_sha1_key_exchange * Diffie-Hellman Group Exchange Key Exchange using SHA1 * Negotiates random(ish) group for secret derivation */ static int libssh2_kex_method_diffie_hellman_group_exchange_sha1_key_exchange(LIBSSH2_SESSION *session) { unsigned char request[13], *s, *data; unsigned long data_len, p_len, g_len, request_len; _libssh2_bn *p = _libssh2_bn_init (); _libssh2_bn *g = _libssh2_bn_init (); int ret; /* Ask for a P and G pair */ #ifdef LIBSSH2_DH_GEX_NEW request[0] = SSH_MSG_KEX_DH_GEX_REQUEST; libssh2_htonu32(request + 1, LIBSSH2_DH_GEX_MINGROUP); libssh2_htonu32(request + 5, LIBSSH2_DH_GEX_OPTGROUP); libssh2_htonu32(request + 9, LIBSSH2_DH_GEX_MAXGROUP); request_len = 13; #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Initiating Diffie-Hellman Group-Exchange (New Method)"); #endif #else request[0] = SSH_MSG_KEX_DH_GEX_REQUEST_OLD; libssh2_htonu32(request + 1, LIBSSH2_DH_GEX_OPTGROUP); request_len = 5; #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Initiating Diffie-Hellman Group-Exchange (Old Method)"); #endif #endif if (libssh2_packet_write(session, request, request_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send Group Exchange Request", 0); ret = -1; goto dh_gex_clean_exit; } if (libssh2_packet_require(session, SSH_MSG_KEX_DH_GEX_GROUP, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, "Timeout waiting for GEX_GROUP reply", 0); ret = -1; goto dh_gex_clean_exit; } s = data + 1; p_len = libssh2_ntohu32(s); s += 4; _libssh2_bn_from_bin(p, p_len, s); s += p_len; g_len = libssh2_ntohu32(s); s += 4; _libssh2_bn_from_bin(g, g_len, s); s += g_len; ret = libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange(session, g, p, p_len, SSH_MSG_KEX_DH_GEX_INIT, SSH_MSG_KEX_DH_GEX_REPLY, data + 1, data_len - 1); LIBSSH2_FREE(session, data); dh_gex_clean_exit: _libssh2_bn_free(g); _libssh2_bn_free(p); return ret; } /* }}} */ #define LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY 0x0001 #define LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY 0x0002 LIBSSH2_KEX_METHOD libssh2_kex_method_diffie_helman_group1_sha1 = { "diffie-hellman-group1-sha1", libssh2_kex_method_diffie_hellman_group1_sha1_key_exchange, LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; LIBSSH2_KEX_METHOD libssh2_kex_method_diffie_helman_group14_sha1 = { "diffie-hellman-group14-sha1", libssh2_kex_method_diffie_hellman_group14_sha1_key_exchange, LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; LIBSSH2_KEX_METHOD libssh2_kex_method_diffie_helman_group_exchange_sha1 = { "diffie-hellman-group-exchange-sha1", libssh2_kex_method_diffie_hellman_group_exchange_sha1_key_exchange, LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = { &libssh2_kex_method_diffie_helman_group14_sha1, &libssh2_kex_method_diffie_helman_group_exchange_sha1, &libssh2_kex_method_diffie_helman_group1_sha1, NULL }; typedef struct _LIBSSH2_COMMON_METHOD { char *name; } LIBSSH2_COMMON_METHOD; /* {{{ libssh2_kex_method_strlen * Calculate the length of a particular method list's resulting string * Includes SUM(strlen() of each individual method plus 1 (for coma)) - 1 (because the last coma isn't used) * Another sign of bad coding practices gone mad. Pretend you don't see this. */ static size_t libssh2_kex_method_strlen(LIBSSH2_COMMON_METHOD **method) { size_t len = 0; if (!method || !*method) { return 0; } while (*method && (*method)->name) { len += strlen((*method)->name) + 1; method++; } return len - 1; } /* }}} */ /* {{{ libssh2_kex_method_list * Generate formatted preference list in buf */ static size_t libssh2_kex_method_list(unsigned char *buf, size_t list_strlen, LIBSSH2_COMMON_METHOD **method) { libssh2_htonu32(buf, list_strlen); buf += 4; if (!method || !*method) { return 4; } while (*method && (*method)->name) { int mlen = strlen((*method)->name); memcpy(buf, (*method)->name, mlen); buf += mlen; *(buf++) = ','; method++; } return list_strlen + 4; } /* }}} */ #define LIBSSH2_METHOD_PREFS_LEN(prefvar, defaultvar) ((prefvar) ? strlen(prefvar) : libssh2_kex_method_strlen((LIBSSH2_COMMON_METHOD**)(defaultvar))) #define LIBSSH2_METHOD_PREFS_STR(buf, prefvarlen, prefvar, defaultvar) \ if (prefvar) { \ libssh2_htonu32((buf), (prefvarlen)); \ buf += 4; \ memcpy((buf), (prefvar), (prefvarlen)); \ buf += (prefvarlen); \ } else { \ buf += libssh2_kex_method_list((buf), (prefvarlen), (LIBSSH2_COMMON_METHOD**)(defaultvar)); \ } /* {{{ libssh2_kexinit * Send SSH_MSG_KEXINIT packet */ static int libssh2_kexinit(LIBSSH2_SESSION *session) { size_t data_len = 62; /* packet_type(1) + cookie(16) + first_packet_follows(1) + reserved(4) + length longs(40) */ size_t kex_len, hostkey_len = 0; size_t crypt_cs_len, crypt_sc_len; size_t comp_cs_len, comp_sc_len; size_t mac_cs_len, mac_sc_len; size_t lang_cs_len, lang_sc_len; unsigned char *data, *s; kex_len = LIBSSH2_METHOD_PREFS_LEN(session->kex_prefs, libssh2_kex_methods); hostkey_len = LIBSSH2_METHOD_PREFS_LEN(session->hostkey_prefs, libssh2_hostkey_methods()); crypt_cs_len = LIBSSH2_METHOD_PREFS_LEN(session->local.crypt_prefs, libssh2_crypt_methods()); crypt_sc_len = LIBSSH2_METHOD_PREFS_LEN(session->remote.crypt_prefs, libssh2_crypt_methods()); mac_cs_len = LIBSSH2_METHOD_PREFS_LEN(session->local.mac_prefs, libssh2_mac_methods()); mac_sc_len = LIBSSH2_METHOD_PREFS_LEN(session->remote.mac_prefs, libssh2_mac_methods()); comp_cs_len = LIBSSH2_METHOD_PREFS_LEN(session->local.comp_prefs, libssh2_comp_methods()); comp_sc_len = LIBSSH2_METHOD_PREFS_LEN(session->remote.comp_prefs, libssh2_comp_methods()); lang_cs_len = LIBSSH2_METHOD_PREFS_LEN(session->local.lang_prefs, NULL); lang_sc_len = LIBSSH2_METHOD_PREFS_LEN(session->remote.lang_prefs, NULL); data_len += kex_len + hostkey_len + \ crypt_cs_len + crypt_sc_len + \ comp_cs_len + comp_sc_len + \ mac_cs_len + mac_sc_len + \ lang_cs_len + lang_sc_len; s = data = LIBSSH2_ALLOC(session, data_len); if (!data) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory", 0); return -1; } *(s++) = SSH_MSG_KEXINIT; libssh2_random(s, 16); s += 16; /* Ennumerating through these lists twice is probably (certainly?) inefficient from a CPU standpoint, but it saves multiple malloc/realloc calls */ LIBSSH2_METHOD_PREFS_STR(s, kex_len, session->kex_prefs, libssh2_kex_methods); LIBSSH2_METHOD_PREFS_STR(s, hostkey_len, session->hostkey_prefs, libssh2_hostkey_methods()); LIBSSH2_METHOD_PREFS_STR(s, crypt_cs_len, session->local.crypt_prefs, libssh2_crypt_methods()); LIBSSH2_METHOD_PREFS_STR(s, crypt_sc_len, session->remote.crypt_prefs, libssh2_crypt_methods()); LIBSSH2_METHOD_PREFS_STR(s, mac_cs_len, session->local.mac_prefs, libssh2_mac_methods()); LIBSSH2_METHOD_PREFS_STR(s, mac_sc_len, session->remote.mac_prefs, libssh2_mac_methods()); LIBSSH2_METHOD_PREFS_STR(s, comp_cs_len, session->local.comp_prefs, libssh2_comp_methods()); LIBSSH2_METHOD_PREFS_STR(s, comp_sc_len, session->remote.comp_prefs, libssh2_comp_methods()); LIBSSH2_METHOD_PREFS_STR(s, lang_cs_len, session->local.lang_prefs, NULL); LIBSSH2_METHOD_PREFS_STR(s, lang_sc_len, session->remote.lang_prefs, NULL); /* No optimistic KEX packet follows */ /* Deal with optimistic packets * session->flags |= KEXINIT_OPTIMISTIC * session->flags |= KEXINIT_METHODSMATCH */ *(s++) = 0; /* Reserved == 0 */ *(s++) = 0; *(s++) = 0; *(s++) = 0; *(s++) = 0; #ifdef LIBSSH2_DEBUG_KEX { /* Funnily enough, they'll all "appear" to be '\0' terminated */ char *p = data + 21; /* type(1) + cookie(16) + len(4) */ _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sent KEX: %s", p); p += kex_len + 4; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sent HOSTKEY: %s", p); p += hostkey_len + 4; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sent CRYPT_CS: %s", p); p += crypt_cs_len + 4; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sent CRYPT_SC: %s", p); p += crypt_sc_len + 4; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sent MAC_CS: %s", p); p += mac_cs_len + 4; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sent MAC_SC: %s", p); p += mac_sc_len + 4; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sent COMP_CS: %s", p); p += comp_cs_len + 4; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sent COMP_SC: %s", p); p += comp_sc_len + 4; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sent LANG_CS: %s", p); p += lang_cs_len + 4; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Sent LANG_SC: %s", p); p += lang_sc_len + 4; } #endif /* LIBSSH2_DEBUG_KEX */ if (libssh2_packet_write(session, data, data_len)) { LIBSSH2_FREE(session, data); libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send KEXINIT packet to remote host", 0); return -1; } if (session->local.kexinit) { LIBSSH2_FREE(session, session->local.kexinit); } session->local.kexinit = data; session->local.kexinit_len = data_len; return 0; } /* }}} */ /* {{{ libssh2_kex_agree_instr * Kex specific variant of strstr() * Needle must be preceed by BOL or ',', and followed by ',' or EOL */ static unsigned char *libssh2_kex_agree_instr(unsigned char *haystack, unsigned long haystack_len, unsigned char *needle, unsigned long needle_len) { unsigned char *s; /* Haystack too short to bother trying */ if (haystack_len < needle_len) { return NULL; } /* Needle at start of haystack */ if ((strncmp((const char *)haystack, (const char *)needle, needle_len) == 0) && (needle_len == haystack_len || haystack[needle_len] == ',')) { return haystack; } s = haystack; /* Search until we run out of comas or we run out of haystack, whichever comes first */ while ((s = (unsigned char *)strchr((const char *)s, ',')) && ((haystack_len - (s - haystack)) > needle_len)) { s++; /* Needle at X position */ if ((strncmp((const char *)s, (const char *)needle, needle_len) == 0) && (((s - haystack) + needle_len) == haystack_len || s[needle_len] == ',')) { return s; } } return NULL; } /* }}} */ /* {{{ libssh2_get_method_by_name */ static LIBSSH2_COMMON_METHOD *libssh2_get_method_by_name(char *name, int name_len, LIBSSH2_COMMON_METHOD **methodlist) { while (*methodlist) { if ((strlen((*methodlist)->name) == name_len) && (strncmp((*methodlist)->name, name, name_len) == 0)) { return *methodlist; } methodlist++; } return NULL; } /* }}} */ /* {{{ libssh2_kex_agree_hostkey * Agree on a Hostkey which works with this kex */ static int libssh2_kex_agree_hostkey(LIBSSH2_SESSION *session, unsigned long kex_flags, unsigned char *hostkey, unsigned long hostkey_len) { LIBSSH2_HOSTKEY_METHOD **hostkeyp = libssh2_hostkey_methods(); unsigned char *s; if (session->hostkey_prefs) { s = (unsigned char *)session->hostkey_prefs; while (s && *s) { unsigned char *p = (unsigned char *)strchr((const char *)s, ','); int method_len = (p ? (p - s) : strlen((const char *)s)); if (libssh2_kex_agree_instr(hostkey, hostkey_len, s, method_len)) { LIBSSH2_HOSTKEY_METHOD *method = (LIBSSH2_HOSTKEY_METHOD*)libssh2_get_method_by_name((char *)s, method_len, (LIBSSH2_COMMON_METHOD**)hostkeyp); if (!method) { /* Invalid method -- Should never be reached */ return -1; } /* So far so good, but does it suit our purposes? (Encrypting vs Signing) */ if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) == 0) || (method->encrypt)) { /* Either this hostkey can do encryption or this kex just doesn't require it */ if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY) == 0) || (method->sig_verify)) { /* Either this hostkey can do signing or this kex just doesn't require it */ session->hostkey = method; return 0; } } } s = p ? p + 1 : NULL; } return -1; } while (hostkeyp && (*hostkeyp)->name) { s = libssh2_kex_agree_instr(hostkey, hostkey_len, (unsigned char *)(*hostkeyp)->name, strlen((*hostkeyp)->name)); if (s) { /* So far so good, but does it suit our purposes? (Encrypting vs Signing) */ if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) == 0) || ((*hostkeyp)->encrypt)) { /* Either this hostkey can do encryption or this kex just doesn't require it */ if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY) == 0) || ((*hostkeyp)->sig_verify)) { /* Either this hostkey can do signing or this kex just doesn't require it */ session->hostkey = *hostkeyp; return 0; } } } hostkeyp++; } return -1; } /* }}} */ /* {{{ libssh2_kex_agree_kex_hostkey * Agree on a Key Exchange method and a hostkey encoding type */ static int libssh2_kex_agree_kex_hostkey(LIBSSH2_SESSION *session, unsigned char *kex, unsigned long kex_len, unsigned char *hostkey, unsigned long hostkey_len) { LIBSSH2_KEX_METHOD **kexp = libssh2_kex_methods; unsigned char *s; if (session->kex_prefs) { s = (unsigned char *)session->kex_prefs; while (s && *s) { unsigned char *q, *p = (unsigned char *)strchr((const char *)s, ','); int method_len = (p ? (p - s) : strlen((const char *)s)); if ((q = libssh2_kex_agree_instr(kex, kex_len, s, method_len))) { LIBSSH2_KEX_METHOD *method = (LIBSSH2_KEX_METHOD*)libssh2_get_method_by_name((char *)s, method_len, (LIBSSH2_COMMON_METHOD**)kexp); if (!method) { /* Invalid method -- Should never be reached */ return -1; } /* We've agreed on a key exchange method, * Can we agree on a hostkey that works with this kex? */ if (libssh2_kex_agree_hostkey(session, method->flags, hostkey, hostkey_len) == 0) { session->kex = method; if (session->burn_optimistic_kexinit && (kex == q)) { /* Server sent an optimistic packet, * and client agrees with preference * cancel burning the first KEX_INIT packet that comes in */ session->burn_optimistic_kexinit = 0; } return 0; } } s = p ? p + 1 : NULL; } return -1; } while (*kexp && (*kexp)->name) { s = libssh2_kex_agree_instr(kex, kex_len, (unsigned char *)(*kexp)->name, strlen((*kexp)->name)); if (s) { /* We've agreed on a key exchange method, * Can we agree on a hostkey that works with this kex? */ if (libssh2_kex_agree_hostkey(session, (*kexp)->flags, hostkey, hostkey_len) == 0) { session->kex = *kexp; if (session->burn_optimistic_kexinit && (kex == s)) { /* Server sent an optimistic packet, * and client agrees with preference * cancel burning the first KEX_INIT packet that comes in */ session->burn_optimistic_kexinit = 0; } return 0; } } kexp++; } return -1; } /* }}} */ /* {{{ libssh2_kex_agree_crypt * Agree on a cipher algo */ static int libssh2_kex_agree_crypt(LIBSSH2_SESSION *session, libssh2_endpoint_data *endpoint, unsigned char *crypt, unsigned long crypt_len) { LIBSSH2_CRYPT_METHOD **cryptp = libssh2_crypt_methods(); unsigned char *s; if (endpoint->crypt_prefs) { s = (unsigned char *)endpoint->crypt_prefs; while (s && *s) { unsigned char *p = (unsigned char *)strchr((const char *)s, ','); int method_len = (p ? (p - s) : strlen((const char *)s)); if (libssh2_kex_agree_instr(crypt, crypt_len, s, method_len)) { LIBSSH2_CRYPT_METHOD *method = (LIBSSH2_CRYPT_METHOD*)libssh2_get_method_by_name((char *)s, method_len, (LIBSSH2_COMMON_METHOD**)cryptp); if (!method) { /* Invalid method -- Should never be reached */ return -1; } endpoint->crypt = method; return 0; } s = p ? p + 1 : NULL; } return -1; } while (*cryptp && (*cryptp)->name) { s = libssh2_kex_agree_instr(crypt, crypt_len, (unsigned char *)(*cryptp)->name, strlen((*cryptp)->name)); if (s) { endpoint->crypt = *cryptp; return 0; } cryptp++; } return -1; } /* }}} */ /* {{{ libssh2_kex_agree_mac * Agree on a message authentication hash */ static int libssh2_kex_agree_mac(LIBSSH2_SESSION *session, libssh2_endpoint_data *endpoint, unsigned char *mac, unsigned long mac_len) { LIBSSH2_MAC_METHOD **macp = libssh2_mac_methods(); unsigned char *s; if (endpoint->mac_prefs) { s = (unsigned char *)endpoint->mac_prefs; while (s && *s) { unsigned char *p = (unsigned char *)strchr((const char *)s, ','); int method_len = (p ? (p - s) : strlen((const char *)s)); if (libssh2_kex_agree_instr(mac, mac_len, s, method_len)) { LIBSSH2_MAC_METHOD *method = (LIBSSH2_MAC_METHOD*)libssh2_get_method_by_name((char *)s, method_len, (LIBSSH2_COMMON_METHOD**)macp); if (!method) { /* Invalid method -- Should never be reached */ return -1; } endpoint->mac = method; return 0; } s = p ? p + 1 : NULL; } return -1; } while (*macp && (*macp)->name) { s = libssh2_kex_agree_instr(mac, mac_len, (unsigned char *)(*macp)->name, strlen((*macp)->name)); if (s) { endpoint->mac = *macp; return 0; } macp++; } return -1; } /* }}} */ /* {{{ libssh2_kex_agree_comp * Agree on a compression scheme */ static int libssh2_kex_agree_comp(LIBSSH2_SESSION *session, libssh2_endpoint_data *endpoint, unsigned char *comp, unsigned long comp_len) { LIBSSH2_COMP_METHOD **compp = libssh2_comp_methods(); unsigned char *s; if (endpoint->comp_prefs) { s = (unsigned char *)endpoint->comp_prefs; while (s && *s) { unsigned char *p = (unsigned char *)strchr((const char *)s, ','); int method_len = (p ? (p - s) : strlen((const char *)s)); if (libssh2_kex_agree_instr(comp, comp_len, s, method_len)) { LIBSSH2_COMP_METHOD *method = (LIBSSH2_COMP_METHOD*)libssh2_get_method_by_name((char *)s, method_len, (LIBSSH2_COMMON_METHOD**)compp); if (!method) { /* Invalid method -- Should never be reached */ return -1; } endpoint->comp = method; return 0; } s = p ? p + 1 : NULL; } return -1; } while (*compp && (*compp)->name) { s = libssh2_kex_agree_instr(comp, comp_len, (unsigned char *)(*compp)->name, strlen((*compp)->name)); if (s) { endpoint->comp = *compp; return 0; } compp++; } return -1; } /* }}} */ /* TODO: When in server mode we need to turn this logic on its head * The Client gets to make the final call on "agreed methods" */ /* {{{ libssh2_kex_agree_methods * Decide which specific method to use of the methods offered by each party */ static int libssh2_kex_agree_methods(LIBSSH2_SESSION *session, unsigned char *data, unsigned data_len) { unsigned char *kex, *hostkey, *crypt_cs, *crypt_sc, *comp_cs, *comp_sc, *mac_cs, *mac_sc, *lang_cs, *lang_sc; size_t kex_len, hostkey_len, crypt_cs_len, crypt_sc_len, comp_cs_len, comp_sc_len, mac_cs_len, mac_sc_len, lang_cs_len, lang_sc_len; unsigned char *s = data; /* Skip packet_type, we know it already */ s++; /* Skip cookie, don't worry, it's preserved in the kexinit field */ s += 16; /* Locate each string */ kex_len = libssh2_ntohu32(s); kex = s + 4; s += 4 + kex_len; hostkey_len = libssh2_ntohu32(s); hostkey = s + 4; s += 4 + hostkey_len; crypt_cs_len = libssh2_ntohu32(s); crypt_cs = s + 4; s += 4 + crypt_cs_len; crypt_sc_len = libssh2_ntohu32(s); crypt_sc = s + 4; s += 4 + crypt_sc_len; mac_cs_len = libssh2_ntohu32(s); mac_cs = s + 4; s += 4 + mac_cs_len; mac_sc_len = libssh2_ntohu32(s); mac_sc = s + 4; s += 4 + mac_sc_len; comp_cs_len = libssh2_ntohu32(s); comp_cs = s + 4; s += 4 + comp_cs_len; comp_sc_len = libssh2_ntohu32(s); comp_sc = s + 4; s += 4 + comp_sc_len; lang_cs_len = libssh2_ntohu32(s); lang_cs = s + 4; s += 4 + lang_cs_len; lang_sc_len = libssh2_ntohu32(s); lang_sc = s + 4; s += 4 + lang_sc_len; /* If the server sent an optimistic packet, assume that it guessed wrong. * If the guess is determined to be right (by libssh2_kex_agree_kex_hostkey) * This flag will be reset to zero so that it's not ignored */ session->burn_optimistic_kexinit = *(s++); /* Next uint32 in packet is all zeros (reserved) */ if (libssh2_kex_agree_kex_hostkey(session, kex, kex_len, hostkey, hostkey_len)) { return -1; } if (libssh2_kex_agree_crypt(session, &session->local, crypt_cs, crypt_cs_len) || libssh2_kex_agree_crypt(session, &session->remote, crypt_sc, crypt_sc_len)) { return -1; } if (libssh2_kex_agree_mac(session, &session->local, mac_cs, mac_cs_len) || libssh2_kex_agree_mac(session, &session->remote, mac_sc, mac_sc_len)) { return -1; } if (libssh2_kex_agree_comp(session, &session->local, comp_cs, comp_cs_len) || libssh2_kex_agree_comp(session, &session->remote, comp_sc, comp_sc_len)) { return -1; } if (libssh2_kex_agree_lang(session, &session->local, lang_cs, lang_cs_len) || libssh2_kex_agree_lang(session, &session->remote, lang_sc, lang_sc_len)) { return -1; } #ifdef LIBSSH2_DEBUG_KEX _libssh2_debug(session, LIBSSH2_DBG_KEX, "Agreed on KEX method: %s", session->kex->name); _libssh2_debug(session, LIBSSH2_DBG_KEX, "Agreed on HOSTKEY method: %s", session->hostkey->name); _libssh2_debug(session, LIBSSH2_DBG_KEX, "Agreed on CRYPT_CS method: %s", session->local.crypt->name); _libssh2_debug(session, LIBSSH2_DBG_KEX, "Agreed on CRYPT_SC method: %s", session->remote.crypt->name); _libssh2_debug(session, LIBSSH2_DBG_KEX, "Agreed on MAC_CS method: %s", session->local.mac->name); _libssh2_debug(session, LIBSSH2_DBG_KEX, "Agreed on MAC_SC method: %s", session->remote.mac->name); _libssh2_debug(session, LIBSSH2_DBG_KEX, "Agreed on COMP_CS method: %s", session->local.comp->name); _libssh2_debug(session, LIBSSH2_DBG_KEX, "Agreed on COMP_SC method: %s", session->remote.comp->name); _libssh2_debug(session, LIBSSH2_DBG_KEX, "Agreed on LANG_CS method:"); /* None yet */ _libssh2_debug(session, LIBSSH2_DBG_KEX, "Agreed on LANG_SC method:"); /* None yet */ #endif /* Initialize compression layer */ if (session->local.comp && session->local.comp->init && session->local.comp->init(session, 1, &session->local.comp_abstract)) { return -1; } if (session->remote.comp && session->remote.comp->init && session->remote.comp->init(session, 0, &session->remote.comp_abstract)) { return -1; } return 0; } /* }}} */ /* {{{ libssh2_kex_exchange * Exchange keys * Returns 0 on success, non-zero on failure */ int libssh2_kex_exchange(LIBSSH2_SESSION *session, int reexchange) /* session->flags |= SERVER */ { unsigned char *data; unsigned long data_len; /* Prevent loop in packet_add() */ session->state |= LIBSSH2_STATE_EXCHANGING_KEYS; if (reexchange) { session->kex = NULL; if (session->hostkey && session->hostkey->dtor) { session->hostkey->dtor(session, &session->server_hostkey_abstract); } session->hostkey = NULL; } if (!session->kex || !session->hostkey) { /* Preserve in case of failure */ unsigned char *oldlocal = session->local.kexinit; unsigned long oldlocal_len = session->local.kexinit_len; session->local.kexinit = NULL; if (libssh2_kexinit(session)) { session->local.kexinit = oldlocal; session->local.kexinit_len = oldlocal_len; return -1; } if (libssh2_packet_require(session, SSH_MSG_KEXINIT, &data, &data_len)) { if (session->local.kexinit) { LIBSSH2_FREE(session, session->local.kexinit); } session->local.kexinit = oldlocal; session->local.kexinit_len = oldlocal_len; return -1; } if (session->remote.kexinit) { LIBSSH2_FREE(session, session->remote.kexinit); } session->remote.kexinit = data; session->remote.kexinit_len = data_len; if (libssh2_kex_agree_methods(session, data, data_len)) { return -1; } } if (session->kex->exchange_keys(session)) { libssh2_error(session, LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE, "Unrecoverable error exchanging keys", 0); return -1; } /* Done with kexinit buffers */ if (session->local.kexinit) { LIBSSH2_FREE(session, session->local.kexinit); session->local.kexinit = NULL; } if (session->remote.kexinit) { LIBSSH2_FREE(session, session->remote.kexinit); session->remote.kexinit = NULL; } session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; return 0; } /* }}} */ /* {{{ libssh2_session_method_pref * Set preferred method */ LIBSSH2_API int libssh2_session_method_pref(LIBSSH2_SESSION *session, int method_type, const char *prefs) { char **prefvar, *s, *newprefs; int prefs_len = strlen(prefs); LIBSSH2_COMMON_METHOD **mlist; switch (method_type) { case LIBSSH2_METHOD_KEX: prefvar = &session->kex_prefs; mlist = (LIBSSH2_COMMON_METHOD**)libssh2_kex_methods; break; case LIBSSH2_METHOD_HOSTKEY: prefvar = &session->hostkey_prefs; mlist = (LIBSSH2_COMMON_METHOD**)libssh2_hostkey_methods(); break; case LIBSSH2_METHOD_CRYPT_CS: prefvar = &session->local.crypt_prefs; mlist = (LIBSSH2_COMMON_METHOD**)libssh2_crypt_methods(); break; case LIBSSH2_METHOD_CRYPT_SC: prefvar = &session->remote.crypt_prefs; mlist = (LIBSSH2_COMMON_METHOD**)libssh2_crypt_methods(); break; case LIBSSH2_METHOD_MAC_CS: prefvar = &session->local.mac_prefs; mlist = (LIBSSH2_COMMON_METHOD**)libssh2_mac_methods(); break; case LIBSSH2_METHOD_MAC_SC: prefvar = &session->remote.mac_prefs; mlist = (LIBSSH2_COMMON_METHOD**)libssh2_mac_methods(); break; case LIBSSH2_METHOD_COMP_CS: prefvar = &session->local.comp_prefs; mlist = (LIBSSH2_COMMON_METHOD**)libssh2_comp_methods(); break; case LIBSSH2_METHOD_COMP_SC: prefvar = &session->remote.comp_prefs; mlist = (LIBSSH2_COMMON_METHOD**)libssh2_comp_methods(); break; case LIBSSH2_METHOD_LANG_CS: prefvar = &session->local.lang_prefs; mlist = NULL; break; case LIBSSH2_METHOD_LANG_SC: prefvar = &session->remote.lang_prefs; mlist = NULL; break; default: libssh2_error(session, LIBSSH2_ERROR_INVAL, "Invalid parameter specified for method_type", 0); return -1; } s = newprefs = LIBSSH2_ALLOC(session, prefs_len + 1); if (!newprefs) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Error allocated space for method preferences", 0); return -1; } memcpy(s, prefs, prefs_len + 1); while (s && *s) { char *p = strchr(s, ','); int method_len = p ? (p - s) : strlen(s); if (!libssh2_get_method_by_name(s, method_len, mlist)) { /* Strip out unsupported method */ if (p) { memcpy(s, p + 1, strlen(s) - method_len); } else { if (s > newprefs) { *(--s) = '\0'; } else { *s = '\0'; } } } s = p ? (p + 1) : NULL; } if (strlen(newprefs) == 0) { libssh2_error(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, "The requested method(s) are not currently supported", 0); LIBSSH2_FREE(session, newprefs); return -1; } if (*prefvar) { LIBSSH2_FREE(session, *prefvar); } *prefvar = newprefs; return 0; } /* }}} */ ================================================ FILE: Example/ktlogviewer_main.m ================================================ // // ktlogviewer_main.m // Connection // // Created by Greg Hulands on 29/09/06. // Copyright 2006 __MyCompanyName__. All rights reserved. // #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **) argv); } ================================================ FILE: Example/libssh2.h ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #ifndef LIBSSH2_H #define LIBSSH2_H 1 #ifdef __cplusplus extern "C" { #endif #include #include #include /* Allow alternate API prefix from CFLAGS or calling app */ #ifndef LIBSSH2_API # ifdef LIBSSH2_WIN32 # ifdef LIBSSH2_LIBRARY # define LIBSSH2_API __declspec(dllexport) # else # define LIBSSH2_API __declspec(dllimport) # endif /* LIBSSH2_LIBRARY */ # else /* !LIBSSH2_WIN32 */ # define LIBSSH2_API # endif /* LIBSSH2_WIN32 */ #endif /* LIBSSH2_API */ #if defined(LIBSSH2_DARWIN) || (defined(LIBSSH2_WIN32) && !defined(_MSC_VER)) # include #endif #if defined(LIBSSH2_WIN32) && _MSC_VER < 1300 typedef unsigned __int64 libssh2_uint64_t; typedef __int64 libssh2_int64_t; #else typedef unsigned long long libssh2_uint64_t; typedef long long libssh2_int64_t; #endif #define LIBSSH2_VERSION "0.14" #define LIBSSH2_APINO 200507211326 /* Part of every banner, user specified or not */ #define LIBSSH2_SSH_BANNER "SSH-2.0-libssh2_" LIBSSH2_VERSION /* We *could* add a comment here if we so chose */ #define LIBSSH2_SSH_DEFAULT_BANNER LIBSSH2_SSH_BANNER #define LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF LIBSSH2_SSH_DEFAULT_BANNER "\r\n" /* Default generate and safe prime sizes for diffie-hellman-group-exchange-sha1 */ #define LIBSSH2_DH_GEX_MINGROUP 1024 #define LIBSSH2_DH_GEX_OPTGROUP 1536 #define LIBSSH2_DH_GEX_MAXGROUP 2048 /* Defaults for pty requests */ #define LIBSSH2_TERM_WIDTH 80 #define LIBSSH2_TERM_HEIGHT 24 #define LIBSSH2_TERM_WIDTH_PX 0 #define LIBSSH2_TERM_HEIGHT_PX 0 /* 1/4 second */ #define LIBSSH2_SOCKET_POLL_UDELAY 250000 /* 0.25 * 120 == 30 seconds */ #define LIBSSH2_SOCKET_POLL_MAXLOOPS 120 /* Maximum size to allow a payload to compress to, plays it safe by falling short of spec limits */ #define LIBSSH2_PACKET_MAXCOMP 32000 /* Maximum size to allow a payload to deccompress to, plays it safe by allowing more than spec requires */ #define LIBSSH2_PACKET_MAXDECOMP 40000 /* Maximum size for an inbound compressed payload, plays it safe by overshooting spec limits */ #define LIBSSH2_PACKET_MAXPAYLOAD 40000 /* Malloc callbacks */ #define LIBSSH2_ALLOC_FUNC(name) void *name(size_t count, void **abstract) #define LIBSSH2_REALLOC_FUNC(name) void *name(void *ptr, size_t count, void **abstract) #define LIBSSH2_FREE_FUNC(name) void name(void *ptr, void **abstract) typedef struct _LIBSSH2_USERAUTH_KBDINT_PROMPT { char* text; unsigned int length; unsigned char echo; } LIBSSH2_USERAUTH_KBDINT_PROMPT; typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE { char* text; unsigned int length; } LIBSSH2_USERAUTH_KBDINT_RESPONSE; /* 'keyboard-interactive' authentication callback */ #define LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC(name_) void name_(const char* name, int name_len, const char* instruction, int instruction_len, int num_prompts, const LIBSSH2_USERAUTH_KBDINT_PROMPT* prompts, LIBSSH2_USERAUTH_KBDINT_RESPONSE* responses, void **abstract) /* Callbacks for reading and writing to a custom stream */ #define LIBSSH2_WRITE_FUNC(name) int name(uint8_t *buffer, int length, LIBSSH2_SESSION *session, void *userInfo) #define LIBSSH2_READ_FUNC(name) int name(uint8_t *buffer, int length, LIBSSH2_SESSION *session, void *userInfo) /* Callbacks for special SSH packets */ #define LIBSSH2_IGNORE_FUNC(name) void name(LIBSSH2_SESSION *session, const char *message, int message_len, void **abstract) #define LIBSSH2_DEBUG_FUNC(name) void name(LIBSSH2_SESSION *session, int always_display, const char *message, int message_len, const char *language, int language_len,void **abstract) #define LIBSSH2_DISCONNECT_FUNC(name) void name(LIBSSH2_SESSION *session, int reason, const char *message, int message_len, const char *language, int language_len, void **abstract) #define LIBSSH2_PASSWD_CHANGEREQ_FUNC(name) void name(LIBSSH2_SESSION *session, char **newpw, int *newpw_len, void **abstract) #define LIBSSH2_MACERROR_FUNC(name) int name(LIBSSH2_SESSION *session, const char *packet, int packet_len, void **abstract) #define LIBSSH2_X11_OPEN_FUNC(name) void name(LIBSSH2_SESSION *session, LIBSSH2_CHANNEL *channel, char *shost, int sport, void **abstract) #define LIBSSH2_CHANNEL_CLOSE_FUNC(name) void name(LIBSSH2_SESSION *session, void **session_abstract, LIBSSH2_CHANNEL *channel, void **channel_abstract) /* libssh2_session_callback_set() constants */ #define LIBSSH2_CALLBACK_IGNORE 0 #define LIBSSH2_CALLBACK_DEBUG 1 #define LIBSSH2_CALLBACK_DISCONNECT 2 #define LIBSSH2_CALLBACK_MACERROR 3 #define LIBSSH2_CALLBACK_X11 4 /* libssh2_session_method_pref() constants */ #define LIBSSH2_METHOD_KEX 0 #define LIBSSH2_METHOD_HOSTKEY 1 #define LIBSSH2_METHOD_CRYPT_CS 2 #define LIBSSH2_METHOD_CRYPT_SC 3 #define LIBSSH2_METHOD_MAC_CS 4 #define LIBSSH2_METHOD_MAC_SC 5 #define LIBSSH2_METHOD_COMP_CS 6 #define LIBSSH2_METHOD_COMP_SC 7 #define LIBSSH2_METHOD_LANG_CS 8 #define LIBSSH2_METHOD_LANG_SC 9 /* session.flags bits */ #define LIBSSH2_FLAG_SIGPIPE 0x00000001 typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION; typedef struct _LIBSSH2_CHANNEL LIBSSH2_CHANNEL; typedef struct _LIBSSH2_LISTENER LIBSSH2_LISTENER; typedef struct _LIBSSH2_POLLFD { unsigned char type; /* LIBSSH2_POLLFD_* below */ union { int socket; /* File descriptors -- examined with system select() call */ LIBSSH2_CHANNEL *channel; /* Examined by checking internal state */ LIBSSH2_LISTENER *listener; /* Read polls only -- are inbound connections waiting to be accepted? */ } fd; unsigned long events; /* Requested Events */ unsigned long revents; /* Returned Events */ } LIBSSH2_POLLFD; /* Poll FD Descriptor Types */ #define LIBSSH2_POLLFD_SOCKET 1 #define LIBSSH2_POLLFD_CHANNEL 2 #define LIBSSH2_POLLFD_LISTENER 3 /* Note: Win32 Doesn't actually have a poll() implementation, so some of these values are faked with select() data */ /* Poll FD events/revents -- Match sys/poll.h where possible */ #define LIBSSH2_POLLFD_POLLIN 0x0001 /* Data available to be read or connection available -- All */ #define LIBSSH2_POLLFD_POLLPRI 0x0002 /* Priority data available to be read -- Socket only */ #define LIBSSH2_POLLFD_POLLEXT 0x0002 /* Extended data available to be read -- Channel only */ #define LIBSSH2_POLLFD_POLLOUT 0x0004 /* Can may be written -- Socket/Channel */ /* revents only */ #define LIBSSH2_POLLFD_POLLERR 0x0008 /* Error Condition -- Socket */ #define LIBSSH2_POLLFD_POLLHUP 0x0010 /* HangUp/EOF -- Socket */ #define LIBSSH2_POLLFD_SESSION_CLOSED 0x0010 /* Session Disconnect */ #define LIBSSH2_POLLFD_POLLNVAL 0x0020 /* Invalid request -- Socket Only */ #define LIBSSH2_POLLFD_POLLEX 0x0040 /* Exception Condition -- Socket/Win32 */ #define LIBSSH2_POLLFD_CHANNEL_CLOSED 0x0080 /* Channel Disconnect */ #define LIBSSH2_POLLFD_LISTENER_CLOSED 0x0080 /* Listener Disconnect */ /* Hash Types */ #define LIBSSH2_HOSTKEY_HASH_MD5 1 #define LIBSSH2_HOSTKEY_HASH_SHA1 2 /* Disconnect Codes (defined by SSH protocol) */ #define SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 #define SSH_DISCONNECT_PROTOCOL_ERROR 2 #define SSH_DISCONNECT_KEY_EXCHANGE_FAILED 3 #define SSH_DISCONNECT_RESERVED 4 #define SSH_DISCONNECT_MAC_ERROR 5 #define SSH_DISCONNECT_COMPRESSION_ERROR 6 #define SSH_DISCONNECT_SERVICE_NOT_AVAILABLE 7 #define SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 #define SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 #define SSH_DISCONNECT_CONNECTION_LOST 10 #define SSH_DISCONNECT_BY_APPLICATION 11 #define SSH_DISCONNECT_TOO_MANY_CONNECTIONS 12 #define SSH_DISCONNECT_AUTH_CANCELLED_BY_USER 13 #define SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 #define SSH_DISCONNECT_ILLEGAL_USER_NAME 15 /* Error Codes (defined by libssh2) */ #define LIBSSH2_ERROR_SOCKET_NONE -1 #define LIBSSH2_ERROR_BANNER_NONE -2 #define LIBSSH2_ERROR_BANNER_SEND -3 #define LIBSSH2_ERROR_INVALID_MAC -4 #define LIBSSH2_ERROR_KEX_FAILURE -5 #define LIBSSH2_ERROR_ALLOC -6 #define LIBSSH2_ERROR_SOCKET_SEND -7 #define LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE -8 #define LIBSSH2_ERROR_TIMEOUT -9 #define LIBSSH2_ERROR_HOSTKEY_INIT -10 #define LIBSSH2_ERROR_HOSTKEY_SIGN -11 #define LIBSSH2_ERROR_DECRYPT -12 #define LIBSSH2_ERROR_SOCKET_DISCONNECT -13 #define LIBSSH2_ERROR_PROTO -14 #define LIBSSH2_ERROR_PASSWORD_EXPIRED -15 #define LIBSSH2_ERROR_FILE -16 #define LIBSSH2_ERROR_METHOD_NONE -17 #define LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED -18 #define LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED -19 #define LIBSSH2_ERROR_CHANNEL_OUTOFORDER -20 #define LIBSSH2_ERROR_CHANNEL_FAILURE -21 #define LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED -22 #define LIBSSH2_ERROR_CHANNEL_UNKNOWN -23 #define LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED -24 #define LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED -25 #define LIBSSH2_ERROR_CHANNEL_CLOSED -26 #define LIBSSH2_ERROR_CHANNEL_EOF_SENT -27 #define LIBSSH2_ERROR_SCP_PROTOCOL -28 #define LIBSSH2_ERROR_ZLIB -29 #define LIBSSH2_ERROR_SOCKET_TIMEOUT -30 #define LIBSSH2_ERROR_SFTP_PROTOCOL -31 #define LIBSSH2_ERROR_REQUEST_DENIED -32 #define LIBSSH2_ERROR_METHOD_NOT_SUPPORTED -33 #define LIBSSH2_ERROR_INVAL -34 #define LIBSSH2_ERROR_INVALID_POLL_TYPE -35 #define LIBSSH2_ERROR_PUBLICKEY_PROTOCOL -36 /* Session API */ LIBSSH2_API LIBSSH2_SESSION *libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), LIBSSH2_FREE_FUNC((*my_free)), LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract); #define libssh2_session_init() libssh2_session_init_ex(NULL, NULL, NULL, NULL) LIBSSH2_API void **libssh2_session_abstract(LIBSSH2_SESSION *session); /* Custom Read/Write functors */ LIBSSH2_API void libssh2_session_set_user_info(LIBSSH2_SESSION *session, void *ui); LIBSSH2_API void libssh2_session_set_write(LIBSSH2_SESSION *session, void *callback); LIBSSH2_API void libssh2_session_set_read(LIBSSH2_SESSION *session, void *callback); LIBSSH2_API void *libssh2_session_callback_set(LIBSSH2_SESSION *session, int cbtype, void *callback); LIBSSH2_API int libssh2_banner_set(LIBSSH2_SESSION *session, const char *banner); LIBSSH2_API int libssh2_session_startup(LIBSSH2_SESSION *session, int socket); LIBSSH2_API int libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason, const char *description, const char *lang); #define libssh2_session_disconnect(session, description) libssh2_session_disconnect_ex((session), SSH_DISCONNECT_BY_APPLICATION, (description), "") LIBSSH2_API void libssh2_session_free(LIBSSH2_SESSION *session); LIBSSH2_API const char *libssh2_hostkey_hash(LIBSSH2_SESSION *session, int hash_type); LIBSSH2_API int libssh2_session_method_pref(LIBSSH2_SESSION *session, int method_type, const char *prefs); LIBSSH2_API const char *libssh2_session_methods(LIBSSH2_SESSION *session, int method_type); LIBSSH2_API int libssh2_session_last_error(LIBSSH2_SESSION *session, char **errmsg, int *errmsg_len, int want_buf); LIBSSH2_API int libssh2_session_flag(LIBSSH2_SESSION *session, int flag, int value); /* Userauth API */ LIBSSH2_API char *libssh2_userauth_list(LIBSSH2_SESSION *session, const char *username, unsigned int username_len); LIBSSH2_API int libssh2_userauth_authenticated(LIBSSH2_SESSION *session); LIBSSH2_API int libssh2_userauth_password_ex(LIBSSH2_SESSION *session, const char *username, unsigned int username_len, const char *password, unsigned int password_len, LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb))); #define libssh2_userauth_password(session, username, password) libssh2_userauth_password_ex((session), (username), strlen(username), (password), strlen(password), NULL) LIBSSH2_API int libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, const char *username, unsigned int username_len, const char *publickey, const char *privatekey, const char *passphrase); #define libssh2_userauth_publickey_fromfile(session, username, publickey, privatekey, passphrase) \ libssh2_userauth_publickey_fromfile_ex((session), (username), strlen(username), (publickey), (privatekey), (passphrase)) LIBSSH2_API int libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session, const char *username, unsigned int username_len, const char *publickey, const char *privatekey, const char *passphrase, const char *hostname, unsigned int hostname_len, const char *local_username, unsigned int local_username_len); #define libssh2_userauth_hostbased_fromfile(session, username, publickey, privatekey, passphrase, hostname) \ libssh2_userauth_hostbased_fromfile_ex((session), (username), strlen(username), (publickey), (privatekey), (passphrase), (hostname), strlen(hostname), (username), strlen(username)) /* * response_callback is provided with filled by library prompts array, * but client must allocate and fill individual responses. Responses * array is already allocated. Responses data will be freed by libssh2 * after callback return, but before subsequent callback invokation. */ LIBSSH2_API int libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION* session, const char *username, unsigned int username_len, LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*response_callback))); #define libssh2_userauth_keyboard_interactive(session, username, response_callback) \ libssh2_userauth_keyboard_interactive_ex((session), (username), strlen(username), (response_callback)) LIBSSH2_API int libssh2_poll(LIBSSH2_POLLFD *fds, unsigned int nfds, long timeout); /* Channel API */ #define LIBSSH2_CHANNEL_WINDOW_DEFAULT 65536 #define LIBSSH2_CHANNEL_PACKET_DEFAULT 16384 #define LIBSSH2_CHANNEL_MINADJUST 1024 /* Extended Data Handling */ #define LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL 0 #define LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE 1 #define LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE 2 #define SSH_EXTENDED_DATA_STDERR 1 LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_open_ex(LIBSSH2_SESSION *session, const char *channel_type, unsigned int channel_type_len, unsigned int window_size, unsigned int packet_size, const char *message, unsigned int message_len); #define libssh2_channel_open_session(session) libssh2_channel_open_ex((session), "session", sizeof("session") - 1, LIBSSH2_CHANNEL_WINDOW_DEFAULT, LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, 0) LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *session, char *host, int port, char *shost, int sport); #define libssh2_channel_direct_tcpip(session, host, port) libssh2_channel_direct_tcpip_ex((session), (host), (port), "127.0.0.1", 22) LIBSSH2_API LIBSSH2_LISTENER *libssh2_channel_forward_listen_ex(LIBSSH2_SESSION *session, char *host, int port, int *bound_port, int queue_maxsize); #define libssh2_channel_forward_listen(session, port) libssh2_channel_forward_listen_ex((session), NULL, (port), NULL, 16) LIBSSH2_API int libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener); LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener); LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, char *varname, unsigned int varname_len, char *value, unsigned int value_len); #define libssh2_channel_setenv(channel, varname, value) libssh2_channel_setenv_ex((channel), (varname), strlen(varname), (value), strlen(value)) LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, char *term, unsigned int term_len, char *modes, unsigned int modes_len, int width, int height, int width_px, int height_px); #define libssh2_channel_request_pty(channel, term) libssh2_channel_request_pty_ex((channel), (term), strlen(term), NULL, 0, LIBSSH2_TERM_WIDTH, LIBSSH2_TERM_HEIGHT, LIBSSH2_TERM_WIDTH_PX, LIBSSH2_TERM_HEIGHT_PX) LIBSSH2_API int libssh2_channel_x11_req_ex(LIBSSH2_CHANNEL *channel, int single_connection, char *auth_proto, char *auth_cookie, int screen_number); #define libssh2_channel_x11_req(channel, screen_number) libssh2_channel_x11_req_ex((channel), 0, NULL, NULL, (screen_number)) LIBSSH2_API int libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, const char *request, unsigned int request_len, const char *message, unsigned int message_len); #define libssh2_channel_shell(channel) libssh2_channel_process_startup((channel), "shell", sizeof("shell") - 1, NULL, 0) #define libssh2_channel_exec(channel, command) libssh2_channel_process_startup((channel), "exec", sizeof("exec") - 1, (command), strlen(command)) #define libssh2_channel_subsystem(channel, subsystem) libssh2_channel_process_startup((channel), "subsystem", sizeof("subsystem") - 1, (subsystem), strlen(subsystem)) LIBSSH2_API int libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, int stream_id, char *buf, size_t buflen); #define libssh2_channel_read(channel, buf, buflen) libssh2_channel_read_ex((channel), 0, (char *)(buf), (buflen)) #define libssh2_channel_read_stderr(channel, buf, buflen) libssh2_channel_read_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) LIBSSH2_API int libssh2_poll_channel_read(LIBSSH2_CHANNEL *channel, int extended); LIBSSH2_API unsigned long libssh2_channel_window_read_ex(LIBSSH2_CHANNEL *channel, unsigned long *read_avail, unsigned long *window_size_initial); #define libssh2_channel_window_read(channel) libssh2_channel_window_read_ex((channel), NULL, NULL) LIBSSH2_API unsigned long libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL *channel, unsigned long adjustment, unsigned char force); LIBSSH2_API int libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, const char *buf, size_t buflen); #define libssh2_channel_write(channel, buf, buflen) libssh2_channel_write_ex((channel), 0, (char *)(buf), (buflen)) #define libssh2_channel_write_stderr(channel, buf, buflen) libssh2_channel_write_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) LIBSSH2_API unsigned long libssh2_channel_window_write_ex(LIBSSH2_CHANNEL *channel, unsigned long *window_size_initial); #define libssh2_channel_window_write(channel) libssh2_channel_window_write_ex((channel), NULL) LIBSSH2_API void libssh2_channel_set_blocking(LIBSSH2_CHANNEL *channel, int blocking); LIBSSH2_API void libssh2_channel_handle_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode); /* libssh2_channel_ignore_extended_data() is defined below for BC with version 0.1 * Future uses should use libssh2_channel_handle_extended_data() directly * if LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE is passed, extended data will be read (FIFO) from the standard data channel */ /* DEPRECATED */ #define libssh2_channel_ignore_extended_data(channel, ignore) libssh2_channel_handle_extended_data((channel), (ignore) ? LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE : LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL ) #define LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA -1 #define LIBSSH2_CHANNEL_FLUSH_ALL -2 LIBSSH2_API int libssh2_channel_flush_ex(LIBSSH2_CHANNEL *channel, int streamid); #define libssh2_channel_flush(channel) libssh2_channel_flush_ex((channel), 0) #define libssh2_channel_flush_stderr(channel) libssh2_channel_flush_ex((channel), SSH_EXTENDED_DATA_STDERR) LIBSSH2_API int libssh2_channel_get_exit_status(LIBSSH2_CHANNEL* channel); LIBSSH2_API int libssh2_channel_send_eof(LIBSSH2_CHANNEL *channel); LIBSSH2_API int libssh2_channel_eof(LIBSSH2_CHANNEL *channel); LIBSSH2_API int libssh2_channel_close(LIBSSH2_CHANNEL *channel); LIBSSH2_API int libssh2_channel_wait_closed(LIBSSH2_CHANNEL *channel); LIBSSH2_API int libssh2_channel_free(LIBSSH2_CHANNEL *channel); LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const char *path, struct stat *sb); LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, size_t size, long mtime, long atime); #define libssh2_scp_send(session, path, mode, size) libssh2_scp_send_ex((session), (path), (mode), (size), 0, 0) LIBSSH2_API int libssh2_base64_decode(LIBSSH2_SESSION *session, char **dest, unsigned int *dest_len, char *src, unsigned int src_len); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* LIBSSH2_H */ ================================================ FILE: Example/libssh2_config.h ================================================ /* include/libssh2_config.h. Generated by configure. */ /* include/libssh2_config.h.in. Generated from configure.in by autoheader. */ /* Define to 1 if you have the header file. */ #define HAVE_ERRNO_H 1 /* Define to 1 if you have the header file. */ #define HAVE_FCNTL_H 1 /* Define to 1 if you have the `gettimeofday' function. */ #define HAVE_GETTIMEOFDAY 1 /* Define to 1 if you have the header file. */ #define HAVE_INTTYPES_H 1 /* Define to 1 if you have the header file. */ #define HAVE_MEMORY_H 1 /* Define to 1 if you have the `poll' function. */ #define HAVE_POLL 1 /* Define to 1 if you have the `select' function. */ #define HAVE_SELECT 1 /* Define to 1 if you have the header file. */ #define HAVE_STDINT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDIO_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDLIB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRINGS_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRING_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_SELECT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_STAT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_TYPES_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_UIO_H 1 /* Define to 1 if you have the header file. */ #define HAVE_UNISTD_H 1 /* Enable "none" cipher -- NOT RECOMMENDED */ /* #undef LIBSSH2_CRYPT_NONE */ /* Output connection layer debugging info to stderr */ /* #undef LIBSSH2_DEBUG_CONNECTION */ //#define LIBSSH2_DEBUG_CONNECTION /* Output failure events to stderr */ /* #undef LIBSSH2_DEBUG_ERRORS */ /* Output Key Exchange debugging info to stderr */ /* #undef LIBSSH2_DEBUG_KEX */ /* Output publickey subsystem debugging info to stderr */ /* #undef LIBSSH2_DEBUG_PUBLICKEY */ /* Output scp subsystem debugging info to stderr */ /* #undef LIBSSH2_DEBUG_SCP */ /* Output sftp subsystem debugging info to stderr */ /* #undef LIBSSH2_DEBUG_SFTP */ /* Output transport layer debugging info to stderr */ /* #undef LIBSSH2_DEBUG_TRANSPORT */ /* Output userauth layer debugging info to stderr */ /* #undef LIBSSH2_DEBUG_USERAUTH */ /* Enable newer diffie-hellman-group-exchange-sha1 syntax */ #define LIBSSH2_DH_GEX_NEW 1 /* Compile in zlib support */ #define LIBSSH2_HAVE_ZLIB 1 /* Enable "none" MAC -- NOT RECOMMENDED */ /* #undef LIBSSH2_MAC_NONE */ /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "sarag@libssh2.org" /* Define to the full name of this package. */ #define PACKAGE_NAME "libssh2" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "libssh2 0.14" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "libssh2" /* Define to the version of this package. */ #define PACKAGE_VERSION "0.14" /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel and VAX). */ #define WORDS_BIGENDIAN 1 /* Define to empty if `const' does not conform to ANSI C. */ /* #undef const */ ================================================ FILE: Example/libssh2_priv.h ================================================ /* Copyright (c) 2004-2007, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #ifndef LIBSSH2_PRIV_H #define LIBSSH2_PRIV_H 1 #define LIBSSH2_LIBRARY #include "libssh2_config.h" #include "libssh2.h" #include #ifndef WIN32 #include #endif #ifdef LIBSSH2_LIBGCRYPT #include "libgcrypt.h" #else #include "openssl.h" #endif #define LIBSSH2_ALLOC(session, count) session->alloc((count), &(session)->abstract) #define LIBSSH2_REALLOC(session, ptr, count) ((ptr) ? session->realloc((ptr), (count), &(session)->abstract) : session->alloc((count), &(session)->abstract)) #define LIBSSH2_FREE(session, ptr) session->free((ptr), &(session)->abstract) #define LIBSSH2_IGNORE(session, data, datalen) session->ssh_msg_ignore((session), (data), (datalen), &(session)->abstract) #define LIBSSH2_DEBUG(session, always_display, message, message_len, language, language_len) \ session->ssh_msg_disconnect((session), (always_display), (message), (message_len), (language), (language_len), &(session)->abstract) #define LIBSSH2_DISCONNECT(session, reason, message, message_len, language, language_len) \ session->ssh_msg_disconnect((session), (reason), (message), (message_len), (language), (language_len), &(session)->abstract) #define LIBSSH2_MACERROR(session, data, datalen) session->macerror((session), (data), (datalen), &(session)->abstract) #define LIBSSH2_X11_OPEN(channel, shost, sport) channel->session->x11(((channel)->session), (channel), (shost), (sport), (&(channel)->session->abstract)) #define LIBSSH2_CHANNEL_CLOSE(session, channel) channel->close_cb((session), &(session)->abstract, (channel), &(channel)->abstract) #define LIBSSH2_WRITE(session, buffer, length) session->ssh_write((uint8_t *)buffer, length, session, session->userInfo) #define LIBSSH2_READ(session, buffer, length) session->ssh_read((uint8_t *)buffer, length, session, session->userInfo) typedef struct _LIBSSH2_KEX_METHOD LIBSSH2_KEX_METHOD; typedef struct _LIBSSH2_HOSTKEY_METHOD LIBSSH2_HOSTKEY_METHOD; typedef struct _LIBSSH2_MAC_METHOD LIBSSH2_MAC_METHOD; typedef struct _LIBSSH2_CRYPT_METHOD LIBSSH2_CRYPT_METHOD; typedef struct _LIBSSH2_COMP_METHOD LIBSSH2_COMP_METHOD; typedef struct _LIBSSH2_PACKET LIBSSH2_PACKET; typedef struct _LIBSSH2_PACKET_BRIGADE LIBSSH2_PACKET_BRIGADE; typedef struct _LIBSSH2_CHANNEL_BRIGADE LIBSSH2_CHANNEL_BRIGADE; struct _LIBSSH2_PACKET { unsigned char type; /* Unencrypted Payload (no type byte, no padding, just the facts ma'am) */ unsigned char *data; unsigned long data_len; /* Where to start reading data from, * used for channel data that's been partially consumed */ unsigned long data_head; /* Can the message be confirmed? */ int mac; LIBSSH2_PACKET_BRIGADE *brigade; LIBSSH2_PACKET *next, *prev; }; struct _LIBSSH2_PACKET_BRIGADE { LIBSSH2_PACKET *head, *tail; }; typedef struct _libssh2_channel_data { /* Identifier */ unsigned long id; /* Limits and restrictions */ unsigned long window_size_initial, window_size, packet_size; /* Set to 1 when CHANNEL_CLOSE / CHANNEL_EOF sent/received */ char close, eof, extended_data_ignore_mode; } libssh2_channel_data; struct _LIBSSH2_CHANNEL { unsigned char *channel_type; unsigned channel_type_len; int blocking; /* channel's program exit status */ int exit_status; libssh2_channel_data local, remote; unsigned long adjust_queue; /* Amount of bytes to be refunded to receive window (but not yet sent) */ LIBSSH2_SESSION *session; LIBSSH2_CHANNEL *next, *prev; void *abstract; LIBSSH2_CHANNEL_CLOSE_FUNC((*close_cb)); }; struct _LIBSSH2_CHANNEL_BRIGADE { LIBSSH2_CHANNEL *head, *tail; }; struct _LIBSSH2_LISTENER { LIBSSH2_SESSION *session; char *host; int port; LIBSSH2_CHANNEL *queue; int queue_size; int queue_maxsize; LIBSSH2_LISTENER *prev, *next; }; typedef struct _libssh2_endpoint_data { unsigned char *banner; unsigned char *kexinit; unsigned long kexinit_len; LIBSSH2_CRYPT_METHOD *crypt; void *crypt_abstract; LIBSSH2_MAC_METHOD *mac; unsigned long seqno; void *mac_abstract; LIBSSH2_COMP_METHOD *comp; void *comp_abstract; /* Method Preferences -- NULL yields "load order" */ char *crypt_prefs; char *mac_prefs; char *comp_prefs; char *lang_prefs; } libssh2_endpoint_data; struct _LIBSSH2_SESSION { /* Memory management callbacks */ void *abstract; LIBSSH2_ALLOC_FUNC((*alloc)); LIBSSH2_REALLOC_FUNC((*realloc)); LIBSSH2_FREE_FUNC((*free)); /* Read/Write callbacks */ LIBSSH2_WRITE_FUNC((*ssh_write)); LIBSSH2_READ_FUNC((*ssh_read)); void *userInfo; /* Other callbacks */ LIBSSH2_IGNORE_FUNC((*ssh_msg_ignore)); LIBSSH2_DEBUG_FUNC((*ssh_msg_debug)); LIBSSH2_DISCONNECT_FUNC((*ssh_msg_disconnect)); LIBSSH2_MACERROR_FUNC((*macerror)); LIBSSH2_X11_OPEN_FUNC((*x11)); /* Method preferences -- NULL yields "load order" */ char *kex_prefs; char *hostkey_prefs; int state; int flags; /* Agreed Key Exchange Method */ LIBSSH2_KEX_METHOD *kex; int burn_optimistic_kexinit:1; unsigned char *session_id; unsigned long session_id_len; /* Server's public key */ LIBSSH2_HOSTKEY_METHOD *hostkey; void *server_hostkey_abstract; /* Either set with libssh2_session_hostkey() (for server mode) * Or read from server in (eg) KEXDH_INIT (for client mode) */ unsigned char *server_hostkey; unsigned long server_hostkey_len; #if LIBSSH2_MD5 unsigned char server_hostkey_md5[MD5_DIGEST_LENGTH]; #endif /* ! LIBSSH2_MD5 */ unsigned char server_hostkey_sha1[SHA_DIGEST_LENGTH]; /* (remote as source of data -- packet_read ) */ libssh2_endpoint_data remote; /* (local as source of data -- packet_write ) */ libssh2_endpoint_data local; /* Inbound Data buffer -- Sometimes the packet that comes in isn't the packet we're ready for */ LIBSSH2_PACKET_BRIGADE packets; /* Active connection channels */ LIBSSH2_CHANNEL_BRIGADE channels; unsigned long next_channel; LIBSSH2_LISTENER *listeners; /* Actual I/O socket */ int socket_fd; int socket_block; int socket_state; /* Error tracking */ char *err_msg; unsigned long err_msglen; int err_should_free; int err_code; }; /* session.state bits */ #define LIBSSH2_STATE_EXCHANGING_KEYS 0x00000001 #define LIBSSH2_STATE_NEWKEYS 0x00000002 #define LIBSSH2_STATE_AUTHENTICATED 0x00000004 /* session.flag helpers */ #ifdef MSG_NOSIGNAL #define LIBSSH2_SOCKET_SEND_FLAGS(session) (((session)->flags & LIBSSH2_FLAG_SIGPIPE) ? 0 : MSG_NOSIGNAL) #define LIBSSH2_SOCKET_RECV_FLAGS(session) (((session)->flags & LIBSSH2_FLAG_SIGPIPE) ? 0 : MSG_NOSIGNAL) #else /* If MSG_NOSIGNAL isn't defined we're SOL on blocking SIGPIPE */ #define LIBSSH2_SOCKET_SEND_FLAGS(session) 0 #define LIBSSH2_SOCKET_RECV_FLAGS(session) 0 #endif /* libssh2 extensible ssh api, ultimately I'd like to allow loading additional methods via .so/.dll */ struct _LIBSSH2_KEX_METHOD { const char *name; /* Key exchange, populates session->* and returns 0 on success, non-0 on error */ int (*exchange_keys)(LIBSSH2_SESSION *session); long flags; }; struct _LIBSSH2_HOSTKEY_METHOD { const char *name; unsigned long hash_len; int (*init)(LIBSSH2_SESSION *session, unsigned char *hostkey_data, unsigned long hostkey_data_len, void **abstract); int (*initPEM)(LIBSSH2_SESSION *session, const char *privkeyfile, unsigned const char *passphrase, void **abstract); int (*sig_verify)(LIBSSH2_SESSION *session, const unsigned char *sig, unsigned long sig_len, const unsigned char *m, unsigned long m_len, void **abstract); int (*signv)(LIBSSH2_SESSION *session, unsigned char **signature, unsigned long *signature_len, unsigned long veccount, const struct iovec datavec[], void **abstract); int (*encrypt)(LIBSSH2_SESSION *session, unsigned char **dst, unsigned long *dst_len, const unsigned char *src, unsigned long src_len, void **abstract); int (*dtor)(LIBSSH2_SESSION *session, void **abstract); }; struct _LIBSSH2_CRYPT_METHOD { const char *name; int blocksize; /* iv and key sizes (-1 for variable length) */ int iv_len; int secret_len; long flags; int (*init)(LIBSSH2_SESSION *session, LIBSSH2_CRYPT_METHOD *method, unsigned char *iv, int *free_iv, unsigned char *secret, int *free_secret, int encrypt, void **abstract); int (*crypt)(LIBSSH2_SESSION *session, unsigned char *block, void **abstract); int (*dtor)(LIBSSH2_SESSION *session, void **abstract); _libssh2_cipher_type(algo); }; struct _LIBSSH2_COMP_METHOD { const char *name; int (*init)(LIBSSH2_SESSION *session, int compress, void **abstract); int (*comp)(LIBSSH2_SESSION *session, int compress, unsigned char **dest, unsigned long *dest_len, unsigned long payload_limit, int *free_dest, const unsigned char *src, unsigned long src_len, void **abstract); int (*dtor)(LIBSSH2_SESSION *session, int compress, void **abstract); }; struct _LIBSSH2_MAC_METHOD { const char *name; /* The length of a given MAC packet */ int mac_len; /* integrity key length */ int key_len; /* Message Authentication Code Hashing algo */ int (*init)(LIBSSH2_SESSION *session, unsigned char *key, int *free_key, void **abstract); int (*hash)(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, const unsigned char *packet, unsigned long packet_len, const unsigned char *addtl, unsigned long addtl_len, void **abstract); int (*dtor)(LIBSSH2_SESSION *session, void **abstract); }; #if defined(LIBSSH2_DEBUG_TRANSPORT) || defined(LIBSSH2_DEBUG_KEX) || defined(LIBSSH2_DEBUG_USERAUTH) || defined(LIBSSH2_DEBUG_CONNECTION) || defined(LIBSSH2_DEBUG_SCP) || defined(LIBSSH2_DEBUG_SFTP) || defined(LIBSSH2_DEBUG_ERRORS) #define LIBSSH2_DEBUG_ENABLED /* Internal debugging contexts -- Used with --enable-debug-* */ #define LIBSSH2_DBG_TRANS 1 #define LIBSSH2_DBG_KEX 2 #define LIBSSH2_DBG_AUTH 3 #define LIBSSH2_DBG_CONN 4 #define LIBSSH2_DBG_SCP 5 #define LIBSSH2_DBG_SFTP 6 #define LIBSSH2_DBG_ERROR 7 #define LIBSSH2_DBG_PUBLICKEY 8 void _libssh2_debug(LIBSSH2_SESSION *session, int context, const char *format, ...); #endif /* LIBSSH2_DEBUG_ENABLED */ #ifdef LIBSSH2_DEBUG_ERRORS #define libssh2_error(session, errcode, errmsg, should_free) \ { \ if (session->err_msg && session->err_should_free) { \ LIBSSH2_FREE(session, session->err_msg); \ } \ session->err_msg = errmsg; \ session->err_msglen = strlen(errmsg); \ session->err_should_free = should_free; \ session->err_code = errcode; \ _libssh2_debug(session, LIBSSH2_DBG_ERROR, "%d - %s", session->err_code, session->err_msg); \ } #else /* ! LIBSSH2_DEBUG_ERRORS */ #define libssh2_error(session, errcode, errmsg, should_free) \ { \ if (session->err_msg && session->err_should_free) { \ LIBSSH2_FREE(session, session->err_msg); \ } \ session->err_msg = errmsg; \ session->err_msglen = strlen(errmsg); \ session->err_should_free = should_free; \ session->err_code = errcode; \ } #endif /* LIBSSH2_DEBUG_ENABLED */ #define LIBSSH2_SOCKET_UNKNOWN 1 #define LIBSSH2_SOCKET_CONNECTED 0 #define LIBSSH2_SOCKET_DISCONNECTED -1 /* Initial packet state, prior to MAC check */ #define LIBSSH2_MAC_UNCONFIRMED 1 /* When MAC type is "none" (proto initiation phase) all packets are deemed "confirmed" */ #define LIBSSH2_MAC_CONFIRMED 0 /* Something very bad is going on */ #define LIBSSH2_MAC_INVALID -1 /* SSH Packet Types -- Defined by internet draft */ /* Transport Layer */ #define SSH_MSG_DISCONNECT 1 #define SSH_MSG_IGNORE 2 #define SSH_MSG_UNIMPLEMENTED 3 #define SSH_MSG_DEBUG 4 #define SSH_MSG_SERVICE_REQUEST 5 #define SSH_MSG_SERVICE_ACCEPT 6 #define SSH_MSG_KEXINIT 20 #define SSH_MSG_NEWKEYS 21 /* diffie-hellman-group1-sha1 */ #define SSH_MSG_KEXDH_INIT 30 #define SSH_MSG_KEXDH_REPLY 31 /* diffie-hellman-group-exchange-sha1 */ #define SSH_MSG_KEX_DH_GEX_REQUEST_OLD 30 #define SSH_MSG_KEX_DH_GEX_REQUEST 34 #define SSH_MSG_KEX_DH_GEX_GROUP 31 #define SSH_MSG_KEX_DH_GEX_INIT 32 #define SSH_MSG_KEX_DH_GEX_REPLY 33 /* User Authentication */ #define SSH_MSG_USERAUTH_REQUEST 50 #define SSH_MSG_USERAUTH_FAILURE 51 #define SSH_MSG_USERAUTH_SUCCESS 52 #define SSH_MSG_USERAUTH_BANNER 53 /* "public key" method */ #define SSH_MSG_USERAUTH_PK_OK 60 /* "password" method */ #define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* "keyboard-interactive" method */ #define SSH_MSG_USERAUTH_INFO_REQUEST 60 #define SSH_MSG_USERAUTH_INFO_RESPONSE 61 /* Channels */ #define SSH_MSG_GLOBAL_REQUEST 80 #define SSH_MSG_REQUEST_SUCCESS 81 #define SSH_MSG_REQUEST_FAILURE 82 #define SSH_MSG_CHANNEL_OPEN 90 #define SSH_MSG_CHANNEL_OPEN_CONFIRMATION 91 #define SSH_MSG_CHANNEL_OPEN_FAILURE 92 #define SSH_MSG_CHANNEL_WINDOW_ADJUST 93 #define SSH_MSG_CHANNEL_DATA 94 #define SSH_MSG_CHANNEL_EXTENDED_DATA 95 #define SSH_MSG_CHANNEL_EOF 96 #define SSH_MSG_CHANNEL_CLOSE 97 #define SSH_MSG_CHANNEL_REQUEST 98 #define SSH_MSG_CHANNEL_SUCCESS 99 #define SSH_MSG_CHANNEL_FAILURE 100 void libssh2_session_shutdown(LIBSSH2_SESSION *session); unsigned long libssh2_ntohu32(const unsigned char *buf); libssh2_uint64_t libssh2_ntohu64(const unsigned char *buf); void libssh2_htonu32(unsigned char *buf, unsigned long val); void libssh2_htonu64(unsigned char *buf, libssh2_uint64_t val); int libssh2_packet_read(LIBSSH2_SESSION *session, int block); int libssh2_packet_ask_ex(LIBSSH2_SESSION *session, unsigned char packet_type, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len, int poll_socket); #define libssh2_packet_ask(session, packet_type, data, data_len, poll_socket) \ libssh2_packet_ask_ex((session), (packet_type), (data), (data_len), 0, NULL, 0, (poll_socket)) int libssh2_packet_askv_ex(LIBSSH2_SESSION *session, unsigned char *packet_types, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len, int poll_socket); #define libssh2_packet_askv(session, packet_types, data, data_len, poll_socket) \ libssh2_packet_askv_ex((session), (packet_types), (data), (data_len), 0, NULL, 0, (poll_socket)) int libssh2_packet_require_ex(LIBSSH2_SESSION *session, unsigned char packet_type, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len); #define libssh2_packet_require(session, packet_type, data, data_len) \ libssh2_packet_require_ex((session), (packet_type), (data), (data_len), 0, NULL, 0) int libssh2_packet_requirev_ex(LIBSSH2_SESSION *session, unsigned char *packet_types, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len); #define libssh2_packet_requirev(session, packet_types, data, data_len) \ libssh2_packet_requirev_ex((session), (packet_types), (data), (data_len), 0, NULL, 0) int libssh2_packet_burn(LIBSSH2_SESSION *session); int libssh2_packet_write(LIBSSH2_SESSION *session, unsigned char *data, unsigned long data_len); int libssh2_kex_exchange(LIBSSH2_SESSION *session, int reexchange); unsigned long libssh2_channel_nextid(LIBSSH2_SESSION *session); LIBSSH2_CHANNEL *libssh2_channel_locate(LIBSSH2_SESSION *session, unsigned long channel_id); /* Let crypt.c/hostkey.c/comp.c/mac.c expose their method structs */ LIBSSH2_CRYPT_METHOD **libssh2_crypt_methods(void); LIBSSH2_HOSTKEY_METHOD **libssh2_hostkey_methods(void); LIBSSH2_COMP_METHOD **libssh2_comp_methods(void); LIBSSH2_MAC_METHOD **libssh2_mac_methods(void); /* Language API doesn't exist yet. Just act like we've agreed on a language */ #define libssh2_kex_agree_lang(session, endpoint, str, str_len) 0 /* pem.c */ int _libssh2_pem_parse (LIBSSH2_SESSION *session, const char *headerbegin, const char *headerend, FILE *fp, char **data, unsigned int *datalen); int _libssh2_pem_decode_sequence (unsigned char **data, unsigned int *datalen); int _libssh2_pem_decode_integer (unsigned char **data, unsigned int *datalen, unsigned char **i, unsigned int *ilen); #endif /* LIBSSH2_H */ ================================================ FILE: Example/libssh2_publickey.h ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ /* Note: This include file is only needed for using the * publickey SUBSYSTEM which is not the same as publickey * authentication. For authentication you only need libssh2.h * * For more information on the publickey subsystem, * refer to IETF draft: secsh-publickey */ #ifndef LIBSSH2_PUBLICKEY_H #define LIBSSH2_PUBLICKEY_H 1 typedef struct _LIBSSH2_PUBLICKEY LIBSSH2_PUBLICKEY; typedef struct _libssh2_publickey_attribute { char *name; unsigned long name_len; char *value; unsigned long value_len; char mandatory; } libssh2_publickey_attribute; typedef struct _libssh2_publickey_list { unsigned char *packet; /* For freeing */ unsigned char *name; unsigned long name_len; unsigned char *blob; unsigned long blob_len; unsigned long num_attrs; libssh2_publickey_attribute *attrs; /* free me */ } libssh2_publickey_list; /* Generally use the first macro here, but if both name and value are string literals, you can use _fast() to take advantage of preprocessing */ #define libssh2_publickey_attribute(name, value, mandatory) { (name), strlen(name), (value), strlen(value), (mandatory) }, #define libssh2_publickey_attribute_fast(name, value, mandatory) { (name), sizeof(name) - 1, (value), sizeof(value) - 1, (mandatory) }, #ifdef __cplusplus extern "C" { #endif /* Publickey Subsystem */ LIBSSH2_API LIBSSH2_PUBLICKEY *libssh2_publickey_init(LIBSSH2_SESSION *session); LIBSSH2_API int libssh2_publickey_add_ex(LIBSSH2_PUBLICKEY *pkey, const unsigned char *name, unsigned long name_len, const unsigned char *blob, unsigned long blob_len, char overwrite, unsigned long num_attrs, libssh2_publickey_attribute attrs[]); #define libssh2_publickey_add(pkey, name, blob, blob_len, overwrite, num_attrs, attrs) \ libssh2_publickey_add_ex((pkey), (name), strlen(name), (blob), (blob_len), (overwrite), (num_attrs), (attrs)) LIBSSH2_API int libssh2_publickey_remove_ex(LIBSSH2_PUBLICKEY *pkey, const unsigned char *name, unsigned long name_len, const unsigned char *blob, unsigned long blob_len); #define libssh2_publickey_remove(pkey, name, blob, blob_len) \ libssh2_publickey_remove_ex((pkey), (name), strlen(name), (blob), (blob_len)) LIBSSH2_API int libssh2_publickey_list_fetch(LIBSSH2_PUBLICKEY *pkey, unsigned long *num_keys, libssh2_publickey_list **pkey_list); LIBSSH2_API void libssh2_publickey_list_free(LIBSSH2_PUBLICKEY *pkey, libssh2_publickey_list *pkey_list); LIBSSH2_API void libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* ndef: LIBSSH2_PUBLICKEY_H */ ================================================ FILE: Example/libssh2_sftp.h ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #ifndef LIBSSH2_SFTP_H #define LIBSSH2_SFTP_H 1 #ifdef __cplusplus extern "C" { #endif /* Note: Version 6 was documented at the time of writing * However it was marked as "DO NOT IMPLEMENT" due to pending changes * * Let's start with Version 3 (The version found in OpenSSH) and go from there */ #define LIBSSH2_SFTP_VERSION 3 #define LIBSSH2_SFTP_PACKET_MAXLEN 40000 typedef struct _LIBSSH2_SFTP LIBSSH2_SFTP; typedef struct _LIBSSH2_SFTP_HANDLE LIBSSH2_SFTP_HANDLE; typedef struct _LIBSSH2_SFTP_ATTRIBUTES LIBSSH2_SFTP_ATTRIBUTES; /* Flags for open_ex() */ #define LIBSSH2_SFTP_OPENFILE 0 #define LIBSSH2_SFTP_OPENDIR 1 /* Flags for rename_ex() */ #define LIBSSH2_SFTP_RENAME_OVERWRITE 0x00000001 #define LIBSSH2_SFTP_RENAME_ATOMIC 0x00000002 #define LIBSSH2_SFTP_RENAME_NATIVE 0x00000004 /* Flags for stat_ex() */ #define LIBSSH2_SFTP_STAT 0 #define LIBSSH2_SFTP_LSTAT 1 #define LIBSSH2_SFTP_SETSTAT 2 /* Flags for symlink_ex() */ #define LIBSSH2_SFTP_SYMLINK 0 #define LIBSSH2_SFTP_READLINK 1 #define LIBSSH2_SFTP_REALPATH 2 /* SFTP attribute flag bits */ #define LIBSSH2_SFTP_ATTR_SIZE 0x00000001 #define LIBSSH2_SFTP_ATTR_UIDGID 0x00000002 #define LIBSSH2_SFTP_ATTR_PERMISSIONS 0x00000004 #define LIBSSH2_SFTP_ATTR_ACMODTIME 0x00000008 #define LIBSSH2_SFTP_ATTR_EXTENDED 0x80000000 struct _LIBSSH2_SFTP_ATTRIBUTES { /* If flags & ATTR_* bit is set, then the value in this struct will be meaningful * Otherwise it should be ignored */ unsigned long flags; libssh2_uint64_t filesize; unsigned long uid, gid; unsigned long permissions; unsigned long atime, mtime; }; /* SFTP filetypes */ #define LIBSSH2_SFTP_TYPE_REGULAR 1 #define LIBSSH2_SFTP_TYPE_DIRECTORY 2 #define LIBSSH2_SFTP_TYPE_SYMLINK 3 #define LIBSSH2_SFTP_TYPE_SPECIAL 4 #define LIBSSH2_SFTP_TYPE_UNKNOWN 5 #define LIBSSH2_SFTP_TYPE_SOCKET 6 #define LIBSSH2_SFTP_TYPE_CHAR_DEVICE 7 #define LIBSSH2_SFTP_TYPE_BLOCK_DEVICE 8 #define LIBSSH2_SFTP_TYPE_FIFO 9 /* * Reproduce the POSIX file modes here for systems that are not * POSIX compliant. * * These is used in "permissions" of "struct _LIBSSH2_SFTP_ATTRIBUTES" */ /* File type */ #define LIBSSH2_SFTP_S_IFMT 0170000 /* type of file mask */ #define LIBSSH2_SFTP_S_IFIFO 0010000 /* named pipe (fifo) */ #define LIBSSH2_SFTP_S_IFCHR 0020000 /* character special */ #define LIBSSH2_SFTP_S_IFDIR 0040000 /* directory */ #define LIBSSH2_SFTP_S_IFBLK 0060000 /* block special */ #define LIBSSH2_SFTP_S_IFREG 0100000 /* regular */ #define LIBSSH2_SFTP_S_IFLNK 0120000 /* symbolic link */ #define LIBSSH2_SFTP_S_IFSOCK 0140000 /* socket */ /* File mode */ /* Read, write, execute/search by owner */ #define LIBSSH2_SFTP_S_IRWXU 0000700 /* RWX mask for owner */ #define LIBSSH2_SFTP_S_IRUSR 0000400 /* R for owner */ #define LIBSSH2_SFTP_S_IWUSR 0000200 /* W for owner */ #define LIBSSH2_SFTP_S_IXUSR 0000100 /* X for owner */ /* Read, write, execute/search by group */ #define LIBSSH2_SFTP_S_IRWXG 0000070 /* RWX mask for group */ #define LIBSSH2_SFTP_S_IRGRP 0000040 /* R for group */ #define LIBSSH2_SFTP_S_IWGRP 0000020 /* W for group */ #define LIBSSH2_SFTP_S_IXGRP 0000010 /* X for group */ /* Read, write, execute/search by others */ #define LIBSSH2_SFTP_S_IRWXO 0000007 /* RWX mask for other */ #define LIBSSH2_SFTP_S_IROTH 0000004 /* R for other */ #define LIBSSH2_SFTP_S_IWOTH 0000002 /* W for other */ #define LIBSSH2_SFTP_S_IXOTH 0000001 /* X for other */ /* SFTP File Transfer Flags -- (e.g. flags parameter to sftp_open()) * Danger will robinson... APPEND doesn't have any effect on OpenSSH servers */ #define LIBSSH2_FXF_READ 0x00000001 #define LIBSSH2_FXF_WRITE 0x00000002 #define LIBSSH2_FXF_APPEND 0x00000004 #define LIBSSH2_FXF_CREAT 0x00000008 #define LIBSSH2_FXF_TRUNC 0x00000010 #define LIBSSH2_FXF_EXCL 0x00000020 /* SFTP Status Codes (returned by libssh2_sftp_last_error() ) */ #define LIBSSH2_FX_OK 0 #define LIBSSH2_FX_EOF 1 #define LIBSSH2_FX_NO_SUCH_FILE 2 #define LIBSSH2_FX_PERMISSION_DENIED 3 #define LIBSSH2_FX_FAILURE 4 #define LIBSSH2_FX_BAD_MESSAGE 5 #define LIBSSH2_FX_NO_CONNECTION 6 #define LIBSSH2_FX_CONNECTION_LOST 7 #define LIBSSH2_FX_OP_UNSUPPORTED 8 #define LIBSSH2_FX_INVALID_HANDLE 9 #define LIBSSH2_FX_NO_SUCH_PATH 10 #define LIBSSH2_FX_FILE_ALREADY_EXISTS 11 #define LIBSSH2_FX_WRITE_PROTECT 12 #define LIBSSH2_FX_NO_MEDIA 13 #define LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM 14 #define LIBSSH2_FX_QUOTA_EXCEEDED 15 #define LIBSSH2_FX_UNKNOWN_PRINCIPLE 16 #define LIBSSH2_FX_LOCK_CONFlICT 17 #define LIBSSH2_FX_DIR_NOT_EMPTY 18 #define LIBSSH2_FX_NOT_A_DIRECTORY 19 #define LIBSSH2_FX_INVALID_FILENAME 20 #define LIBSSH2_FX_LINK_LOOP 21 /* SFTP API */ LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session); LIBSSH2_API int libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp); LIBSSH2_API unsigned long libssh2_sftp_last_error(LIBSSH2_SFTP *sftp); /* File / Directory Ops */ LIBSSH2_API LIBSSH2_SFTP_HANDLE *libssh2_sftp_open_ex(LIBSSH2_SFTP *sftp, char *filename, unsigned int filename_len, unsigned long flags, long mode, int open_type); #define libssh2_sftp_open(sftp, filename, flags, mode) libssh2_sftp_open_ex((sftp), (char *)(filename), strlen((char *)filename), (flags), (mode), LIBSSH2_SFTP_OPENFILE) #define libssh2_sftp_opendir(sftp, path) libssh2_sftp_open_ex((sftp), (char *)(path), strlen((char *)path), 0, 0, LIBSSH2_SFTP_OPENDIR) LIBSSH2_API size_t libssh2_sftp_read(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen); LIBSSH2_API int libssh2_sftp_readdir(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen, LIBSSH2_SFTP_ATTRIBUTES *attrs); LIBSSH2_API size_t libssh2_sftp_write(LIBSSH2_SFTP_HANDLE *handle, const char *buffer, size_t count); LIBSSH2_API int libssh2_sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle); #define libssh2_sftp_close(handle) libssh2_sftp_close_handle(handle) #define libssh2_sftp_closedir(handle) libssh2_sftp_close_handle(handle) LIBSSH2_API void libssh2_sftp_seek(LIBSSH2_SFTP_HANDLE *handle, size_t offset); #define libssh2_sftp_rewind(handle) libssh2_sftp_seek((handle), 0) LIBSSH2_API size_t libssh2_sftp_tell(LIBSSH2_SFTP_HANDLE *handle); LIBSSH2_API int libssh2_sftp_fstat_ex(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_ATTRIBUTES *attrs, int setstat); #define libssh2_sftp_fstat(handle, attrs) libssh2_sftp_fstat_ex((handle), (attrs), 0) #define libssh2_sftp_fsetstat(handle, attrs) libssh2_sftp_fstat_ex((handle), (attrs), 1) /* Miscellaneous Ops */ LIBSSH2_API int libssh2_sftp_rename_ex(LIBSSH2_SFTP *sftp, char *source_filename, unsigned int srouce_filename_len, char *dest_filename, unsigned int dest_filename_len, long flags); #define libssh2_sftp_rename(sftp, sourcefile, destfile) libssh2_sftp_rename_ex((sftp), (sourcefile), strlen(sourcefile), (destfile), strlen(destfile), \ LIBSSH2_SFTP_RENAME_OVERWRITE | LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE) LIBSSH2_API int libssh2_sftp_unlink_ex(LIBSSH2_SFTP *sftp, char *filename, unsigned int filename_len); #define libssh2_sftp_unlink(sftp, filename) libssh2_sftp_unlink_ex((sftp), (filename), strlen(filename)) LIBSSH2_API int libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, char *path, unsigned int path_len, long mode); #define libssh2_sftp_mkdir(sftp, path, mode) libssh2_sftp_mkdir_ex((sftp), (char *)(path), strlen((char *)path), (mode)) LIBSSH2_API int libssh2_sftp_rmdir_ex(LIBSSH2_SFTP *sftp, char *path, unsigned int path_len); #define libssh2_sftp_rmdir(sftp, path) libssh2_sftp_rmdir_ex((sftp), (path), strlen(path)) LIBSSH2_API int libssh2_sftp_stat_ex(LIBSSH2_SFTP *sftp, char *path, unsigned int path_len, int stat_type, LIBSSH2_SFTP_ATTRIBUTES *attrs); #define libssh2_sftp_stat(sftp, path, attrs) libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_STAT, (attrs)) #define libssh2_sftp_lstat(sftp, path, attrs) libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_LSTAT, (attrs)) #define libssh2_sftp_setstat(sftp, path, attrs) libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_SETSTAT, (attrs)) LIBSSH2_API int libssh2_sftp_symlink_ex(LIBSSH2_SFTP *sftp, const char *path, unsigned int path_len, char *target, unsigned int target_len, int link_type); #define libssh2_sftp_symlink(sftp, orig, linkpath) libssh2_sftp_symlink_ex((sftp), (orig), strlen(orig), (linkpath), strlen(linkpath), LIBSSH2_SFTP_SYMLINK) #define libssh2_sftp_readlink(sftp, path, target, maxlen) libssh2_sftp_symlink_ex((sftp), (path), strlen(path), (target), (maxlen), LIBSSH2_SFTP_READLINK) #define libssh2_sftp_realpath(sftp, path, target, maxlen) libssh2_sftp_symlink_ex((sftp), (path), strlen(path), (target), (maxlen), LIBSSH2_SFTP_REALPATH) #ifdef __cplusplus } /* extern "C" */ #endif #endif /* LIBSSH2_SFTP_H */ ================================================ FILE: Example/mac.c ================================================ /* Copyright (c) 2004-2007, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #ifdef LIBSSH2_MAC_NONE /* {{{ libssh2_mac_none_MAC * Minimalist MAC: No MAC */ static int libssh2_mac_none_MAC(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, const unsigned char *packet, unsigned long packet_len, const unsigned char *addtl, unsigned long addtl_len, void **abstract) { return 0; } /* }}} */ static LIBSSH2_MAC_METHOD libssh2_mac_method_none = { "none", 0, 0, NULL, libssh2_mac_none_MAC, NULL }; #endif /* LIBSSH2_MAC_NONE */ /* {{{ libssh2_mac_method_common_init * Initialize simple mac methods */ static int libssh2_mac_method_common_init(LIBSSH2_SESSION *session, unsigned char *key, int *free_key, void **abstract) { *abstract = key; *free_key = 0; (void)session; return 0; } /* }}} */ /* {{{ libssh2_mac_method_common_dtor * Cleanup simple mac methods */ static int libssh2_mac_method_common_dtor(LIBSSH2_SESSION *session, void **abstract) { if (*abstract) { LIBSSH2_FREE(session, *abstract); } *abstract = NULL; return 0; } /* }}} */ /* {{{ libssh2_mac_method_hmac_sha1_hash * Calculate hash using full sha1 value */ static int libssh2_mac_method_hmac_sha1_hash(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, const unsigned char *packet, unsigned long packet_len, const unsigned char *addtl, unsigned long addtl_len, void **abstract) { libssh2_hmac_ctx ctx; unsigned char seqno_buf[4]; (void)session; libssh2_htonu32(seqno_buf, seqno); libssh2_hmac_sha1_init(&ctx, *abstract, 20); libssh2_hmac_update(ctx, seqno_buf, 4); libssh2_hmac_update(ctx, packet, packet_len); if (addtl && addtl_len) { libssh2_hmac_update(ctx, addtl, addtl_len); } libssh2_hmac_final(ctx, buf); libssh2_hmac_cleanup(&ctx); return 0; } /* }}} */ static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_sha1 = { "hmac-sha1", 20, 20, libssh2_mac_method_common_init, libssh2_mac_method_hmac_sha1_hash, libssh2_mac_method_common_dtor, }; /* {{{ libssh2_mac_method_hmac_sha1_96_hash * Calculate hash using first 96 bits of sha1 value */ static int libssh2_mac_method_hmac_sha1_96_hash(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, const unsigned char *packet, unsigned long packet_len, const unsigned char *addtl, unsigned long addtl_len, void **abstract) { unsigned char temp[SHA_DIGEST_LENGTH]; libssh2_mac_method_hmac_sha1_hash(session, temp, seqno, packet, packet_len, addtl, addtl_len, abstract); memcpy(buf, (char *)temp, 96 / 8); return 0; } /* }}} */ static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_sha1_96 = { "hmac-sha1-96", 12, 20, libssh2_mac_method_common_init, libssh2_mac_method_hmac_sha1_96_hash, libssh2_mac_method_common_dtor, }; /* {{{ libssh2_mac_method_hmac_md5_hash * Calculate hash using full md5 value */ static int libssh2_mac_method_hmac_md5_hash(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, const unsigned char *packet, unsigned long packet_len, const unsigned char *addtl, unsigned long addtl_len, void **abstract) { libssh2_hmac_ctx ctx; unsigned char seqno_buf[4]; (void)session; libssh2_htonu32(seqno_buf, seqno); libssh2_hmac_md5_init(&ctx, *abstract, 16); libssh2_hmac_update(ctx, seqno_buf, 4); libssh2_hmac_update(ctx, packet, packet_len); if (addtl && addtl_len) { libssh2_hmac_update(ctx, addtl, addtl_len); } libssh2_hmac_final(ctx, buf); libssh2_hmac_cleanup(&ctx); return 0; } /* }}} */ static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_md5 = { "hmac-md5", 16, 16, libssh2_mac_method_common_init, libssh2_mac_method_hmac_md5_hash, libssh2_mac_method_common_dtor, }; /* {{{ libssh2_mac_method_hmac_md5_96_hash * Calculate hash using first 96 bits of md5 value */ static int libssh2_mac_method_hmac_md5_96_hash(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, const unsigned char *packet, unsigned long packet_len, const unsigned char *addtl, unsigned long addtl_len, void **abstract) { unsigned char temp[MD5_DIGEST_LENGTH]; libssh2_mac_method_hmac_md5_hash(session, temp, seqno, packet, packet_len, addtl, addtl_len, abstract); memcpy(buf, (char *)temp, 96 / 8); return 0; } /* }}} */ static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_md5_96 = { "hmac-md5-96", 12, 16, libssh2_mac_method_common_init, libssh2_mac_method_hmac_md5_96_hash, libssh2_mac_method_common_dtor, }; #if LIBSSH2_HMAC_RIPEMD /* {{{ libssh2_mac_method_hmac_ripemd160_hash * Calculate hash using ripemd160 value */ static int libssh2_mac_method_hmac_ripemd160_hash(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, const unsigned char *packet, unsigned long packet_len, const unsigned char *addtl, unsigned long addtl_len, void **abstract) { libssh2_hmac_ctx ctx; unsigned char seqno_buf[4]; (void)session; libssh2_htonu32(seqno_buf, seqno); libssh2_hmac_ripemd160_init(&ctx, *abstract, 20); libssh2_hmac_update(ctx, seqno_buf, 4); libssh2_hmac_update(ctx, packet, packet_len); if (addtl && addtl_len) { libssh2_hmac_update(ctx, addtl, addtl_len); } libssh2_hmac_final(ctx, buf); libssh2_hmac_cleanup(&ctx); return 0; } /* }}} */ static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_ripemd160 = { "hmac-ripemd160", 20, 20, libssh2_mac_method_common_init, libssh2_mac_method_hmac_ripemd160_hash, libssh2_mac_method_common_dtor, }; static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_ripemd160_openssh_com = { "hmac-ripemd160@openssh.com", 20, 20, libssh2_mac_method_common_init, libssh2_mac_method_hmac_ripemd160_hash, libssh2_mac_method_common_dtor, }; #endif /* LIBSSH2_HMAC_RIPEMD */ static LIBSSH2_MAC_METHOD *_libssh2_mac_methods[] = { &libssh2_mac_method_hmac_sha1, &libssh2_mac_method_hmac_sha1_96, &libssh2_mac_method_hmac_md5, &libssh2_mac_method_hmac_md5_96, #ifdef LIBSSH2_HMAC_RIPEMD &libssh2_mac_method_hmac_ripemd160, &libssh2_mac_method_hmac_ripemd160_openssh_com, #endif /* LIBSSH2_HMAC_RIPEMD */ #ifdef LIBSSH2_MAC_NONE &libssh2_mac_method_none, #endif /* LIBSSH2_MAC_NONE */ NULL }; LIBSSH2_MAC_METHOD **libssh2_mac_methods(void) { return _libssh2_mac_methods; } ================================================ FILE: Example/main.m ================================================ // // main.m // FTPConnection // // Created by Greg Hulands on 3/12/04. // Copyright __MyCompanyName__ 2004. All rights reserved. // #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **) argv); } ================================================ FILE: Example/misc.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" /* {{{ libssh2_ntohu32 */ unsigned long libssh2_ntohu32(const unsigned char *buf) { return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; } /* }}} */ /* {{{ libssh2_ntohu64 * Note: Some 32-bit platforms have issues with bitops on long longs * Work around this by doing expensive (but safer) arithmetic ops with optimization defying parentheses */ libssh2_uint64_t libssh2_ntohu64(const unsigned char *buf) { unsigned long msl, lsl; msl = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; lsl = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; return ((msl * 65536) * 65536) + lsl; } /* }}} */ /* {{{ libssh2_htonu32 */ void libssh2_htonu32(unsigned char *buf, unsigned long value) { buf[0] = (value >> 24) & 0xFF; buf[1] = (value >> 16) & 0xFF; buf[2] = (value >> 8) & 0xFF; buf[3] = value & 0xFF; } /* }}} */ /* {{{ libssh2_htonu64 */ void libssh2_htonu64(unsigned char *buf, libssh2_uint64_t value) { unsigned long msl = (value / 65536) / 65536; buf[0] = (msl >> 24) & 0xFF; buf[1] = (msl >> 16) & 0xFF; buf[2] = (msl >> 8) & 0xFF; buf[3] = msl & 0xFF; buf[4] = (value >> 24) & 0xFF; buf[5] = (value >> 16) & 0xFF; buf[6] = (value >> 8) & 0xFF; buf[7] = value & 0xFF; } /* }}} */ /* Base64 Conversion */ /* {{{ */ static const char libssh2_base64_table[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '\0' }; static const char libssh2_base64_pad = '='; static const short libssh2_base64_reverse_table[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; /* }}} */ /* {{{ libssh2_base64_decode * Decode a base64 chunk and store it into a newly alloc'd buffer */ LIBSSH2_API int libssh2_base64_decode(LIBSSH2_SESSION *session, char **data, unsigned int *datalen, char *src, unsigned int src_len) { unsigned char *s, *d; short v; int i = 0, len = 0; *data = LIBSSH2_ALLOC(session, (3 * src_len / 4) + 1); d = (unsigned char *)*data; if (!d) { return -1; } for(s = (unsigned char *)src; ((char*)s) < (src + src_len); s++) { if ((v = libssh2_base64_reverse_table[*s]) < 0) continue; switch (i % 4) { case 0: d[len] = v << 2; break; case 1: d[len++] |= v >> 4; d[len] = v << 4; break; case 2: d[len++] |= v >> 2; d[len] = v << 6; break; case 3: d[len++] |= v; break; } i++; } if ((i % 4) == 1) { /* Invalid -- We have a byte which belongs exclusively to a partial octet */ LIBSSH2_FREE(session, *data); return -1; } *datalen = len; return 0; } /* }}} */ #ifdef LIBSSH2_DEBUG_ENABLED /* {{{ _libssh2_debug * Internal debug logging facility * Just writes to stderr until a good reason comes up to support anything else */ void _libssh2_debug(LIBSSH2_SESSION *session, int context, const char *format, ...) { char buffer[1536]; int len; va_list vargs; char *contexts[9] = { "Unknown", "Transport", "Key Exhange", "Userauth", "Connection", "scp", "SFTP Subsystem", "Failure Event", "Publickey Subsystem", }; if (context < 1 || context > 8) { context = 0; } len = snprintf(buffer, 1535, "[libssh2] %s(%d): ", contexts[context], session->socket_fd); va_start(vargs, format); len += vsnprintf(buffer + len, 1535 - len, format, vargs); buffer[len] = '\n'; va_end(vargs); write(2, buffer, len + 1); } /* }}} */ #endif ================================================ FILE: Example/openssl.c ================================================ /* Copyright (C) 2006, 2007 The Written Word, Inc. All rights reserved. * Author: Simon Josefsson * Copyright (c) 2004-2006, Sara Golemon * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #include int _libssh2_rsa_new(libssh2_rsa_ctx **rsa, const unsigned char *edata, unsigned long elen, const unsigned char *ndata, unsigned long nlen, const unsigned char *ddata, unsigned long dlen, const unsigned char *pdata, unsigned long plen, const unsigned char *qdata, unsigned long qlen, const unsigned char *e1data, unsigned long e1len, const unsigned char *e2data, unsigned long e2len, const unsigned char *coeffdata, unsigned long coefflen) { *rsa = RSA_new(); (*rsa)->e = BN_new(); BN_bin2bn(edata, elen, (*rsa)->e); (*rsa)->n = BN_new(); BN_bin2bn(ndata, nlen, (*rsa)->n); if (ddata) { (*rsa)->d = BN_new(); BN_bin2bn(ddata, dlen, (*rsa)->d); (*rsa)->p = BN_new(); BN_bin2bn(pdata, plen, (*rsa)->p); (*rsa)->q = BN_new(); BN_bin2bn(qdata, qlen, (*rsa)->q); (*rsa)->dmp1 = BN_new(); BN_bin2bn(e1data, e1len, (*rsa)->dmp1); (*rsa)->dmq1 = BN_new(); BN_bin2bn(e2data, e2len, (*rsa)->dmq1); (*rsa)->iqmp = BN_new(); BN_bin2bn(coeffdata, coefflen, (*rsa)->iqmp); } return 0; } int _libssh2_rsa_sha1_verify(libssh2_rsa_ctx *rsactx, const unsigned char *sig, unsigned long sig_len, const unsigned char *m, unsigned long m_len) { unsigned char hash[SHA_DIGEST_LENGTH]; int ret; SHA1(m, m_len, hash); ret = RSA_verify(NID_sha1, hash, SHA_DIGEST_LENGTH, (unsigned char *)sig, sig_len, rsactx); return (ret == 1) ? 0 : -1; } int _libssh2_dsa_new(libssh2_dsa_ctx **dsactx, const unsigned char *p, unsigned long p_len, const unsigned char *q, unsigned long q_len, const unsigned char *g, unsigned long g_len, const unsigned char *y, unsigned long y_len, const unsigned char *x, unsigned long x_len) { *dsactx = DSA_new(); (*dsactx)->p = BN_new(); BN_bin2bn(p, p_len, (*dsactx)->p); (*dsactx)->q = BN_new(); BN_bin2bn(q, q_len, (*dsactx)->q); (*dsactx)->g = BN_new(); BN_bin2bn(g, g_len, (*dsactx)->g); (*dsactx)->pub_key = BN_new(); BN_bin2bn(y, y_len, (*dsactx)->pub_key); if (x_len) { (*dsactx)->priv_key = BN_new(); BN_bin2bn(x, x_len, (*dsactx)->priv_key); } return 0; } int _libssh2_dsa_sha1_verify(libssh2_dsa_ctx *dsactx, const unsigned char *sig, const unsigned char *m, unsigned long m_len) { unsigned char hash[SHA_DIGEST_LENGTH]; DSA_SIG dsasig; int ret; dsasig.r = BN_new(); BN_bin2bn(sig, 20, dsasig.r); dsasig.s = BN_new(); BN_bin2bn(sig + 20, 20, dsasig.s); libssh2_sha1(m, m_len, hash); ret = DSA_do_verify(hash, SHA_DIGEST_LENGTH, &dsasig, dsactx); return (ret == 1) ? 0 : -1; } int _libssh2_cipher_init (_libssh2_cipher_ctx *h, _libssh2_cipher_type(algo), unsigned char *iv, unsigned char *secret, int encrypt) { EVP_CIPHER_CTX_init(h); EVP_CipherInit(h, algo(), secret, iv, encrypt); return 0; } int _libssh2_cipher_crypt(_libssh2_cipher_ctx *ctx, _libssh2_cipher_type(algo), int encrypt, unsigned char *block) { int blocksize = ctx->cipher->block_size; unsigned char buf[EVP_MAX_BLOCK_LENGTH]; int ret; (void)algo; (void)encrypt; if (blocksize == 1) { /* Hack for arcfour. */ blocksize = 8; } ret = EVP_Cipher(ctx, buf, block, blocksize); if (ret == 1) { memcpy(block, buf, blocksize); } return ret == 1 ? 0 : 1; } /* TODO: Optionally call a passphrase callback specified by the * calling program */ static int passphrase_cb(char *buf, int size, int rwflag, char *passphrase) { int passphrase_len = strlen(passphrase); (void)rwflag; if (passphrase_len > (size - 1)) { passphrase_len = size - 1; } memcpy(buf, passphrase, passphrase_len); buf[passphrase_len] = '\0'; return passphrase_len; } int _libssh2_rsa_new_private (libssh2_rsa_ctx **rsa, LIBSSH2_SESSION *session, FILE *fp, unsigned const char *passphrase) { (void)session; if (!EVP_get_cipherbyname("des")) { /* If this cipher isn't loaded it's a pretty good indication that none are. * I have *NO DOUBT* that there's a better way to deal with this ($#&%#$(%$#( * Someone buy me an OpenSSL manual and I'll read up on it. */ OpenSSL_add_all_ciphers(); } *rsa = PEM_read_RSAPrivateKey(fp, NULL, (void*)passphrase_cb, (void*)passphrase); if (!*rsa) { return -1; } return 0; } int _libssh2_dsa_new_private (libssh2_dsa_ctx **dsa, LIBSSH2_SESSION *session, FILE *fp, unsigned const char *passphrase) { (void)session; if (!EVP_get_cipherbyname("des")) { /* If this cipher isn't loaded it's a pretty good indication that none are. * I have *NO DOUBT* that there's a better way to deal with this ($#&%#$(%$#( * Someone buy me an OpenSSL manual and I'll read up on it. */ OpenSSL_add_all_ciphers(); } *dsa = PEM_read_DSAPrivateKey(fp, NULL, (void*)passphrase_cb, (void*)passphrase); if (!*dsa) { return -1; } return 0; } int _libssh2_rsa_sha1_sign(LIBSSH2_SESSION *session, libssh2_rsa_ctx *rsactx, const unsigned char *hash, unsigned long hash_len, unsigned char **signature, unsigned long *signature_len) { int ret; unsigned char *sig; unsigned int sig_len; sig_len = RSA_size(rsactx); sig = LIBSSH2_ALLOC(session, sig_len); if (!sig) { return -1; } ret = RSA_sign(NID_sha1, hash, hash_len, sig, &sig_len, rsactx); if (!ret) { LIBSSH2_FREE(session, sig); return -1; } *signature = sig; *signature_len = sig_len; return 0; } int _libssh2_dsa_sha1_sign(libssh2_dsa_ctx *dsactx, const unsigned char *hash, unsigned long hash_len, unsigned char *signature) { DSA_SIG *sig; int r_len, s_len, rs_pad; (void)hash_len; sig = DSA_do_sign(hash, SHA_DIGEST_LENGTH, dsactx); if (!sig) { return -1; } r_len = BN_num_bytes(sig->r); s_len = BN_num_bytes(sig->s); rs_pad = (2 * SHA_DIGEST_LENGTH) - (r_len + s_len); if (rs_pad < 0) { DSA_SIG_free(sig); return -1; } BN_bn2bin(sig->r, signature + rs_pad); BN_bn2bin(sig->s, signature + rs_pad + r_len); DSA_SIG_free(sig); return 0; } ================================================ FILE: Example/openssl.h ================================================ /* Copyright (C) 2006, 2007 The Written Word, Inc. All rights reserved. * Author: Simon Josefsson * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include #include #ifndef OPENSSL_NO_MD5 #include #endif #include #include #include #include #include #ifdef OPENSSL_NO_RSA # define LIBSSH2_RSA 0 #else # define LIBSSH2_RSA 1 #endif #ifdef OPENSSL_NO_DSA # define LIBSSH2_DSA 0 #else # define LIBSSH2_DSA 1 #endif #ifdef OPENSSL_NO_MD5 # define LIBSSH2_MD5 0 #else # define LIBSSH2_MD5 1 #endif #ifdef OPENSSL_NO_RIPEMD # define LIBSSH2_HMAC_RIPEMD 0 #else # define LIBSSH2_HMAC_RIPEMD 1 #endif #if OPENSSL_VERSION_NUMBER >= 0x00907000L && !defined(OPENSSL_NO_AES) # define LIBSSH2_AES 1 #else # define LIBSSH2_AES 0 #endif #ifdef OPENSSL_NO_BLOWFISH # define LIBSSH2_BLOWFISH 0 #else # define LIBSSH2_BLOWFISH 1 #endif #ifdef OPENSSL_NO_RC4 # define LIBSSH2_RC4 0 #else # define LIBSSH2_RC4 1 #endif #ifdef OPENSSL_NO_CAST # define LIBSSH2_CAST 0 #else # define LIBSSH2_CAST 1 #endif #ifdef OPENSSL_NO_DES # define LIBSSH2_3DES 0 #else # define LIBSSH2_3DES 1 #endif #define libssh2_random(buf, len) \ RAND_bytes ((buf), (len)) #define libssh2_sha1_ctx SHA_CTX #define libssh2_sha1_init(ctx) SHA1_Init(ctx) #define libssh2_sha1_update(ctx, data, len) SHA1_Update(&(ctx), data, len) #define libssh2_sha1_final(ctx, out) SHA1_Final(out, &(ctx)) #define libssh2_sha1(message, len, out) SHA1(message, len, out) #define libssh2_md5_ctx MD5_CTX #define libssh2_md5_init(ctx) MD5_Init(ctx) #define libssh2_md5_update(ctx, data, len) MD5_Update(&(ctx), data, len) #define libssh2_md5_final(ctx, out) MD5_Final(out, &(ctx)) #define libssh2_md5(message, len, out) MD5(message, len, out) #define libssh2_hmac_ctx HMAC_CTX #define libssh2_hmac_sha1_init(ctx, key, keylen) \ HMAC_Init(ctx, key, keylen, EVP_sha1()) #define libssh2_hmac_md5_init(ctx, key, keylen) \ HMAC_Init(ctx, key, keylen, EVP_md5()) #define libssh2_hmac_ripemd160_init(ctx, key, keylen) \ HMAC_Init(ctx, key, keylen, EVP_ripemd160()) #define libssh2_hmac_update(ctx, data, datalen) \ HMAC_Update(&(ctx), data, datalen) #define libssh2_hmac_final(ctx, data) HMAC_Final(&(ctx), data, NULL) #define libssh2_hmac_cleanup(ctx) HMAC_cleanup(ctx) #define libssh2_crypto_init() #define libssh2_rsa_ctx RSA int _libssh2_rsa_new(libssh2_rsa_ctx **rsa, const unsigned char *edata, unsigned long elen, const unsigned char *ndata, unsigned long nlen, const unsigned char *ddata, unsigned long dlen, const unsigned char *pdata, unsigned long plen, const unsigned char *qdata, unsigned long qlen, const unsigned char *e1data, unsigned long e1len, const unsigned char *e2data, unsigned long e2len, const unsigned char *coeffdata, unsigned long coefflen); int _libssh2_rsa_new_private (libssh2_rsa_ctx **rsa, LIBSSH2_SESSION *session, FILE *fp, unsigned const char *passphrase); int _libssh2_rsa_sha1_verify(libssh2_rsa_ctx *rsa, const unsigned char *sig, unsigned long sig_len, const unsigned char *m, unsigned long m_len); int _libssh2_rsa_sha1_sign(LIBSSH2_SESSION *session, libssh2_rsa_ctx *rsactx, const unsigned char *hash, unsigned long hash_len, unsigned char **signature, unsigned long *signature_len); #define _libssh2_rsa_free(rsactx) RSA_free(rsactx) #define libssh2_dsa_ctx DSA int _libssh2_dsa_new(libssh2_dsa_ctx **dsa, const unsigned char *pdata, unsigned long plen, const unsigned char *qdata, unsigned long qlen, const unsigned char *gdata, unsigned long glen, const unsigned char *ydata, unsigned long ylen, const unsigned char *x, unsigned long x_len); int _libssh2_dsa_new_private (libssh2_dsa_ctx **dsa, LIBSSH2_SESSION *session, FILE *fp, unsigned const char *passphrase); int _libssh2_dsa_sha1_verify(libssh2_dsa_ctx *dsactx, const unsigned char *sig, const unsigned char *m, unsigned long m_len); int _libssh2_dsa_sha1_sign(libssh2_dsa_ctx *dsactx, const unsigned char *hash, unsigned long hash_len, unsigned char *sig); #define _libssh2_dsa_free(dsactx) DSA_free(dsactx) #define _libssh2_cipher_type(name) const EVP_CIPHER *(*name)(void) #define _libssh2_cipher_ctx EVP_CIPHER_CTX #define _libssh2_cipher_aes256 EVP_aes_256_cbc #define _libssh2_cipher_aes192 EVP_aes_192_cbc #define _libssh2_cipher_aes128 EVP_aes_128_cbc #define _libssh2_cipher_blowfish EVP_bf_cbc #define _libssh2_cipher_arcfour EVP_rc4 #define _libssh2_cipher_cast5 EVP_cast5_cbc #define _libssh2_cipher_3des EVP_des_ede3_cbc int _libssh2_cipher_init (_libssh2_cipher_ctx *h, _libssh2_cipher_type(algo), unsigned char *iv, unsigned char *secret, int encrypt); int _libssh2_cipher_crypt(_libssh2_cipher_ctx *ctx, _libssh2_cipher_type(algo), int encrypt, unsigned char *block); #define _libssh2_cipher_dtor(ctx) EVP_CIPHER_CTX_cleanup(ctx) #define _libssh2_bn BIGNUM #define _libssh2_bn_ctx BN_CTX #define _libssh2_bn_ctx_new() BN_CTX_new() #define _libssh2_bn_ctx_free(bnctx) BN_CTX_free(bnctx) #define _libssh2_bn_init() BN_new() #define _libssh2_bn_rand(bn, bits, top, bottom) BN_rand(bn, bits, top, bottom) #define _libssh2_bn_mod_exp(r, a, p, m, ctx) BN_mod_exp(r, a, p, m, ctx) #define _libssh2_bn_set_word(bn, val) BN_set_word(bn, val) #define _libssh2_bn_from_bin(bn, len, val) BN_bin2bn(val, len, bn) #define _libssh2_bn_to_bin(bn, val) BN_bn2bin(bn, val) #define _libssh2_bn_bytes(bn) BN_num_bytes(bn) #define _libssh2_bn_bits(bn) BN_num_bits(bn) #define _libssh2_bn_free(bn) BN_clear_free(bn) ================================================ FILE: Example/packet.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #include #include #ifndef WIN32 #include #endif /* Needed for struct iovec on some platforms */ #ifdef HAVE_SYS_UIO_H #include #endif #ifdef HAVE_POLL # include #else # ifdef HAVE_SELECT # ifdef HAVE_SYS_SELECT_H # include # else # include # include # endif # endif #endif #include /* RFC4253 section 6.1 Maximum Packet Length says: * * "All implementations MUST be able to process packets with * uncompressed payload length of 32768 bytes or less and * total packet size of 35000 bytes or less (including length, * padding length, payload, padding, and MAC.)." */ #define MAX_SSH_PACKET_LEN 35000 inline int libssh2_packet_queue_listener(LIBSSH2_SESSION *session, unsigned char *data, unsigned long datalen); inline int libssh2_packet_x11_open(LIBSSH2_SESSION *session, unsigned char *data, unsigned long datalen); /* {{{ libssh2_packet_queue_listener * Queue a connection request for a listener */ inline int libssh2_packet_queue_listener(LIBSSH2_SESSION *session, unsigned char *data, unsigned long datalen) { /* Look for a matching listener */ unsigned char *s = data + (sizeof("forwarded-tcpip") - 1) + 5; /* 17 = packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */ unsigned long packet_len = 17 + (sizeof("Forward not requested") - 1); unsigned char *p, packet[17 + (sizeof("Forward not requested") - 1)]; LIBSSH2_LISTENER *l = session->listeners; char failure_code = 1; /* SSH_OPEN_ADMINISTRATIVELY_PROHIBITED */ uint32_t sender_channel, initial_window_size, packet_size; unsigned char *host, *shost; uint32_t port, sport, host_len, shost_len; sender_channel = libssh2_ntohu32(s); s += 4; initial_window_size = libssh2_ntohu32(s); s += 4; packet_size = libssh2_ntohu32(s); s += 4; host_len = libssh2_ntohu32(s); s += 4; host = s; s += host_len; port = libssh2_ntohu32(s); s += 4; shost_len = libssh2_ntohu32(s); s += 4; shost = s; s += shost_len; sport = libssh2_ntohu32(s); s += 4; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Remote received connection from %s:%ld to %s:%ld", shost, sport, host, port); #endif while (l) { if ((l->port == port) && (strlen(l->host) == host_len) && (memcmp(l->host, host, host_len) == 0)) { /* This is our listener */ LIBSSH2_CHANNEL *channel, *last_queued = l->queue; if (l->queue_maxsize && (l->queue_maxsize <= l->queue_size)) { /* Queue is full */ failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */ #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Listener queue full, ignoring"); #endif break; } channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL)); if (!channel) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a channel for new connection", 0); failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */ break; } memset(channel, 0, sizeof(LIBSSH2_CHANNEL)); channel->session = session; channel->channel_type_len = sizeof("forwarded-tcpip") - 1; channel->channel_type = LIBSSH2_ALLOC(session, channel->channel_type_len + 1); if (!channel->channel_type) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a channel for new connection", 0); LIBSSH2_FREE(session, channel); failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */ break; } memcpy(channel->channel_type, "forwarded-tcpip", channel->channel_type_len + 1); channel->remote.id = sender_channel; channel->remote.window_size_initial = LIBSSH2_CHANNEL_WINDOW_DEFAULT; channel->remote.window_size = LIBSSH2_CHANNEL_WINDOW_DEFAULT; channel->remote.packet_size = LIBSSH2_CHANNEL_PACKET_DEFAULT; channel->local.id = libssh2_channel_nextid(session); channel->local.window_size_initial = initial_window_size; channel->local.window_size = initial_window_size; channel->local.packet_size = packet_size; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Connection queued: channel %lu/%lu win %lu/%lu packet %lu/%lu", channel->local.id, channel->remote.id, channel->local.window_size, channel->remote.window_size, channel->local.packet_size, channel->remote.packet_size); #endif p = packet; *(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION; libssh2_htonu32(p, channel->remote.id); p += 4; libssh2_htonu32(p, channel->local.id); p += 4; libssh2_htonu32(p, channel->remote.window_size_initial); p += 4; libssh2_htonu32(p, channel->remote.packet_size); p += 4; if (libssh2_packet_write(session, packet, 17)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel open confirmation", 0); return -1; } /* Link the channel into the end of the queue list */ if (!last_queued) { l->queue = channel; return 0; } while (last_queued->next) last_queued = last_queued->next; last_queued->next = channel; channel->prev = last_queued; l->queue_size++; return 0; } l = l->next; } /* We're not listening to you */ { p = packet; *(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE; libssh2_htonu32(p, sender_channel); p += 4; libssh2_htonu32(p, failure_code); p += 4; libssh2_htonu32(p, sizeof("Forward not requested") - 1); p += 4; memcpy(s, "Forward not requested", sizeof("Forward not requested") - 1); p += sizeof("Forward not requested") - 1; libssh2_htonu32(p, 0); if (libssh2_packet_write(session, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send open failure", 0); return -1; } return 0; } } /* }}} */ /* {{{ libssh2_packet_x11_open * Accept a forwarded X11 connection */ inline int libssh2_packet_x11_open(LIBSSH2_SESSION *session, unsigned char *data, unsigned long datalen) { int failure_code = 2; /* SSH_OPEN_CONNECT_FAILED */ unsigned char *s = data + (sizeof("x11") - 1) + 5; unsigned long packet_len = 17 + (sizeof("X11 Forward Unavailable") - 1); unsigned char *p, packet[17 + (sizeof("X11 Forward Unavailable") - 1)]; /* packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */ LIBSSH2_CHANNEL *channel; unsigned long sender_channel, initial_window_size, packet_size; unsigned char *shost; unsigned long sport, shost_len; sender_channel = libssh2_ntohu32(s); s += 4; initial_window_size = libssh2_ntohu32(s); s += 4; packet_size = libssh2_ntohu32(s); s += 4; shost_len = libssh2_ntohu32(s); s += 4; shost = s; s += shost_len; sport = libssh2_ntohu32(s); s += 4; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "X11 Connection Received from %s:%ld on channel %lu", shost, sport, sender_channel); #endif if (session->x11) { channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL)); if (!channel) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a channel for new connection", 0); failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */ goto x11_exit; } memset(channel, 0, sizeof(LIBSSH2_CHANNEL)); channel->session = session; channel->channel_type_len = sizeof("x11") - 1; channel->channel_type = LIBSSH2_ALLOC(session, channel->channel_type_len + 1); if (!channel->channel_type) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a channel for new connection", 0); LIBSSH2_FREE(session, channel); failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */ goto x11_exit; } memcpy(channel->channel_type, "x11", channel->channel_type_len + 1); channel->remote.id = sender_channel; channel->remote.window_size_initial = LIBSSH2_CHANNEL_WINDOW_DEFAULT; channel->remote.window_size = LIBSSH2_CHANNEL_WINDOW_DEFAULT; channel->remote.packet_size = LIBSSH2_CHANNEL_PACKET_DEFAULT; channel->local.id = libssh2_channel_nextid(session); channel->local.window_size_initial = initial_window_size; channel->local.window_size = initial_window_size; channel->local.packet_size = packet_size; #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "X11 Connection established: channel %lu/%lu win %lu/%lu packet %lu/%lu", channel->local.id, channel->remote.id, channel->local.window_size, channel->remote.window_size, channel->local.packet_size, channel->remote.packet_size); #endif p = packet; *(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION; libssh2_htonu32(p, channel->remote.id); p += 4; libssh2_htonu32(p, channel->local.id); p += 4; libssh2_htonu32(p, channel->remote.window_size_initial); p += 4; libssh2_htonu32(p, channel->remote.packet_size); p += 4; if (libssh2_packet_write(session, packet, 17)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel open confirmation", 0); return -1; } /* Link the channel into the session */ if (session->channels.tail) { session->channels.tail->next = channel; channel->prev = session->channels.tail; } else { session->channels.head = channel; channel->prev = NULL; } channel->next = NULL; session->channels.tail = channel; /* * Pass control to the callback, they may turn right around and * free the channel, or actually use it */ LIBSSH2_X11_OPEN(channel, (char *)shost, sport); return 0; } else { failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */ } x11_exit: p = packet; *(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE; libssh2_htonu32(p, sender_channel); p += 4; libssh2_htonu32(p, failure_code); p += 4; libssh2_htonu32(p, sizeof("X11 Forward Unavailable") - 1); p += 4; memcpy(s, "X11 Forward Unavailable", sizeof("X11 Forward Unavailable") - 1); p += sizeof("X11 Forward Unavailable") - 1; libssh2_htonu32(p, 0); if (libssh2_packet_write(session, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send open failure", 0); return -1; } return 0; } /* }}} */ /* {{{ libssh2_packet_new * Create a new packet and attach it to the brigade */ static int libssh2_packet_add(LIBSSH2_SESSION *session, unsigned char *data, size_t datalen, int macstate) { LIBSSH2_PACKET *packet; unsigned long data_head = 0; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Packet type %d received, length=%d", (int)data[0], (int)datalen); #endif if (macstate == LIBSSH2_MAC_INVALID) { if (session->macerror) { if (LIBSSH2_MACERROR(session, (char *)data, datalen) == 0) { /* Calling app has given the OK, Process it anyway */ macstate = LIBSSH2_MAC_CONFIRMED; } else { libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid Message Authentication Code received", 0); if (session->ssh_msg_disconnect) { LIBSSH2_DISCONNECT(session, SSH_DISCONNECT_MAC_ERROR, "Invalid MAC received", sizeof("Invalid MAC received") - 1, "", 0); } return -1; } } else { libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid Message Authentication Code received", 0); if (session->ssh_msg_disconnect) { LIBSSH2_DISCONNECT(session, SSH_DISCONNECT_MAC_ERROR, "Invalid MAC received", sizeof("Invalid MAC received") - 1, "", 0); } return -1; } } /* A couple exceptions to the packet adding rule: */ switch (data[0]) { case SSH_MSG_DISCONNECT: { char *message, *language; int reason, message_len, language_len; reason = libssh2_ntohu32(data + 1); message_len = libssh2_ntohu32(data + 5); message = (char *)data + 9; /* packet_type(1) + reason(4) + message_len(4) */ language_len = libssh2_ntohu32(data + 9 + message_len); /* This is where we hack on the data a little, * Use the MSB of language_len to to a terminating NULL (In all liklihood it is already) * Shift the language tag back a byte (In all likelihood it's zero length anyway * Store a NULL in the last byte of the packet to terminate the language string * With the lengths passed this isn't *REALLY* necessary, but it's "kind" */ message[message_len] = '\0'; language = (char *)data + 9 + message_len + 3; if (language_len) { memcpy(language, language + 1, language_len); } language[language_len] = '\0'; if (session->ssh_msg_disconnect) { LIBSSH2_DISCONNECT(session, reason, message, message_len, language, language_len); } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Disconnect(%d): %s(%s)", reason, message, language); #endif LIBSSH2_FREE(session, data); session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; return -1; } break; case SSH_MSG_IGNORE: /* As with disconnect, back it up one and add a trailing NULL */ memcpy(data + 4, data + 5, datalen - 5); data[datalen] = '\0'; if (session->ssh_msg_ignore) { LIBSSH2_IGNORE(session, (char *)data + 4, datalen - 5); } LIBSSH2_FREE(session, (char *)data); return 0; break; case SSH_MSG_DEBUG: { int always_display = data[0]; char *message, *language; int message_len, language_len; message_len = libssh2_ntohu32(data + 2); message = (char *)data + 6; /* packet_type(1) + display(1) + message_len(4) */ language_len = libssh2_ntohu32(data + 6 + message_len); /* This is where we hack on the data a little, * Use the MSB of language_len to to a terminating NULL (In all liklihood it is already) * Shift the language tag back a byte (In all likelihood it's zero length anyway * Store a NULL in the last byte of the packet to terminate the language string * With the lengths passed this isn't *REALLY* necessary, but it's "kind" */ message[message_len] = '\0'; language = (char *)data + 6 + message_len + 3; if (language_len) { memcpy(language, language + 1, language_len); } language[language_len] = '\0'; if (session->ssh_msg_debug) { LIBSSH2_DEBUG(session, always_display, message, message_len, language, language_len); } #ifdef LIBSSH2_DEBUG_TRANSPORT /* _libssh2_debug will actually truncate this for us so that it's not an inordinate about of data */ _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Debug Packet: %s", message); #endif LIBSSH2_FREE(session, data); return 0; } break; case SSH_MSG_CHANNEL_EXTENDED_DATA: data_head += 4; /* streamid(4) */ case SSH_MSG_CHANNEL_DATA: data_head += 9; /* packet_type(1) + channelno(4) + datalen(4) */ { LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); if (!channel) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, "Packet received for unknown channel, ignoring", 0); LIBSSH2_FREE(session, data); return 0; } #ifdef LIBSSH2_DEBUG_CONNECTION { unsigned long stream_id = 0; if (data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) { stream_id = libssh2_ntohu32(data + 5); } _libssh2_debug(session, LIBSSH2_DBG_CONN, "%d bytes received for channel %lu/%lu stream #%lu", (int)(datalen - data_head), channel->local.id, channel->remote.id, stream_id); } #endif if ((channel->remote.extended_data_ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) && (data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) { /* Pretend we didn't receive this */ LIBSSH2_FREE(session, data); #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Ignoring extended data and refunding %d bytes", (int)(datalen - 13)); #endif /* Adjust the window based on the block we just freed */ libssh2_channel_receive_window_adjust(channel, datalen - 13, 0); return 0; } /* REMEMBER! remote means remote as source of data, NOT remote window! */ if (channel->remote.packet_size < (datalen - data_head)) { /* Spec says we MAY ignore bytes sent beyond packet_size */ libssh2_error(session, LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, "Packet contains more data than we offered to receive, truncating", 0); datalen = channel->remote.packet_size + data_head; } if (channel->remote.window_size <= 0) { /* Spec says we MAY ignore bytes sent beyond window_size */ libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "The current receive window is full, data ignored", 0); LIBSSH2_FREE(session, data); return 0; } /* Reset EOF status */ channel->remote.eof = 0; if ((datalen - data_head) > channel->remote.window_size) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "Remote sent more data than current window allows, truncating", 0); datalen = channel->remote.window_size + data_head; } else { /* Now that we've received it, shrink our window */ channel->remote.window_size -= datalen - data_head; } } break; case SSH_MSG_CHANNEL_EOF: { LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); if (!channel) { /* We may have freed already, just quietly ignore this... */ LIBSSH2_FREE(session, data); return 0; } #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "EOF received for channel %lu/%lu", channel->local.id, channel->remote.id); #endif channel->remote.eof = 1; LIBSSH2_FREE(session, data); return 0; } break; case SSH_MSG_CHANNEL_REQUEST: { if (libssh2_ntohu32(data+5) == sizeof("exit-status") - 1 && !memcmp("exit-status", data + 9, sizeof("exit-status") - 1)) { /* we've got "exit-status" packet. Set the session value */ LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data+1)); if (channel) { channel->exit_status = libssh2_ntohu32(data + 9 + sizeof("exit-status")); #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Exit status %lu received for channel %lu/%lu", channel->exit_status, channel->local.id, channel->remote.id); #endif } LIBSSH2_FREE(session, data); return 0; } } break; case SSH_MSG_CHANNEL_CLOSE: { LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); if (!channel) { /* We may have freed already, just quietly ignore this... */ LIBSSH2_FREE(session, data); return 0; } #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Close received for channel %lu/%lu", channel->local.id, channel->remote.id); #endif channel->remote.close = 1; channel->remote.eof = 1; /* TODO: Add a callback for this */ LIBSSH2_FREE(session, data); return 0; } break; case SSH_MSG_CHANNEL_OPEN: if ((datalen >= (sizeof("forwarded-tcpip") + 4)) && ((sizeof("forwarded-tcpip")-1) == libssh2_ntohu32(data + 1)) && (memcmp(data + 5, "forwarded-tcpip", sizeof("forwarded-tcpip") - 1) == 0)) { int retval = libssh2_packet_queue_listener(session, data, datalen); LIBSSH2_FREE(session, data); return retval; } if ((datalen >= (sizeof("x11") + 4)) && ((sizeof("x11")-1) == libssh2_ntohu32(data + 1)) && (memcmp(data + 5, "x11", sizeof("x11") - 1) == 0)) { int retval = libssh2_packet_x11_open(session, data, datalen); LIBSSH2_FREE(session, data); return retval; } break; case SSH_MSG_CHANNEL_WINDOW_ADJUST: { LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); unsigned long bytestoadd = libssh2_ntohu32(data + 5); if (channel && bytestoadd) { channel->local.window_size += bytestoadd; } #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Window adjust received for channel %lu/%lu, adding %lu bytes, new window_size=%lu", channel->local.id, channel->remote.id, bytestoadd, channel->local.window_size); #endif LIBSSH2_FREE(session, data); return 0; } break; } packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); memset(packet, 0, sizeof(LIBSSH2_PACKET)); packet->data = data; packet->data_len = datalen; packet->data_head = data_head; packet->mac = macstate; packet->brigade = &session->packets; packet->next = NULL; if (session->packets.tail) { packet->prev = session->packets.tail; packet->prev->next = packet; session->packets.tail = packet; } else { session->packets.head = packet; session->packets.tail = packet; packet->prev = NULL; } if (data[0] == SSH_MSG_KEXINIT && !(session->state & LIBSSH2_STATE_EXCHANGING_KEYS)) { /* Remote wants new keys * Well, it's already in the brigade, * let's just call back into ourselves */ #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Renegotiating Keys"); #endif libssh2_kex_exchange(session, 1); /* If there was a key reexchange failure, let's just hope we didn't send NEWKEYS yet, otherwise remote will drop us like a rock */ } return 0; } /* }}} */ /* {{{ libssh2_blocking_read * Force a blocking read, regardless of socket settings */ static ssize_t libssh2_blocking_read(LIBSSH2_SESSION *session, unsigned char *buf, size_t count) { size_t bytes_read = 0; #if !defined(HAVE_POLL) && !defined(HAVE_SELECT) int polls = 0; #endif #ifndef WIN32 fcntl(session->socket_fd, F_SETFL, 0); #else { u_long block = FALSE; ioctlsocket(session->socket_fd, FIONBIO, &block); } #endif #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Blocking read: %d bytes", (int)count); #endif while (bytes_read < count) { int ret; ret = LIBSSH2_READ(session, buf + bytes_read, count - bytes_read); if (ret < 0) { #ifdef WIN32 switch (WSAGetLastError()) { case WSAEWOULDBLOCK: errno = EAGAIN; break; case WSAENOTSOCK: errno = EBADF; break; case WSAENOTCONN: case WSAECONNABORTED: errno = ENOTCONN; break; case WSAEINTR: errno = EINTR; break; } #endif if (errno == EAGAIN) { #ifdef HAVE_POLL struct pollfd read_socket; read_socket.fd = session->socket_fd; read_socket.events = POLLIN; if (poll(&read_socket, 1, 30000) <= 0) { return -1; } #elif defined(HAVE_SELECT) fd_set read_socket; struct timeval timeout; FD_ZERO(&read_socket); FD_SET(session->socket_fd, &read_socket); timeout.tv_sec = 30; timeout.tv_usec = 0; if (select(session->socket_fd + 1, &read_socket, NULL, NULL, &timeout) <= 0) { return -1; } #else if (polls++ > LIBSSH2_SOCKET_POLL_MAXLOOPS) { return -1; } usleep(LIBSSH2_SOCKET_POLL_UDELAY); #endif /* POLL/SELECT/SLEEP */ continue; } if (errno == EINTR) { continue; } if ((errno == EBADF) || (errno == EIO) || (errno == ENOTCONN)) { session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; } return -1; } if (ret == 0) continue; bytes_read += ret; } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Blocking read: %d bytes actually read", (int)bytes_read); #endif return bytes_read; } /* }}} */ /* {{{ libssh2_packet_read * Collect a packet into the input brigade * block only controls whether or not to wait for a packet to start, * Once a packet starts, libssh2 will block until it is complete * Returns packet type added to input brigade (0 if nothing added), or -1 on failure */ int libssh2_packet_read(LIBSSH2_SESSION *session, int should_block) { int packet_type = -1; if (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) { return 0; } #ifndef WIN32 fcntl(session->socket_fd, F_SETFL, O_NONBLOCK); #else { u_long non_block = TRUE; ioctlsocket(session->socket_fd, FIONBIO, &non_block); } #endif #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Checking for packet: will%s block", should_block ? "" : " not"); #endif if (session->state & LIBSSH2_STATE_NEWKEYS) { /* Temporary Buffer * The largest blocksize (currently) is 32, the largest MAC (currently) is 20 */ unsigned char block[2 * 32], *payload, *s, *p, tmp[6]; ssize_t read_len; unsigned long blocksize = session->remote.crypt->blocksize; unsigned long packet_len, payload_len; int padding_len; int macstate; int free_payload = 1; /* Note: If we add any cipher with a blocksize less than 6 we'll need to get more creative with this * For now, all blocksize sizes are 8+ */ if (should_block) { read_len = libssh2_blocking_read(session, block, blocksize); if(read_len <= 0) return read_len; } else { ssize_t nread; read_len = LIBSSH2_READ(session, block, 1); if (read_len <= 0) { return 0; } nread = libssh2_blocking_read(session, block + read_len, blocksize - read_len); if(nread <= 0) return nread; read_len += nread; } if (read_len < blocksize) { return (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) ? 0 : -1; } if (session->remote.crypt->crypt(session, block, &session->remote.crypt_abstract)) { libssh2_error(session, LIBSSH2_ERROR_DECRYPT, "Error decrypting packet preamble", 0); return -1; } packet_len = libssh2_ntohu32(block); /* RFC4253 section 6.1 Maximum Packet Length says: * * "All implementations MUST be able to process packets with * uncompressed payload length of 32768 bytes or less and * total packet size of 35000 bytes or less (including length, * padding length, payload, padding, and MAC.)." */ if(packet_len > MAX_SSH_PACKET_LEN) { return -1; } padding_len = block[4]; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Processing packet %lu bytes long (with %lu bytes padding)", packet_len, padding_len); #endif memcpy(tmp, block, 5); /* Use this for MAC later */ payload_len = packet_len - 1; /* padding_len(1) */ /* Sanity Check */ if ((payload_len > LIBSSH2_PACKET_MAXPAYLOAD) || ((packet_len + 4) % blocksize)) { /* If something goes horribly wrong during the decryption phase, just bailout and die gracefully */ session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; libssh2_error(session, LIBSSH2_ERROR_PROTO, "Fatal protocol error, invalid payload size", 0); return -1; } s = payload = LIBSSH2_ALLOC(session, payload_len); memcpy(s, block + 5, blocksize - 5); s += blocksize - 5; p = s; while (p - payload < payload_len) { read_len = payload_len - (p - payload); if (read_len > 4096) read_len = 4096; if (libssh2_blocking_read(session, p, read_len) < read_len) { LIBSSH2_FREE(session, payload); return -1; } p += read_len; while (s < p) { memcpy(block, s, blocksize); if (session->remote.crypt->crypt(session, block, &session->remote.crypt_abstract)) { libssh2_error(session, LIBSSH2_ERROR_DECRYPT, "Error decrypting packet preamble", 0); LIBSSH2_FREE(session, payload); return -1; } memcpy(s, block, blocksize); s += blocksize; } } read_len = libssh2_blocking_read(session, block, session->remote.mac->mac_len); if (read_len < session->remote.mac->mac_len) { LIBSSH2_FREE(session, payload); return -1; } /* Calculate MAC hash */ session->remote.mac->hash(session, block + session->remote.mac->mac_len, session->remote.seqno, tmp, 5, payload, payload_len, &session->remote.mac_abstract); macstate = (strncmp((char *)block, (char *)block + session->remote.mac->mac_len, session->remote.mac->mac_len) == 0) ? LIBSSH2_MAC_CONFIRMED : LIBSSH2_MAC_INVALID; session->remote.seqno++; /* Ignore padding */ payload_len -= padding_len; if (session->remote.comp && strcmp(session->remote.comp->name, "none")) { /* Decompress */ unsigned char *data; unsigned long data_len; if (session->remote.comp->comp(session, 0, &data, &data_len, LIBSSH2_PACKET_MAXDECOMP, &free_payload, payload, payload_len, &session->remote.comp_abstract)) { LIBSSH2_FREE(session, payload); return -1; } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Payload decompressed: %lu bytes(compressed) to %lu bytes(uncompressed)", data_len, payload_len); #endif if (free_payload) { LIBSSH2_FREE(session, payload); payload = data; payload_len = data_len; } else { if (data == payload) { /* It's not to be freed, because the compression layer reused payload, * So let's do the same! */ payload_len = data_len; } else { /* No comp_method actually lets this happen, but let's prepare for the future */ LIBSSH2_FREE(session, payload); /* We need a freeable struct otherwise the brigade won't know what to do with it */ payload = LIBSSH2_ALLOC(session, data_len); if (!payload) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for copy of uncompressed data", 0); return -1; } memcpy(payload, data, data_len); payload_len = data_len; } } } packet_type = payload[0]; libssh2_packet_add(session, payload, payload_len, macstate); } else { /* No cipher active */ unsigned char *payload; unsigned char buf[24]; ssize_t buf_len; unsigned long payload_len; uint32_t packet_length; unsigned long padding_length; if (should_block) { buf_len = libssh2_blocking_read(session, buf, 5); } else { ssize_t nread; buf_len = LIBSSH2_READ(session, buf, 1); if (buf_len <= 0) { return buf_len; } nread = libssh2_blocking_read(session, buf, 5 - buf_len); if(nread <= 0) return -1; buf_len += nread; } if (buf_len < 5) { /* Something bad happened */ return -1; } packet_length = libssh2_ntohu32(buf); /* RFC4253 section 6.1 Maximum Packet Length says: * * "All implementations MUST be able to process packets with * uncompressed payload length of 32768 bytes or less and * total packet size of 35000 bytes or less (including length, * padding length, payload, padding, and MAC.)." */ if(packet_length > MAX_SSH_PACKET_LEN) { return -1; } padding_length = buf[4]; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Processing plaintext packet %lu bytes long (with %lu bytes padding)", packet_length, padding_length); #endif payload_len = packet_length - padding_length - 1; /* padding_length(1) */ payload = LIBSSH2_ALLOC(session, payload_len); if (!payload) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for copy of plaintext data", 0); return -1; } if (libssh2_blocking_read(session, payload, payload_len) < payload_len) { return (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) ? 0 : -1; } while (padding_length) { int l; /* Flush padding */ l = libssh2_blocking_read(session, buf, padding_length); if (l > 0) padding_length -= l; else break; } packet_type = payload[0]; /* MACs don't exist in non-encrypted mode */ libssh2_packet_add(session, payload, payload_len, LIBSSH2_MAC_CONFIRMED); session->remote.seqno++; } return packet_type; } /* }}} */ /* {{{ libssh2_packet_ask * Scan the brigade for a matching packet type, optionally poll the socket for a packet first */ int libssh2_packet_ask_ex(LIBSSH2_SESSION *session, unsigned char packet_type, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len, int poll_socket) { LIBSSH2_PACKET *packet = session->packets.head; if (poll_socket) { if (libssh2_packet_read(session, 0) < 0) { return -1; } } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Looking for packet of type: %d", (int)packet_type); #endif while (packet) { if (packet->data[0] == packet_type && (packet->data_len >= (match_ofs + match_len)) && (!match_buf || (memcmp(packet->data + match_ofs, match_buf, match_len) == 0))) { *data = packet->data; *data_len = packet->data_len; if (packet->prev) { packet->prev->next = packet->next; } else { session->packets.head = packet->next; } if (packet->next) { packet->next->prev = packet->prev; } else { session->packets.tail = packet->prev; } LIBSSH2_FREE(session, packet); return 0; } packet = packet->next; } return -1; } /* }}} */ /* {{{ libssh2_packet_askv * Scan for any of a list of packet types in the brigade, optionally poll the socket for a packet first */ int libssh2_packet_askv_ex(LIBSSH2_SESSION *session, unsigned char *packet_types, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len, int poll_socket) { int i, packet_types_len = strlen((char *)packet_types); for(i = 0; i < packet_types_len; i++) { if (0 == libssh2_packet_ask_ex(session, packet_types[i], data, data_len, match_ofs, match_buf, match_len, i ? 0 : poll_socket)) { return 0; } } return -1; } /* }}} */ /* {{{ libssh2_packet_require * Loops libssh2_packet_read() until the packet requested is available * SSH_DISCONNECT or a SOCKET_DISCONNECTED will cause a bailout */ int libssh2_packet_require_ex(LIBSSH2_SESSION *session, unsigned char packet_type, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len) { if (libssh2_packet_ask_ex(session, packet_type, data, data_len, match_ofs, match_buf, match_len, 0) == 0) { /* A packet was available in the packet brigade */ return 0; } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Blocking until packet of type %d becomes available", (int)packet_type); #endif while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { int ret = libssh2_packet_read(session, 1); if (ret < 0) { return -1; } if (ret == 0) continue; if (packet_type == ret) { /* Be lazy, let packet_ask pull it out of the brigade */ return libssh2_packet_ask_ex(session, packet_type, data, data_len, match_ofs, match_buf, match_len, 0); } } /* Only reached if the socket died */ return -1; } /* }}} */ /* {{{ libssh2_packet_burn * Loops libssh2_packet_read() until any packet is available and promptly discards it * Used during KEX exchange to discard badly guessed KEX_INIT packets */ int libssh2_packet_burn(LIBSSH2_SESSION *session) { unsigned char *data; unsigned long data_len; unsigned char all_packets[255]; int i; for(i = 1; i < 256; i++) all_packets[i - 1] = i; if (libssh2_packet_askv_ex(session, all_packets, &data, &data_len, 0, NULL, 0, 0) == 0) { i = data[0]; /* A packet was available in the packet brigade, burn it */ LIBSSH2_FREE(session, data); return i; } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Blocking until packet becomes available to burn"); #endif while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { int ret = libssh2_packet_read(session, 1); if (ret < 0) { return -1; } if (ret == 0) continue; /* Be lazy, let packet_ask pull it out of the brigade */ if (0 == libssh2_packet_ask_ex(session, ret, &data, &data_len, 0, NULL, 0, 0)) { /* Smoke 'em if you got 'em */ LIBSSH2_FREE(session, data); return ret; } } /* Only reached if the socket died */ return -1; } /* }}} */ /* {{{ libssh2_packet_requirev * Loops libssh2_packet_read() until one of a list of packet types requested is available * SSH_DISCONNECT or a SOCKET_DISCONNECTED will cause a bailout * packet_types is a null terminated list of packet_type numbers */ int libssh2_packet_requirev_ex(LIBSSH2_SESSION *session, unsigned char *packet_types, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len) { if (libssh2_packet_askv_ex(session, packet_types, data, data_len, match_ofs, match_buf, match_len, 0) == 0) { /* One of the packets listed was available in the packet brigade */ return 0; } while (session->socket_state != LIBSSH2_SOCKET_DISCONNECTED) { int ret = libssh2_packet_read(session, 1); if (ret < 0) { return -1; } if (ret == 0) { continue; } if (strchr((char *)packet_types, ret)) { /* Be lazy, let packet_ask pull it out of the brigade */ return libssh2_packet_askv_ex(session, packet_types, data, data_len, match_ofs, match_buf, match_len, 0); } } /* Only reached if the socket died */ return -1; } /* }}} */ /* {{{ libssh2_packet_write * Send a packet, encrypting it and adding a MAC code if necessary * Returns 0 on success, non-zero on failure */ int libssh2_packet_write(LIBSSH2_SESSION *session, unsigned char *data, unsigned long data_len) { unsigned long packet_length = data_len + 1; unsigned long block_size = (session->state & LIBSSH2_STATE_NEWKEYS) ? session->local.crypt->blocksize : 8; /* At this point packet_length doesn't include the packet_len field itself */ unsigned long padding_length; int free_data = 0; unsigned char buf[246]; /* 6 byte header plus max padding size(240) */ #ifdef LIBSSH2_DEBUG_TRANSPORT { /* Show a hint of what's being sent */ char excerpt[32]; int ex_len = 0, db_ofs = 0; for (; ex_len < 24 && db_ofs < data_len; ex_len += 3, db_ofs++) snprintf(excerpt + ex_len, 4, "%02X ", data[db_ofs]); _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Sending packet type %d, length=%lu, %s", (int)data[0], data_len, excerpt); } #endif if ((session->state & LIBSSH2_STATE_NEWKEYS) && strcmp(session->local.comp->name, "none")) { if (session->local.comp->comp(session, 1, &data, &data_len, LIBSSH2_PACKET_MAXCOMP, &free_data, data, data_len, &session->local.comp_abstract)) { return -1; } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Compressed payload to %lu bytes", data_len); #endif } #ifndef WIN32 fcntl(session->socket_fd, F_SETFL, 0); #else { u_long non_block = FALSE; ioctlsocket(session->socket_fd, FIONBIO, &non_block); } #endif packet_length = data_len + 1; /* padding_length(1) -- MAC doesn't count -- Padding to be added soon */ padding_length = block_size - ((packet_length + 4) % block_size); if (padding_length < 4) { padding_length += block_size; } /* TODO: Maybe add 1 or 2 times block_size to padding_length randomly -- shake things up a bit... */ packet_length += padding_length; libssh2_htonu32(buf, packet_length); buf[4] = padding_length; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Sending packet with total length %lu (%lu bytes padding)", packet_length, padding_length); #endif if (session->state & LIBSSH2_STATE_NEWKEYS) { /* Encryption is in effect */ unsigned char *encbuf, *s; int ret, size, written = 0; /* include packet_length(4) itself and room for the hash at the end */ encbuf = LIBSSH2_ALLOC(session, 4 + packet_length + session->local.mac->mac_len); if (!encbuf) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate encryption buffer", 0); if (free_data) { LIBSSH2_FREE(session, data); } return -1; } /* Copy packet to encoding buffer */ memcpy(encbuf, buf, 5); memcpy(encbuf + 5, data, data_len); libssh2_random(encbuf + 5 + data_len, padding_length); if (free_data) { LIBSSH2_FREE(session, data); } /* Calculate MAC hash */ session->local.mac->hash(session, encbuf + 4 + packet_length , session->local.seqno, encbuf, 4 + packet_length, NULL, 0, &session->local.mac_abstract); /* Encrypt data */ for(s = encbuf; (s - encbuf) < (4 + packet_length) ; s += session->local.crypt->blocksize) { session->local.crypt->crypt(session, s, &session->local.crypt_abstract); } session->local.seqno++; /* Send It */ size = 4 + packet_length + session->local.mac->mac_len; written = 0; while(written < size) { ret = LIBSSH2_WRITE(session, encbuf + written, size - written); if(ret > 0) written += ret; else break; } ret = written == size ? 0 : -1; /* Cleanup environment */ LIBSSH2_FREE(session, encbuf); return ret; } else { /* LIBSSH2_ENDPOINT_CRYPT_NONE */ /* Simplified write for non-encrypted mode */ struct iovec data_vector[3]; /* Using vectors means we don't have to alloc a new buffer -- a byte saved is a byte earned * No MAC during unencrypted phase */ data_vector[0].iov_base = buf; data_vector[0].iov_len = 5; data_vector[1].iov_base = (char*)data; data_vector[1].iov_len = data_len; data_vector[2].iov_base = buf + 5; data_vector[2].iov_len = padding_length; session->local.seqno++; /* Ignore this, it can't actually happen :) */ if (free_data) { LIBSSH2_FREE(session, data); } return ((packet_length + 4) == writev(session->socket_fd, data_vector, 3)) ? 0 : 1; } } /* }}} */ ================================================ FILE: Example/pem.c ================================================ /* Copyright (C) 2007 The Written Word, Inc. All rights reserved. * Author: Simon Josefsson * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" static int readline (char *line, int line_size, FILE *fp) { if (!fgets(line, line_size, fp)) { return -1; } if (*line && line[strlen(line) - 1] == '\r') { line[strlen(line) - 1] = '\0'; } if (*line && line[strlen(line) - 1] == '\n') { line[strlen(line) - 1] = '\0'; } return 0; } #define LINE_SIZE 128 int _libssh2_pem_parse (LIBSSH2_SESSION *session, const char *headerbegin, const char *headerend, FILE *fp, char **data, unsigned int *datalen) { char line[LINE_SIZE]; char *b64data = NULL; unsigned int b64datalen = 0; int ret; do { if (readline(line, LINE_SIZE, fp)) { return -1; } } while (strcmp (line, headerbegin) != 0); *line = '\0'; do { if (*line) { char *tmp; size_t linelen; linelen = strlen (line); tmp = LIBSSH2_REALLOC (session, b64data, b64datalen + linelen); if (!tmp) { ret = -1; goto out; } memcpy (tmp + b64datalen, line, linelen); b64data = tmp; b64datalen += linelen; } if (readline(line, LINE_SIZE, fp)) { ret = -1; goto out; } } while (strcmp (line, headerend) != 0); if (libssh2_base64_decode(session, data, datalen, b64data, b64datalen)) { ret = -1; goto out; } ret = 0; out: if (b64data) { LIBSSH2_FREE (session, b64data); } return ret; } static int read_asn1_length (const unsigned char *data, unsigned int datalen, unsigned int *len) { unsigned int lenlen; int nextpos; if (datalen < 1) { return -1; } *len = data[0]; if (*len >= 0x80) { lenlen = *len & 0x7F; *len = data[1]; if (1 + lenlen > datalen) { return -1; } if (lenlen > 1) { *len <<= 8; *len |= data[2]; } } else { lenlen = 0; } nextpos = 1 + lenlen; if (lenlen > 2 || 1 + lenlen + *len > datalen) { return -1; } return nextpos; } int _libssh2_pem_decode_sequence (unsigned char **data, unsigned int *datalen) { unsigned int len; int lenlen; if (*datalen < 1) { return -1; } if ((*data)[0] != '\x30') { return -1; } (*data)++; (*datalen)--; lenlen = read_asn1_length (*data, *datalen, &len); if (lenlen < 0 || lenlen + len != *datalen) { return -1; } *data += lenlen; *datalen -= lenlen; return 0; } int _libssh2_pem_decode_integer (unsigned char **data, unsigned int *datalen, unsigned char **i, unsigned int *ilen) { unsigned int len; int lenlen; if (*datalen < 1) { return -1; } if ((*data)[0] != '\x02') { return -1; } (*data)++; (*datalen)--; lenlen = read_asn1_length (*data, *datalen, &len); if (lenlen < 0 || lenlen + len > *datalen) { return -1; } *data += lenlen; *datalen -= lenlen; *i = *data; *ilen = len; *data += len; *datalen -= len; return 0; } ================================================ FILE: Example/publickey.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #include "libssh2_publickey.h" struct _LIBSSH2_PUBLICKEY { LIBSSH2_CHANNEL *channel; unsigned long version; }; #define LIBSSH2_PUBLICKEY_VERSION 2 /* Numericised response codes -- Not IETF standard, just a local representation */ #define LIBSSH2_PUBLICKEY_RESPONSE_STATUS 0 #define LIBSSH2_PUBLICKEY_RESPONSE_VERSION 1 #define LIBSSH2_PUBLICKEY_RESPONSE_PUBLICKEY 2 typedef struct _LIBSSH2_PUBLICKEY_CODE_LIST { int code; char *name; int name_len; } LIBSSH2_PUBLICKEY_CODE_LIST; static LIBSSH2_PUBLICKEY_CODE_LIST libssh2_publickey_response_codes[] = { { LIBSSH2_PUBLICKEY_RESPONSE_STATUS, "status", sizeof("status") - 1 }, { LIBSSH2_PUBLICKEY_RESPONSE_VERSION, "version", sizeof("version") - 1 }, { LIBSSH2_PUBLICKEY_RESPONSE_PUBLICKEY, "publickey", sizeof("publickey") - 1 }, { 0, NULL, 0 } }; /* PUBLICKEY status codes -- IETF defined */ #define LIBSSH2_PUBLICKEY_SUCCESS 0 #define LIBSSH2_PUBLICKEY_ACCESS_DENIED 1 #define LIBSSH2_PUBLICKEY_STORAGE_EXCEEDED 2 #define LIBSSH2_PUBLICKEY_VERSION_NOT_SUPPORTED 3 #define LIBSSH2_PUBLICKEY_KEY_NOT_FOUND 4 #define LIBSSH2_PUBLICKEY_KEY_NOT_SUPPORTED 5 #define LIBSSH2_PUBLICKEY_KEY_ALREADY_PRESENT 6 #define LIBSSH2_PUBLICKEY_GENERAL_FAILURE 7 #define LIBSSH2_PUBLICKEY_REQUEST_NOT_SUPPORTED 8 #define LIBSSH2_PUBLICKEY_STATUS_CODE_MAX 8 static LIBSSH2_PUBLICKEY_CODE_LIST libssh2_publickey_status_codes[] = { { LIBSSH2_PUBLICKEY_SUCCESS, "success", sizeof("success") - 1 }, { LIBSSH2_PUBLICKEY_ACCESS_DENIED, "access denied", sizeof("access denied") - 1 }, { LIBSSH2_PUBLICKEY_STORAGE_EXCEEDED, "storage exceeded", sizeof("storage exceeded") - 1 }, { LIBSSH2_PUBLICKEY_VERSION_NOT_SUPPORTED, "version not supported", sizeof("version not supported") - 1 }, { LIBSSH2_PUBLICKEY_KEY_NOT_FOUND, "key not found", sizeof("key not found") - 1 }, { LIBSSH2_PUBLICKEY_KEY_NOT_SUPPORTED, "key not supported", sizeof("key not supported") - 1 }, { LIBSSH2_PUBLICKEY_KEY_ALREADY_PRESENT, "key already present", sizeof("key already present") - 1 }, { LIBSSH2_PUBLICKEY_GENERAL_FAILURE, "general failure", sizeof("general failure") - 1 }, { LIBSSH2_PUBLICKEY_REQUEST_NOT_SUPPORTED, "request not supported", sizeof("request not supported") - 1 }, { 0, NULL, 0 } }; /* {{{ libssh2_publickey_status_error * Format an error message from a status code */ #define LIBSSH2_PUBLICKEY_STATUS_TEXT_START "Publickey Subsystem Error: \"" #define LIBSSH2_PUBLICKEY_STATUS_TEXT_MID "\" Server Resports: \"" #define LIBSSH2_PUBLICKEY_STATUS_TEXT_END "\"" static void libssh2_publickey_status_error(LIBSSH2_PUBLICKEY *pkey, LIBSSH2_SESSION *session, int status, unsigned char *message, int message_len) { char *status_text; int status_text_len; char *m, *s; int m_len; /* GENERAL_FAILURE got remapped between version 1 and 2 */ if (status == 6 && pkey && pkey->version == 1) { status = 7; } if (status < 0 || status > LIBSSH2_PUBLICKEY_STATUS_CODE_MAX) { status_text = "unknown"; status_text_len = sizeof("unknown") - 1; } else { status_text = libssh2_publickey_status_codes[status].name; status_text_len = libssh2_publickey_status_codes[status].name_len; } m_len = (sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_START) - 1) + status_text_len + (sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_MID) - 1) + message_len + (sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_END) - 1); m = LIBSSH2_ALLOC(session, m_len + 1); if (!m) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for status message", 0); return; } s = m; memcpy(s, LIBSSH2_PUBLICKEY_STATUS_TEXT_START, sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_START) - 1); s += sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_START) - 1; memcpy(s, status_text, status_text_len); s += status_text_len; memcpy(s, LIBSSH2_PUBLICKEY_STATUS_TEXT_MID, sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_MID) - 1); s += sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_MID) - 1; memcpy(s, message, message_len); s += message_len; memcpy(s, LIBSSH2_PUBLICKEY_STATUS_TEXT_END, sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_END) - 1); s += sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_END); libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, m, 1); } /* }}} */ /* {{{ libssh2_publickey_packet_receive * Read a packet from the subsystem */ static int libssh2_publickey_packet_receive(LIBSSH2_PUBLICKEY *pkey, unsigned char **data, unsigned long *data_len) { LIBSSH2_CHANNEL *channel = pkey->channel; LIBSSH2_SESSION *session = channel->session; unsigned char buffer[4]; unsigned long packet_len; unsigned char *packet; if (libssh2_channel_read(channel, (char *)buffer, 4) != 4) { libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Invalid response from publickey subsystem", 0); return -1; } packet_len = libssh2_ntohu32(buffer); packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate publickey response buffer", 0); return -1; } if (libssh2_channel_read(channel, (char *)packet, packet_len) != packet_len) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for publickey subsystem response packet", 0); LIBSSH2_FREE(session, packet); return -1; } *data = packet; *data_len = packet_len; return 0; } /* }}} */ /* {{{ libssh2_publickey_response_id * Translate a string response name to a numeric code * Data will be incremented by 4 + response_len on success only */ static int libssh2_publickey_response_id(unsigned char **pdata, int data_len) { unsigned long response_len; unsigned char *data = *pdata; LIBSSH2_PUBLICKEY_CODE_LIST *codes = libssh2_publickey_response_codes; if (data_len < 4) { /* Malformed response */ return -1; } response_len = libssh2_ntohu32(data); data += 4; data_len -= 4; if (data_len < response_len) { /* Malformed response */ return -1; } while (codes->name) { if (codes->name_len == response_len && strncmp(codes->name, (char *)data, response_len) == 0) { *pdata = data + response_len; return codes->code; } codes++; } return -1; } /* }}} */ /* {{{ libssh2_publickey_response_success * Generic helper routine to wait for success response and nothing else */ static int libssh2_publickey_response_success(LIBSSH2_PUBLICKEY *pkey) { LIBSSH2_SESSION *session = pkey->channel->session; unsigned char *data, *s; unsigned long data_len; int response; while (1) { if (libssh2_publickey_packet_receive(pkey, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for response from publickey subsystem", 0); return -1; } s = data; if ((response = libssh2_publickey_response_id(&s, data_len)) < 0) { libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Invalid publickey subsystem response code", 0); LIBSSH2_FREE(session, data); return -1; } switch (response) { case LIBSSH2_PUBLICKEY_RESPONSE_STATUS: /* Error, or processing complete */ { unsigned long status, descr_len, lang_len; unsigned char *descr, *lang; status = libssh2_ntohu32(s); s += 4; descr_len = libssh2_ntohu32(s); s += 4; descr = s; s += descr_len; lang_len = libssh2_ntohu32(s); s += 4; lang = s; s += lang_len; if (s > data + data_len) { libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Malformed publickey subsystem packet", 0); LIBSSH2_FREE(session, data); return -1; } if (status == LIBSSH2_PUBLICKEY_SUCCESS) { LIBSSH2_FREE(session, data); return 0; } libssh2_publickey_status_error(pkey, session, status, descr, descr_len); LIBSSH2_FREE(session, data); return -1; } default: /* Unknown/Unexpected */ libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Unexpected publickey subsystem response, ignoring", 0); LIBSSH2_FREE(session, data); data = NULL; } } /* never reached, but include `return` to silence compiler warnings */ return -1; } /* }}} */ /* ***************** * Publickey API * ***************** */ /* {{{ libssh2_publickey_init * Startup the publickey subsystem */ LIBSSH2_API LIBSSH2_PUBLICKEY *libssh2_publickey_init(LIBSSH2_SESSION *session) { LIBSSH2_PUBLICKEY *pkey = NULL; LIBSSH2_CHANNEL *channel = NULL; unsigned char buffer[19]; /* packet_len(4) + version_len(4) + "version"(7) + version_num(4) */ unsigned char *s, *data = NULL; unsigned long data_len; int response; #ifdef LIBSSH2_DEBUG_PUBLICKEY _libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Initializing publickey subsystem"); #endif channel = libssh2_channel_open_session(session); if (!channel) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Unable to startup channel", 0); goto err_exit; } if (libssh2_channel_subsystem(channel, "publickey")) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Unable to request publickey subsystem", 0); goto err_exit; } libssh2_channel_set_blocking(channel, 1); libssh2_channel_handle_extended_data(channel, LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE); pkey = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PUBLICKEY)); if (!pkey) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a new publickey structure", 0); goto err_exit; } pkey->channel = channel; pkey->version = 0; s = buffer; libssh2_htonu32(s, 4 + (sizeof("version") - 1) + 4); s += 4; libssh2_htonu32(s, sizeof("version") - 1); s += 4; memcpy(s, "version", sizeof("version") - 1); s += sizeof("version") - 1; libssh2_htonu32(s, LIBSSH2_PUBLICKEY_VERSION); s += 4; #ifdef LIBSSH2_DEBUG_PUBLICKEY _libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Sending publickey version packet advertising version %d support", (int)LIBSSH2_PUBLICKEY_VERSION); #endif if ((s - buffer) != libssh2_channel_write(channel, (char*)buffer, (s - buffer))) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send publickey version packet", 0); goto err_exit; } while (1) { if (libssh2_publickey_packet_receive(pkey, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for response from publickey subsystem", 0); goto err_exit; } s = data; if ((response = libssh2_publickey_response_id(&s, data_len)) < 0) { libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Invalid publickey subsystem response code", 0); goto err_exit; } switch (response) { case LIBSSH2_PUBLICKEY_RESPONSE_STATUS: /* Error */ { unsigned long status, descr_len, lang_len; unsigned char *descr, *lang; status = libssh2_ntohu32(s); s += 4; descr_len = libssh2_ntohu32(s); s += 4; descr = s; s += descr_len; lang_len = libssh2_ntohu32(s); s += 4; lang = s; s += lang_len; if (s > data + data_len) { libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Malformed publickey subsystem packet", 0); goto err_exit; } libssh2_publickey_status_error(NULL, session, status, descr, descr_len); goto err_exit; } case LIBSSH2_PUBLICKEY_RESPONSE_VERSION: /* What we want */ pkey->version = libssh2_ntohu32(s); if (pkey->version > LIBSSH2_PUBLICKEY_VERSION) { #ifdef LIBSSH2_DEBUG_PUBLICKEY _libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Truncating remote publickey version from %lu", pkey->version); #endif pkey->version = LIBSSH2_PUBLICKEY_VERSION; } #ifdef LIBSSH2_DEBUG_PUBLICKEY _libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Enabling publickey subsystem version %lu", pkey->version); #endif LIBSSH2_FREE(session, data); return pkey; default: /* Unknown/Unexpected */ libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Unexpected publickey subsystem response, ignoring", 0); LIBSSH2_FREE(session, data); data = NULL; } } /* Never reached except by direct goto */ err_exit: if (channel) { libssh2_channel_close(channel); } if (pkey) { LIBSSH2_FREE(session, pkey); } if (data) { LIBSSH2_FREE(session, data); } return NULL; } /* }}} */ /* {{{ libssh2_publickey_add_ex * Add a new public key entry */ LIBSSH2_API int libssh2_publickey_add_ex(LIBSSH2_PUBLICKEY *pkey, const unsigned char *name, unsigned long name_len, const unsigned char *blob, unsigned long blob_len, char overwrite, unsigned long num_attrs, libssh2_publickey_attribute attrs[]) { LIBSSH2_CHANNEL *channel = pkey->channel; LIBSSH2_SESSION *session = channel->session; unsigned char *packet = NULL, *s; unsigned long i, packet_len = 19 + name_len + blob_len; unsigned char *comment = NULL; unsigned long comment_len = 0; /* packet_len(4) + add_len(4) + "add"(3) + name_len(4) + {name} blob_len(4) + {blob} */ #ifdef LIBSSH2_DEBUG_PUBLICKEY _libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Adding %s pubickey", name); #endif if (pkey->version == 1) { for(i = 0; i < num_attrs; i++) { /* Search for a comment attribute */ if (attrs[i].name_len == (sizeof("comment") - 1) && strncmp(attrs[i].name, "comment", sizeof("comment") - 1) == 0) { comment = (unsigned char *)attrs[i].value; comment_len = attrs[i].value_len; break; } } packet_len += 4 + comment_len; } else { packet_len += 5; /* overwrite(1) + attribute_count(4) */ for(i = 0; i < num_attrs; i++) { packet_len += 9 + attrs[i].name_len + attrs[i].value_len; /* name_len(4) + value_len(4) + mandatory(1) */ } } packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for publickey \"add\" packet", 0); return -1; } s = packet; libssh2_htonu32(s, packet_len - 4); s += 4; libssh2_htonu32(s, sizeof("add") - 1); s += 4; memcpy(s, "add", sizeof("add") - 1); s += sizeof("add") - 1; if (pkey->version == 1) { libssh2_htonu32(s, comment_len); s += 4; if (comment) { memcpy(s, comment, comment_len); s += comment_len; } libssh2_htonu32(s, name_len); s += 4; memcpy(s, name, name_len); s += name_len; libssh2_htonu32(s, blob_len); s += 4; memcpy(s, blob, blob_len); s += blob_len; } else { /* Version == 2 */ libssh2_htonu32(s, name_len); s += 4; memcpy(s, name, name_len); s += name_len; libssh2_htonu32(s, blob_len); s += 4; memcpy(s, blob, blob_len); s += blob_len; *(s++) = overwrite ? 0xFF : 0; libssh2_htonu32(s, num_attrs); s += 4; for(i = 0; i < num_attrs; i++) { libssh2_htonu32(s, attrs[i].name_len); s += 4; memcpy(s, attrs[i].name, attrs[i].name_len); s += attrs[i].name_len; libssh2_htonu32(s, attrs[i].value_len); s += 4; memcpy(s, attrs[i].value, attrs[i].value_len); s += attrs[i].value_len; *(s++) = attrs[i].mandatory ? 0xFF : 0; } } #ifdef LIBSSH2_DEBUG_PUBLICKEY _libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Sending publickey \"add\" packet: type=%s blob_len=%ld num_attrs=%ld", name, blob_len, num_attrs); #endif if ((s - packet) != libssh2_channel_write(channel, (char *)packet, (s - packet))) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send publickey add packet", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); packet = NULL; return libssh2_publickey_response_success(pkey); } /* }}} */ /* {{{ libssh2_publickey_remove_ex * Remove an existing publickey so that authentication can no longer be performed using it */ LIBSSH2_API int libssh2_publickey_remove_ex(LIBSSH2_PUBLICKEY *pkey, const unsigned char *name, unsigned long name_len, const unsigned char *blob, unsigned long blob_len) { LIBSSH2_CHANNEL *channel = pkey->channel; LIBSSH2_SESSION *session = channel->session; unsigned char *s, *packet = NULL; unsigned long packet_len = 22 + name_len + blob_len; /* packet_len(4) + remove_len(4) + "remove"(6) + name_len(4) + {name} blob_len(4) + {blob} */ packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for publickey \"remove\" packet", 0); return -1; } s = packet; libssh2_htonu32(s, packet_len - 4); s += 4; libssh2_htonu32(s, sizeof("remove") - 1); s += 4; memcpy(s, "remove", sizeof("remove") - 1); s += sizeof("remove") - 1; libssh2_htonu32(s, name_len); s += 4; memcpy(s, name, name_len); s += name_len; libssh2_htonu32(s, blob_len); s += 4; memcpy(s, blob, blob_len); s += blob_len; #ifdef LIBSSH2_DEBUG_PUBLICKEY _libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Sending publickey \"remove\" packet: type=%s blob_len=%ld", name, blob_len); #endif if ((s - packet) != libssh2_channel_write(channel, (char *)packet, (s - packet))) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send publickey remove packet", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); packet = NULL; return libssh2_publickey_response_success(pkey); } /* }}} */ /* {{{ libssh2_publickey_list_fetch * Fetch a list of supported public key from a server */ LIBSSH2_API int libssh2_publickey_list_fetch(LIBSSH2_PUBLICKEY *pkey, unsigned long *num_keys, libssh2_publickey_list **pkey_list) { LIBSSH2_CHANNEL *channel = pkey->channel; LIBSSH2_SESSION *session = channel->session; libssh2_publickey_list *list = NULL; unsigned char *s, buffer[12], *data = NULL; unsigned long buffer_len = 12, keys = 0, max_keys = 0, data_len, i; /* packet_len(4) + list_len(4) + "list"(4) */ int response; s = buffer; libssh2_htonu32(s, buffer_len - 4); s += 4; libssh2_htonu32(s, sizeof("list") - 1); s += 4; memcpy(s, "list", sizeof("list") - 1); s += sizeof("list") - 1; #ifdef LIBSSH2_DEBUG_PUBLICKEY _libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Sending publickey \"list\" packet"); #endif if ((s - buffer) != libssh2_channel_write(channel, (char *)buffer, (s - buffer))) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send publickey list packet", 0); return -1; } while (1) { if (libssh2_publickey_packet_receive(pkey, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for response from publickey subsystem", 0); goto err_exit; } s = data; if ((response = libssh2_publickey_response_id(&s, data_len)) < 0) { libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Invalid publickey subsystem response code", 0); goto err_exit; } switch (response) { case LIBSSH2_PUBLICKEY_RESPONSE_STATUS: /* Error, or processing complete */ { unsigned long status, descr_len, lang_len; unsigned char *descr, *lang; status = libssh2_ntohu32(s); s += 4; descr_len = libssh2_ntohu32(s); s += 4; descr = s; s += descr_len; lang_len = libssh2_ntohu32(s); s += 4; lang = s; s += lang_len; if (s > data + data_len) { libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Malformed publickey subsystem packet", 0); goto err_exit; } if (status == LIBSSH2_PUBLICKEY_SUCCESS) { LIBSSH2_FREE(session, data); *pkey_list = list; *num_keys = keys; return 0; } libssh2_publickey_status_error(pkey, session, status, descr, descr_len); goto err_exit; } case LIBSSH2_PUBLICKEY_RESPONSE_PUBLICKEY: /* What we want */ if (keys >= max_keys) { /* Grow the key list if necessary */ max_keys += 8; list = LIBSSH2_REALLOC(session, list, (max_keys + 1) * sizeof(libssh2_publickey_list)); if (!list) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for publickey list", 0); goto err_exit; } } if (pkey->version == 1) { unsigned long comment_len; comment_len = libssh2_ntohu32(s); s += 4; if (comment_len) { list[keys].num_attrs = 1; list[keys].attrs = LIBSSH2_ALLOC(session, sizeof(libssh2_publickey_attribute)); if (!list[keys].attrs) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for publickey attributes", 0); goto err_exit; } list[keys].attrs[0].name = "comment"; list[keys].attrs[0].name_len = sizeof("comment") - 1; list[keys].attrs[0].value = (char *)s; list[keys].attrs[0].value_len = comment_len; list[keys].attrs[0].mandatory = 0; s += comment_len; } else { list[keys].num_attrs = 0; list[keys].attrs = NULL; } list[keys].name_len = libssh2_ntohu32(s); s += 4; list[keys].name = s; s += list[keys].name_len; list[keys].blob_len = libssh2_ntohu32(s); s += 4; list[keys].blob = s; s += list[keys].blob_len; } else { /* Version == 2 */ list[keys].name_len = libssh2_ntohu32(s); s += 4; list[keys].name = s; s += list[keys].name_len; list[keys].blob_len = libssh2_ntohu32(s); s += 4; list[keys].blob = s; s += list[keys].blob_len; list[keys].num_attrs = libssh2_ntohu32(s); s += 4; if (list[keys].num_attrs) { list[keys].attrs = LIBSSH2_ALLOC(session, list[keys].num_attrs * sizeof(libssh2_publickey_attribute)); if (!list[keys].attrs) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for publickey attributes", 0); goto err_exit; } for(i = 0; i < list[keys].num_attrs; i++) { list[keys].attrs[i].name_len = libssh2_ntohu32(s); s += 4; list[keys].attrs[i].name = (char *)s; s += list[keys].attrs[i].name_len; list[keys].attrs[i].value_len = libssh2_ntohu32(s); s += 4; list[keys].attrs[i].value = (char *)s; s += list[keys].attrs[i].value_len; list[keys].attrs[i].mandatory = 0; /* actually an ignored value */ } } else { list[keys].attrs = NULL; } } list[keys].packet = data; /* To be FREEd in libssh2_publickey_list_free() */ keys++; list[keys].packet = NULL; /* Terminate the list */ data = NULL; break; default: /* Unknown/Unexpected */ libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Unexpected publickey subsystem response, ignoring", 0); LIBSSH2_FREE(session, data); } } /* Only reached via explicit goto */ err_exit: if (data) { LIBSSH2_FREE(session, data); } if (list) { libssh2_publickey_list_free(pkey, list); } return -1; } /* }}} */ /* {{{ libssh2_publickey_list_free * Free a previously fetched list of public keys */ LIBSSH2_API void libssh2_publickey_list_free(LIBSSH2_PUBLICKEY *pkey, libssh2_publickey_list *pkey_list) { LIBSSH2_SESSION *session = pkey->channel->session; libssh2_publickey_list *p = pkey_list; while (p->packet) { if (p->attrs) { LIBSSH2_FREE(session, p->attrs); } LIBSSH2_FREE(session, p->packet); p++; } LIBSSH2_FREE(session, pkey_list); } /* }}} */ /* {{{ libssh2_publickey_shutdown * Shutdown the publickey subsystem */ LIBSSH2_API void libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey) { LIBSSH2_SESSION *session = pkey->channel->session; libssh2_channel_free(pkey->channel); LIBSSH2_FREE(session, pkey); } /* }}} */ ================================================ FILE: Example/scp.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #include #include #define LIBSSH2_SCP_RESPONSE_BUFLEN 256 /* {{{ libssh2_scp_recv * Open a channel and request a remote file via SCP */ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const char *path, struct stat *sb) { int path_len = strlen(path); unsigned char *command, response[LIBSSH2_SCP_RESPONSE_BUFLEN]; unsigned long command_len = path_len + sizeof("scp -f "), response_len; LIBSSH2_CHANNEL *channel; long mode = 0, size = 0, mtime = 0, atime = 0; if (sb) { command_len++; } command = LIBSSH2_ALLOC(session, command_len); if (!command) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a command buffer for scp session", 0); return NULL; } if (sb) { memcpy(command, "scp -pf ", sizeof("scp -pf ") - 1); memcpy(command + sizeof("scp -pf ") - 1, path, path_len); } else { memcpy(command, "scp -f ", sizeof("scp -f ") - 1); memcpy(command + sizeof("scp -f ") - 1, path, path_len); } command[command_len - 1] = '\0'; #ifdef LIBSSH2_DEBUG_SCP _libssh2_debug(session, LIBSSH2_DBG_SCP, "Opening channel for SCP receive"); #endif /* Allocate a channel */ if ((channel = libssh2_channel_open_session(session)) == NULL) { LIBSSH2_FREE(session, command); return NULL; } /* Use blocking I/O for negotiation phase */ libssh2_channel_set_blocking(channel, 1); /* Request SCP for the desired file */ if (libssh2_channel_process_startup(channel, "exec", sizeof("exec") - 1, (const char *)command, command_len)) { LIBSSH2_FREE(session, command); libssh2_channel_free(channel); return NULL; } LIBSSH2_FREE(session, command); #ifdef LIBSSH2_DEBUG_SCP _libssh2_debug(session, LIBSSH2_DBG_SCP, "Sending initial wakeup"); #endif /* SCP ACK */ response[0] = '\0'; if (libssh2_channel_write(channel, response, 1) != 1) { libssh2_channel_free(channel); return NULL; } /* Parse SCP response */ response_len = 0; while (sb && (response_len < LIBSSH2_SCP_RESPONSE_BUFLEN)) { unsigned char *s, *p; if (libssh2_channel_read(channel, response + response_len, 1) <= 0) { /* Timeout, give up */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Timed out waiting for SCP response", 0); libssh2_channel_free(channel); return NULL; } response_len++; if (response[0] != 'T') { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response, missing Time data", 0); libssh2_channel_free(channel); return NULL; } if ((response_len > 1) && ((response[response_len-1] < '0') || (response[response_len-1] > '9')) && (response[response_len-1] != ' ') && (response[response_len-1] != '\r') && (response[response_len-1] != '\n')) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response", 0); libssh2_channel_free(channel); return NULL; } if ((response_len < 9) || (response[response_len-1] != '\n')) { if (response_len == LIBSSH2_SCP_RESPONSE_BUFLEN) { /* You had your chance */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Unterminated response from SCP server", 0); libssh2_channel_free(channel); return NULL; } /* Way too short to be an SCP response, or not done yet, short circuit */ continue; } /* We're guaranteed not to go under response_len == 0 by the logic above */ while ((response[response_len-1] == '\r') || (response[response_len-1] == '\n')) response_len--; response[response_len] = '\0'; if (response_len < 8) { /* EOL came too soon */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short", 0); libssh2_channel_free(channel); return NULL; } s = response + 1; p = (unsigned char *)strchr((const char *)s, ' '); if (!p || ((p - s) <= 0)) { /* No spaces or space in the wrong spot */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, malformed mtime", 0); libssh2_channel_free(channel); return NULL; } *(p++) = '\0'; /* Make sure we don't get fooled by leftover values */ errno = 0; mtime = strtol((const char *)s, NULL, 10); if (errno) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid mtime", 0); libssh2_channel_free(channel); return NULL; } s = (unsigned char *)strchr((const char *)p, ' '); if (!s || ((s - p) <= 0)) { /* No spaces or space in the wrong spot */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, malformed mtime.usec", 0); libssh2_channel_free(channel); return NULL; } /* Ignore mtime.usec */ s++; p = (unsigned char *)strchr((const char *)s, ' '); if (!p || ((p - s) <= 0)) { /* No spaces or space in the wrong spot */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short or malformed", 0); libssh2_channel_free(channel); return NULL; } *(p++) = '\0'; /* Make sure we don't get fooled by leftover values */ errno = 0; atime = strtol((const char *)s, NULL, 10); if (errno) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid atime", 0); libssh2_channel_free(channel); return NULL; } /* SCP ACK */ response[0] = '\0'; if (libssh2_channel_write(channel, response, 1) != 1) { libssh2_channel_free(channel); return NULL; } #ifdef LIBSSH2_DEBUG_SCP _libssh2_debug(session, LIBSSH2_DBG_SCP, "mtime = %ld, atime = %ld", mtime, atime); #endif /* We *should* check that atime.usec is valid, but why let that stop use? */ break; } response_len = 0; while (response_len < LIBSSH2_SCP_RESPONSE_BUFLEN) { char *s, *p, *e = NULL; if (libssh2_channel_read(channel, response + response_len, 1) <= 0) { /* Timeout, give up */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Timed out waiting for SCP response", 0); libssh2_channel_free(channel); return NULL; } response_len++; if (response[0] != 'C') { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server", 0); libssh2_channel_free(channel); return NULL; } if ((response_len > 1) && (response[response_len-1] != '\r') && (response[response_len-1] != '\n') && ((response[response_len-1] < 32) || (response[response_len-1] > 126))) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response", 0); libssh2_channel_free(channel); return NULL; } if ((response_len < 7) || (response[response_len-1] != '\n')) { if (response_len == LIBSSH2_SCP_RESPONSE_BUFLEN) { /* You had your chance */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Unterminated response from SCP server", 0); libssh2_channel_free(channel); return NULL; } /* Way too short to be an SCP response, or not done yet, short circuit */ continue; } /* We're guaranteed not to go under response_len == 0 by the logic above */ while ((response[response_len-1] == '\r') || (response[response_len-1] == '\n')) response_len--; response[response_len] = '\0'; if (response_len < 6) { /* EOL came too soon */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short", 0); libssh2_channel_free(channel); return NULL; } s = (char *)(response + 1); p = strchr(s, ' '); if (!p || ((p - s) <= 0)) { /* No spaces or space in the wrong spot */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, malformed mode", 0); libssh2_channel_free(channel); return NULL; } *(p++) = '\0'; /* Make sure we don't get fooled by leftover values */ errno = 0; mode = strtol(s, &e, 8); if ((e && *e) || errno) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid mode", 0); libssh2_channel_free(channel); return NULL; } s = strchr(p, ' '); if (!s || ((s - p) <= 0)) { /* No spaces or space in the wrong spot */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short or malformed", 0); libssh2_channel_free(channel); return NULL; } *(s++) = '\0'; /* Make sure we don't get fooled by leftover values */ errno = 0; size = strtol(p, &e, 10); if ((e && *e) || errno) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid size", 0); libssh2_channel_free(channel); return NULL; } /* SCP ACK */ response[0] = '\0'; if (libssh2_channel_write(channel, response, 1) != 1) { libssh2_channel_free(channel); return NULL; } #ifdef LIBSSH2_DEBUG_SCP _libssh2_debug(session, LIBSSH2_DBG_SCP, "mod = 0%lo size = %ld", mode, size); #endif /* We *should* check that basename is valid, but why let that stop us? */ break; } if (sb) { memset(sb, 0, sizeof(struct stat)); sb->st_mtime = mtime; sb->st_atime = atime; sb->st_size = size; sb->st_mode = mode; } /* Revert to non-blocking and let the data BEGIN! */ libssh2_channel_set_blocking(channel, 0); return channel; } /* }}} */ /* {{{ libssh2_scp_send_ex * Send a file using SCP */ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, size_t size, long mtime, long atime) { int path_len = strlen(path); unsigned char *command, response[LIBSSH2_SCP_RESPONSE_BUFLEN]; unsigned long response_len, command_len = path_len + sizeof("scp -t "); unsigned const char *base; LIBSSH2_CHANNEL *channel; if (mtime || atime) { command_len++; } command = LIBSSH2_ALLOC(session, command_len); if (!command) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a command buffer for scp session", 0); return NULL; } if (mtime || atime) { memcpy(command, "scp -pt ", sizeof("scp -pt ") - 1); memcpy(command + sizeof("scp -pt ") - 1, path, path_len); } else { memcpy(command, "scp -t ", sizeof("scp -t ") - 1); memcpy(command + sizeof("scp -t ") - 1, path, path_len); } command[command_len - 1] = '\0'; #ifdef LIBSSH2_DEBUG_SCP _libssh2_debug(session, LIBSSH2_DBG_SCP, "Opening channel for SCP send"); #endif /* Allocate a channel */ if ((channel = libssh2_channel_open_session(session)) == NULL) { /* previous call set libssh2_session_last_error(), pass it through */ LIBSSH2_FREE(session, command); return NULL; } /* Use blocking I/O for negotiation phase */ libssh2_channel_set_blocking(channel, 1); /* Request SCP for the desired file */ if (libssh2_channel_process_startup(channel, "exec", sizeof("exec") - 1, (const char *)command, command_len)) { /* previous call set libssh2_session_last_error(), pass it through */ LIBSSH2_FREE(session, command); libssh2_channel_free(channel); return NULL; } LIBSSH2_FREE(session, command); /* Wait for ACK */ if ((libssh2_channel_read(channel, response, 1) <= 0) || (response[0] != 0)) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0); libssh2_channel_free(channel); return NULL; } /* Send mtime and atime to be used for file */ if (mtime || atime) { response_len = snprintf((char *)response, LIBSSH2_SCP_RESPONSE_BUFLEN, "T%ld 0 %ld 0\n", mtime, atime); #ifdef LIBSSH2_DEBUG_SCP _libssh2_debug(session, LIBSSH2_DBG_SCP, "Sent %s", response); #endif if (libssh2_channel_write(channel, response, response_len) != response_len) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send time data for SCP file", 0); libssh2_channel_free(channel); return NULL; } /* Wait for ACK */ if ((libssh2_channel_read(channel, response, 1) <= 0) || (response[0] != 0)) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0); libssh2_channel_free(channel); return NULL; } } /* Send mode, size, and basename */ base = (unsigned char *)strrchr(path, '/'); if (base) { base++; } else { base = (unsigned char *)path; } response_len = snprintf((char *)response, LIBSSH2_SCP_RESPONSE_BUFLEN, "C0%o %lu %s\n", mode, (unsigned long)size, base); #ifdef LIBSSH2_DEBUG_SCP _libssh2_debug(session, LIBSSH2_DBG_SCP, "Sent %s", response); #endif if (libssh2_channel_write(channel, response, response_len) != response_len) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send core file data for SCP file", 0); libssh2_channel_free(channel); return NULL; } /* Wait for ACK */ if ((libssh2_channel_read(channel, response, 1) <= 0) || (response[0] != 0)) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0); libssh2_channel_free(channel); return NULL; } /* Ready to start, switch to non-blocking and let calling app send file */ libssh2_channel_set_blocking(channel, 0); return channel; } /* }}} */ ================================================ FILE: Example/session.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #include #ifndef WIN32 #include #endif #include #ifdef HAVE_GETTIMEOFDAY #include #include #endif #ifdef HAVE_POLL # include #else # ifdef HAVE_SELECT # ifdef HAVE_SYS_SELECT_H # include # else # include # include # endif # endif #endif /* {{{ libssh2_default_alloc */ static LIBSSH2_ALLOC_FUNC(libssh2_default_alloc) { return malloc(count); } /* }}} */ /* {{{ libssh2_default_free */ static LIBSSH2_FREE_FUNC(libssh2_default_free) { free(ptr); } /* }}} */ /* {{{ libssh2_default_realloc */ static LIBSSH2_REALLOC_FUNC(libssh2_default_realloc) { return realloc(ptr, count); } /* }}} */ /* {{{ libssh2_default_write */ static LIBSSH2_WRITE_FUNC(libssh2_default_write) { return send(session->socket_fd, buffer, length, LIBSSH2_SOCKET_SEND_FLAGS(session)); } static LIBSSH2_READ_FUNC(libssh2_default_read) { return recv(session->socket_fd, buffer, length, LIBSSH2_SOCKET_RECV_FLAGS(session)); } /* {{{ libssh2_session_set_user_info * ui is passed into the custom read / write functions */ LIBSSH2_API void libssh2_session_set_user_info(LIBSSH2_SESSION *session, void *ui) { session->userInfo = ui; } /* }}} */ /* {{{ libssh2_session_set_write * Set a custom function to handle the writing to the socket/stream */ LIBSSH2_API void libssh2_session_set_write(LIBSSH2_SESSION *session, void *my_write) { session->ssh_write = my_write; } /* }}} */ /* {{{ libssh2_session_set_read * Set a custom function to handle the reading from the socket/stream */ LIBSSH2_API void libssh2_session_set_read(LIBSSH2_SESSION *session, void *my_read) { session->ssh_read = my_read; } /* }}} */ /* {{{ libssh2_banner_receive * Wait for a hello from the remote host * Allocate a buffer and store the banner in session->remote.banner * Returns: 0 on success, 1 on failure */ static int libssh2_banner_receive(LIBSSH2_SESSION *session) { char banner[256]; int banner_len = 0; //unused int tries = 10; while ((banner_len < sizeof(banner)) && ((banner_len == 0) || (banner[banner_len-1] != '\n'))) { char c = '\0'; int ret; ret = LIBSSH2_READ(session, &c, 1); if (ret < 0) { #ifdef WIN32 switch (WSAGetLastError()) { case WSAEWOULDBLOCK: errno = EAGAIN; break; case WSAENOTSOCK: errno = EBADF; break; case WSAENOTCONN: case WSAECONNABORTED: errno = ENOTCONN; break; case WSAEINTR: errno = EINTR; break; } #endif /* WIN32 */ if (errno != EAGAIN) { /* Some kinda error, but don't break for non-blocking issues */ return 1; } } if (ret <= 0) continue; if (c == '\0') { /* NULLs are not allowed in SSH banners */ return 1; } banner[banner_len++] = c; } while (banner_len && ((banner[banner_len-1] == '\n') || (banner[banner_len-1] == '\r'))) { banner_len--; } if (!banner_len) return 1; session->remote.banner = LIBSSH2_ALLOC(session, banner_len + 1); memcpy(session->remote.banner, banner, banner_len); session->remote.banner[banner_len] = '\0'; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Received Banner: %s", session->remote.banner); #endif return 0; } /* }}} */ /* {{{ libssh2_banner_send * Send the default banner, or the one set via libssh2_setopt_string */ static int libssh2_banner_send(LIBSSH2_SESSION *session) { char *banner = LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF; int banner_len = sizeof(LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF) - 1; if (session->local.banner) { /* setopt_string will have given us our \r\n characters */ banner_len = strlen((const char *)session->local.banner); banner = (char *)session->local.banner; } #ifdef LIBSSH2_DEBUG_TRANSPORT { /* Hack and slash to avoid sending CRLF in debug output */ char banner_dup[256]; if (banner_len < 256) { memcpy(banner_dup, banner, banner_len - 2); banner_dup[banner_len - 2] = '\0'; } else { memcpy(banner_dup, banner, 255); banner[255] = '\0'; } _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Sending Banner: %s", banner_dup); } #endif return (LIBSSH2_WRITE(session, banner, banner_len) == banner_len) ? 0 : 1; } /* }}} */ /* {{{ libssh2_banner_set * Set the local banner */ LIBSSH2_API int libssh2_banner_set(LIBSSH2_SESSION *session, const char *banner) { int banner_len = banner ? strlen(banner) : 0; if (session->local.banner) { LIBSSH2_FREE(session, session->local.banner); session->local.banner = NULL; } if (!banner_len) { return 0; } session->local.banner = LIBSSH2_ALLOC(session, banner_len + 3); if (!session->local.banner) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for local banner", 0); return -1; } memcpy(session->local.banner, banner, banner_len); #ifdef LIBSSH2_DEBUG_TRANSPORT session->local.banner[banner_len] = '\0'; _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Setting local Banner: %s", session->local.banner); #endif session->local.banner[banner_len++] = '\r'; session->local.banner[banner_len++] = '\n'; session->local.banner[banner_len++] = '\0'; return 0; } /* }}} */ /* {{{ proto libssh2_session_init * Allocate and initialize a libssh2 session structure * Allows for malloc callbacks in case the calling program has its own memory manager * It's allowable (but unadvisable) to define some but not all of the malloc callbacks * An additional pointer value may be optionally passed to be sent to the callbacks (so they know who's asking) */ LIBSSH2_API LIBSSH2_SESSION *libssh2_session_init_ex( LIBSSH2_ALLOC_FUNC((*my_alloc)), LIBSSH2_FREE_FUNC((*my_free)), LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract) { LIBSSH2_ALLOC_FUNC((*local_alloc)) = libssh2_default_alloc; LIBSSH2_FREE_FUNC((*local_free)) = libssh2_default_free; LIBSSH2_REALLOC_FUNC((*local_realloc)) = libssh2_default_realloc; LIBSSH2_WRITE_FUNC((*local_write)) = libssh2_default_write; LIBSSH2_READ_FUNC((*local_read)) = libssh2_default_read; LIBSSH2_SESSION *session; if (my_alloc) local_alloc = my_alloc; if (my_free) local_free = my_free; if (my_realloc) local_realloc = my_realloc; session = local_alloc(sizeof(LIBSSH2_SESSION), abstract); memset(session, 0, sizeof(LIBSSH2_SESSION)); session->alloc = local_alloc; session->free = local_free; session->realloc = local_realloc; session->abstract = abstract; session->ssh_write = local_write; session->ssh_read = local_read; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "New session resource allocated"); #endif libssh2_crypto_init (); return session; } /* }}} */ /* {{{ libssh2_session_callback_set * Set (or reset) a callback function * Returns the prior address */ LIBSSH2_API void* libssh2_session_callback_set(LIBSSH2_SESSION *session, int cbtype, void *callback) { void *oldcb; switch (cbtype) { case LIBSSH2_CALLBACK_IGNORE: oldcb = session->ssh_msg_ignore; session->ssh_msg_ignore = callback; return oldcb; break; case LIBSSH2_CALLBACK_DEBUG: oldcb = session->ssh_msg_debug; session->ssh_msg_debug = callback; return oldcb; break; case LIBSSH2_CALLBACK_DISCONNECT: oldcb = session->ssh_msg_disconnect; session->ssh_msg_disconnect = callback; return oldcb; break; case LIBSSH2_CALLBACK_MACERROR: oldcb = session->macerror; session->macerror = callback; return oldcb; break; case LIBSSH2_CALLBACK_X11: oldcb = session->x11; session->x11 = callback; return oldcb; break; } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Setting Callback %d", cbtype); #endif return NULL; } /* }}} */ /* {{{ proto libssh2_session_startup * session: LIBSSH2_SESSION struct allocated and owned by the calling program * Returns: 0 on success, or non-zero on failure * Any memory allocated by libssh2 will use alloc/realloc/free callbacks in session * socket *must* be populated with an opened socket */ LIBSSH2_API int libssh2_session_startup(LIBSSH2_SESSION *session, int socket) { unsigned char *data; unsigned long data_len; unsigned char service[sizeof("ssh-userauth") + 5 - 1]; unsigned long service_length; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "session_startup for socket %d", socket); #endif if (socket < 0) { /* Did we forget something? */ libssh2_error(session, LIBSSH2_ERROR_SOCKET_NONE, "Bad socket provided", 0); return LIBSSH2_ERROR_SOCKET_NONE; } session->socket_fd = socket; /* TODO: Liveness check */ if (libssh2_banner_send(session)) { /* Unable to send banner? */ libssh2_error(session, LIBSSH2_ERROR_BANNER_SEND, "Error sending banner to remote host", 0); return LIBSSH2_ERROR_BANNER_SEND; } if (libssh2_banner_receive(session)) { /* Unable to receive banner from remote */ libssh2_error(session, LIBSSH2_ERROR_BANNER_NONE, "Timeout waiting for banner", 0); return LIBSSH2_ERROR_BANNER_NONE; } if (libssh2_kex_exchange(session, 0)) { libssh2_error(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to exchange encryption keys", 0); return LIBSSH2_ERROR_KEX_FAILURE; } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Requesting userauth service"); #endif /* Request the userauth service */ service[0] = SSH_MSG_SERVICE_REQUEST; libssh2_htonu32(service + 1, sizeof("ssh-userauth") - 1); memcpy(service + 5, "ssh-userauth", sizeof("ssh-userauth") - 1); if (libssh2_packet_write(session, service, sizeof("ssh-userauth") + 5 - 1)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to ask for ssh-userauth service", 0); return LIBSSH2_ERROR_SOCKET_SEND; } if (libssh2_packet_require(session, SSH_MSG_SERVICE_ACCEPT, &data, &data_len)) { return LIBSSH2_ERROR_SOCKET_DISCONNECT; } service_length = libssh2_ntohu32(data + 1); if ((service_length != (sizeof("ssh-userauth") - 1)) || strncmp("ssh-userauth", (const char *)data + 5, service_length)) { LIBSSH2_FREE(session, data); libssh2_error(session, LIBSSH2_ERROR_PROTO, "Invalid response received from server", 0); return LIBSSH2_ERROR_PROTO; } LIBSSH2_FREE(session, data); return 0; } /* }}} */ /* {{{ proto libssh2_session_free * Frees the memory allocated to the session * Also closes and frees any channels attached to this session */ LIBSSH2_API void libssh2_session_free(LIBSSH2_SESSION *session) { #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Freeing session resource", session->remote.banner); #endif while (session->channels.head) { LIBSSH2_CHANNEL *tmp = session->channels.head; libssh2_channel_free(session->channels.head); if (tmp == session->channels.head) { /* channel_free couldn't do it's job, perform a messy cleanup */ tmp = session->channels.head; /* unlink */ session->channels.head = tmp->next; /* free */ LIBSSH2_FREE(session, tmp); /* reverse linking isn't important here, we're killing the structure */ } } while (session->listeners) { libssh2_channel_forward_cancel(session->listeners); } if (session->state & LIBSSH2_STATE_NEWKEYS) { /* hostkey */ if (session->hostkey && session->hostkey->dtor) { session->hostkey->dtor(session, &session->server_hostkey_abstract); } /* Client to Server */ /* crypt */ if (session->local.crypt && session->local.crypt->dtor) { session->local.crypt->dtor(session, &session->local.crypt_abstract); } /* comp */ if (session->local.comp && session->local.comp->dtor) { session->local.comp->dtor(session, 1, &session->local.comp_abstract); } /* mac */ if (session->local.mac && session->local.mac->dtor) { session->local.mac->dtor(session, &session->local.mac_abstract); } /* Server to Client */ /* crypt */ if (session->remote.crypt && session->remote.crypt->dtor) { session->remote.crypt->dtor(session, &session->remote.crypt_abstract); } /* comp */ if (session->remote.comp && session->remote.comp->dtor) { session->remote.comp->dtor(session, 0, &session->remote.comp_abstract); } /* mac */ if (session->remote.mac && session->remote.mac->dtor) { session->remote.mac->dtor(session, &session->remote.mac_abstract); } /* session_id */ if (session->session_id) { LIBSSH2_FREE(session, session->session_id); } } /* Free banner(s) */ if (session->remote.banner) { LIBSSH2_FREE(session, session->remote.banner); } if (session->local.banner) { LIBSSH2_FREE(session, session->local.banner); } /* Free preference(s) */ if (session->kex_prefs) { LIBSSH2_FREE(session, session->kex_prefs); } if (session->hostkey_prefs) { LIBSSH2_FREE(session, session->hostkey_prefs); } if (session->local.crypt_prefs) { LIBSSH2_FREE(session, session->local.crypt_prefs); } if (session->local.mac_prefs) { LIBSSH2_FREE(session, session->local.mac_prefs); } if (session->local.comp_prefs) { LIBSSH2_FREE(session, session->local.comp_prefs); } if (session->local.lang_prefs) { LIBSSH2_FREE(session, session->local.lang_prefs); } if (session->remote.crypt_prefs) { LIBSSH2_FREE(session, session->remote.crypt_prefs); } if (session->remote.mac_prefs) { LIBSSH2_FREE(session, session->remote.mac_prefs); } if (session->remote.comp_prefs) { LIBSSH2_FREE(session, session->remote.comp_prefs); } if (session->remote.lang_prefs) { LIBSSH2_FREE(session, session->remote.lang_prefs); } /* Cleanup any remaining packets */ while (session->packets.head) { LIBSSH2_PACKET *tmp = session->packets.head; /* unlink */ session->packets.head = tmp->next; /* free */ LIBSSH2_FREE(session, tmp->data); LIBSSH2_FREE(session, tmp); } LIBSSH2_FREE(session, session); } /* }}} */ /* {{{ libssh2_session_disconnect_ex */ LIBSSH2_API int libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason, const char *description, const char *lang) { unsigned char *s, *data; unsigned long data_len, descr_len = 0, lang_len = 0; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Disconnecting: reason=%d, desc=%s, lang=%s", reason, description, lang); #endif if (description) { descr_len = strlen(description); } if (lang) { lang_len = strlen(lang); } data_len = descr_len + lang_len + 13; /* packet_type(1) + reason code(4) + descr_len(4) + lang_len(4) */ s = data = LIBSSH2_ALLOC(session, data_len); if (!data) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for disconnect packet", 0); return -1; } *(s++) = SSH_MSG_DISCONNECT; libssh2_htonu32(s, reason); s += 4; libssh2_htonu32(s, descr_len); s += 4; if (description) { memcpy(s, description, descr_len); s += descr_len; } libssh2_htonu32(s, lang_len); s += 4; if (lang) { memcpy(s, lang, lang_len); s += lang_len; } libssh2_packet_write(session, data, data_len); LIBSSH2_FREE(session, data); return 0; } /* }}} */ /* {{{ libssh2_session_methods * Return the currently active methods for method_type * NOTE: Currently lang_cs and lang_sc are ALWAYS set to empty string regardless of actual negotiation * Strings should NOT be freed */ LIBSSH2_API const char *libssh2_session_methods(LIBSSH2_SESSION *session, int method_type) { /* All methods have char *name as their first element */ LIBSSH2_KEX_METHOD *method = NULL; switch(method_type) { case LIBSSH2_METHOD_KEX: method = session->kex; break; case LIBSSH2_METHOD_HOSTKEY: method = (LIBSSH2_KEX_METHOD*)session->hostkey; break; case LIBSSH2_METHOD_CRYPT_CS: method = (LIBSSH2_KEX_METHOD*)session->local.crypt; break; case LIBSSH2_METHOD_CRYPT_SC: method = (LIBSSH2_KEX_METHOD*)session->remote.crypt; break; case LIBSSH2_METHOD_MAC_CS: method = (LIBSSH2_KEX_METHOD*)session->local.mac; break; case LIBSSH2_METHOD_MAC_SC: method = (LIBSSH2_KEX_METHOD*)session->remote.mac; break; case LIBSSH2_METHOD_COMP_CS: method = (LIBSSH2_KEX_METHOD*)session->local.comp; break; case LIBSSH2_METHOD_COMP_SC: method = (LIBSSH2_KEX_METHOD*)session->remote.comp; break; case LIBSSH2_METHOD_LANG_CS: return ""; break; case LIBSSH2_METHOD_LANG_SC: return ""; break; default: libssh2_error(session, LIBSSH2_ERROR_INVAL, "Invalid parameter specified for method_type", 0); return NULL; break; } if (!method) { libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, "No method negotiated", 0); return NULL; } return method->name; } /* }}} */ /* {{{ libssh2_session_abstract * Retrieve a pointer to the abstract property */ LIBSSH2_API void **libssh2_session_abstract(LIBSSH2_SESSION *session) { return &session->abstract; } /* }}} */ /* {{{ libssh2_session_last_error * Returns error code and populates an error string into errmsg * If want_buf is non-zero then the string placed into errmsg must be freed by the calling program * Otherwise it is assumed to be owned by libssh2 */ LIBSSH2_API int libssh2_session_last_error(LIBSSH2_SESSION *session, char **errmsg, int *errmsg_len, int want_buf) { /* No error to report */ if (!session->err_code) { if (errmsg) { if (want_buf) { *errmsg = LIBSSH2_ALLOC(session, 1); if (*errmsg) { **errmsg = 0; } } else { *errmsg = ""; } } if (errmsg_len) { *errmsg_len = 0; } return 0; } if (errmsg) { char *serrmsg = session->err_msg ? session->err_msg : ""; int ownbuf = session->err_msg ? session->err_should_free : 0; if (want_buf) { if (ownbuf) { /* Just give the calling program the buffer */ *errmsg = serrmsg; session->err_should_free = 0; } else { /* Make a copy so the calling program can own it */ *errmsg = LIBSSH2_ALLOC(session, session->err_msglen + 1); if (*errmsg) { memcpy(*errmsg, session->err_msg, session->err_msglen); (*errmsg)[session->err_msglen] = 0; } } } else { *errmsg = serrmsg; } } if (errmsg_len) { *errmsg_len = session->err_msglen; } return session->err_code; } /* }}} */ /* {{{ libssh2_session_flag * Set/Get session flags * Passing flag==0 will avoid changing session->flags while still returning its current value */ LIBSSH2_API int libssh2_session_flag(LIBSSH2_SESSION *session, int flag, int value) { if (value) { session->flags |= flag; } else { session->flags &= ~flag; } return session->flags; } /* }}} */ /* {{{ libssh2_poll_channel_read * Returns 0 if no data is waiting on channel, * non-0 if data is available */ LIBSSH2_API int libssh2_poll_channel_read(LIBSSH2_CHANNEL *channel, int extended) { LIBSSH2_SESSION *session = channel->session; LIBSSH2_PACKET *packet = session->packets.head; while (packet) { if (((packet->data[0] == SSH_MSG_CHANNEL_DATA) && (extended == 0) && (channel->local.id == libssh2_ntohu32(packet->data + 1))) || ((packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) && (extended != 0) && (channel->local.id == libssh2_ntohu32(packet->data + 1)))) { /* Found data waiting to be read */ return 1; } packet = packet->next; } return 0; } /* }}} */ /* {{{ libssh2_poll_channel_write * Returns 0 if writing to channel would block, * non-0 if data can be written without blocking */ inline int libssh2_poll_channel_write(LIBSSH2_CHANNEL *channel) { return channel->local.window_size ? 1 : 0; } /* }}} */ /* {{{ libssh2_poll_listener_queued * Returns 0 if no connections are waiting to be accepted * non-0 if one or more connections are available */ inline int libssh2_poll_listener_queued(LIBSSH2_LISTENER *listener) { return listener->queue ? 1 : 0; } /* }}} */ /* {{{ libssh2_poll * Poll sockets, channels, and listeners for activity */ LIBSSH2_API int libssh2_poll(LIBSSH2_POLLFD *fds, unsigned int nfds, long timeout) { long timeout_remaining; int i, active_fds; #ifdef HAVE_POLL LIBSSH2_SESSION *session = NULL; struct pollfd sockets[nfds]; /* Setup sockets for polling */ for(i = 0; i < nfds; i++) { fds[i].revents = 0; switch (fds[i].type) { case LIBSSH2_POLLFD_SOCKET: sockets[i].fd = fds[i].fd.socket; sockets[i].events = fds[i].events; sockets[i].revents = 0; break; case LIBSSH2_POLLFD_CHANNEL: sockets[i].fd = fds[i].fd.channel->session->socket_fd; sockets[i].events = POLLIN; sockets[i].revents = 0; if (!session) session = fds[i].fd.channel->session; break; case LIBSSH2_POLLFD_LISTENER: sockets[i].fd = fds[i].fd.listener->session->socket_fd; sockets[i].events = POLLIN; sockets[i].revents = 0; if (!session) session = fds[i].fd.listener->session; break; default: if (session) libssh2_error(session, LIBSSH2_ERROR_INVALID_POLL_TYPE, "Invalid descriptor passed to libssh2_poll()", 0); return -1; } } #elif defined(HAVE_SELECT) LIBSSH2_SESSION *session = NULL; int maxfd = 0; fd_set rfds,wfds; struct timeval tv; FD_ZERO(&rfds); FD_ZERO(&wfds); for(i = 0; i < nfds; i++) { fds[i].revents = 0; switch (fds[i].type) { case LIBSSH2_POLLFD_SOCKET: if (fds[i].events & LIBSSH2_POLLFD_POLLIN) { FD_SET(fds[i].fd.socket, &rfds); if (fds[i].fd.socket > maxfd) maxfd = fds[i].fd.socket; } if (fds[i].events & LIBSSH2_POLLFD_POLLOUT) { FD_SET(fds[i].fd.socket, &wfds); if (fds[i].fd.socket > maxfd) maxfd = fds[i].fd.socket; } break; case LIBSSH2_POLLFD_CHANNEL: FD_SET(fds[i].fd.channel->session->socket_fd, &rfds); if (fds[i].fd.channel->session->socket_fd > maxfd) maxfd = fds[i].fd.channel->session->socket_fd; if (!session) session = fds[i].fd.channel->session; break; case LIBSSH2_POLLFD_LISTENER: FD_SET(fds[i].fd.listener->session->socket_fd, &rfds); if (fds[i].fd.listener->session->socket_fd > maxfd) maxfd = fds[i].fd.listener->session->socket_fd; if (!session) session = fds[i].fd.listener->session; break; default: if (session) libssh2_error(session, LIBSSH2_ERROR_INVALID_POLL_TYPE, "Invalid descriptor passed to libssh2_poll()", 0); return -1; } } #else /* No select() or poll() * no sockets sturcture to setup */ timeout = 0; #endif /* HAVE_POLL or HAVE_SELECT */ timeout_remaining = timeout; do { #if defined(HAVE_POLL) || defined(HAVE_SELECT) int sysret; #endif active_fds = 0; for (i = 0; i < nfds; i++) { if (fds[i].events != fds[i].revents) { switch (fds[i].type) { case LIBSSH2_POLLFD_CHANNEL: if ((fds[i].events & LIBSSH2_POLLFD_POLLIN) && /* Want to be ready for read */ ((fds[i].revents & LIBSSH2_POLLFD_POLLIN) == 0)) { /* Not yet known to be ready for read */ fds[i].revents |= libssh2_poll_channel_read(fds[i].fd.channel, 0) ? LIBSSH2_POLLFD_POLLIN : 0; } if ((fds[i].events & LIBSSH2_POLLFD_POLLEXT) && /* Want to be ready for extended read */ ((fds[i].revents & LIBSSH2_POLLFD_POLLEXT) == 0)) { /* Not yet known to be ready for extended read */ fds[i].revents |= libssh2_poll_channel_read(fds[i].fd.channel, 1) ? LIBSSH2_POLLFD_POLLEXT : 0; } if ((fds[i].events & LIBSSH2_POLLFD_POLLOUT) && /* Want to be ready for write */ ((fds[i].revents & LIBSSH2_POLLFD_POLLOUT) == 0)) { /* Not yet known to be ready for write */ fds[i].revents |= libssh2_poll_channel_write(fds[i].fd.channel) ? LIBSSH2_POLLFD_POLLOUT : 0; } if (fds[i].fd.channel->remote.close || fds[i].fd.channel->local.close) { fds[i].revents |= LIBSSH2_POLLFD_CHANNEL_CLOSED; } if (fds[i].fd.channel->session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) { fds[i].revents |= LIBSSH2_POLLFD_CHANNEL_CLOSED | LIBSSH2_POLLFD_SESSION_CLOSED; } break; case LIBSSH2_POLLFD_LISTENER: if ((fds[i].events & LIBSSH2_POLLFD_POLLIN) && /* Want a connection */ ((fds[i].revents & LIBSSH2_POLLFD_POLLIN) == 0)) { /* No connections known of yet */ fds[i].revents |= libssh2_poll_listener_queued(fds[i].fd.listener) ? LIBSSH2_POLLFD_POLLIN : 0; } if (fds[i].fd.listener->session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) { fds[i].revents |= LIBSSH2_POLLFD_LISTENER_CLOSED | LIBSSH2_POLLFD_SESSION_CLOSED; } break; } } if (fds[i].revents) { active_fds++; } } if (active_fds) { /* Don't block on the sockets if we have channels/listeners which are ready */ timeout_remaining = 0; } #ifdef HAVE_POLL #ifdef HAVE_GETTIMEOFDAY { struct timeval tv_begin, tv_end; gettimeofday((struct timeval *)&tv_begin, NULL); sysret = poll(sockets, nfds, timeout_remaining); gettimeofday((struct timeval *)&tv_end, NULL); timeout_remaining -= (tv_end.tv_sec - tv_begin.tv_sec) * 1000; timeout_remaining -= (tv_end.tv_usec - tv_begin.tv_usec) / 1000; } #else /* If the platform doesn't support gettimeofday, * then just make the call non-blocking and walk away */ sysret = poll(sockets, nfds, timeout_remaining); timeout_remaining = 0; #endif /* HAVE_GETTIMEOFDAY */ if (sysret > 0) { for (i = 0; i < nfds; i++) { switch (fds[i].type) { case LIBSSH2_POLLFD_SOCKET: fds[i].revents = sockets[i].revents; sockets[i].revents = 0; /* In case we loop again, be nice */ if (fds[i].revents) { active_fds++; } break; case LIBSSH2_POLLFD_CHANNEL: if (sockets[i].events & POLLIN) { /* Spin session until no data available */ while (libssh2_packet_read(fds[i].fd.channel->session, 0) > 0); } if (sockets[i].revents & POLLHUP) { fds[i].revents |= LIBSSH2_POLLFD_CHANNEL_CLOSED | LIBSSH2_POLLFD_SESSION_CLOSED; } sockets[i].revents = 0; break; case LIBSSH2_POLLFD_LISTENER: if (sockets[i].events & POLLIN) { /* Spin session until no data available */ while (libssh2_packet_read(fds[i].fd.listener->session, 0) > 0); } if (sockets[i].revents & POLLHUP) { fds[i].revents |= LIBSSH2_POLLFD_LISTENER_CLOSED | LIBSSH2_POLLFD_SESSION_CLOSED; } sockets[i].revents = 0; break; } } } #elif defined(HAVE_SELECT) tv.tv_sec = timeout_remaining / 1000; tv.tv_usec = (timeout_remaining % 1000) * 1000; #ifdef HAVE_GETTIMEOFDAY { struct timeval tv_begin, tv_end; gettimeofday((struct timeval *)&tv_begin, NULL); sysret = select(maxfd, &rfds, &wfds, NULL, &tv); gettimeofday((struct timeval *)&tv_end, NULL); timeout_remaining -= (tv_end.tv_sec - tv_begin.tv_sec) * 1000; timeout_remaining -= (tv_end.tv_usec - tv_begin.tv_usec) / 1000; } #else /* If the platform doesn't support gettimeofday, * then just make the call non-blocking and walk away */ sysret = select(maxfd, &rfds, &wfds, NULL, &tv); timeout_remaining = 0; #endif if (sysret > 0) { for (i = 0; i < nfds; i++) { switch (fds[i].type) { case LIBSSH2_POLLFD_SOCKET: if (FD_ISSET(fds[i].fd.socket, &rfds)) { fds[i].revents |= LIBSSH2_POLLFD_POLLIN; } if (FD_ISSET(fds[i].fd.socket, &wfds)) { fds[i].revents |= LIBSSH2_POLLFD_POLLOUT; } if (fds[i].revents) { active_fds++; } break; case LIBSSH2_POLLFD_CHANNEL: if (FD_ISSET(fds[i].fd.channel->session->socket_fd, &rfds)) { /* Spin session until no data available */ while (libssh2_packet_read(fds[i].fd.channel->session, 0) > 0); } break; case LIBSSH2_POLLFD_LISTENER: if (FD_ISSET(fds[i].fd.listener->session->socket_fd, &rfds)) { /* Spin session until no data available */ while (libssh2_packet_read(fds[i].fd.listener->session, 0) > 0); } break; } } } #endif /* else no select() or poll() -- timeout (and by extension timeout_remaining) will be equal to 0 */ } while ((timeout_remaining > 0) && !active_fds); return active_fds; } /* }}} */ ================================================ FILE: Example/sftp.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #include "libssh2_sftp.h" /* Note: Version 6 was documented at the time of writing * However it was marked as "DO NOT IMPLEMENT" due to pending changes * * This release of libssh2 implements Version 5 with automatic downgrade * based on server's declaration */ /* SFTP packet types */ #define SSH_FXP_INIT 1 #define SSH_FXP_VERSION 2 #define SSH_FXP_OPEN 3 #define SSH_FXP_CLOSE 4 #define SSH_FXP_READ 5 #define SSH_FXP_WRITE 6 #define SSH_FXP_LSTAT 7 #define SSH_FXP_FSTAT 8 #define SSH_FXP_SETSTAT 9 #define SSH_FXP_FSETSTAT 10 #define SSH_FXP_OPENDIR 11 #define SSH_FXP_READDIR 12 #define SSH_FXP_REMOVE 13 #define SSH_FXP_MKDIR 14 #define SSH_FXP_RMDIR 15 #define SSH_FXP_REALPATH 16 #define SSH_FXP_STAT 17 #define SSH_FXP_RENAME 18 #define SSH_FXP_READLINK 19 #define SSH_FXP_SYMLINK 20 #define SSH_FXP_STATUS 101 #define SSH_FXP_HANDLE 102 #define SSH_FXP_DATA 103 #define SSH_FXP_NAME 104 #define SSH_FXP_ATTRS 105 #define SSH_FXP_EXTENDED 200 #define SSH_FXP_EXTENDED_REPLY 201 struct _LIBSSH2_SFTP { LIBSSH2_CHANNEL *channel; unsigned long request_id, version; LIBSSH2_PACKET_BRIGADE packets; LIBSSH2_SFTP_HANDLE *handles; unsigned long last_errno; }; #define LIBSSH2_SFTP_HANDLE_FILE 0 #define LIBSSH2_SFTP_HANDLE_DIR 1 /* S_IFREG */ #define LIBSSH2_SFTP_ATTR_PFILETYPE_FILE 0100000 /* S_IFDIR */ #define LIBSSH2_SFTP_ATTR_PFILETYPE_DIR 0040000 struct _LIBSSH2_SFTP_HANDLE { LIBSSH2_SFTP *sftp; LIBSSH2_SFTP_HANDLE *prev, *next; char *handle; int handle_len; char handle_type; union _libssh2_sftp_handle_data { struct _libssh2_sftp_handle_file_data { libssh2_uint64_t offset; } file; struct _libssh2_sftp_handle_dir_data { unsigned long names_left; void *names_packet; char *next_name; } dir; } u; }; /* {{{ libssh2_sftp_packet_add * Add a packet to the SFTP packet brigade */ static int libssh2_sftp_packet_add(LIBSSH2_SFTP *sftp, unsigned char *data, unsigned long data_len) { if (!sftp) { return -1; } LIBSSH2_SESSION *session = sftp->channel->session; LIBSSH2_PACKET *packet; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Received packet %d", (int)data[0]); #endif packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate datablock for SFTP packet", 0); return -1; } memset(packet, 0, sizeof(LIBSSH2_PACKET)); packet->data = data; packet->data_len = data_len; packet->data_head = 5; packet->brigade = &sftp->packets; packet->next = NULL; packet->prev = sftp->packets.tail; if (packet->prev) { packet->prev->next = packet; } else { sftp->packets.head = packet; } sftp->packets.tail = packet; return 0; } /* }}} */ /* {{{ libssh2_sftp_packet_read * Frame an SFTP packet off the channel */ static int libssh2_sftp_packet_read(LIBSSH2_SFTP *sftp, int should_block) { if (!sftp) { return -1; } LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; unsigned char buffer[4]; /* To store the packet length */ unsigned char *packet; unsigned long packet_len, packet_received; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Waiting for packet: %s block", should_block ? "will" : "willnot"); #endif if (should_block) { libssh2_channel_set_blocking(channel, 1); if (4 != libssh2_channel_read(channel, buffer, 4)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for FXP packet", 0); return -1; } } else { libssh2_channel_set_blocking(channel, 0); if (1 != libssh2_channel_read(channel, buffer, 1)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for FXP packet", 0); return 0; } libssh2_channel_set_blocking(channel, 1); if (3 != libssh2_channel_read(channel, buffer + 1, 3)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for FXP packet", 0); return -1; } } packet_len = libssh2_ntohu32(buffer); #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Data begin - Packet Length: %lu", packet_len); #endif if (packet_len > LIBSSH2_SFTP_PACKET_MAXLEN) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, "SFTP packet too large", 0); return -1; } packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate SFTP packet", 0); return -1; } packet_received = 0; while (packet_len > packet_received) { long bytes_received = libssh2_channel_read(channel, packet + packet_received, packet_len - packet_received); if (bytes_received < 0) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Receive error waiting for SFTP packet", 0); LIBSSH2_FREE(session, packet); return -1; } packet_received += bytes_received; } if (libssh2_sftp_packet_add(sftp, packet, packet_len)) { LIBSSH2_FREE(session, packet); return -1; } return packet[0]; } /* }}} */ /* {{{ libssh2_sftp_packet_ask * A la libssh2_packet_ask() */ static int libssh2_sftp_packet_ask(LIBSSH2_SFTP *sftp, unsigned char packet_type, unsigned long request_id, unsigned char **data, unsigned long *data_len, int poll_channel) { if (!sftp) { return -1; } LIBSSH2_SESSION *session = sftp->channel->session; LIBSSH2_PACKET *packet = sftp->packets.head; unsigned char match_buf[5]; int match_len = 5; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Asking for %d packet", (int)packet_type); #endif if (poll_channel) { if (libssh2_sftp_packet_read(sftp, 0) < 0) { return -1; } } match_buf[0] = packet_type; if (packet_type == SSH_FXP_VERSION) { /* Special consideration when matching VERSION packet */ match_len = 1; } else { libssh2_htonu32(match_buf + 1, request_id); } while (packet) { if (strncmp((const char *)packet->data, (const char *)match_buf, match_len) == 0) { *data = packet->data; *data_len = packet->data_len; if (packet->prev) { packet->prev->next = packet->next; } else { sftp->packets.head = packet->next; } if (packet->next) { packet->next->prev = packet->prev; } else { sftp->packets.tail = packet->prev; } LIBSSH2_FREE(session, packet); return 0; } packet = packet->next; } return -1; } /* }}} */ /* {{{ libssh2_sftp_packet_require * A la libssh2_packet_require */ static int libssh2_sftp_packet_require(LIBSSH2_SFTP *sftp, unsigned char packet_type, unsigned long request_id, unsigned char **data, unsigned long *data_len) { if (!sftp) { return -1; } LIBSSH2_SESSION *session = sftp->channel->session; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Requiring %d packet", (int)packet_type); #endif if (libssh2_sftp_packet_ask(sftp, packet_type, request_id, data, data_len, 0) == 0) { /* A packet was available in the packet brigade */ return 0; } while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { int ret = libssh2_sftp_packet_read(sftp, 1); if (ret < 0) { return -1; } if (ret == 0) continue; if (packet_type == ret) { /* Be lazy, let packet_ask pull it out of the brigade */ return libssh2_sftp_packet_ask(sftp, packet_type, request_id, data, data_len, 0); } } /* Only reached if the socket died */ return -1; } /* }}} */ /* {{{ libssh2_sftp_packet_requirev * Requie one of N possible reponses */ static int libssh2_sftp_packet_requirev(LIBSSH2_SFTP *sftp, int num_valid_responses, unsigned char *valid_responses, unsigned long request_id, unsigned char **data, unsigned long *data_len) { if (!sftp) { return -1; } int i; /* Flush */ while (libssh2_sftp_packet_read(sftp, 0) > 0); while (sftp->channel->session->socket_state == LIBSSH2_SOCKET_CONNECTED) { int ret; for(i = 0; i < num_valid_responses; i++) { if (libssh2_sftp_packet_ask(sftp, valid_responses[i], request_id, data, data_len, 0) == 0) { return 0; } } ret = libssh2_sftp_packet_read(sftp, 1); if (ret < 0) { return -1; } if (ret == 0) continue; } return -1; } /* }}} */ /* {{{ libssh2_sftp_attrsize * Size that attr will occupy when turned into a bin struct */ static int libssh2_sftp_attrsize(LIBSSH2_SFTP_ATTRIBUTES *attrs) { if (!attrs) { return -1; } int attrsize = 4; /* flags(4) */ if (!attrs) { return attrsize; } if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) attrsize += 8; if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) attrsize += 8; if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) attrsize += 4; if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) attrsize += 8; /* atime + mtime as u32 */ return attrsize; } /* }}} */ /* {{{ libssh2_sftp_attr2bin * Populate attributes into an SFTP block */ static int libssh2_sftp_attr2bin(unsigned char *p, LIBSSH2_SFTP_ATTRIBUTES *attrs) { if (!attrs) { return -1; } unsigned char *s = p; unsigned long flag_mask = LIBSSH2_SFTP_ATTR_SIZE | LIBSSH2_SFTP_ATTR_UIDGID | LIBSSH2_SFTP_ATTR_PERMISSIONS | LIBSSH2_SFTP_ATTR_ACMODTIME; /* TODO: When we add SFTP4+ functionality flag_mask can get additional bits */ if (!attrs) { libssh2_htonu32(s, 0); return 4; } libssh2_htonu32(s, attrs->flags & flag_mask); s += 4; if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { libssh2_htonu64(s, attrs->filesize); s += 8; } if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { libssh2_htonu32(s, attrs->uid); s += 4; libssh2_htonu32(s, attrs->gid); s += 4; } if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { libssh2_htonu32(s, attrs->permissions); s += 4; } if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { libssh2_htonu32(s, attrs->atime); s += 4; libssh2_htonu32(s, attrs->mtime); s += 4; } return (s - p); } /* }}} */ /* {{{ libssh2_sftp_bin2attr */ static int libssh2_sftp_bin2attr(LIBSSH2_SFTP_ATTRIBUTES *attrs, unsigned char *p) { if (!attrs) { return -1; } unsigned char *s = p; memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); attrs->flags = libssh2_ntohu32(s); s += 4; if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { attrs->filesize = libssh2_ntohu64(s); s += 8; } if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { attrs->uid = libssh2_ntohu32(s); s += 4; attrs->gid = libssh2_ntohu32(s); s += 4; } if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { attrs->permissions = libssh2_ntohu32(s); s += 4; } if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { attrs->atime = libssh2_ntohu32(s); s += 4; attrs->mtime = libssh2_ntohu32(s); s += 4; } return (s - p); } /* }}} */ /* ************ * SFTP API * ************ */ /* {{{ libssh2_sftp_dtor * Shutdown an SFTP stream when the channel closes */ LIBSSH2_CHANNEL_CLOSE_FUNC(libssh2_sftp_dtor) { LIBSSH2_SFTP *sftp = (LIBSSH2_SFTP*)(*channel_abstract); /* Loop through handles closing them */ while (sftp->handles) { libssh2_sftp_close_handle(sftp->handles); } LIBSSH2_FREE(session, sftp); } /* }}} */ /* {{{ libssh2_sftp_init * Startup an SFTP session */ LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session) { if (!session) { return NULL; } LIBSSH2_SFTP *sftp; LIBSSH2_CHANNEL *channel; unsigned char *data, *s, buffer[9]; /* sftp_header(5){excludes request_id} + version_id(4) */ unsigned long data_len; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Initializing SFTP subsystem"); #endif channel = libssh2_channel_open_session(session); if (!channel) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Unable to startup channel", 0); return NULL; } if (libssh2_channel_subsystem(channel, "sftp")) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Unable to request SFTP subsystem", 0); libssh2_channel_free(channel); return NULL; } libssh2_channel_set_blocking(channel, 1); libssh2_channel_handle_extended_data(channel, LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE); sftp = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP)); if (!sftp) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a new SFTP structure", 0); libssh2_channel_free(channel); return NULL; } memset(sftp, 0, sizeof(LIBSSH2_SFTP)); sftp->channel = channel; sftp->request_id = 0; libssh2_htonu32(buffer, 5); buffer[4] = SSH_FXP_INIT; libssh2_htonu32(buffer + 5, LIBSSH2_SFTP_VERSION); #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Sending FXP_INIT packet advertising version %d support", (int)LIBSSH2_SFTP_VERSION); #endif if (9 != libssh2_channel_write(channel, buffer, 9)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send SSH_FXP_INIT", 0); libssh2_channel_free(channel); LIBSSH2_FREE(session, sftp); return NULL; } if (libssh2_sftp_packet_require(sftp, SSH_FXP_VERSION, 0, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for response from SFTP subsystem", 0); libssh2_channel_free(channel); LIBSSH2_FREE(session, sftp); return NULL; } if (data_len < 5) { libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Invalid SSH_FXP_VERSION response", 0); libssh2_channel_free(channel); LIBSSH2_FREE(session, sftp); return NULL; } s = data + 1; sftp->version = libssh2_ntohu32(s); s += 4; if (sftp->version > LIBSSH2_SFTP_VERSION) { #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Truncating remote SFTP version from %lu", sftp->version); #endif sftp->version = LIBSSH2_SFTP_VERSION; } #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Enabling SFTP version %lu compatability", sftp->version); #endif while (s < (data + data_len)) { char *extension_name, *extension_data; unsigned long extname_len, extdata_len; extname_len = libssh2_ntohu32(s); s += 4; extension_name = (char *)s; s += extname_len; extdata_len = libssh2_ntohu32(s); s += 4; extension_data = (char *)s; s += extdata_len; /* TODO: Actually process extensions */ } LIBSSH2_FREE(session, data); /* Make sure that when the channel gets closed, the SFTP service is shut down too */ sftp->channel->abstract = sftp; sftp->channel->close_cb = libssh2_sftp_dtor; return sftp; } /* }}} */ /* {{{ libssh2_sftp_shutdown * Shutsdown the SFTP subsystem */ LIBSSH2_API int libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp) { if (!sftp) { return -1; } return libssh2_channel_free(sftp->channel); } /* }}} */ /* ******************************* * SFTP File and Directory Ops * ******************************* */ /* {{{ libssh2_sftp_open_ex * */ LIBSSH2_API LIBSSH2_SFTP_HANDLE *libssh2_sftp_open_ex(LIBSSH2_SFTP *sftp, char *filename, unsigned int filename_len, unsigned long flags, long mode, int open_type) { if (!sftp) { return NULL; } LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; LIBSSH2_SFTP_HANDLE *fp; LIBSSH2_SFTP_ATTRIBUTES attrs = { LIBSSH2_SFTP_ATTR_PERMISSIONS }; unsigned long data_len, packet_len = filename_len + 13 + ((open_type == LIBSSH2_SFTP_OPENFILE) ? (4 + libssh2_sftp_attrsize(&attrs)) : 0); /* packet_len(4) + packet_type(1) + request_id(4) + filename_len(4) + flags(4) */ unsigned char *packet, *data, *s; unsigned char fopen_responses[2] = { SSH_FXP_HANDLE, SSH_FXP_STATUS }; unsigned long request_id; s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_OPEN or FXP_OPENDIR packet", 0); return NULL; } /* Filetype in SFTP 3 and earlier */ attrs.permissions = mode | ((open_type == LIBSSH2_SFTP_OPENFILE) ? LIBSSH2_SFTP_ATTR_PFILETYPE_FILE : LIBSSH2_SFTP_ATTR_PFILETYPE_DIR); libssh2_htonu32(s, packet_len - 4); s += 4; *(s++) = (open_type == LIBSSH2_SFTP_OPENFILE) ? SSH_FXP_OPEN : SSH_FXP_OPENDIR; request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, filename_len); s += 4; memcpy(s, filename, filename_len); s += filename_len; if (open_type == LIBSSH2_SFTP_OPENFILE) { libssh2_htonu32(s, flags); s += 4; s += libssh2_sftp_attr2bin(s, &attrs); } #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Sending %s open request", (open_type == LIBSSH2_SFTP_OPENFILE) ? "file" : "directory"); #endif if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_OPEN or FXP_OPENDIR command", 0); LIBSSH2_FREE(session, packet); return NULL; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_requirev(sftp, 2, fopen_responses, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return NULL; } if (data[0] == SSH_FXP_STATUS) { libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Failed opening remote file", 0); sftp->last_errno = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); return NULL; } fp = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP_HANDLE)); if (!fp) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate new SFTP handle structure", 0); LIBSSH2_FREE(session, data); return NULL; } memset(fp, 0, sizeof(LIBSSH2_SFTP_HANDLE)); fp->handle_type = (open_type == LIBSSH2_SFTP_OPENFILE) ? LIBSSH2_SFTP_HANDLE_FILE : LIBSSH2_SFTP_HANDLE_DIR; fp->handle_len = libssh2_ntohu32(data + 5); if (fp->handle_len > 256) { /* SFTP doesn't allow handles longer than 256 characters */ fp->handle_len = 256; } fp->handle = LIBSSH2_ALLOC(session, fp->handle_len); if (!fp->handle) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate space for SFTP file/dir handle", 0); LIBSSH2_FREE(session, data); LIBSSH2_FREE(session, fp); return NULL; } memcpy(fp->handle, data + 9, fp->handle_len); LIBSSH2_FREE(session, data); /* Link the file and the sftp session together */ fp->next = sftp->handles; if (fp->next) { fp->next->prev = fp; } fp->sftp = sftp; fp->u.file.offset = 0; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Open command successful"); #endif return fp; } /* }}} */ /* {{{ libssh2_sftp_read * Read from an SFTP file handle */ LIBSSH2_API size_t libssh2_sftp_read(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen) { if (!handle) { return -1; } LIBSSH2_SFTP *sftp = handle->sftp; LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; unsigned long data_len, request_id; unsigned long packet_len = handle->handle_len + 25; /* packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) + offset(8) + length(4) */ unsigned char *packet, *s, *data; unsigned char read_responses[2] = { SSH_FXP_DATA, SSH_FXP_STATUS }; size_t bytes_read = 0; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Reading %lu bytes from SFTP handle", (unsigned long)buffer_maxlen); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_CLOSE packet", 0); return -1; } libssh2_htonu32(s, packet_len - 4); s += 4; *(s++) = SSH_FXP_READ; request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, handle->handle_len); s += 4; memcpy(s, handle->handle, handle->handle_len); s += handle->handle_len; libssh2_htonu64(s, handle->u.file.offset); s += 8; libssh2_htonu32(s, buffer_maxlen); s += 4; if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_READ command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_requirev(sftp, 2, read_responses, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } switch (data[0]) { case SSH_FXP_STATUS: sftp->last_errno = libssh2_ntohu32(data + 5); libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); LIBSSH2_FREE(session, data); return -1; case SSH_FXP_DATA: bytes_read = libssh2_ntohu32(data + 5); if (bytes_read > (data_len - 9)) { return -1; } #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "%lu bytes returned", (unsigned long)bytes_read); #endif memcpy(buffer, data + 9, bytes_read); handle->u.file.offset += bytes_read; LIBSSH2_FREE(session, data); return bytes_read; } return -1; } /* }}} */ /* {{{ libssh2_sftp_readdir * Read from an SFTP directory handle */ LIBSSH2_API int libssh2_sftp_readdir(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen, LIBSSH2_SFTP_ATTRIBUTES *attrs) { if (!handle) { return -1; } LIBSSH2_SFTP *sftp = handle->sftp; LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; LIBSSH2_SFTP_ATTRIBUTES attrs_dummy; unsigned long data_len, request_id, filename_len, num_names; unsigned long packet_len = handle->handle_len + 13; /* packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ unsigned char *packet, *s, *data; unsigned char read_responses[2] = { SSH_FXP_NAME, SSH_FXP_STATUS }; if (handle->u.dir.names_left) { /* A prior request returned more than one directory entry, feed it back from the buffer */ unsigned char *s = (unsigned char *)handle->u.dir.next_name; unsigned long real_filename_len = libssh2_ntohu32(s); filename_len = real_filename_len; s += 4; if (filename_len > buffer_maxlen) { filename_len = buffer_maxlen; } memcpy(buffer, s, filename_len); s += real_filename_len; /* Skip longname */ s += 4 + libssh2_ntohu32(s); if (attrs) { memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); } s += libssh2_sftp_bin2attr(attrs ? attrs : &attrs_dummy, s); handle->u.dir.next_name = (char *)s; if ((--handle->u.dir.names_left) == 0) { LIBSSH2_FREE(session, handle->u.dir.names_packet); } return filename_len; } /* Request another entry(entries?) */ s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_READDIR packet", 0); return -1; } libssh2_htonu32(s, packet_len - 4); s += 4; *(s++) = SSH_FXP_READDIR; request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, handle->handle_len); s += 4; memcpy(s, handle->handle, handle->handle_len); s += handle->handle_len; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Reading entries from directory handle"); #endif if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_READ command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_requirev(sftp, 2, read_responses, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } if (data[0] == SSH_FXP_STATUS) { int retcode; retcode = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); if (retcode == LIBSSH2_FX_EOF) { return 0; } else { sftp->last_errno = retcode; libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); return -1; } } num_names = libssh2_ntohu32(data + 5); #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "%lu entries returned", num_names); #endif if (num_names <= 0) { LIBSSH2_FREE(session, data); return (num_names == 0) ? 0 : -1; } if (num_names == 1) { unsigned long real_filename_len = libssh2_ntohu32(data + 9); filename_len = real_filename_len; if (filename_len > buffer_maxlen) { filename_len = buffer_maxlen; } memcpy(buffer, data + 13, filename_len); /* The filename is not null terminated, make it so if possible */ if (filename_len < buffer_maxlen) { buffer[filename_len] = '\0'; } if (attrs) { memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); libssh2_sftp_bin2attr(attrs, data + 13 + real_filename_len + (4 + libssh2_ntohu32(data + 13 + real_filename_len))); } LIBSSH2_FREE(session, data); return filename_len; } handle->u.dir.names_left = num_names; handle->u.dir.names_packet = data; handle->u.dir.next_name = (char *)data + 9; /* Be lazy, just use the name popping mechanism from the start of the function */ return libssh2_sftp_readdir(handle, buffer, buffer_maxlen, attrs); } /* }}} */ /* {{{ libssh2_sftp_write * Write data to a file handle */ LIBSSH2_API size_t libssh2_sftp_write(LIBSSH2_SFTP_HANDLE *handle, const char *buffer, size_t count) { if (!handle) { return -1; } LIBSSH2_SFTP *sftp = handle->sftp; LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; unsigned long data_len, request_id, retcode; unsigned long packet_len = handle->handle_len + count + 25; /* packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) + offset(8) + count(4) */ unsigned char *packet, *s, *data; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Writing %lu bytes", (unsigned long)count); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_WRITE packet", 0); return -1; } libssh2_htonu32(s, packet_len - 4); s += 4; *(s++) = SSH_FXP_WRITE; request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, handle->handle_len); s += 4; memcpy(s, handle->handle, handle->handle_len); s += handle->handle_len; libssh2_htonu64(s, handle->u.file.offset); s += 8; libssh2_htonu32(s, count); s += 4; memcpy(s, buffer, count); s += count; if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_WRITE command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } retcode = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); if (retcode == LIBSSH2_FX_OK) { handle->u.file.offset += count; return count; } libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); sftp->last_errno = retcode; return -1; } /* }}} */ /* {{{ libssh2_sftp_fstat_ex * Get or Set stat on a file */ LIBSSH2_API int libssh2_sftp_fstat_ex(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_ATTRIBUTES *attrs, int setstat) { if (!handle) { return -1; } LIBSSH2_SFTP *sftp = handle->sftp; LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; unsigned long data_len, request_id; unsigned long packet_len = handle->handle_len + 13 + (setstat ? libssh2_sftp_attrsize(attrs) : 0); /* packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ unsigned char *packet, *s, *data; unsigned char fstat_responses[2] = { SSH_FXP_ATTRS, SSH_FXP_STATUS }; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Issuing %s command", setstat ? "set-stat" : "stat"); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FSTAT/FSETSTAT packet", 0); return -1; } libssh2_htonu32(s, packet_len - 4); s += 4; *(s++) = setstat ? SSH_FXP_FSETSTAT : SSH_FXP_FSTAT; request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, handle->handle_len); s += 4; memcpy(s, handle->handle, handle->handle_len); s += handle->handle_len; if (setstat) { s += libssh2_sftp_attr2bin(s, attrs); } if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, setstat ? "Unable to send FXP_FSETSTAT" : "Unable to send FXP_FSTAT command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_requirev(sftp, 2, fstat_responses, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } if (data[0] == SSH_FXP_STATUS) { int retcode; retcode = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); if (retcode == LIBSSH2_FX_OK) { return 0; } else { sftp->last_errno = retcode; libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); return -1; } } libssh2_sftp_bin2attr(attrs, data + 5); return 0; } /* }}} */ /* {{{ libssh2_sftp_seek * Set the read/write pointer to an arbitrary position within the file */ LIBSSH2_API void libssh2_sftp_seek(LIBSSH2_SFTP_HANDLE *handle, size_t offset) { if (handle) handle->u.file.offset = offset; } /* }}} */ /* {{{ libssh2_sftp_tell * Return the current read/write pointer's offset */ LIBSSH2_API size_t libssh2_sftp_tell(LIBSSH2_SFTP_HANDLE *handle) { if (handle) return handle->u.file.offset; return -1; } /* }}} */ /* {{{ libssh2_sftp_close_handle * Close a file or directory handle * Also frees handle resource and unlinks it from the SFTP structure */ LIBSSH2_API int libssh2_sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle) { if (!handle) { return -1; } LIBSSH2_SFTP *sftp = handle->sftp; LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; unsigned long data_len, retcode, request_id; unsigned long packet_len = handle->handle_len + 13; /* packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ unsigned char *packet, *s, *data; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Closing handle"); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_CLOSE packet", 0); return -1; } libssh2_htonu32(s, packet_len - 4); s += 4; *(s++) = SSH_FXP_CLOSE; request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, handle->handle_len); s += 4; memcpy(s, handle->handle, handle->handle_len); s += handle->handle_len; if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_CLOSE command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } retcode = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); if (retcode != LIBSSH2_FX_OK) { sftp->last_errno = retcode; libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); return -1; } if (handle == sftp->handles) { sftp->handles = handle->next; } if (handle->next) { handle->next->prev = NULL; } if ((handle->handle_type == LIBSSH2_SFTP_HANDLE_DIR) && handle->u.dir.names_left) { LIBSSH2_FREE(session, handle->u.dir.names_packet); } LIBSSH2_FREE(session, handle->handle); LIBSSH2_FREE(session, handle); return 0; } /* }}} */ /* ********************** * SFTP Miscellaneous * ********************** */ /* {{{ libssh2_sftp_unlink_ex * Delete a file from the remote server */ LIBSSH2_API int libssh2_sftp_unlink_ex(LIBSSH2_SFTP *sftp, char *filename, unsigned int filename_len) { if (!sftp) { return -1; } LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; unsigned long data_len, retcode, request_id; unsigned long packet_len = filename_len + 13; /* packet_len(4) + packet_type(1) + request_id(4) + filename_len(4) */ unsigned char *packet, *s, *data; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Unlinking %s", filename); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_REMOVE packet", 0); return -1; } libssh2_htonu32(s, packet_len - 4); s += 4; *(s++) = SSH_FXP_REMOVE; request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, filename_len); s += 4; memcpy(s, filename, filename_len); s += filename_len; if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_REMOVE command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } retcode = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); if (retcode == LIBSSH2_FX_OK) { return 0; } else { sftp->last_errno = retcode; libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); return -1; } } /* }}} */ /* {{{ libssh2_sftp_rename_ex * Rename a file on the remote server */ LIBSSH2_API int libssh2_sftp_rename_ex(LIBSSH2_SFTP *sftp, char *source_filename, unsigned int source_filename_len, char *dest_filename, unsigned int dest_filename_len, long flags) { if (!sftp) { return -1; } LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; unsigned long data_len, retcode = -1, request_id; unsigned long packet_len = source_filename_len + dest_filename_len + 17 + (sftp->version >= 5 ? 4 : 0); /* packet_len(4) + packet_type(1) + request_id(4) + source_filename_len(4) + dest_filename_len(4) + flags(4){SFTP5+) */ unsigned char *packet, *s, *data; if (sftp->version < 2) { libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Server does not support RENAME", 0); return -1; } #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Renaming %s to %s", source_filename, dest_filename); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_RENAME packet", 0); return -1; } libssh2_htonu32(s, packet_len - 4); s += 4; *(s++) = SSH_FXP_RENAME; request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, source_filename_len); s += 4; memcpy(s, source_filename, source_filename_len); s += source_filename_len; libssh2_htonu32(s, dest_filename_len); s += 4; memcpy(s, dest_filename, dest_filename_len); s += dest_filename_len; if (sftp->version >= 5) { libssh2_htonu32(s, flags); s += 4; } if (packet_len != libssh2_channel_write(channel, packet, s - packet)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_RENAME command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } retcode = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); switch (retcode) { case LIBSSH2_FX_OK: retcode = 0; break; case LIBSSH2_FX_FILE_ALREADY_EXISTS: libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "File already exists and SSH_FXP_RENAME_OVERWRITE not specified", 0); sftp->last_errno = retcode; retcode = -1; break; case LIBSSH2_FX_OP_UNSUPPORTED: libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Operation Not Supported", 0); sftp->last_errno = retcode; retcode = -1; break; default: libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); sftp->last_errno = retcode; retcode = -1; } return retcode; } /* }}} */ /* {{{ libssh2_sftp_mkdir_ex * Create a directory */ LIBSSH2_API int libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, char *path, unsigned int path_len, long mode) { if (!sftp) { return -1; } LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; LIBSSH2_SFTP_ATTRIBUTES attrs = { LIBSSH2_SFTP_ATTR_PERMISSIONS }; unsigned long data_len, retcode, request_id; unsigned long packet_len = path_len + 13 + libssh2_sftp_attrsize(&attrs); /* packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ unsigned char *packet, *s, *data; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Creating directory %s with mode 0%lo", path, mode); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_MKDIR packet", 0); return -1; } /* Filetype in SFTP 3 and earlier */ attrs.permissions = mode | LIBSSH2_SFTP_ATTR_PFILETYPE_DIR; libssh2_htonu32(s, packet_len - 4); s += 4; *(s++) = SSH_FXP_MKDIR; request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, path_len); s += 4; memcpy(s, path, path_len); s += path_len; s += libssh2_sftp_attr2bin(s, &attrs); if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_MKDIR command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } retcode = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); if (retcode == LIBSSH2_FX_OK) { return 0; } else { libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); sftp->last_errno = retcode; return -1; } } /* }}} */ /* {{{ libssh2_sftp_rmdir_ex * Remove a directory */ LIBSSH2_API int libssh2_sftp_rmdir_ex(LIBSSH2_SFTP *sftp, char *path, unsigned int path_len) { if (!sftp) { return -1; } LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; unsigned long data_len, retcode, request_id; unsigned long packet_len = path_len + 13; /* packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ unsigned char *packet, *s, *data; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Removing directory: %s", path); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_MKDIR packet", 0); return -1; } libssh2_htonu32(s, packet_len - 4); s += 4; *(s++) = SSH_FXP_RMDIR; request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, path_len); s += 4; memcpy(s, path, path_len); s += path_len; if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_MKDIR command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } retcode = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); if (retcode == LIBSSH2_FX_OK) { return 0; } else { sftp->last_errno = retcode; libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); return -1; } } /* }}} */ /* {{{ libssh2_sftp_stat_ex * Stat a file or symbolic link */ LIBSSH2_API int libssh2_sftp_stat_ex(LIBSSH2_SFTP *sftp, char *path, unsigned int path_len, int stat_type, LIBSSH2_SFTP_ATTRIBUTES *attrs) { if (!sftp) { return -1; } LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; unsigned long data_len, request_id; unsigned long packet_len = path_len + 13 + ((stat_type == LIBSSH2_SFTP_SETSTAT) ? libssh2_sftp_attrsize(attrs) : 0); /* packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ unsigned char *packet, *s, *data; unsigned char stat_responses[2] = { SSH_FXP_ATTRS, SSH_FXP_STATUS }; #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "%s %s", (stat_type == LIBSSH2_SFTP_SETSTAT) ? "Set-statting" : (stat_type == LIBSSH2_SFTP_LSTAT ? "LStatting" : "Statting"), path); #endif s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_MKDIR packet", 0); return -1; } libssh2_htonu32(s, packet_len - 4); s += 4; switch (stat_type) { case LIBSSH2_SFTP_SETSTAT: *(s++) = SSH_FXP_SETSTAT; break; case LIBSSH2_SFTP_LSTAT: *(s++) = SSH_FXP_LSTAT; break; case LIBSSH2_SFTP_STAT: default: *(s++) = SSH_FXP_STAT; } request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, path_len); s += 4; memcpy(s, path, path_len); s += path_len; if (stat_type == LIBSSH2_SFTP_SETSTAT) { s += libssh2_sftp_attr2bin(s, attrs); } if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send STAT/LSTAT/SETSTAT command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_requirev(sftp, 2, stat_responses, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } if (data[0] == SSH_FXP_STATUS) { int retcode; retcode = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); if (retcode == LIBSSH2_FX_OK) { return 0; } else { sftp->last_errno = retcode; libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); return -1; } } memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); libssh2_sftp_bin2attr(attrs, data + 5); LIBSSH2_FREE(session, data); return 0; } /* }}} */ /* {{{ libssh2_sftp_symlink_ex * Read or set a symlink */ LIBSSH2_API int libssh2_sftp_symlink_ex(LIBSSH2_SFTP *sftp, const char *path, unsigned int path_len, char *target, unsigned int target_len, int link_type) { if (!sftp) { return -1; } LIBSSH2_CHANNEL *channel = sftp->channel; LIBSSH2_SESSION *session = channel->session; unsigned long data_len, request_id, link_len; unsigned long packet_len = path_len + 13 + ((link_type == LIBSSH2_SFTP_SYMLINK) ? (4 + target_len) : 0); /* packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ unsigned char *packet, *s, *data; unsigned char link_responses[2] = { SSH_FXP_NAME, SSH_FXP_STATUS }; if ((sftp->version < 3) && (link_type != LIBSSH2_SFTP_REALPATH)) { libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Server does not support SYMLINK or READLINK", 0); return -1; } s = packet = LIBSSH2_ALLOC(session, packet_len); if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for SYMLINK/READLINK/REALPATH packet", 0); return -1; } #ifdef LIBSSH2_DEBUG_SFTP _libssh2_debug(session, LIBSSH2_DBG_SFTP, "%s %s on %s", (link_type == LIBSSH2_SFTP_SYMLINK) ? "Creating" : "Reading", (link_type == LIBSSH2_SFTP_REALPATH) ? "realpath" : "symlink", path); #endif libssh2_htonu32(s, packet_len - 4); s += 4; switch (link_type) { case LIBSSH2_SFTP_REALPATH: *(s++) = SSH_FXP_REALPATH; break; case LIBSSH2_SFTP_SYMLINK: *(s++) = SSH_FXP_SYMLINK; break; case LIBSSH2_SFTP_READLINK: default: *(s++) = SSH_FXP_READLINK; } request_id = sftp->request_id++; libssh2_htonu32(s, request_id); s += 4; libssh2_htonu32(s, path_len); s += 4; memcpy(s, path, path_len); s += path_len; if (link_type == LIBSSH2_SFTP_SYMLINK) { libssh2_htonu32(s, target_len); s += 4; memcpy(s, target, target_len); s += target_len; } if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send SYMLINK/READLINK command", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_sftp_packet_requirev(sftp, 2, link_responses, request_id, &data, &data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); return -1; } if (data[0] == SSH_FXP_STATUS) { int retcode; retcode = libssh2_ntohu32(data + 5); LIBSSH2_FREE(session, data); if (retcode == LIBSSH2_FX_OK) { return 0; } else { sftp->last_errno = retcode; libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", 0); return -1; } } if (libssh2_ntohu32(data + 5) < 1) { libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Invalid READLINK/REALPATH response, no name entries", 0); LIBSSH2_FREE(session, data); return -1; } link_len = libssh2_ntohu32(data + 9); if (link_len >= target_len) { link_len = target_len - 1; } memcpy(target, data + 13, link_len); target[link_len] = 0; LIBSSH2_FREE(session, data); return link_len; } /* }}} */ /* {{{ libssh2_sftp_last_error * Returns the last error code reported by SFTP */ LIBSSH2_API unsigned long libssh2_sftp_last_error(LIBSSH2_SFTP *sftp) { if (!sftp) { return -1; } return sftp->last_errno; } /* }}} */ ================================================ FILE: Example/userauth.c ================================================ /* Copyright (c) 2004-2006, Sara Golemon * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" /* Needed for struct iovec on some platforms */ #ifdef HAVE_SYS_UIO_H #include #endif /* {{{ proto libssh2_userauth_list * List authentication methods * Will yield successful login if "none" happens to be allowable for this user * Not a common configuration for any SSH server though * username should be NULL, or a null terminated string */ LIBSSH2_API char *libssh2_userauth_list(LIBSSH2_SESSION *session, const char *username, unsigned int username_len) { unsigned char reply_codes[3] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, 0 }; unsigned long data_len = username_len + 31; /* packet_type(1) + username_len(4) + service_len(4) + service(14)"ssh-connection" + method_len(4) + method(4)"none" */ unsigned long methods_len; unsigned char *data, *s; s = data = LIBSSH2_ALLOC(session, data_len); if (!data) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for userauth_list", 0); return NULL; } *(s++) = SSH_MSG_USERAUTH_REQUEST; libssh2_htonu32(s, username_len); s += 4; if (username) { memcpy(s, username, username_len); s += username_len; } libssh2_htonu32(s, 14); s += 4; memcpy(s, "ssh-connection", 14); s += 14; libssh2_htonu32(s, 4); s += 4; memcpy(s, "none", 4); s += 4; if (libssh2_packet_write(session, data, data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-none request", 0); LIBSSH2_FREE(session, data); return NULL; } LIBSSH2_FREE(session, data); if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) { return NULL; } if (data[0] == SSH_MSG_USERAUTH_SUCCESS) { /* Wow, who'dve thought... */ LIBSSH2_FREE(session, data); session->state |= LIBSSH2_STATE_AUTHENTICATED; return NULL; } methods_len = libssh2_ntohu32(data + 1); memcpy(data, data + 5, methods_len); data[methods_len] = '\0'; #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Permitted auth methods: %s", data); #endif return (char *)data; } /* }}} */ /* {{{ libssh2_userauth_authenticated * 0 if not yet authenticated * non-zero is already authenticated */ LIBSSH2_API int libssh2_userauth_authenticated(LIBSSH2_SESSION *session) { return (session != NULL) && (session->state & LIBSSH2_STATE_AUTHENTICATED); } /* }}} */ /* {{{ libssh2_userauth_password * Plain ol' login */ LIBSSH2_API int libssh2_userauth_password_ex(LIBSSH2_SESSION *session, const char *username, unsigned int username_len, const char *password, unsigned int password_len, LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb))) { unsigned char *data, *s, reply_codes[4] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, 0 }; unsigned long data_len = username_len + password_len + 40; /* packet_type(1) + username_len(4) + service_len(4) + service(14)"ssh-connection" + method_len(4) + method(8)"password" + chgpwdbool(1) + password_len(4) */ s = data = LIBSSH2_ALLOC(session, data_len); if (!data) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for userauth-password request", 0); return -1; } *(s++) = SSH_MSG_USERAUTH_REQUEST; libssh2_htonu32(s, username_len); s += 4; memcpy(s, username, username_len); s += username_len; libssh2_htonu32(s, sizeof("ssh-connection") - 1); s += 4; memcpy(s, "ssh-connection", sizeof("ssh-connection") - 1); s += sizeof("ssh-connection") - 1; libssh2_htonu32(s, sizeof("password") - 1); s += 4; memcpy(s, "password", sizeof("password") - 1); s += sizeof("password") - 1; *s = '\0'; s++; libssh2_htonu32(s, password_len); s += 4; memcpy(s, password, password_len); s += password_len; #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Attempting to login using password authentication"); #endif if (libssh2_packet_write(session, data, data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-password request", 0); LIBSSH2_FREE(session, data); return -1; } LIBSSH2_FREE(session, data); password_response: if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) { return -1; } if (data[0] == SSH_MSG_USERAUTH_SUCCESS) { #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Password authentication successful"); #endif LIBSSH2_FREE(session, data); session->state |= LIBSSH2_STATE_AUTHENTICATED; return 0; } if (data[0] == SSH_MSG_USERAUTH_PASSWD_CHANGEREQ) { char *newpw = NULL; int newpw_len = 0; #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Password change required"); #endif LIBSSH2_FREE(session, data); if (passwd_change_cb) { passwd_change_cb(session, &newpw, &newpw_len, &session->abstract); if (!newpw) { libssh2_error(session, LIBSSH2_ERROR_PASSWORD_EXPIRED, "Password expired, and callback failed", 0); return -1; } data_len = username_len + password_len + 44 + newpw_len; /* basic data_len + newpw_len(4) */ s = data = LIBSSH2_ALLOC(session, data_len); if (!data) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for userauth-password-change request", 0); return -1; } *(s++) = SSH_MSG_USERAUTH_REQUEST; libssh2_htonu32(s, username_len); s += 4; memcpy(s, username, username_len); s += username_len; libssh2_htonu32(s, sizeof("ssh-connection") - 1); s += 4; memcpy(s, "ssh-connection", sizeof("ssh-connection") - 1); s += sizeof("ssh-connection") - 1; libssh2_htonu32(s, sizeof("password") - 1); s += 4; memcpy(s, "password", sizeof("password") - 1); s += sizeof("password") - 1; *s = 0xFF; s++; libssh2_htonu32(s, password_len); s += 4; memcpy(s, password, password_len); s += password_len; libssh2_htonu32(s, newpw_len); s += 4; memcpy(s, newpw, newpw_len); s += newpw_len; if (libssh2_packet_write(session, data, data_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-password-change request", 0); LIBSSH2_FREE(session, data); return -1; } LIBSSH2_FREE(session, data); LIBSSH2_FREE(session, newpw); /* Ugliest use of goto ever. Blame it on the askN => requirev migration. */ goto password_response; } else { libssh2_error(session, LIBSSH2_ERROR_PASSWORD_EXPIRED, "Password Expired, and no callback specified", 0); return -1; } } /* FAILURE */ LIBSSH2_FREE(session, data); return -1; } /* }}} */ /* {{{ libssh2_file_read_publickey * Read a public key from an id_???.pub style file */ static int libssh2_file_read_publickey(LIBSSH2_SESSION *session, unsigned char **method, unsigned long *method_len, unsigned char **pubkeydata, unsigned long *pubkeydata_len, const char *pubkeyfile) { FILE *fd; char *pubkey = NULL, c, *sp1, *sp2, *tmp; int pubkey_len = 0; unsigned int tmp_len; #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Loading public key file: %s", pubkeyfile); #endif /* Read Public Key */ fd = fopen(pubkeyfile, "r"); if (!fd) { libssh2_error(session, LIBSSH2_ERROR_FILE, "Unable to open public key file", 0); return -1; } while (!feof(fd) && (c = fgetc(fd)) != '\r' && c != '\n') pubkey_len++; rewind(fd); if (pubkey_len <= 1) { libssh2_error(session, LIBSSH2_ERROR_FILE, "Invalid data in public key file", 0); fclose(fd); return -1; } pubkey = LIBSSH2_ALLOC(session, pubkey_len); if (!pubkey) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for public key data", 0); fclose(fd); return -1; } if (fread(pubkey, 1, pubkey_len, fd) != pubkey_len) { libssh2_error(session, LIBSSH2_ERROR_FILE, "Unable to read public key from file", 0); LIBSSH2_FREE(session, pubkey); fclose(fd); return -1; } fclose(fd); while (pubkey_len && (pubkey[pubkey_len-1] == '\r' || pubkey[pubkey_len-1] == '\n')) pubkey_len--; if (!pubkey_len) { libssh2_error(session, LIBSSH2_ERROR_FILE, "Missing public key data", 0); LIBSSH2_FREE(session, pubkey); return -1; } if ((sp1 = memchr(pubkey, ' ', pubkey_len)) == NULL) { libssh2_error(session, LIBSSH2_ERROR_FILE, "Invalid public key data", 0); LIBSSH2_FREE(session, pubkey); return -1; } /* Wasting some bytes here (okay, more than some), * but since it's likely to be freed soon anyway, * we'll just avoid the extra free/alloc and call it a wash */ *method = (unsigned char *)pubkey; *method_len = sp1 - pubkey; sp1++; if ((sp2 = memchr(sp1, ' ', pubkey_len - *method_len)) == NULL) { /* Assume that the id string is missing, but that it's okay */ sp2 = pubkey + pubkey_len; } if (libssh2_base64_decode(session, &tmp, &tmp_len, sp1, sp2 - sp1)) { libssh2_error(session, LIBSSH2_ERROR_FILE, "Invalid key data, not base64 encoded", 0); LIBSSH2_FREE(session, pubkey); return -1; } *pubkeydata = (unsigned char *)tmp; *pubkeydata_len = tmp_len; return 0; } /* }}} */ /* {{{ libssh2_file_read_privatekey * Read a PEM encoded private key from an id_??? style file */ static int libssh2_file_read_privatekey(LIBSSH2_SESSION *session, LIBSSH2_HOSTKEY_METHOD **hostkey_method, void **hostkey_abstract, const char *method, int method_len, const char *privkeyfile, const char *passphrase) { LIBSSH2_HOSTKEY_METHOD **hostkey_methods_avail = libssh2_hostkey_methods(); #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Loading private key file: %s", privkeyfile); #endif *hostkey_method = NULL; *hostkey_abstract = NULL; while (*hostkey_methods_avail && (*hostkey_methods_avail)->name) { if ((*hostkey_methods_avail)->initPEM && strncmp((*hostkey_methods_avail)->name, method, method_len) == 0) { *hostkey_method = *hostkey_methods_avail; break; } hostkey_methods_avail++; } if (!*hostkey_method) { libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, "No handler for specified private key", 0); return -1; } if ((*hostkey_method)->initPEM(session, (const char *)privkeyfile, (unsigned const char *)passphrase, hostkey_abstract)) { libssh2_error(session, LIBSSH2_ERROR_FILE, "Unable to initialize private key from file", 0); return -1; } return 0; } /* }}} */ /* {{{ libssh2_userauth_hostbased_fromfile_ex * Authenticate using a keypair found in the named files */ LIBSSH2_API int libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session, const char *username, unsigned int username_len, const char *publickey, const char *privatekey, const char *passphrase, const char *hostname, unsigned int hostname_len, const char *local_username, unsigned int local_username_len) { LIBSSH2_HOSTKEY_METHOD *privkeyobj; void *abstract; unsigned char buf[5]; struct iovec datavec[4]; unsigned char *method, *pubkeydata, *packet, *s, *sig, *data, reply_codes[3] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, 0 }; unsigned long method_len, pubkeydata_len, packet_len, sig_len, data_len; if (libssh2_file_read_publickey(session, &method, &method_len, &pubkeydata, &pubkeydata_len, publickey)) { return -1; } packet_len = username_len + method_len + hostname_len + local_username_len + pubkeydata_len + 48; /* packet_type(1) + username_len(4) + servicename_len(4) + service_name(14)"ssh-connection" + * authmethod_len(4) + authmethod(9)"hostbased" + method_len(4) + pubkeydata_len(4) + * local_username_len(4) */ /* Preallocate space for an overall length, method name again, * and the signature, which won't be any larger than the size of the publickeydata itself */ s = packet = LIBSSH2_ALLOC(session, packet_len + 4 + (4 + method_len) + (4 + pubkeydata_len)); *(s++) = SSH_MSG_USERAUTH_REQUEST; libssh2_htonu32(s, username_len); s += 4; memcpy(s, username, username_len); s += username_len; libssh2_htonu32(s, 14); s += 4; memcpy(s, "ssh-connection", 14); s += 14; libssh2_htonu32(s, 9); s += 4; memcpy(s, "hostbased", 9); s += 9; libssh2_htonu32(s, method_len); s += 4; memcpy(s, method, method_len); s += method_len; libssh2_htonu32(s, pubkeydata_len); s += 4; memcpy(s, pubkeydata, pubkeydata_len); s += pubkeydata_len; libssh2_htonu32(s, hostname_len); s += 4; memcpy(s, hostname, hostname_len); s += hostname_len; libssh2_htonu32(s, local_username_len); s += 4; memcpy(s, local_username, local_username_len); s += local_username_len; if (libssh2_file_read_privatekey(session, &privkeyobj, &abstract, (const char *)method, method_len, privatekey, passphrase)) { LIBSSH2_FREE(session, method); LIBSSH2_FREE(session, packet); return -1; } libssh2_htonu32(buf, session->session_id_len); datavec[0].iov_base = buf; datavec[0].iov_len = 4; datavec[1].iov_base = session->session_id; datavec[1].iov_len = session->session_id_len; datavec[2].iov_base = packet; datavec[2].iov_len = packet_len; if (privkeyobj->signv(session, &sig, &sig_len, 3, datavec, &abstract)) { LIBSSH2_FREE(session, method); LIBSSH2_FREE(session, packet); if (privkeyobj->dtor) { privkeyobj->dtor(session, &abstract); } return -1; } if (privkeyobj->dtor) { privkeyobj->dtor(session, &abstract); } if (sig_len > pubkeydata_len ) { /* Should *NEVER* happen, but...well.. better safe than sorry */ packet = LIBSSH2_REALLOC(session, packet, packet_len + 4 + (4 + method_len) + (4 + sig_len)); /* PK sigblob */ if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Failed allocating additional space for userauth-hostbased packet", 0); LIBSSH2_FREE(session, method); return -1; } } s = packet + packet_len; libssh2_htonu32(s, 4 + method_len + 4 + sig_len); s += 4; libssh2_htonu32(s, method_len); s += 4; memcpy(s, method, method_len); s += method_len; LIBSSH2_FREE(session, method); libssh2_htonu32(s, sig_len); s += 4; memcpy(s, sig, sig_len); s += sig_len; LIBSSH2_FREE(session, sig); #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Attempting hostbased authentication"); #endif if (libssh2_packet_write(session, packet, s - packet)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-hostbased request", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) { return -1; } if (data[0] == SSH_MSG_USERAUTH_SUCCESS) { #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Hostbased authentication successful"); #endif /* We are us and we've proved it. */ LIBSSH2_FREE(session, data); session->state |= LIBSSH2_STATE_AUTHENTICATED; return 0; } /* This public key is not allowed for this user on this server */ LIBSSH2_FREE(session, data); libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, "Invalid signature for supplied public key, or bad username/public key combination", 0); return -1; } /* }}} */ /* {{{ libssh2_userauth_publickey_fromfile_ex * Authenticate using a keypair found in the named files */ LIBSSH2_API int libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, const char *username, unsigned int username_len, const char *publickey, const char *privatekey, const char *passphrase) { LIBSSH2_HOSTKEY_METHOD *privkeyobj; void *abstract; unsigned char buf[5]; struct iovec datavec[4]; unsigned char *method, *pubkeydata, *packet, *s, *b, *sig, *data; unsigned char reply_codes[4] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_PK_OK, 0 }; unsigned long method_len, pubkeydata_len, packet_len, sig_len, data_len; if (libssh2_file_read_publickey(session, &method, &method_len, &pubkeydata, &pubkeydata_len, publickey)) { return -1; } packet_len = username_len + method_len + pubkeydata_len + 45; /* packet_type(1) + username_len(4) + servicename_len(4) + service_name(14)"ssh-connection" + authmethod_len(4) + authmethod(9)"publickey" + sig_included(1)'\0' + algmethod_len(4) + publickey_len(4) */ /* Preallocate space for an overall length, method name again, * and the signature, which won't be any larger than the size of the publickeydata itself */ s = packet = LIBSSH2_ALLOC(session, packet_len + 4 + (4 + method_len) + (4 + pubkeydata_len)); *(s++) = SSH_MSG_USERAUTH_REQUEST; libssh2_htonu32(s, username_len); s += 4; memcpy(s, username, username_len); s += username_len; libssh2_htonu32(s, 14); s += 4; memcpy(s, "ssh-connection", 14); s += 14; libssh2_htonu32(s, 9); s += 4; memcpy(s, "publickey", 9); s += 9; b = s; *(s++) = 0; /* Not sending signature with *this* packet */ libssh2_htonu32(s, method_len); s += 4; memcpy(s, method, method_len); s += method_len; libssh2_htonu32(s, pubkeydata_len); s += 4; memcpy(s, pubkeydata, pubkeydata_len); s += pubkeydata_len; #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Attempting publickey authentication"); #endif if (libssh2_packet_write(session, packet, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-publickey request", 0); LIBSSH2_FREE(session, packet); LIBSSH2_FREE(session, method); LIBSSH2_FREE(session, pubkeydata); return -1; } if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) { LIBSSH2_FREE(session, packet); LIBSSH2_FREE(session, method); LIBSSH2_FREE(session, pubkeydata); return -1; } if (data[0] == SSH_MSG_USERAUTH_SUCCESS) { #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Pubkey authentication prematurely successful"); #endif /* God help any SSH server that allows an UNVERIFIED public key to validate the user */ LIBSSH2_FREE(session, data); LIBSSH2_FREE(session, packet); LIBSSH2_FREE(session, method); LIBSSH2_FREE(session, pubkeydata); session->state |= LIBSSH2_STATE_AUTHENTICATED; return 0; } if (data[0] == SSH_MSG_USERAUTH_FAILURE) { /* This public key is not allowed for this user on this server */ LIBSSH2_FREE(session, data); LIBSSH2_FREE(session, packet); LIBSSH2_FREE(session, method); LIBSSH2_FREE(session, pubkeydata); libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED, "Username/PublicKey combination invalid", 0); return -1; } /* Semi-Success! */ LIBSSH2_FREE(session, data); LIBSSH2_FREE(session, pubkeydata); if (libssh2_file_read_privatekey(session, &privkeyobj, &abstract, (const char *)method, method_len, privatekey, passphrase)) { LIBSSH2_FREE(session, method); LIBSSH2_FREE(session, packet); return -1; } *b = 0xFF; libssh2_htonu32(buf, session->session_id_len); datavec[0].iov_base = buf; datavec[0].iov_len = 4; datavec[1].iov_base = session->session_id; datavec[1].iov_len = session->session_id_len; datavec[2].iov_base = packet; datavec[2].iov_len = packet_len; if (privkeyobj->signv(session, &sig, &sig_len, 3, datavec, &abstract)) { LIBSSH2_FREE(session, method); LIBSSH2_FREE(session, packet); if (privkeyobj->dtor) { privkeyobj->dtor(session, &abstract); } return -1; } if (privkeyobj->dtor) { privkeyobj->dtor(session, &abstract); } if (sig_len > pubkeydata_len) { /* Should *NEVER* happen, but...well.. better safe than sorry */ packet = LIBSSH2_REALLOC(session, packet, packet_len + 4 + (4 + method_len) + (4 + sig_len)); /* PK sigblob */ if (!packet) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Failed allocating additional space for userauth-publickey packet", 0); LIBSSH2_FREE(session, method); return -1; } } s = packet + packet_len; libssh2_htonu32(s, 4 + method_len + 4 + sig_len); s += 4; libssh2_htonu32(s, method_len); s += 4; memcpy(s, method, method_len); s += method_len; LIBSSH2_FREE(session, method); libssh2_htonu32(s, sig_len); s += 4; memcpy(s, sig, sig_len); s += sig_len; LIBSSH2_FREE(session, sig); #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Attempting publickey authentication -- phase 2"); #endif if (libssh2_packet_write(session, packet, s - packet)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-publickey request", 0); LIBSSH2_FREE(session, packet); return -1; } LIBSSH2_FREE(session, packet); /* PK_OK is no longer valid */ reply_codes[2] = 0; if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) { return -1; } if (data[0] == SSH_MSG_USERAUTH_SUCCESS) { #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Publickey authentication successful"); #endif /* We are us and we've proved it. */ LIBSSH2_FREE(session, data); session->state |= LIBSSH2_STATE_AUTHENTICATED; return 0; } /* This public key is not allowed for this user on this server */ LIBSSH2_FREE(session, data); libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, "Invalid signature for supplied public key, or bad username/public key combination", 0); return -1; } /* }}} */ /* {{{ libssh2_userauth_keyboard_interactive * Authenticate using a challenge-response authentication */ LIBSSH2_API int libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION *session, const char *username, unsigned int username_len, LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*response_callback))) { unsigned char *s, *data; /* packet */ unsigned long packet_len; packet_len = 1 /* byte SSH_MSG_USERAUTH_REQUEST */ + 4 + username_len /* string user name (ISO-10646 UTF-8, as defined in [RFC-3629]) */ + 4 + 14 /* string service name (US-ASCII) */ + 4 + 20 /* string "keyboard-interactive" (US-ASCII) */ + 4 + 0 /* string language tag (as defined in [RFC-3066]) */ + 4 + 0 /* string submethods (ISO-10646 UTF-8) */ ; if (!(data = s = LIBSSH2_ALLOC(session, packet_len))) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive authentication", 0); return -1; } *s++ = SSH_MSG_USERAUTH_REQUEST; /* user name */ libssh2_htonu32(s, username_len); s += 4; memcpy(s, username, username_len); s += username_len; /* service name */ libssh2_htonu32(s, sizeof("ssh-connection") - 1); s += 4; memcpy(s, "ssh-connection", sizeof("ssh-connection") - 1); s += sizeof("ssh-connection") - 1; /* "keyboard-interactive" */ libssh2_htonu32(s, sizeof("keyboard-interactive") - 1); s += 4; memcpy(s, "keyboard-interactive", sizeof("keyboard-interactive") - 1); s += sizeof("keyboard-interactive") - 1; /* language tag */ libssh2_htonu32(s, 0); s += 4; /* submethods */ libssh2_htonu32(s, 0); s += 4; #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Attempting keyboard-interactive authentication"); #endif if (libssh2_packet_write(session, data, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send keyboard-interactive request", 0); LIBSSH2_FREE(session, data); return -1; } LIBSSH2_FREE(session, data); for (;;) { unsigned char reply_codes[4] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_INFO_REQUEST, 0 }; unsigned int auth_name_len; char* auth_name = NULL; unsigned auth_instruction_len; char* auth_instruction = NULL; unsigned int language_tag_len; unsigned long data_len; unsigned int num_prompts = 0; unsigned int i; int auth_failure = 1; LIBSSH2_USERAUTH_KBDINT_PROMPT* prompts = NULL; LIBSSH2_USERAUTH_KBDINT_RESPONSE* responses = NULL; if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) { return -1; } if (data[0] == SSH_MSG_USERAUTH_SUCCESS) { #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Keyboard-interactive authentication successful"); #endif LIBSSH2_FREE(session, data); session->state |= LIBSSH2_STATE_AUTHENTICATED; return 0; } if (data[0] == SSH_MSG_USERAUTH_FAILURE) { LIBSSH2_FREE(session, data); return -1; } /* server requested PAM-like conversation */ s = data + 1; /* string name (ISO-10646 UTF-8) */ auth_name_len = libssh2_ntohu32(s); s += 4; if (!(auth_name = LIBSSH2_ALLOC(session, auth_name_len))) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive 'name' request field", 0); goto cleanup; } memcpy(auth_name, s, auth_name_len); s += auth_name_len; /* string instruction (ISO-10646 UTF-8) */ auth_instruction_len = libssh2_ntohu32(s); s += 4; if (!(auth_instruction = LIBSSH2_ALLOC(session, auth_instruction_len))) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive 'instruction' request field", 0); goto cleanup; } memcpy(auth_instruction, s, auth_instruction_len); s += auth_instruction_len; /* string language tag (as defined in [RFC-3066]) */ language_tag_len = libssh2_ntohu32(s); s += 4; /* ignoring this field as deprecated */ s += language_tag_len; /* int num-prompts */ num_prompts = libssh2_ntohu32(s); s += 4; prompts = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * num_prompts); if (!prompts) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive prompts array", 0); goto cleanup; } memset(prompts, 0, sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * num_prompts); responses = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * num_prompts); if (!responses) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive responses array", 0); goto cleanup; } memset(responses, 0, sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * num_prompts); for(i = 0; i != num_prompts; ++i) { /* string prompt[1] (ISO-10646 UTF-8) */ prompts[i].length = libssh2_ntohu32(s); s += 4; if (!(prompts[i].text = LIBSSH2_ALLOC(session, prompts[i].length))) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive prompt message", 0); goto cleanup; } memcpy(prompts[i].text, s, prompts[i].length); s += prompts[i].length; /* boolean echo[1] */ prompts[i].echo = *s++; } response_callback(auth_name, auth_name_len, auth_instruction, auth_instruction_len, num_prompts, prompts, responses, &session->abstract); #ifdef LIBSSH2_DEBUG_USERAUTH _libssh2_debug(session, LIBSSH2_DBG_AUTH, "Keyboard-interactive response callback function invoked"); #endif packet_len = 1 /* byte SSH_MSG_USERAUTH_INFO_RESPONSE */ + 4 /* int num-responses */ ; for (i = 0; i != num_prompts; ++i) { packet_len += 4 + responses[i].length; /* string response[1] (ISO-10646 UTF-8) */ } if (!(data = s = LIBSSH2_ALLOC(session, packet_len))) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive response packet", 0); goto cleanup; } *s = SSH_MSG_USERAUTH_INFO_RESPONSE; s++; libssh2_htonu32(s, num_prompts); s += 4; for (i = 0; i != num_prompts; ++i) { libssh2_htonu32(s, responses[i].length); s += 4; memcpy(s, responses[i].text, responses[i].length); s += responses[i].length; } if (libssh2_packet_write(session, data, packet_len)) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-keyboard-interactive request", 0); goto cleanup; } auth_failure = 0; cleanup: /* It's safe to clean all the data here, because unallocated pointers * are filled by zeroes */ LIBSSH2_FREE(session, data); if (prompts) { for (i = 0; i != num_prompts; ++i) { LIBSSH2_FREE(session, prompts[i].text); } } if (responses) { for (i = 0; i != num_prompts; ++i) { LIBSSH2_FREE(session, responses[i].text); } } LIBSSH2_FREE(session, prompts); LIBSSH2_FREE(session, responses); if (auth_failure) { return -1; } } } /* }}} */ ================================================ FILE: Example/version.plist ================================================ BuildVersion 92 CFBundleVersion 1.0 ProductBuildVersion 7K571 ProjectName NibPBTemplates SourceVersion 1200000 ================================================ FILE: Example/zh_CN.lproj/DropletLauncher.nib/classes.nib ================================================ { IBClasses = ( {CLASS = DropletLauncherDelegate; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/zh_CN.lproj/DropletLauncher.nib/info.nib ================================================ IBDocumentLocation 415 56 356 240 0 0 1280 778 IBEditorPositions 29 69 259 338 44 0 0 1280 778 IBFramework Version 446.1 IBSystem Version 8S165 IBUsesTextArchiving ================================================ FILE: Example/zh_CN.lproj/DropletLauncher.nib/keyedobjects.nib ================================================ $archiver NSKeyedArchiver $objects $null $class CF$UID 396 NSAccessibilityConnectors CF$UID 393 NSAccessibilityOidsKeys CF$UID 394 NSAccessibilityOidsValues CF$UID 395 NSClassesKeys CF$UID 288 NSClassesValues CF$UID 289 NSConnections CF$UID 9 NSFontManager CF$UID 0 NSFramework CF$UID 6 NSNamesKeys CF$UID 264 NSNamesValues CF$UID 265 NSNextOid 250 NSObjectsKeys CF$UID 176 NSObjectsValues CF$UID 263 NSOidsKeys CF$UID 290 NSOidsValues CF$UID 291 NSRoot CF$UID 2 NSVisibleWindows CF$UID 7 $class CF$UID 5 NSClassName CF$UID 3 $class CF$UID 4 NS.string NSApplication $classes NSMutableString NSString NSObject $classname NSMutableString $classes NSCustomObject NSObject $classname NSCustomObject $class CF$UID 4 NS.string IBCocoaFramework $class CF$UID 8 NS.objects $classes NSMutableSet NSSet NSObject $classname NSMutableSet $class CF$UID 175 NS.objects CF$UID 10 CF$UID 24 CF$UID 29 CF$UID 35 CF$UID 40 CF$UID 46 CF$UID 51 CF$UID 57 CF$UID 61 CF$UID 66 CF$UID 70 CF$UID 74 CF$UID 79 CF$UID 84 CF$UID 90 CF$UID 95 CF$UID 100 CF$UID 105 CF$UID 110 CF$UID 115 CF$UID 120 CF$UID 125 CF$UID 130 CF$UID 134 CF$UID 138 CF$UID 142 CF$UID 148 CF$UID 152 CF$UID 156 CF$UID 160 CF$UID 165 CF$UID 170 $class CF$UID 23 NSLabel CF$UID 22 NSSource CF$UID 11 $class CF$UID 21 NSKeyEquiv CF$UID 14 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 13 $class CF$UID 196 NSMenuItems CF$UID 233 NSName CF$UID 235 NSTitle CF$UID 232 最小化 m $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 17 NSImage NSMenuCheckmark $classes NSCustomResource NSObject $classname NSCustomResource $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 20 NSMenuMixedState $classes NSMenuItem NSObject $classname NSMenuItem $class CF$UID 4 NS.string performMiniaturize: $classes NSNibControlConnector NSNibConnector NSObject $classname NSNibControlConnector $class CF$UID 23 NSLabel CF$UID 28 NSSource CF$UID 25 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 26 全部调到前面 $class CF$UID 4 NS.string arrangeInFront: $class CF$UID 23 NSLabel CF$UID 34 NSSource CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 33 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 32 $class CF$UID 196 NSMenuItems CF$UID 211 NSTitle CF$UID 210 打印… p $class CF$UID 4 NS.string print: $class CF$UID 23 NSLabel CF$UID 39 NSSource CF$UID 36 $class CF$UID 21 NSKeyEquiv CF$UID 38 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 37 页面设置… P $class CF$UID 4 NS.string runPageLayout: $class CF$UID 23 NSLabel CF$UID 45 NSSource CF$UID 41 $class CF$UID 21 NSKeyEquiv CF$UID 44 NSKeyEquivModMask 1048576 NSMenu CF$UID 42 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 43 $class CF$UID 196 NSMenuItems CF$UID 247 NSTitle CF$UID 246 NewApplication 帮助 ? $class CF$UID 4 NS.string showHelp: $class CF$UID 23 NSLabel CF$UID 50 NSSource CF$UID 47 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 48 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 49 $class CF$UID 196 NSMenuItems CF$UID 219 NSName CF$UID 220 NSTitle CF$UID 218 清除菜单 $class CF$UID 4 NS.string clearRecentDocuments: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 56 NSSource CF$UID 52 $class CF$UID 21 NSKeyEquiv CF$UID 55 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 54 $class CF$UID 196 NSMenuItems CF$UID 249 NSName CF$UID 253 NSTitle CF$UID 248 退出 NewApplication q $class CF$UID 4 NS.string terminate: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 60 NSSource CF$UID 58 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 59 关于 NewApplication $class CF$UID 4 NS.string orderFrontStandardAboutPanel: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 65 NSSource CF$UID 62 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1572864 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 63 隐藏其他 h hideOtherApplications: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 69 NSSource CF$UID 67 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 68 隐藏 NewApplication hide: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 73 NSSource CF$UID 71 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 72 显示全部 unhideAllApplications: $class CF$UID 23 NSLabel CF$UID 78 NSSource CF$UID 75 $class CF$UID 21 NSKeyEquiv CF$UID 77 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 76 关闭 w performClose: $class CF$UID 23 NSLabel CF$UID 83 NSSource CF$UID 80 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 82 $class CF$UID 196 NSMenuItems CF$UID 201 NSTitle CF$UID 200 键入时检查拼写 toggleContinuousSpellChecking: $class CF$UID 23 NSLabel CF$UID 89 NSSource CF$UID 85 $class CF$UID 21 NSKeyEquiv CF$UID 88 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 87 $class CF$UID 196 NSMenuItems CF$UID 188 NSTitle CF$UID 187 还原 z $class CF$UID 4 NS.string undo: $class CF$UID 23 NSLabel CF$UID 94 NSSource CF$UID 91 $class CF$UID 21 NSKeyEquiv CF$UID 93 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 92 拷贝 c $class CF$UID 4 NS.string copy: $class CF$UID 23 NSLabel CF$UID 99 NSSource CF$UID 96 $class CF$UID 21 NSKeyEquiv CF$UID 98 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 97 检查拼写 ; $class CF$UID 4 NS.string checkSpelling: $class CF$UID 23 NSLabel CF$UID 104 NSSource CF$UID 101 $class CF$UID 21 NSKeyEquiv CF$UID 103 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 102 粘贴 v $class CF$UID 4 NS.string paste: $class CF$UID 23 NSLabel CF$UID 109 NSSource CF$UID 106 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 108 $class CF$UID 196 NSMenuItems CF$UID 206 NSTitle CF$UID 205 停止说话 stopSpeaking: $class CF$UID 23 NSLabel CF$UID 114 NSSource CF$UID 111 $class CF$UID 21 NSKeyEquiv CF$UID 113 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 112 剪切 x $class CF$UID 4 NS.string cut: $class CF$UID 23 NSLabel CF$UID 119 NSSource CF$UID 116 $class CF$UID 21 NSKeyEquiv CF$UID 118 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 117 拼写… : $class CF$UID 4 NS.string showGuessPanel: $class CF$UID 23 NSLabel CF$UID 124 NSSource CF$UID 121 $class CF$UID 21 NSKeyEquiv CF$UID 123 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 122 重做 Z $class CF$UID 4 NS.string redo: $class CF$UID 23 NSLabel CF$UID 129 NSSource CF$UID 126 $class CF$UID 21 NSKeyEquiv CF$UID 128 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 127 全选 a $class CF$UID 4 NS.string selectAll: $class CF$UID 23 NSLabel CF$UID 133 NSSource CF$UID 131 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 132 开始讲话 startSpeaking: $class CF$UID 23 NSLabel CF$UID 137 NSSource CF$UID 135 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 136 删除 delete: $class CF$UID 23 NSLabel CF$UID 141 NSSource CF$UID 139 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 140 缩放 performZoom: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 143 $class CF$UID 21 NSKeyEquiv CF$UID 146 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 1 NSTitle CF$UID 145 $class CF$UID 196 NSMenuItems CF$UID 195 NSTitle CF$UID 194 查找… f performFindPanelAction: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 149 $class CF$UID 21 NSKeyEquiv CF$UID 151 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 2 NSTitle CF$UID 150 查找下一个 g $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 153 $class CF$UID 21 NSKeyEquiv CF$UID 155 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 3 NSTitle CF$UID 154 查找上一个 G $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 157 $class CF$UID 21 NSKeyEquiv CF$UID 159 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 7 NSTitle CF$UID 158 查找所选内容 e $class CF$UID 23 NSLabel CF$UID 164 NSSource CF$UID 161 $class CF$UID 21 NSKeyEquiv CF$UID 163 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 162 跳到所选部分 j centerSelectionInVisibleArea: $class CF$UID 23 NSLabel CF$UID 169 NSSource CF$UID 166 $class CF$UID 21 NSKeyEquiv CF$UID 168 NSKeyEquivModMask 1572864 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 167 粘贴和匹配样式 V pasteAsPlainText: $class CF$UID 174 NSDestination CF$UID 171 NSLabel CF$UID 173 NSSource CF$UID 2 $class CF$UID 5 NSClassName CF$UID 172 DropletLauncherDelegate delegate $classes NSNibOutletConnector NSNibConnector NSObject $classname NSNibOutletConnector $classes NSMutableArray NSArray NSObject $classname NSMutableArray $class CF$UID 262 NS.objects CF$UID 67 CF$UID 96 CF$UID 177 CF$UID 11 CF$UID 180 CF$UID 157 CF$UID 47 CF$UID 183 CF$UID 86 CF$UID 207 CF$UID 62 CF$UID 202 CF$UID 212 CF$UID 190 CF$UID 101 CF$UID 36 CF$UID 222 CF$UID 48 CF$UID 75 CF$UID 231 CF$UID 52 CF$UID 161 CF$UID 121 CF$UID 143 CF$UID 80 CF$UID 215 CF$UID 71 CF$UID 12 CF$UID 191 CF$UID 166 CF$UID 41 CF$UID 189 CF$UID 225 CF$UID 171 CF$UID 106 CF$UID 30 CF$UID 131 CF$UID 236 CF$UID 149 CF$UID 243 CF$UID 53 CF$UID 42 CF$UID 81 CF$UID 252 CF$UID 234 CF$UID 58 CF$UID 144 CF$UID 197 CF$UID 111 CF$UID 251 CF$UID 135 CF$UID 228 CF$UID 254 CF$UID 31 CF$UID 250 CF$UID 184 CF$UID 153 CF$UID 107 CF$UID 239 CF$UID 259 CF$UID 85 CF$UID 139 CF$UID 116 CF$UID 221 CF$UID 25 CF$UID 126 CF$UID 91 CF$UID 230 $class CF$UID 21 NSKeyEquiv CF$UID 179 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 178 新建 n $class CF$UID 21 NSKeyEquiv CF$UID 182 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 181 偏好设置… , $class CF$UID 21 NSAction CF$UID 186 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 184 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 86 NSTitle CF$UID 185 $class CF$UID 196 NSMenuItems CF$UID 258 NSName CF$UID 261 NSTitle CF$UID 257 编辑 submenuAction: 编辑 $class CF$UID 175 NS.objects CF$UID 85 CF$UID 121 CF$UID 189 CF$UID 111 CF$UID 91 CF$UID 101 CF$UID 166 CF$UID 135 CF$UID 126 CF$UID 190 CF$UID 191 CF$UID 197 CF$UID 202 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 193 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 144 NSTitle CF$UID 192 查找 submenuAction: 查找 $class CF$UID 175 NS.objects CF$UID 143 CF$UID 149 CF$UID 153 CF$UID 157 CF$UID 161 $classes NSMenu NSObject $classname NSMenu $class CF$UID 21 NSAction CF$UID 199 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 81 NSTitle CF$UID 198 拼写 submenuAction: 拼写 $class CF$UID 175 NS.objects CF$UID 116 CF$UID 96 CF$UID 80 $class CF$UID 21 NSAction CF$UID 204 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 107 NSTitle CF$UID 203 说话 submenuAction: 说话 $class CF$UID 175 NS.objects CF$UID 131 CF$UID 106 $class CF$UID 21 NSAction CF$UID 209 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 184 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 31 NSTitle CF$UID 208 文件 submenuAction: 文件 $class CF$UID 175 NS.objects CF$UID 177 CF$UID 212 CF$UID 215 CF$UID 221 CF$UID 75 CF$UID 222 CF$UID 225 CF$UID 228 CF$UID 230 CF$UID 36 CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 214 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 213 打开… o $class CF$UID 21 NSAction CF$UID 217 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 48 NSTitle CF$UID 216 打开最近使用的 submenuAction: 打开最近使用的 $class CF$UID 175 NS.objects CF$UID 47 _NSRecentDocumentsMenu $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSKeyEquiv CF$UID 224 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 223 存储 s $class CF$UID 21 NSKeyEquiv CF$UID 227 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 226 存储为… S $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 229 复原 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 窗口 $class CF$UID 175 NS.objects CF$UID 11 CF$UID 139 CF$UID 234 CF$UID 25 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSWindowsMenu $class CF$UID 21 NSAction CF$UID 238 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 239 NSTitle CF$UID 237 服务 submenuAction: $class CF$UID 196 NSMenuItems CF$UID 241 NSName CF$UID 242 NSTitle CF$UID 240 服务 $class CF$UID 175 NS.objects _NSServicesMenu $class CF$UID 21 NSAction CF$UID 245 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 184 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 42 NSTitle CF$UID 244 帮助 submenuAction: 帮助 $class CF$UID 175 NS.objects CF$UID 41 NewApplication $class CF$UID 175 NS.objects CF$UID 58 CF$UID 250 CF$UID 180 CF$UID 231 CF$UID 236 CF$UID 251 CF$UID 67 CF$UID 62 CF$UID 71 CF$UID 252 CF$UID 52 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSAppleMenu $class CF$UID 21 NSAction CF$UID 256 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 184 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 12 NSTitle CF$UID 255 窗口 submenuAction: DropletLauncher $class CF$UID 175 NS.objects CF$UID 259 CF$UID 207 CF$UID 183 CF$UID 254 CF$UID 243 $class CF$UID 21 NSAction CF$UID 260 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 184 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 53 NSTitle CF$UID 248 submenuAction: _NSMainMenu $classes NSArray NSObject $classname NSArray $class CF$UID 262 NS.objects CF$UID 53 CF$UID 81 CF$UID 31 CF$UID 12 CF$UID 53 CF$UID 144 CF$UID 48 CF$UID 184 CF$UID 183 CF$UID 184 CF$UID 53 CF$UID 86 CF$UID 31 CF$UID 86 CF$UID 86 CF$UID 31 CF$UID 31 CF$UID 215 CF$UID 31 CF$UID 53 CF$UID 53 CF$UID 144 CF$UID 86 CF$UID 144 CF$UID 81 CF$UID 31 CF$UID 53 CF$UID 254 CF$UID 86 CF$UID 86 CF$UID 42 CF$UID 86 CF$UID 31 CF$UID 2 CF$UID 107 CF$UID 31 CF$UID 107 CF$UID 53 CF$UID 144 CF$UID 184 CF$UID 259 CF$UID 243 CF$UID 197 CF$UID 53 CF$UID 12 CF$UID 53 CF$UID 191 CF$UID 86 CF$UID 86 CF$UID 53 CF$UID 86 CF$UID 31 CF$UID 184 CF$UID 207 CF$UID 53 CF$UID 2 CF$UID 144 CF$UID 202 CF$UID 236 CF$UID 184 CF$UID 86 CF$UID 12 CF$UID 81 CF$UID 31 CF$UID 12 CF$UID 86 CF$UID 86 CF$UID 31 $class CF$UID 262 NS.objects CF$UID 243 CF$UID 207 CF$UID 212 CF$UID 225 CF$UID 36 CF$UID 171 CF$UID 42 CF$UID 177 CF$UID 234 CF$UID 81 CF$UID 96 CF$UID 222 CF$UID 197 CF$UID 230 CF$UID 2 CF$UID 30 CF$UID 80 CF$UID 52 CF$UID 31 CF$UID 180 CF$UID 228 CF$UID 12 CF$UID 41 CF$UID 75 CF$UID 184 CF$UID 221 CF$UID 116 $class CF$UID 262 NS.objects CF$UID 266 CF$UID 267 CF$UID 267 CF$UID 268 CF$UID 269 CF$UID 172 CF$UID 270 CF$UID 271 CF$UID 267 CF$UID 272 CF$UID 273 CF$UID 274 CF$UID 273 CF$UID 275 CF$UID 276 CF$UID 277 CF$UID 278 CF$UID 279 CF$UID 267 CF$UID 280 CF$UID 281 CF$UID 282 CF$UID 267 CF$UID 284 CF$UID 285 CF$UID 286 CF$UID 287 $class CF$UID 4 NS.string 1 $class CF$UID 4 NS.string $class CF$UID 4 NS.string 8 $class CF$UID 4 NS.string 5 $class CF$UID 4 NS.string 2 $class CF$UID 4 NS.string 9 NSMenu NSMenuItem $class CF$UID 4 NS.string 3 $class CF$UID 4 NS.string 2 $class CF$UID 4 NS.string File's Owner $class CF$UID 4 NS.string 6 NSMenuItem2 1111 121 $class CF$UID 4 NS.string 10 $class CF$UID 283 $classes NSNull %NSNull NSObject $classname NSNull $class CF$UID 4 NS.string 1 $class CF$UID 4 NS.string MainMenu $class CF$UID 4 NS.string 7 NSMenuItem1 $class CF$UID 262 NS.objects $class CF$UID 262 NS.objects $class CF$UID 262 NS.objects CF$UID 66 CF$UID 190 CF$UID 31 CF$UID 130 CF$UID 197 CF$UID 189 CF$UID 48 CF$UID 70 CF$UID 170 CF$UID 231 CF$UID 202 CF$UID 46 CF$UID 24 CF$UID 134 CF$UID 144 CF$UID 225 CF$UID 90 CF$UID 149 CF$UID 74 CF$UID 212 CF$UID 221 CF$UID 184 CF$UID 91 CF$UID 239 CF$UID 138 CF$UID 57 CF$UID 120 CF$UID 79 CF$UID 62 CF$UID 86 CF$UID 157 CF$UID 230 CF$UID 142 CF$UID 254 CF$UID 251 CF$UID 166 CF$UID 80 CF$UID 47 CF$UID 25 CF$UID 105 CF$UID 116 CF$UID 148 CF$UID 259 CF$UID 243 CF$UID 252 CF$UID 107 CF$UID 11 CF$UID 110 CF$UID 152 CF$UID 35 CF$UID 234 CF$UID 126 CF$UID 40 CF$UID 10 CF$UID 177 CF$UID 180 CF$UID 156 CF$UID 41 CF$UID 12 CF$UID 95 CF$UID 106 CF$UID 191 CF$UID 171 CF$UID 143 CF$UID 222 CF$UID 183 CF$UID 160 CF$UID 2 CF$UID 84 CF$UID 58 CF$UID 111 CF$UID 236 CF$UID 125 CF$UID 29 CF$UID 207 CF$UID 96 CF$UID 165 CF$UID 85 CF$UID 153 CF$UID 228 CF$UID 215 CF$UID 121 CF$UID 101 CF$UID 51 CF$UID 71 CF$UID 52 CF$UID 161 CF$UID 115 CF$UID 36 CF$UID 139 CF$UID 42 CF$UID 135 CF$UID 53 CF$UID 81 CF$UID 61 CF$UID 67 CF$UID 250 CF$UID 100 CF$UID 30 CF$UID 131 CF$UID 75 $class CF$UID 262 NS.objects CF$UID 292 CF$UID 293 CF$UID 294 CF$UID 295 CF$UID 296 CF$UID 297 CF$UID 298 CF$UID 299 CF$UID 300 CF$UID 301 CF$UID 302 CF$UID 303 CF$UID 304 CF$UID 305 CF$UID 306 CF$UID 307 CF$UID 308 CF$UID 309 CF$UID 310 CF$UID 311 CF$UID 312 CF$UID 313 CF$UID 314 CF$UID 315 CF$UID 316 CF$UID 317 CF$UID 318 CF$UID 319 CF$UID 320 CF$UID 321 CF$UID 322 CF$UID 323 CF$UID 324 CF$UID 325 CF$UID 326 CF$UID 327 CF$UID 328 CF$UID 329 CF$UID 330 CF$UID 331 CF$UID 332 CF$UID 333 CF$UID 334 CF$UID 335 CF$UID 336 CF$UID 337 CF$UID 338 CF$UID 339 CF$UID 340 CF$UID 341 CF$UID 342 CF$UID 343 CF$UID 344 CF$UID 345 CF$UID 346 CF$UID 347 CF$UID 348 CF$UID 349 CF$UID 350 CF$UID 351 CF$UID 352 CF$UID 353 CF$UID 354 CF$UID 355 CF$UID 356 CF$UID 357 CF$UID 358 CF$UID 359 CF$UID 360 CF$UID 361 CF$UID 362 CF$UID 363 CF$UID 364 CF$UID 365 CF$UID 366 CF$UID 367 CF$UID 368 CF$UID 369 CF$UID 370 CF$UID 371 CF$UID 372 CF$UID 373 CF$UID 374 CF$UID 375 CF$UID 376 CF$UID 377 CF$UID 378 CF$UID 379 CF$UID 380 CF$UID 381 CF$UID 382 CF$UID 383 CF$UID 384 CF$UID 385 CF$UID 386 CF$UID 387 CF$UID 388 CF$UID 389 CF$UID 390 CF$UID 391 CF$UID 392 152 214 81 233 216 206 125 153 249 143 211 127 39 235 220 80 224 208 193 72 79 29 197 130 240 142 231 222 145 205 221 74 241 19 144 246 219 126 5 227 204 242 56 103 149 212 23 228 243 87 92 198 122 37 82 129 244 111 24 225 195 218 248 209 75 217 245 1 223 58 199 131 232 86 83 201 247 207 213 112 124 215 203 139 150 136 210 230 77 239 106 202 57 200 146 134 236 226 78 196 73 $class CF$UID 175 NS.objects $class CF$UID 262 NS.objects $class CF$UID 262 NS.objects $classes NSIBObjectData NSObject $classname NSIBObjectData $top IB.objectdata CF$UID 1 $version 100000 ================================================ FILE: Example/zh_CN.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Example/zh_CN.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\n您想要怎么做?"; /* ConnectionOpenPanel */ "A Connection Error Occurred" = "发生连接错误"; /* FTP Abort */ "Action Aborted. Local Error" = "放弃操作。本机发生错误"; /* FTP no data stream types available */ "All data connection modes have been exhausted. Check with the server administrator." = "已尝试所有资料连接模式。请与服务器管理员确认。"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An intermediate directory does not exist and needs to be created before the current directory" = "中间目录并不存在,您必须在目前目录前创建该目录。"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An unknown error occurred" = "发生未知的错误"; /* multiple connection joiner */ "and" = "和"; /* close window */ "Are you sure you want to stop the upload?" = "你确定要停止上传吗?"; /* authorise */ "Authorize" = "批准"; /* authorise */ "Authorize Connection?" = "批准连接?"; /* connection type */ "auto" = "自动"; /* config */ "Bad Configuration" = "不良配置"; /* error */ "Bad Droplet" = "不良 Droplet"; /* Transfer Controller */ "Bad Password." = "不良密码"; /* context menu tree controller action gear */ "Browse Packages" = "浏览安装包"; /* filesize: bytes */ "bytes" = "字节"; /* authorise */ "Cancel" = "取消"; /* FTP upload error */ "Cannot Upload File. Storage quota on server exceeded" = "无法上传文件。已超过服务器的存储空间"; /* connected message transfer controller */ "Connected to %@" = "连接到 %@"; /* file transcript */ "Connected to File System\n" = "已连接到文件系统\n"; /* connection string */ "Connecting to %@" = "正在连接到 %@"; /* file transcript */ "Connecting…\n" = "正在连接…\n"; /* close window */ "Continue Upload" = "继续上传"; /* file transcript */ "Copying %@ to %@\n" = "正在拷贝 %1$@ 到 %2$@\n"; /* FileConnection set permissions error */ "Could not change file permissions" = "无法更改文件权限"; /* FileConnection create directory error */ "Could not create directory" = "无法创建目录"; /* config */ "Couldn't find configuration file specified\n %@" = "无法找到指定的配置文件\n %@"; /* file transcript */ "Create Directory %@ (%lo)\n" = "创建目录 %1$@ (%2$lo)\n"; /* FTP Create directory error */ "Create directory operation failed" = "创建目录作业失败"; /* Failed to open a data stream connection */ "Data Stream Timed Out" = "资料流逾时"; /* outline view column header name outline view column context menu item */ "Date Modified" = "修改日期"; /* file transcript */ "Deleting File %@\n" = "正在删除文件 %@\n"; /* status */ "Disconnected" = "已断开"; /* transfer controller */ "Disconnected from %@" = "已经自 %@ 断开"; /* unknown UTI name */ "Document" = "文稿"; /* filesize: exabytes */ "EB" = "EB"; /* Directory Parsing Error */ "Error parsing directory listing" = "解析目录列表时发生错误"; /* FTP no data streams available */ "Exhausted all connection types to server. Please contact server administrator" = "已尝试与此服务器的所有连接类型。请与服务器管理员联系。"; /* Bad ftp command */ "Failed to change to directory" = "无法更改目录"; /* WebDAV Directory Deletion Error couldn't delete the file MobileMe Directory Deletion Error */ "Failed to delete directory" = "无法删除目录"; /* error for deleting a directory */ "Failed to delete directory: %@" = "删除目录失败:%@"; /* WebDAV File Deletion Error couldn't delete the file MobileMe file deletion error */ "Failed to delete file" = "无法删除文件"; /* error for deleting a file */ "Failed to delete file: %@" = "删除文件失败: %@"; /* No MobileMe account or password */ "Failed to retrieve MobileMe account details" = "无法找回 MobileMe 账号的详细资料"; /* FTP Upload error */ "Failed to set permissions for path %@" = "设置路径 %@ 的权限失败"; /* FileConnection copy data error */ "Failed to upload data" = "上传数据失败"; /* error transferring */ "Failed to verify file transferred successfully" = "验证已成功传输的文件失败"; /* FTP file download error */ "File %@ does not exist on server" = "服务器上并没有文件 %@"; /* FTP error */ "File / Directory does not exist" = "文件 / 目录不存在"; /* FileConnection error */ "File Already Exists" = "文件已经存在"; /* FTP file in use */ "File in Use" = "文件正在使用中"; /* FTP Upload error */ "Filename not Allowed" = "不允许此文件名"; /* directory kind */ "Folder" = "文件夹"; /* FTP service timed out */ "FTP service not available; Remote server has closed connection" = "无法取得 FTP 服务;远程服务器已关闭或关闭连接"; /* FTP no service */ "FTP Service Unavailable" = "无法取得 FTP 服务"; /* filesize: gigabytes */ "GB" = "GB"; /* transfer controller */ "Hide Files" = "隐藏文件"; /* Couldn't open the port to the host */ "Host Unavailable" = "无法取得主机"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "Insufficient storage space available" = "可用的存储空间不足"; /* FTP Error */ "Invalid Account name" = "账号名称无效"; /* Stream Error before opening */ "Is the service running on the server" = "是在此服务器上执行的服务"; /* filesize: kilobytes */ "KB" = "KB"; /* outline view column header name outline view column context menu item */ "Kind" = "类型"; /* FTP download error */ "Local File already exists" = "本机文件已存在"; /* filesize: megabytes */ "MB" = "MB"; /* outline view column header name outline view column context menu item */ "Name" = "名称"; /* new cat name */ "New Category" = "新分类"; /* tree controller action gear */ "New Folder" = "新建文件夹"; /* config */ "No configuration file specified" = "没有指定的配置文件"; /* failed to find a connection class */ "No connection available for requested connection type" = "请求的连接类型没有可用的连接"; /* failed to find a connection class */ "No connection available for requested port" = "请求的传输端口没有可用的连接"; /* failed to find a connection class */ "No connection available for requested protocol" = "请求的传输协定没有可用的连接"; /* FTP Error */ "No Storage Space Available" = "没有可用的存储空间"; /* FTP Error */ "Not Logged In" = "尚未登入"; /* OK */ "OK" = "好"; /* WebDAV Uploading Error MobileMe File Uploading Error */ "Parent Folder does not exist" = "主体文件夹不存在"; /* No comment provided by engineer. */ "Password was not accepted." = "密码不被接受。"; /* filesize: petabytes */ "PB" = "PB"; /* mimic Finder naming conventions */ "Plain text document" = "纯文本文稿"; /* config error */ "Quit" = "退出"; /* tree controller action gear */ "Refresh" = "刷新"; /* file transcript */ "Renaming %@ to %@\n" = "正在更名 %1$@ 到 %2$@\n"; /* FTP Error */ "Request Aborted. Page Type Unknown" = "放弃请求。页面类型未知"; /* abbreviation for seconds, e.g. 12 MB/s */ "s" = "秒"; /* category name */ "Saved Hosts" = "已存储的主机"; /* transfer controller */ "Show Files" = "显示文件"; /* context menu tree controller action gear */ "Show Hidden Files" = "显示隐藏文件"; /* context menu tree controller action gear */ "Show Package Extensions" = "显示安装包扩展名"; /* outline view column header name outline view column context menu item */ "Size" = "大小"; /* close window */ "Stop Upload" = "停止上传"; /* close window */ "Stop Upload?" = "停止上传?"; /* Error creating stream */ "Stream Unavailable" = "数据流无法取得"; /* filesize: terabytes */ "TB" = "TB"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The body of the request is not supported" = "不支持此请求的内容"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The directory already exists" = "目录已存在"; /* name of a host to connect to; in this case, the local file system rather than a remote server */ "the File System" = "文件系统"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The server does not allow the creation of directories at the current location" = "此服务器不允许在目前的位置创建目录"; /* error transferring */ "The transferred file has a size of 0." = "所传输的文件大小是零。"; /* MobileMe Directory Contents Error */ "There is no MobileMe access to the directory" = "此目录无法使用 MobileMe 存取"; /* No WebDAV access to the specified path */ "There is no WebDAV access to the directory" = "此目录无法使用 WebDAV 存取"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "该 Droplet 丢失了创建它的原应用程序。你需要重新安装该应用程序。"; /* time out */ "Timed Out waiting for remote host." = "等待远程主机超时。"; /* Transfer Controller */ "Too many files had transfer problems" = "太多文件存在传输错误"; /* FileConnection failed to copy file */ "Unable to store data in file" = "无法存储文件资料"; /* WebDAV Error */ "Unknown Error Occurred" = "发生未知的错误"; /* status message */ "Uploading" = "正在上传"; /* status */ "Uploading %@" = "正在上传 %@"; /* No username or password */ "Username and Password are required for FTP connections" = "若要进行 FTP 连接,用户名与密码为必要项目"; /* No username or password */ "Username and Password are required for S3 connections" = "用户名和密码需要 S3 连接"; /* No username or password */ "Username and Password are required for WebDAV connections" = "若要进行 WebDAV 连接,用户名与密码为必要项目"; /* file transcript */ "Writing data to %@\n" = "正在写数据到 %@\n"; /* Bonjour Error */ "You can not add a child collection to the Bonjour category." = "你不能添加一个子收藏到这 Bonjour 分类。"; /* Bonjour Error */ "You can not add a new server to the Bonjour category." = "你不能添加一个新服务器到这 Bonjour 分类。"; /* FTP file upload error */ "You do not have access to write file %@" = "您没有权限写文件 %@"; /* FTP Error */ "You need an Account to Upload Files" = "您需要一个账号进行文件上传"; ================================================ FILE: Example/zh_TW.lproj/DropletLauncher.nib/classes.nib ================================================ { IBClasses = ( { CLASS = DropletLauncherDelegate; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, { CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; } ================================================ FILE: Example/zh_TW.lproj/DropletLauncher.nib/info.nib ================================================ IBDocumentLocation 415 56 356 240 0 0 1280 778 IBEditorPositions 29 69 259 338 44 0 0 1280 778 IBFramework Version 489.0 IBSystem Version 9D34 IBUsesTextArchiving ================================================ FILE: Example/zh_TW.lproj/DropletLauncher.nib/keyedobjects.nib ================================================ $archiver NSKeyedArchiver $objects $null $class CF$UID 387 NSAccessibilityConnectors CF$UID 384 NSAccessibilityOidsKeys CF$UID 385 NSAccessibilityOidsValues CF$UID 386 NSClassesKeys CF$UID 279 NSClassesValues CF$UID 280 NSConnections CF$UID 9 NSFontManager CF$UID 0 NSFramework CF$UID 6 NSNamesKeys CF$UID 255 NSNamesValues CF$UID 256 NSNextOid 250 NSObjectsKeys CF$UID 176 NSObjectsValues CF$UID 254 NSOidsKeys CF$UID 281 NSOidsValues CF$UID 282 NSRoot CF$UID 2 NSVisibleWindows CF$UID 7 $class CF$UID 5 NSClassName CF$UID 3 $class CF$UID 4 NS.string NSApplication $classes NSMutableString NSString NSObject $classname NSMutableString $classes NSCustomObject NSObject $classname NSCustomObject $class CF$UID 4 NS.string IBCocoaFramework $class CF$UID 8 NS.objects $classes NSMutableSet NSSet NSObject $classname NSMutableSet $class CF$UID 175 NS.objects CF$UID 10 CF$UID 24 CF$UID 29 CF$UID 35 CF$UID 40 CF$UID 46 CF$UID 51 CF$UID 57 CF$UID 61 CF$UID 66 CF$UID 70 CF$UID 74 CF$UID 79 CF$UID 84 CF$UID 90 CF$UID 95 CF$UID 100 CF$UID 105 CF$UID 110 CF$UID 115 CF$UID 120 CF$UID 125 CF$UID 130 CF$UID 134 CF$UID 138 CF$UID 142 CF$UID 148 CF$UID 152 CF$UID 156 CF$UID 160 CF$UID 165 CF$UID 170 $class CF$UID 23 NSLabel CF$UID 22 NSSource CF$UID 11 $class CF$UID 21 NSKeyEquiv CF$UID 14 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 13 $class CF$UID 182 NSMenuItems CF$UID 179 NSName CF$UID 181 NSTitle CF$UID 178 縮到最小 m $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 17 NSImage NSMenuCheckmark $classes NSCustomResource NSObject $classname NSCustomResource $class CF$UID 18 NSClassName CF$UID 16 NSResourceName CF$UID 20 NSMenuMixedState $classes NSMenuItem NSObject $classname NSMenuItem $class CF$UID 4 NS.string performMiniaturize: $classes NSNibControlConnector NSNibConnector NSObject $classname NSNibControlConnector $class CF$UID 23 NSLabel CF$UID 28 NSSource CF$UID 25 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 26 將此程式所有視窗移至最前 $class CF$UID 4 NS.string arrangeInFront: $class CF$UID 23 NSLabel CF$UID 34 NSSource CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 33 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 32 $class CF$UID 182 NSMenuItems CF$UID 190 NSTitle CF$UID 189 列印⋯ p $class CF$UID 4 NS.string print: $class CF$UID 23 NSLabel CF$UID 39 NSSource CF$UID 36 $class CF$UID 21 NSKeyEquiv CF$UID 38 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 37 設定頁面⋯ P $class CF$UID 4 NS.string runPageLayout: $class CF$UID 23 NSLabel CF$UID 45 NSSource CF$UID 41 $class CF$UID 21 NSKeyEquiv CF$UID 44 NSKeyEquivModMask 1048576 NSMenu CF$UID 42 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 43 $class CF$UID 182 NSMenuItems CF$UID 251 NSTitle CF$UID 249 NewApplication 輔助說明 ? $class CF$UID 4 NS.string showHelp: $class CF$UID 23 NSLabel CF$UID 50 NSSource CF$UID 47 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 48 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 49 $class CF$UID 182 NSMenuItems CF$UID 200 NSName CF$UID 201 NSTitle CF$UID 198 清除選單 $class CF$UID 4 NS.string clearRecentDocuments: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 56 NSSource CF$UID 52 $class CF$UID 21 NSKeyEquiv CF$UID 55 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 54 $class CF$UID 182 NSMenuItems CF$UID 223 NSName CF$UID 231 NSTitle CF$UID 221 結束 NewApplication q $class CF$UID 4 NS.string terminate: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 60 NSSource CF$UID 58 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 59 關於 NewApplication $class CF$UID 4 NS.string orderFrontStandardAboutPanel: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 65 NSSource CF$UID 62 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1572864 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 63 隱藏其他 h hideOtherApplications: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 69 NSSource CF$UID 67 $class CF$UID 21 NSKeyEquiv CF$UID 64 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 68 隱藏 NewApplication hide: $class CF$UID 23 NSDestination CF$UID 2 NSLabel CF$UID 73 NSSource CF$UID 71 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 72 顯示全部 unhideAllApplications: $class CF$UID 23 NSLabel CF$UID 78 NSSource CF$UID 75 $class CF$UID 21 NSKeyEquiv CF$UID 77 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 76 關閉 w performClose: $class CF$UID 23 NSLabel CF$UID 83 NSSource CF$UID 80 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 82 $class CF$UID 182 NSMenuItems CF$UID 216 NSTitle CF$UID 215 在輸入時同步檢查拼字 toggleContinuousSpellChecking: $class CF$UID 23 NSLabel CF$UID 89 NSSource CF$UID 85 $class CF$UID 21 NSKeyEquiv CF$UID 88 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 87 $class CF$UID 182 NSMenuItems CF$UID 237 NSTitle CF$UID 235 還原 z $class CF$UID 4 NS.string undo: $class CF$UID 23 NSLabel CF$UID 94 NSSource CF$UID 91 $class CF$UID 21 NSKeyEquiv CF$UID 93 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 92 拷貝 c $class CF$UID 4 NS.string copy: $class CF$UID 23 NSLabel CF$UID 99 NSSource CF$UID 96 $class CF$UID 21 NSKeyEquiv CF$UID 98 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 97 檢查拼字 ; $class CF$UID 4 NS.string checkSpelling: $class CF$UID 23 NSLabel CF$UID 104 NSSource CF$UID 101 $class CF$UID 21 NSKeyEquiv CF$UID 103 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 102 貼上 v $class CF$UID 4 NS.string paste: $class CF$UID 23 NSLabel CF$UID 109 NSSource CF$UID 106 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 108 $class CF$UID 182 NSMenuItems CF$UID 212 NSTitle CF$UID 211 停止朗讀 stopSpeaking: $class CF$UID 23 NSLabel CF$UID 114 NSSource CF$UID 111 $class CF$UID 21 NSKeyEquiv CF$UID 113 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 112 剪下 x $class CF$UID 4 NS.string cut: $class CF$UID 23 NSLabel CF$UID 119 NSSource CF$UID 116 $class CF$UID 21 NSKeyEquiv CF$UID 118 NSKeyEquivModMask 1048576 NSMenu CF$UID 81 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 117 拼字檢查⋯ : $class CF$UID 4 NS.string showGuessPanel: $class CF$UID 23 NSLabel CF$UID 124 NSSource CF$UID 121 $class CF$UID 21 NSKeyEquiv CF$UID 123 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 122 重作 Z $class CF$UID 4 NS.string redo: $class CF$UID 23 NSLabel CF$UID 129 NSSource CF$UID 126 $class CF$UID 21 NSKeyEquiv CF$UID 128 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 127 全選 a $class CF$UID 4 NS.string selectAll: $class CF$UID 23 NSLabel CF$UID 133 NSSource CF$UID 131 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 107 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 132 開始朗讀 startSpeaking: $class CF$UID 23 NSLabel CF$UID 137 NSSource CF$UID 135 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 136 刪除 delete: $class CF$UID 23 NSLabel CF$UID 141 NSSource CF$UID 139 $class CF$UID 21 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 140 縮放 performZoom: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 143 $class CF$UID 21 NSKeyEquiv CF$UID 146 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 1 NSTitle CF$UID 145 $class CF$UID 182 NSMenuItems CF$UID 243 NSTitle CF$UID 241 尋找⋯ f performFindPanelAction: $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 149 $class CF$UID 21 NSKeyEquiv CF$UID 151 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 2 NSTitle CF$UID 150 尋找下一個 g $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 153 $class CF$UID 21 NSKeyEquiv CF$UID 155 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 3 NSTitle CF$UID 154 尋找上一個 G $class CF$UID 23 NSLabel CF$UID 147 NSSource CF$UID 157 $class CF$UID 21 NSKeyEquiv CF$UID 159 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTag 7 NSTitle CF$UID 158 使用所選範圍尋找 e $class CF$UID 23 NSLabel CF$UID 164 NSSource CF$UID 161 $class CF$UID 21 NSKeyEquiv CF$UID 163 NSKeyEquivModMask 1048576 NSMenu CF$UID 144 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 162 跳至所選範圍 j centerSelectionInVisibleArea: $class CF$UID 23 NSLabel CF$UID 169 NSSource CF$UID 166 $class CF$UID 21 NSKeyEquiv CF$UID 168 NSKeyEquivModMask 1572864 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 167 貼上並符合樣式 V pasteAsPlainText: $class CF$UID 174 NSDestination CF$UID 171 NSLabel CF$UID 173 NSSource CF$UID 2 $class CF$UID 5 NSClassName CF$UID 172 DropletLauncherDelegate delegate $classes NSNibOutletConnector NSNibConnector NSObject $classname NSNibOutletConnector $classes NSMutableArray NSArray NSObject $classname NSMutableArray $class CF$UID 253 NS.objects CF$UID 177 CF$UID 12 CF$UID 183 CF$UID 31 CF$UID 36 CF$UID 191 CF$UID 107 CF$UID 180 CF$UID 213 CF$UID 96 CF$UID 81 CF$UID 139 CF$UID 217 CF$UID 194 CF$UID 25 CF$UID 111 CF$UID 135 CF$UID 116 CF$UID 244 CF$UID 86 CF$UID 203 CF$UID 206 CF$UID 11 CF$UID 143 CF$UID 157 CF$UID 238 CF$UID 144 CF$UID 53 CF$UID 71 CF$UID 161 CF$UID 126 CF$UID 230 CF$UID 41 CF$UID 197 CF$UID 47 CF$UID 229 CF$UID 232 CF$UID 228 CF$UID 166 CF$UID 106 CF$UID 30 CF$UID 246 CF$UID 80 CF$UID 202 CF$UID 52 CF$UID 220 CF$UID 171 CF$UID 101 CF$UID 225 CF$UID 153 CF$UID 239 CF$UID 58 CF$UID 67 CF$UID 234 CF$UID 131 CF$UID 240 CF$UID 91 CF$UID 48 CF$UID 85 CF$UID 149 CF$UID 42 CF$UID 186 CF$UID 75 CF$UID 121 CF$UID 248 CF$UID 62 CF$UID 209 CF$UID 224 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 視窗 $class CF$UID 175 NS.objects CF$UID 11 CF$UID 139 CF$UID 180 CF$UID 25 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 12 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSWindowsMenu $classes NSMenu NSObject $classname NSMenu $class CF$UID 21 NSAction CF$UID 185 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 186 NSTitle CF$UID 184 服務 submenuAction: $class CF$UID 182 NSMenuItems CF$UID 187 NSName CF$UID 188 NSTitle CF$UID 184 $class CF$UID 175 NS.objects _NSServicesMenu 檔案 $class CF$UID 175 NS.objects CF$UID 191 CF$UID 194 CF$UID 197 CF$UID 202 CF$UID 75 CF$UID 203 CF$UID 206 CF$UID 209 CF$UID 177 CF$UID 36 CF$UID 30 $class CF$UID 21 NSKeyEquiv CF$UID 193 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 192 新增 n $class CF$UID 21 NSKeyEquiv CF$UID 196 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 195 打開⋯ o $class CF$UID 21 NSAction CF$UID 199 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 48 NSTitle CF$UID 198 打開最近使用過的文件 submenuAction: $class CF$UID 175 NS.objects CF$UID 47 _NSRecentDocumentsMenu $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSKeyEquiv CF$UID 205 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 204 儲存 s $class CF$UID 21 NSKeyEquiv CF$UID 208 NSKeyEquivModMask 1048576 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 207 儲存為⋯ S $class CF$UID 21 NSKeyEquiv CF$UID 27 NSMenu CF$UID 31 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 210 回復 語音 $class CF$UID 175 NS.objects CF$UID 131 CF$UID 106 $class CF$UID 21 NSAction CF$UID 214 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 107 NSTitle CF$UID 211 submenuAction: 拼字檢查 $class CF$UID 175 NS.objects CF$UID 116 CF$UID 96 CF$UID 80 $class CF$UID 182 NSMenuItems CF$UID 219 NSName CF$UID 252 NSTitle CF$UID 218 DropletLauncher $class CF$UID 175 NS.objects CF$UID 220 CF$UID 232 CF$UID 234 CF$UID 246 CF$UID 248 $class CF$UID 21 NSAction CF$UID 222 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 217 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 53 NSTitle CF$UID 221 NewApplication submenuAction: $class CF$UID 175 NS.objects CF$UID 58 CF$UID 224 CF$UID 225 CF$UID 228 CF$UID 183 CF$UID 229 CF$UID 67 CF$UID 62 CF$UID 71 CF$UID 230 CF$UID 52 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSKeyEquiv CF$UID 227 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 226 偏好設定⋯ , $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 53 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 _NSAppleMenu $class CF$UID 21 NSAction CF$UID 233 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 217 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 31 NSTitle CF$UID 189 submenuAction: $class CF$UID 21 NSAction CF$UID 236 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 217 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 86 NSTitle CF$UID 235 編輯 submenuAction: $class CF$UID 175 NS.objects CF$UID 85 CF$UID 121 CF$UID 238 CF$UID 111 CF$UID 91 CF$UID 101 CF$UID 166 CF$UID 135 CF$UID 126 CF$UID 239 CF$UID 240 CF$UID 244 CF$UID 213 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSIsDisabled NSIsSeparator NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSTitle CF$UID 27 $class CF$UID 21 NSAction CF$UID 242 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 144 NSTitle CF$UID 241 尋找 submenuAction: $class CF$UID 175 NS.objects CF$UID 143 CF$UID 149 CF$UID 153 CF$UID 157 CF$UID 161 $class CF$UID 21 NSAction CF$UID 245 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 86 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 81 NSTitle CF$UID 215 submenuAction: $class CF$UID 21 NSAction CF$UID 247 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 217 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 12 NSTitle CF$UID 178 submenuAction: $class CF$UID 21 NSAction CF$UID 250 NSKeyEquiv CF$UID 27 NSKeyEquivModMask 1048576 NSMenu CF$UID 217 NSMixedImage CF$UID 19 NSMnemonicLoc 2147483647 NSOnImage CF$UID 15 NSSubmenu CF$UID 42 NSTitle CF$UID 249 輔助說明 submenuAction: $class CF$UID 175 NS.objects CF$UID 41 _NSMainMenu $classes NSArray NSObject $classname NSArray $class CF$UID 253 NS.objects CF$UID 31 CF$UID 246 CF$UID 53 CF$UID 232 CF$UID 31 CF$UID 31 CF$UID 213 CF$UID 12 CF$UID 86 CF$UID 81 CF$UID 244 CF$UID 12 CF$UID 2 CF$UID 31 CF$UID 12 CF$UID 86 CF$UID 86 CF$UID 81 CF$UID 86 CF$UID 234 CF$UID 31 CF$UID 31 CF$UID 12 CF$UID 144 CF$UID 144 CF$UID 86 CF$UID 240 CF$UID 220 CF$UID 53 CF$UID 144 CF$UID 86 CF$UID 53 CF$UID 42 CF$UID 31 CF$UID 48 CF$UID 53 CF$UID 217 CF$UID 53 CF$UID 86 CF$UID 107 CF$UID 31 CF$UID 217 CF$UID 81 CF$UID 31 CF$UID 53 CF$UID 217 CF$UID 2 CF$UID 86 CF$UID 53 CF$UID 144 CF$UID 86 CF$UID 53 CF$UID 53 CF$UID 217 CF$UID 107 CF$UID 86 CF$UID 86 CF$UID 197 CF$UID 86 CF$UID 144 CF$UID 248 CF$UID 183 CF$UID 31 CF$UID 86 CF$UID 217 CF$UID 53 CF$UID 31 CF$UID 53 $class CF$UID 253 NS.objects CF$UID 177 CF$UID 36 CF$UID 41 CF$UID 31 CF$UID 12 CF$UID 191 CF$UID 232 CF$UID 180 CF$UID 30 CF$UID 80 CF$UID 96 CF$UID 81 CF$UID 217 CF$UID 194 CF$UID 202 CF$UID 2 CF$UID 52 CF$UID 116 CF$UID 171 CF$UID 244 CF$UID 225 CF$UID 203 CF$UID 206 CF$UID 42 CF$UID 75 CF$UID 248 CF$UID 209 $class CF$UID 253 NS.objects CF$UID 257 CF$UID 258 CF$UID 259 CF$UID 259 CF$UID 260 CF$UID 262 CF$UID 259 CF$UID 259 CF$UID 263 CF$UID 264 CF$UID 265 CF$UID 266 CF$UID 267 CF$UID 259 CF$UID 268 CF$UID 269 CF$UID 270 CF$UID 271 CF$UID 172 CF$UID 265 CF$UID 272 CF$UID 273 CF$UID 274 CF$UID 275 CF$UID 276 CF$UID 277 CF$UID 278 $class CF$UID 4 NS.string 2 $class CF$UID 4 NS.string 5 $class CF$UID 4 NS.string $class CF$UID 261 $classes NSNull NSObject $classname NSNull $class CF$UID 4 NS.string 9 $class CF$UID 4 NS.string 6 NSMenuItem2 NSMenuItem NSMenu $class CF$UID 4 NS.string MainMenu $class CF$UID 4 NS.string 7 $class CF$UID 4 NS.string File's Owner 1111 NSMenuItem1 121 $class CF$UID 4 NS.string 3 $class CF$UID 4 NS.string 8 $class CF$UID 4 NS.string 2 $class CF$UID 4 NS.string 1 $class CF$UID 4 NS.string 1 $class CF$UID 4 NS.string 10 $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects CF$UID 177 CF$UID 36 CF$UID 183 CF$UID 31 CF$UID 90 CF$UID 191 CF$UID 180 CF$UID 213 CF$UID 107 CF$UID 96 CF$UID 139 CF$UID 111 CF$UID 84 CF$UID 244 CF$UID 203 CF$UID 11 CF$UID 157 CF$UID 238 CF$UID 53 CF$UID 71 CF$UID 79 CF$UID 161 CF$UID 126 CF$UID 170 CF$UID 47 CF$UID 152 CF$UID 229 CF$UID 106 CF$UID 228 CF$UID 166 CF$UID 30 CF$UID 246 CF$UID 80 CF$UID 125 CF$UID 202 CF$UID 58 CF$UID 220 CF$UID 171 CF$UID 153 CF$UID 52 CF$UID 61 CF$UID 134 CF$UID 234 CF$UID 240 CF$UID 149 CF$UID 66 CF$UID 75 CF$UID 62 CF$UID 209 CF$UID 148 CF$UID 12 CF$UID 29 CF$UID 24 CF$UID 95 CF$UID 81 CF$UID 217 CF$UID 194 CF$UID 35 CF$UID 25 CF$UID 135 CF$UID 116 CF$UID 142 CF$UID 86 CF$UID 105 CF$UID 206 CF$UID 115 CF$UID 100 CF$UID 143 CF$UID 144 CF$UID 74 CF$UID 57 CF$UID 165 CF$UID 230 CF$UID 70 CF$UID 197 CF$UID 41 CF$UID 160 CF$UID 120 CF$UID 232 CF$UID 138 CF$UID 130 CF$UID 110 CF$UID 10 CF$UID 2 CF$UID 225 CF$UID 101 CF$UID 239 CF$UID 46 CF$UID 67 CF$UID 131 CF$UID 51 CF$UID 42 CF$UID 48 CF$UID 91 CF$UID 85 CF$UID 186 CF$UID 156 CF$UID 40 CF$UID 121 CF$UID 248 CF$UID 224 $class CF$UID 253 NS.objects CF$UID 283 CF$UID 284 CF$UID 285 CF$UID 286 CF$UID 287 CF$UID 288 CF$UID 289 CF$UID 290 CF$UID 291 CF$UID 292 CF$UID 293 CF$UID 294 CF$UID 295 CF$UID 296 CF$UID 297 CF$UID 298 CF$UID 299 CF$UID 300 CF$UID 301 CF$UID 302 CF$UID 303 CF$UID 304 CF$UID 305 CF$UID 306 CF$UID 307 CF$UID 308 CF$UID 309 CF$UID 310 CF$UID 311 CF$UID 312 CF$UID 313 CF$UID 314 CF$UID 315 CF$UID 316 CF$UID 317 CF$UID 318 CF$UID 319 CF$UID 320 CF$UID 321 CF$UID 322 CF$UID 323 CF$UID 324 CF$UID 325 CF$UID 326 CF$UID 327 CF$UID 328 CF$UID 329 CF$UID 330 CF$UID 331 CF$UID 332 CF$UID 333 CF$UID 334 CF$UID 335 CF$UID 336 CF$UID 337 CF$UID 338 CF$UID 339 CF$UID 340 CF$UID 341 CF$UID 342 CF$UID 343 CF$UID 344 CF$UID 345 CF$UID 346 CF$UID 347 CF$UID 348 CF$UID 349 CF$UID 350 CF$UID 351 CF$UID 352 CF$UID 353 CF$UID 354 CF$UID 355 CF$UID 356 CF$UID 357 CF$UID 358 CF$UID 359 CF$UID 360 CF$UID 361 CF$UID 362 CF$UID 363 CF$UID 364 CF$UID 365 CF$UID 366 CF$UID 367 CF$UID 368 CF$UID 369 CF$UID 370 CF$UID 371 CF$UID 372 CF$UID 373 CF$UID 374 CF$UID 375 CF$UID 376 CF$UID 377 CF$UID 378 CF$UID 379 CF$UID 380 CF$UID 381 CF$UID 382 CF$UID 383 74 77 131 81 224 82 92 211 212 201 239 199 223 216 75 23 221 206 57 150 222 210 198 249 126 243 144 195 143 246 78 19 219 232 79 58 56 248 213 136 146 235 217 218 208 152 73 145 112 242 24 86 39 225 200 29 72 87 5 202 204 241 205 227 80 230 226 209 220 193 142 247 149 153 124 111 245 231 83 240 233 228 37 1 129 203 214 127 134 196 139 106 125 197 207 130 244 122 215 103 236 $class CF$UID 175 NS.objects $class CF$UID 253 NS.objects $class CF$UID 253 NS.objects $classes NSIBObjectData NSObject $classname NSIBObjectData $top IB.objectdata CF$UID 1 $version 100000 ================================================ FILE: Example/zh_TW.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Example/zh_TW.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\n您打算怎麼做?"; /* ConnectionOpenPanel */ "A Connection Error Occurred" = "發生連線錯誤"; /* FTP Abort */ "Action Aborted. Local Error" = "放棄動作。本機發生錯誤"; /* FTP no data stream types available */ "All data connection modes have been exhausted. Check with the server administrator." = "已嘗試所有資料連線模式。請與伺服器管理員確認。"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An intermediate directory does not exist and needs to be created before the current directory" = "中間目錄並不存在,您必須在目前目錄前製作該目錄。"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An unknown error occurred" = "發生未知的錯誤"; /* multiple connection joiner */ "and" = "和"; /* close window */ "Are you sure you want to stop the upload?" = "您是否確定要停止上傳?"; /* authorise */ "Authorize" = "認證"; /* authorise */ "Authorize Connection?" = "認證連線?"; /* connection type */ "auto" = "自動"; /* config */ "Bad Configuration" = "設定不正確"; /* error */ "Bad Droplet" = "Droplet 不正確"; /* Transfer Controller */ "Bad Password." = "密碼不佳。"; /* context menu tree controller action gear */ "Browse Packages" = "瀏覽套件"; /* filesize: bytes */ "bytes" = "bytes"; /* authorise */ "Cancel" = "取消"; /* FTP upload error */ "Cannot Upload File. Storage quota on server exceeded" = "無法上傳檔案。已超過伺服器的儲存空間"; /* connected message transfer controller */ "Connected to %@" = "已連接至 %@"; /* file transcript */ "Connected to File System\n" = "已連接至檔案系統\n"; /* connection string */ "Connecting to %@" = "正在連接至 %@"; /* file transcript */ "Connecting…\n" = "連接中⋯\n"; /* close window */ "Continue Upload" = "繼續上傳"; /* file transcript */ "Copying %@ to %@\n" = "正在拷貝 %1$@ 到 %2$@\n"; /* FileConnection set permissions error */ "Could not change file permissions" = "無法更改檔案權限"; /* FileConnection create directory error */ "Could not create directory" = "無法製作目錄"; /* config */ "Couldn't find configuration file specified\n %@" = "找不到指定的設定檔\n %@"; /* file transcript */ "Create Directory %@ (%lo)\n" = "建立目錄 %1$@(%2$lo)\n"; /* FTP Create directory error */ "Create directory operation failed" = "製作目錄作業失敗"; /* Failed to open a data stream connection */ "Data Stream Timed Out" = "資料流逾時"; /* outline view column header name outline view column context menu item */ "Date Modified" = "修改日期"; /* file transcript */ "Deleting File %@\n" = "正在刪除檔案 %@\n"; /* status */ "Disconnected" = "已中斷連線"; /* transfer controller */ "Disconnected from %@" = "已從 %@ 中斷連線"; /* unknown UTI name */ "Document" = "文件"; /* filesize: exabytes */ "EB" = "EB"; /* Directory Parsing Error */ "Error parsing directory listing" = "解析目錄列表時發生錯誤"; /* FTP no data streams available */ "Exhausted all connection types to server. Please contact server administrator" = "已嘗試與此伺服器的所有連線類型。請與伺服器管理員聯繫。"; /* Bad ftp command */ "Failed to change to directory" = "無法更改目錄"; /* WebDAV Directory Deletion Error couldn't delete the file MobileMe Directory Deletion Error */ "Failed to delete directory" = "無法刪除目錄"; /* error for deleting a directory */ "Failed to delete directory: %@" = "無法刪除目錄:%@"; /* WebDAV File Deletion Error couldn't delete the file MobileMe file deletion error */ "Failed to delete file" = "無法刪除檔案"; /* error for deleting a file */ "Failed to delete file: %@" = "無法刪除檔案:%@"; /* No MobileMe account or password */ "Failed to retrieve MobileMe account details" = "無法擷取 MobileMe 帳號的詳細資料"; /* FTP Upload error */ "Failed to set permissions for path %@" = "無法設定路徑 %@ 的權限"; /* FileConnection copy data error */ "Failed to upload data" = "無法載入資料"; /* error transferring */ "Failed to verify file transferred successfully" = "無法順利驗證傳輸的檔案"; /* FTP file download error */ "File %@ does not exist on server" = "伺服器上並沒有檔案 %@"; /* FTP error */ "File / Directory does not exist" = "檔案 / 目錄不存在"; /* FileConnection error */ "File Already Exists" = "檔案已存在"; /* FTP file in use */ "File in Use" = "檔案正在使用中"; /* FTP Upload error */ "Filename not Allowed" = "不允許此檔名"; /* directory kind */ "Folder" = "檔案夾"; /* FTP service timed out */ "FTP service not available; Remote server has closed connection" = "FTP 服務無法取得;遠端伺服器已關閉連線"; /* FTP no service */ "FTP Service Unavailable" = "無法取得 FTP 服務"; /* filesize: gigabytes */ "GB" = "GB"; /* transfer controller */ "Hide Files" = "隱藏檔案"; /* Couldn't open the port to the host */ "Host Unavailable" = "無法取得主機"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "Insufficient storage space available" = "可用的儲存空間不足"; /* FTP Error */ "Invalid Account name" = "帳號名稱無效"; /* Stream Error before opening */ "Is the service running on the server" = "是在此伺服器上執行的服務"; /* filesize: kilobytes */ "KB" = "KB"; /* outline view column header name outline view column context menu item */ "Kind" = "種類"; /* FTP download error */ "Local File already exists" = "本機檔案已存在"; /* filesize: megabytes */ "MB" = "MB"; /* outline view column header name outline view column context menu item */ "Name" = "名稱"; /* new cat name */ "New Category" = "新增類別"; /* tree controller action gear */ "New Folder" = "新增檔案夾"; /* config */ "No configuration file specified" = "未指定設定檔"; /* failed to find a connection class */ "No connection available for requested connection type" = "請求的連線類型沒有可用的連線"; /* failed to find a connection class */ "No connection available for requested port" = "請求的傳輸埠沒有可用的連線"; /* failed to find a connection class */ "No connection available for requested protocol" = "請求的傳輸協定沒有可用的連線"; /* FTP Error */ "No Storage Space Available" = "沒有可用的儲存空間"; /* FTP Error */ "Not Logged In" = "尚未登入"; /* OK */ "OK" = "好"; /* WebDAV Uploading Error MobileMe File Uploading Error */ "Parent Folder does not exist" = "主體檔案夾不存在"; /* No comment provided by engineer. */ "Password was not accepted." = "不接受密碼。"; /* filesize: petabytes */ "PB" = "PB"; /* mimic Finder naming conventions */ "Plain text document" = "純文字文件"; /* config error */ "Quit" = "結束"; /* tree controller action gear */ "Refresh" = "重新整理"; /* file transcript */ "Renaming %@ to %@\n" = "正在將 %1$@ 重新命名為 %2$@\n"; /* FTP Error */ "Request Aborted. Page Type Unknown" = "放棄請求。頁面類型未知"; /* abbreviation for seconds, e.g. 12 MB/s */ "s" = "s"; /* category name */ "Saved Hosts" = "儲存的主機"; /* transfer controller */ "Show Files" = "顯示檔案"; /* context menu tree controller action gear */ "Show Hidden Files" = "顯示隱藏檔案"; /* context menu tree controller action gear */ "Show Package Extensions" = "顯示套件擴充內容"; /* outline view column header name outline view column context menu item */ "Size" = "大小"; /* close window */ "Stop Upload" = "停止上傳"; /* close window */ "Stop Upload?" = "停止上傳?"; /* Error creating stream */ "Stream Unavailable" = "資料流無法取得"; /* filesize: terabytes */ "TB" = "TB"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The body of the request is not supported" = "不支援此請求的內容"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The directory already exists" = "目錄已存在"; /* name of a host to connect to; in this case, the local file system rather than a remote server */ "the File System" = "檔案系統"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The server does not allow the creation of directories at the current location" = "此伺服器不允許在目前的位置製作目錄"; /* error transferring */ "The transferred file has a size of 0." = "傳輸的檔案大小為 0。"; /* MobileMe Directory Contents Error */ "There is no MobileMe access to the directory" = "此目錄無法使用 MobileMe 存取"; /* No WebDAV access to the specified path */ "There is no WebDAV access to the directory" = "此目錄無法使用 WebDAV 存取"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "此 droplet 遺失原始的製作應用程式。您需要重新安裝其原始的應用程式。"; /* time out */ "Timed Out waiting for remote host." = "等待遠端主機逾時。"; /* Transfer Controller */ "Too many files had transfer problems" = "太多檔案發生傳輸問題"; /* FileConnection failed to copy file */ "Unable to store data in file" = "無法儲存檔案資料"; /* WebDAV Error */ "Unknown Error Occurred" = "發生未知的錯誤"; /* status message */ "Uploading" = "上傳"; /* status */ "Uploading %@" = "正在上傳 %@"; /* No username or password */ "Username and Password are required for FTP connections" = "若要進行 FTP 連線,使用者名稱與密碼為必要項目"; /* No username or password */ "Username and Password are required for S3 connections" = "S3 連線需要使用者名稱與密碼"; /* No username or password */ "Username and Password are required for WebDAV connections" = "若要進行 WebDAV 連線,使用者名稱與密碼為必要項目"; /* file transcript */ "Writing data to %@\n" = "正在將資料寫入 %@\n"; /* Bonjour Error */ "You can not add a child collection to the Bonjour category." = "您無法將子頁面集加到 Bonjour 類別中。"; /* Bonjour Error */ "You can not add a new server to the Bonjour category." = "您無法將新伺服器加到 Bonjour 類別中。"; /* FTP file upload error */ "You do not have access to write file %@" = "您沒有存取權限可寫入檔案 %@"; /* FTP Error */ "You need an Account to Upload Files" = "您需要“上傳檔案”的帳號"; ================================================ FILE: Framework Debug.xcconfig ================================================ COPY_PHASE_STRIP = NO DYLIB_COMPATIBILITY_VERSION = 1 DYLIB_CURRENT_VERSION = 1 FRAMEWORK_VERSION = A GCC_DYNAMIC_NO_PIC = NO GCC_ENABLE_FIX_AND_CONTINUE = YES GCC_ENABLE_OBJC_EXCEPTIONS = YES GCC_GENERATE_DEBUGGING_SYMBOLS = YES GCC_MODEL_TUNING = G5 GCC_OPTIMIZATION_LEVEL = 0 GCC_PRECOMPILE_PREFIX_HEADER = YES GCC_PREFIX_HEADER = $(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h OTHER_LDFLAGS = -framework Foundation -framework AppKit PREBINDING = NO PRODUCT_NAME = iMediaBrowser SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk ZERO_LINK = YES ================================================ FILE: Framework Release.xcconfig ================================================ ARCHS = ppc i386 COPY_PHASE_STRIP = YES DYLIB_COMPATIBILITY_VERSION = 1 DYLIB_CURRENT_VERSION = 1 FRAMEWORK_VERSION = A GCC_ENABLE_FIX_AND_CONTINUE = NO GCC_ENABLE_OBJC_EXCEPTIONS = YES GCC_GENERATE_DEBUGGING_SYMBOLS = NO GCC_MODEL_TUNING = G5 GCC_PRECOMPILE_PREFIX_HEADER = YES GCC_PREFIX_HEADER = $(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h OTHER_LDFLAGS = -framework Foundation -framework AppKit PREBINDING = NO SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk ZERO_LINK = NO ================================================ FILE: ProfilingTester/AppDelegate.h ================================================ // // AppDelegate.h // ProfilingTester // // Created by Sam Deane on 27/03/2013. // // #import @interface AppDelegate : NSObject @property (assign) IBOutlet NSWindow *window; @end ================================================ FILE: ProfilingTester/AppDelegate.m ================================================ // // AppDelegate.m // ProfilingTester // // Created by Sam Deane on 27/03/2013. // // #import "AppDelegate.h" #import @implementation AppDelegate - (void)dealloc { [super dealloc]; } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Insert code here to initialize your application CK2FileManager* fm = [[CK2FileManager alloc] init]; for (NSUInteger n = 0; n < 100; ++n) { NSURL* url = [NSURL URLWithString:@"ftp://test:test@ftp.secureftp-test.com/"]; [fm contentsOfDirectoryAtURL:url includingPropertiesForKeys:@[] options:NSDirectoryEnumerationSkipsHiddenFiles completionHandler:^(NSArray *contents, NSError *error) { NSLog(@"contents: %@ error: %@", contents, error); }]; } [fm release]; } @end ================================================ FILE: ProfilingTester/ProfilingTester-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: ProfilingTester/ProfilingTester-Prefix.pch ================================================ // // Prefix header for all source files of the 'ProfilingTester' target in the 'ProfilingTester' project // #ifdef __OBJC__ #import #endif ================================================ FILE: ProfilingTester/en.lproj/Credits.rtf ================================================ {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} {\colortbl;\red255\green255\blue255;} \paperw9840\paperh8400 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural \f0\b\fs24 \cf0 Engineering: \b0 \ Some people\ \ \b Human Interface Design: \b0 \ Some other people\ \ \b Testing: \b0 \ Hopefully not nobody\ \ \b Documentation: \b0 \ Whoever\ \ \b With special thanks to: \b0 \ Mom\ } ================================================ FILE: ProfilingTester/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: ProfilingTester/en.lproj/MainMenu.xib ================================================ 1080 11D50 2457 1138.32 568.00 com.apple.InterfaceBuilder.CocoaPlugin 2457 NSWindowTemplate NSView NSMenu NSMenuItem NSCustomObject com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion NSApplication FirstResponder NSApplication AMainMenu ProfilingTester 1048576 2147483647 NSImage NSMenuCheckmark NSImage NSMenuMixedState submenuAction: ProfilingTester About ProfilingTester 2147483647 YES YES 1048576 2147483647 Preferences… , 1048576 2147483647 YES YES 1048576 2147483647 Services 1048576 2147483647 submenuAction: Services _NSServicesMenu YES YES 1048576 2147483647 Hide ProfilingTester h 1048576 2147483647 Hide Others h 1572864 2147483647 Show All 1048576 2147483647 YES YES 1048576 2147483647 Quit ProfilingTester q 1048576 2147483647 _NSAppleMenu File 1048576 2147483647 submenuAction: File New n 1048576 2147483647 Open… o 1048576 2147483647 Open Recent 1048576 2147483647 submenuAction: Open Recent Clear Menu 1048576 2147483647 _NSRecentDocumentsMenu YES YES 1048576 2147483647 Close w 1048576 2147483647 Save… s 1048576 2147483647 Revert to Saved 2147483647 YES YES 1048576 2147483647 Page Setup... P 1179648 2147483647 Print… p 1048576 2147483647 Edit 1048576 2147483647 submenuAction: Edit Undo z 1048576 2147483647 Redo Z 1179648 2147483647 YES YES 1048576 2147483647 Cut x 1048576 2147483647 Copy c 1048576 2147483647 Paste v 1048576 2147483647 Paste and Match Style V 1572864 2147483647 Delete 1048576 2147483647 Select All a 1048576 2147483647 YES YES 1048576 2147483647 Find 1048576 2147483647 submenuAction: Find Find… f 1048576 2147483647 1 Find and Replace… f 1572864 2147483647 12 Find Next g 1048576 2147483647 2 Find Previous G 1179648 2147483647 3 Use Selection for Find e 1048576 2147483647 7 Jump to Selection j 1048576 2147483647 Spelling and Grammar 1048576 2147483647 submenuAction: Spelling and Grammar Show Spelling and Grammar : 1048576 2147483647 Check Document Now ; 1048576 2147483647 YES YES 2147483647 Check Spelling While Typing 1048576 2147483647 Check Grammar With Spelling 1048576 2147483647 Correct Spelling Automatically 2147483647 Substitutions 1048576 2147483647 submenuAction: Substitutions Show Substitutions 2147483647 YES YES 2147483647 Smart Copy/Paste f 1048576 2147483647 1 Smart Quotes g 1048576 2147483647 2 Smart Dashes 2147483647 Smart Links G 1179648 2147483647 3 Text Replacement 2147483647 Transformations 2147483647 submenuAction: Transformations Make Upper Case 2147483647 Make Lower Case 2147483647 Capitalize 2147483647 Speech 1048576 2147483647 submenuAction: Speech Start Speaking 1048576 2147483647 Stop Speaking 1048576 2147483647 Format 2147483647 submenuAction: Format Font 2147483647 submenuAction: Font Show Fonts t 1048576 2147483647 Bold b 1048576 2147483647 2 Italic i 1048576 2147483647 1 Underline u 1048576 2147483647 YES YES 2147483647 Bigger + 1048576 2147483647 3 Smaller - 1048576 2147483647 4 YES YES 2147483647 Kern 2147483647 submenuAction: Kern Use Default 2147483647 Use None 2147483647 Tighten 2147483647 Loosen 2147483647 Ligatures 2147483647 submenuAction: Ligatures Use Default 2147483647 Use None 2147483647 Use All 2147483647 Baseline 2147483647 submenuAction: Baseline Use Default 2147483647 Superscript 2147483647 Subscript 2147483647 Raise 2147483647 Lower 2147483647 YES YES 2147483647 Show Colors C 1048576 2147483647 YES YES 2147483647 Copy Style c 1572864 2147483647 Paste Style v 1572864 2147483647 _NSFontMenu Text 2147483647 submenuAction: Text Align Left { 1048576 2147483647 Center | 1048576 2147483647 Justify 2147483647 Align Right } 1048576 2147483647 YES YES 2147483647 Writing Direction 2147483647 submenuAction: Writing Direction YES Paragraph 2147483647 CURlZmF1bHQ 2147483647 CUxlZnQgdG8gUmlnaHQ 2147483647 CVJpZ2h0IHRvIExlZnQ 2147483647 YES YES 2147483647 YES Selection 2147483647 CURlZmF1bHQ 2147483647 CUxlZnQgdG8gUmlnaHQ 2147483647 CVJpZ2h0IHRvIExlZnQ 2147483647 YES YES 2147483647 Show Ruler 2147483647 Copy Ruler c 1310720 2147483647 Paste Ruler v 1310720 2147483647 View 1048576 2147483647 submenuAction: View Show Toolbar t 1572864 2147483647 Customize Toolbar… 1048576 2147483647 Window 1048576 2147483647 submenuAction: Window Minimize m 1048576 2147483647 Zoom 1048576 2147483647 YES YES 1048576 2147483647 Bring All to Front 1048576 2147483647 _NSWindowsMenu Help 2147483647 submenuAction: Help ProfilingTester Help ? 1048576 2147483647 _NSHelpMenu _NSMainMenu 15 2 {{335, 390}, {480, 360}} 1954021376 ProfilingTester NSWindow 256 {480, 360} {{0, 0}, {2560, 1418}} {10000000000000, 10000000000000} YES AppDelegate NSFontManager terminate: 449 orderFrontStandardAboutPanel: 142 delegate 495 performMiniaturize: 37 arrangeInFront: 39 print: 86 runPageLayout: 87 clearRecentDocuments: 127 performClose: 193 toggleContinuousSpellChecking: 222 undo: 223 copy: 224 checkSpelling: 225 paste: 226 stopSpeaking: 227 cut: 228 showGuessPanel: 230 redo: 231 selectAll: 232 startSpeaking: 233 delete: 235 performZoom: 240 performFindPanelAction: 241 centerSelectionInVisibleArea: 245 toggleGrammarChecking: 347 toggleSmartInsertDelete: 355 toggleAutomaticQuoteSubstitution: 356 toggleAutomaticLinkDetection: 357 saveDocument: 362 revertDocumentToSaved: 364 runToolbarCustomizationPalette: 365 toggleToolbarShown: 366 hide: 367 hideOtherApplications: 368 unhideAllApplications: 370 newDocument: 373 openDocument: 374 raiseBaseline: 426 lowerBaseline: 427 copyFont: 428 subscript: 429 superscript: 430 tightenKerning: 431 underline: 432 orderFrontColorPanel: 433 useAllLigatures: 434 loosenKerning: 435 pasteFont: 436 unscript: 437 useStandardKerning: 438 useStandardLigatures: 439 turnOffLigatures: 440 turnOffKerning: 441 toggleAutomaticSpellingCorrection: 456 orderFrontSubstitutionsPanel: 458 toggleAutomaticDashSubstitution: 461 toggleAutomaticTextReplacement: 463 uppercaseWord: 464 capitalizeWord: 467 lowercaseWord: 468 pasteAsPlainText: 486 performFindPanelAction: 487 performFindPanelAction: 488 performFindPanelAction: 489 showHelp: 493 alignCenter: 518 pasteRuler: 519 toggleRuler: 520 alignRight: 521 copyRuler: 522 alignJustified: 523 alignLeft: 524 makeBaseWritingDirectionNatural: 525 makeBaseWritingDirectionLeftToRight: 526 makeBaseWritingDirectionRightToLeft: 527 makeTextWritingDirectionNatural: 528 makeTextWritingDirectionLeftToRight: 529 makeTextWritingDirectionRightToLeft: 530 performFindPanelAction: 535 addFontTrait: 421 addFontTrait: 422 modifyFont: 423 orderFrontFontPanel: 424 modifyFont: 425 window 532 0 -2 File's Owner -1 First Responder -3 Application 29 19 56 217 83 81 75 78 72 82 124 77 73 79 112 74 125 126 205 202 198 207 214 199 203 197 206 215 218 216 200 219 201 204 220 213 210 221 208 209 57 58 134 150 136 144 129 143 236 131 149 145 130 24 92 5 239 23 295 296 297 298 211 212 195 196 346 348 349 350 351 354 371 372 375 376 377 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 450 451 452 453 454 457 459 460 462 465 466 485 490 491 492 494 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 534 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{380, 496}, {480, 360}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin 535 ABCardController NSObject id id id id id id id addCardViewField: id copy: id cut: id doDelete: id find: id paste: id saveChanges: id ABCardView NSButton NSManagedObjectContext NSSearchField NSTextField NSWindow mCardView ABCardView mEditButton NSButton mManagedObjectContext NSManagedObjectContext mSearchField NSSearchField mStatusTextField NSTextField mWindow NSWindow IBProjectSource ./Classes/ABCardController.h ABCardView NSView id id commitAndSave: id statusImageClicked: id NSObjectController NSImageView NSView ABNameFrameView NSView NSImage ABImageView mBindingsController NSObjectController mBuddyStatusImage NSImageView mHeaderView NSView mNameView ABNameFrameView mNextKeyView NSView mUserImage NSImage mUserImageView ABImageView IBProjectSource ./Classes/ABCardView.h ABImageView NSImageView id id id id copy: id cut: id delete: id paste: id IBProjectSource ./Classes/ABImageView.h DVTBorderedView DVTLayoutView_ML contentView NSView contentView contentView NSView IBProjectSource ./Classes/DVTBorderedView.h DVTDelayedMenuButton NSButton IBProjectSource ./Classes/DVTDelayedMenuButton.h DVTGradientImageButton NSButton IBProjectSource ./Classes/DVTGradientImageButton.h DVTImageAndTextCell NSTextFieldCell IBProjectSource ./Classes/DVTImageAndTextCell.h DVTImageAndTextColumn NSTableColumn IBProjectSource ./Classes/DVTImageAndTextColumn.h DVTLayoutView_ML NSView IBProjectSource ./Classes/DVTLayoutView_ML.h DVTOutlineView NSOutlineView IBProjectSource ./Classes/DVTOutlineView.h DVTSplitView NSSplitView IBProjectSource ./Classes/DVTSplitView.h DVTStackView_ML DVTLayoutView_ML IBProjectSource ./Classes/DVTStackView_ML.h DVTTableView NSTableView IBProjectSource ./Classes/DVTTableView.h DVTViewController NSViewController IBProjectSource ./Classes/DVTViewController.h HFController NSObject selectAll: id selectAll: selectAll: id IBProjectSource ./Classes/HFController.h HFRepresenterTextView NSView selectAll: id selectAll: selectAll: id IBProjectSource ./Classes/HFRepresenterTextView.h IBEditor NSObject id id id id id changeFont: id performCopy: id performCut: id selectAll: id sizeSelectionToFit: id IBProjectSource ./Classes/IBEditor.h IDECapsuleListView DVTStackView_ML dataSource id dataSource dataSource id IBProjectSource ./Classes/IDECapsuleListView.h IDEDMArrayController NSArrayController IBProjectSource ./Classes/IDEDMArrayController.h IDEDMEditor IDEEditor DVTBorderedView NSView IDEDMEditorSourceListController DVTSplitView bottomToolbarBorderView DVTBorderedView sourceListSplitViewPane NSView sourceListViewController IDEDMEditorSourceListController splitView DVTSplitView IBProjectSource ./Classes/IDEDMEditor.h IDEDMEditorController IDEViewController IBProjectSource ./Classes/IDEDMEditorController.h IDEDMEditorSourceListController IDEDMEditorController DVTBorderedView IDEDMEditor DVTImageAndTextColumn DVTOutlineView NSTreeController borderedView DVTBorderedView parentEditor IDEDMEditor primaryColumn DVTImageAndTextColumn sourceListOutlineView DVTOutlineView sourceListTreeController NSTreeController IBProjectSource ./Classes/IDEDMEditorSourceListController.h IDEDMHighlightImageAndTextCell DVTImageAndTextCell IBProjectSource ./Classes/IDEDMHighlightImageAndTextCell.h IDEDataModelBrowserEditor IDEDMEditorController IDEDataModelPropertiesTableController IDECapsuleListView NSArrayController IDEDataModelPropertiesTableController IDEDataModelEntityContentsEditor IDEDataModelPropertiesTableController attributesTableViewController IDEDataModelPropertiesTableController capsuleView IDECapsuleListView entityArrayController NSArrayController fetchedPropertiesTableViewController IDEDataModelPropertiesTableController parentEditor IDEDataModelEntityContentsEditor relationshipsTableViewController IDEDataModelPropertiesTableController IBProjectSource ./Classes/IDEDataModelBrowserEditor.h IDEDataModelConfigurationEditor IDEDMEditorController IDECapsuleListView IDEDataModelEditor IDEDataModelConfigurationTableController capsuleListView IDECapsuleListView parentEditor IDEDataModelEditor tableController IDEDataModelConfigurationTableController IBProjectSource ./Classes/IDEDataModelConfigurationEditor.h IDEDataModelConfigurationTableController IDEDMEditorController NSArrayController NSArrayController IDEDataModelConfigurationEditor XDTableView configurationsArrayController NSArrayController entitiesArrayController NSArrayController parentEditor IDEDataModelConfigurationEditor tableView XDTableView IBProjectSource ./Classes/IDEDataModelConfigurationTableController.h IDEDataModelDiagramEditor IDEDMEditorController XDDiagramView IDEDataModelEntityContentsEditor diagramView XDDiagramView parentEditor IDEDataModelEntityContentsEditor IBProjectSource ./Classes/IDEDataModelDiagramEditor.h IDEDataModelEditor IDEDMEditor DVTDelayedMenuButton DVTDelayedMenuButton NSSegmentedControl IDEDataModelConfigurationEditor IDEDataModelEntityContentsEditor IDEDataModelFetchRequestEditor NSSegmentedControl NSTabView addEntityButton DVTDelayedMenuButton addPropertyButton DVTDelayedMenuButton browserDiagramSegmentControl NSSegmentedControl configurationViewController IDEDataModelConfigurationEditor entityContentsViewController IDEDataModelEntityContentsEditor fetchRequestViewController IDEDataModelFetchRequestEditor hierarchySegmentControl NSSegmentedControl tabView NSTabView IBProjectSource ./Classes/IDEDataModelEditor.h IDEDataModelEntityContentsEditor IDEDMEditorController IDEDataModelBrowserEditor IDEDataModelDiagramEditor IDEDataModelEditor NSTabView browserViewController IDEDataModelBrowserEditor diagramViewController IDEDataModelDiagramEditor parentEditor IDEDataModelEditor tabView NSTabView IBProjectSource ./Classes/IDEDataModelEntityContentsEditor.h IDEDataModelFetchRequestEditor IDEDMEditorController NSArrayController IDEDataModelEditor IDECapsuleListView entityController NSArrayController parentEditor IDEDataModelEditor tableView IDECapsuleListView IBProjectSource ./Classes/IDEDataModelFetchRequestEditor.h IDEDataModelPropertiesTableController IDEDMEditorController IDEDMArrayController NSTableColumn NSArrayController IDEDataModelBrowserEditor IDEDMHighlightImageAndTextCell XDTableView arrayController IDEDMArrayController entitiesColumn NSTableColumn entityArrayController NSArrayController parentEditor IDEDataModelBrowserEditor propertyNameAndImageCell IDEDMHighlightImageAndTextCell tableView XDTableView IBProjectSource ./Classes/IDEDataModelPropertiesTableController.h IDEDocDownloadsTableViewController NSObject NSButtonCell DVTTableView IDEDocViewingPrefPaneController _downloadButtonCell NSButtonCell _tableView DVTTableView prefPaneController IDEDocViewingPrefPaneController IBProjectSource ./Classes/IDEDocDownloadsTableViewController.h IDEDocSetOutlineView NSOutlineView IBProjectSource ./Classes/IDEDocSetOutlineView.h IDEDocSetOutlineViewController NSObject id id id id id getDocSetAction: id showProblemInfoForUpdate: id subscribeToPublisherAction: id unsubscribeFromPublisher: id updateDocSetAction: id docSetOutlineView IDEDocSetOutlineView docSetOutlineView docSetOutlineView IDEDocSetOutlineView IBProjectSource ./Classes/IDEDocSetOutlineViewController.h IDEDocViewingPrefPaneController IDEViewController id id id id id id id id id id id addSubscription: id checkForAndInstallUpdatesNow: id deleteDocSet: id downloadAction: id minimumFontSizeComboBoxAction: id minimumFontSizeEnabledAction: id showHelp: id showSubscriptionSheet: id subscriptionCancelAction: id toggleAutoCheckForAndInstallUpdates: id toggleDocSetInfo: id DVTGradientImageButton DVTGradientImageButton DVTGradientImageButton NSSplitView NSView NSView DVTBorderedView DVTBorderedView NSButton NSTextView IDEDocSetOutlineViewController IDEDocDownloadsTableViewController NSComboBox NSTextField NSButton NSTextField NSWindow NSButton _addButton DVTGradientImageButton _deleteButton DVTGradientImageButton _showInfoAreaButton DVTGradientImageButton _splitView NSSplitView _splitViewDocSetInfoSubview NSView _splitViewDocSetsListSubview NSView borderedViewAroundSplitView DVTBorderedView borderedViewBelowTable DVTBorderedView checkAndInstallNowButton NSButton docSetInfoTextView NSTextView docSetOutlineViewController IDEDocSetOutlineViewController downloadsTableViewController IDEDocDownloadsTableViewController minimumFontSizeControl NSComboBox noUpdatesAvailableMessage NSTextField showInfoButton NSButton subscriptionTextField NSTextField subscriptionWindow NSWindow validateAddSubscriptionButton NSButton IBProjectSource ./Classes/IDEDocViewingPrefPaneController.h IDEEditor IDEViewController IBProjectSource ./Classes/IDEEditor.h IDEViewController DVTViewController IBProjectSource ./Classes/IDEViewController.h IKImageView id id id id copy: id crop: id cut: id paste: id IBProjectSource ./Classes/IKImageView.h NSDocument id id id id id id printDocument: id revertDocumentToSaved: id runPageLayout: id saveDocument: id saveDocumentAs: id saveDocumentTo: id IBProjectSource ./Classes/NSDocument.h NSResponder _insertFindPattern: id _insertFindPattern: _insertFindPattern: id IBProjectSource ./Classes/NSResponder.h QLPreviewBubble NSObject id id hide: id show: id parentWindow NSWindow parentWindow parentWindow NSWindow IBProjectSource ./Classes/QLPreviewBubble.h QTMovieView id id id id id showAll: id showCustomButton: id toggleLoops: id zoomIn: id zoomOut: id IBProjectSource ./Classes/QTMovieView.h WebView id id id id reloadFromOrigin: id resetPageZoom: id zoomPageIn: id zoomPageOut: id IBProjectSource ./Classes/WebView.h XDDiagramView NSView id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id id _graphLayouterMenuItemAction: id _zoomPopUpButtonAction: id alignBottomEdges: id alignCentersHorizontallyInContainer: id alignCentersVerticallyInContainer: id alignHorizontalCenters: id alignLeftEdges: id alignRightEdges: id alignTopEdges: id alignVerticalCenters: id bringToFront: id collapseAllCompartments: id copy: id cut: id delete: id deleteBackward: id deleteForward: id deselectAll: id diagramZoomIn: id diagramZoomOut: id expandAllCompartments: id flipHorizontally: id flipVertically: id layoutGraphicsConcentrically: id layoutGraphicsHierarchically: id lock: id makeSameHeight: id makeSameWidth: id moveDown: id moveDownAndModifySelection: id moveLeft: id moveLeftAndModifySelection: id moveRight: id moveRightAndModifySelection: id moveUp: id moveUpAndModifySelection: id paste: id rollDownAllCompartments: id rollUpAllCompartments: id selectAll: id sendToBack: id sizeToFit: id toggleGridShown: id toggleHiddenGraphicsShown: id togglePageBreaksShown: id toggleRuler: id toggleSnapsToGrid: id unlock: id _diagramController IDEDataModelDiagramEditor _diagramController _diagramController IDEDataModelDiagramEditor IBProjectSource ./Classes/XDDiagramView.h XDTableView NSTableView showAllTableColumns: id showAllTableColumns: showAllTableColumns: id IBProjectSource ./Classes/XDTableView.h AppDelegate NSObject id id applicationShouldTerminate: id applicationWillFinishLaunching: id IBProjectSource ./Classes/AppDelegate.h 0 IBCocoaFramework YES 3 {11, 11} {10, 3} YES ================================================ FILE: ProfilingTester/main.m ================================================ // // main.m // ProfilingTester // // Created by Sam Deane on 27/03/2013. // // #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **)argv); } ================================================ FILE: README.md ================================================ Development =========== ConnectionKit 2 is still under heavy development, but the front-end API is probably stable. Things someone could do if they're feeling nice: * Cancellation support for the File protocol * Improve handling of invalid certificates for FTPS * Amazon S3 protocol * API for downloading/reading files Features ======== ConnectionKit provides a Cocoa-friendly, block-based API for asynchronously working with: * FTP, SFTP and WebDAV servers * Local files ## What's new since the original ConnectionKit? A high-level summary: * Use of blocks for simple completion and error-handling * URLs replace paths throughout the API * Management of raw connections is hidden behind the scenes, handling multiple connections and re-connections for you * Same authentication workflow as `NSURLSession` and friends * No longer tied to the main thread * libcurl is used for FTP, instead of custom implementation * `NSURLConnection` (via DAVKit) is used for WebDAV, instead of custom HTTP stack * libssh2 (via libcurl) is used for SFTP, instead of calling out to the command-line `sftp` program Contributors and Contact Info ======= I'm Mike Abdullah, of [Karelia Software](http://karelia.com). [@mikeabdullah](http://twitter.com/mikeabdullah) on Twitter. Questions about the code are best left as issues at https://github.com/karelia/ConnectionKit but you can also message me on Twitter (just don't expect more than a terse reply!). Big thanks to: * Paul Kim of [Noodlesoft](http://www.noodlesoft.com) for: * `CK2OpenPanel` * Logic for guessing icon etc. of remote files * [Fabian Jäger](https://github.com/fjaeger) for discovering and helping fix various bugs * [Sam Deane](http://twitter.com/samdeane) of [Elegant Chaos](http://www.elegantchaos.com) for: * Sooo much testing, especially Mock Server * The WebDAV protocol implementation * Improving the File protocol implementation * And all contributors to the submodules of course! Dependencies ============ Requires OS X v10.6+ Relies upon CURLHandle and DAVKit. They are provided as submodules and may have their own dependencies in turn. Out of the box, provided you initialise all submodules, `CURLHandle.framework` should be able to nicely build, self-containing all its dependencies. ConnectionKit supports both 64 and 32bit Macs. We hope to expand to iOS before too long too. Note that support for the legacy Objective-C runtime (32bit Mac) currently precludes switching the codebase to ARC. License ======= ## CURLHandle Please see https://github.com/karelia/CurlHandle for details of CURLHandle and its subcomponents' licensing. ## DAVKit Please see https://github.com/karelia/DAVKit for details of DAVKit and its subcomponents' licensing. ## Legacy Existing ConnectionKit code should declare its licensing at the top of the file, most likely BSD or MIT. ## ConnectionKit 2 code Licensed under the BSD License THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Usage ===== ## Getting the code If you're already using Git, you likely want to add ConnectionKit as a submodule of your project: git submodule add https://github.com/karelia/ConnectionKit.git If you're using another version control system, you can grab the code directly: git clone https://github.com/karelia/ConnectionKit.git If you're not using version control at all, remind me to come yell at you at a mutually convenient time. ConnectionKit includes several (nested) submodules of its own, so have Git grab them too: cd ConnectionKit git submodule update --recursive --init Substitute the URL above for your own if you've created a fork of ConnectionKit. Git should automatically checkout the recommended branch for you (`v2.x-beta` at present). Then: 1. Add `Connection.xcodeproj` to your project 2. Add the ConnectionKit framework as a dependency of your project's build target 3. Set `Connection.framework` to be copied into a suitable location inside your build target; e.g the `Frameworks` directory ## Actually, y'know, doing stuff Interacting with ConnectionKit is usually entirely through `CK2FileManager`. It's quite a lot like `NSFileManager`, but asynchronous, and with a few more bells and whistles to handle the complexities of remote servers. Also there's no shared instance; you must create your own. So to get a directory listing from an FTP server for example: - (void)listDirectoryAtPath:(NSString *)path { NSURL *ftpServer = [NSURL URLWithString:@"ftp://example.com/"]; NSURL *directory = [CK2FileManager URLWithPath:path relativeToURL:ftpServer]; CK2FileManager *fileManager = [[CK2FileManager alloc] init]; fileManager.delegate = self; [fileManager contentsOfDirectoryAtURL:directory includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles completionHandler:^(NSArray *contents, NSError *error) { // Display contents in your UI, or present the error if failed }]; } Note how `CK2FileManager` is used to construct URLs. This is to handle the difference in URL formats different protocols require. Delegate methods are used to handle authentication (more on that below) and transcripts. Be sure to read through `CK2FileManager.h` as there's plenty of helpful documentation in there. ## Authentication ConnectionKit follows the same approach as `NSURLSession`: During an operation, it vends out as many authentication challenges as it sees fit. Your delegate should implement this method to reply to the challenges: - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential*))completionHandler; Replies are made by calling `completionHandler` with your preferred disposition, and, if needed, a credential to use. This is asynchronous, giving you a chance to present some UI asking the user what they'd like to do if necessary. Supplying a credential set to `NSURLCredentialPersistencePermanent` will cause ConnectionKit to add it to the keychain if successful. Authentication challenges carry a great deal of information, including `.previousFailureCount` and `.protectionSpace` which are very useful for determining how to treat an individual challenge. ### WebDAV over HTTP WebDAV servers can selectively choose whether to require authentication (e.g. public servers have no need to). If authentication is requested, you'll receive an authentication challenge encapsulating the auth method to be used (e.g. HTTP Digest). Respond with a username and password credential. ConnectionKit will do its best to supply `-proposedCredential` from the user's keychain. ### FTP FTP is very similar to plain WebDAV, except it always asks for authentication. Usually, you respond with a username and password, but can pass a `nil` credential for anonymous FTP login. ### WebDAV and FTP with TLS/SSL The authenticity of the server is checked by examining its certificate during connection. This takes the form of potentially multiple challenges with the either of the following authentication methods: * `NSURLAuthenticationMethodServerTrust` * `NSURLAuthenticationMethodClientCertificate` Generally it's best to use `CK2AuthChallengePerformDefaultHandling` to let Cocoa decide what to do. ### SFTP SFTP is a tricky blighter. You can opt to supply a username and password like other protocols. Our implementation also supports public key authentication, whereby you reply with a credential constructed using: +[NSURLCredential ck2_credentialWithUser:publicKeyURL:privateKeyURL:password:persistence:] The public key is generally optional, as ConnectionKit can derive it from the private key. It's also possible to use SSH-Agent, but Apple discourage this, and it is unavailable to sandboxed apps. Detailed documentation on the above method can be found in `CK2Authentication.h`. Once connected to the server, ConnectionKit checks its fingerprint against the `~/.ssh/known_hosts` file. Note that for sandboxed apps this is inside of your container! An authentication challenge (`CK2AuthenticationMethodHostFingerprint`) is issued with the result of this. Your delegate can use `CK2AuthChallengeCancelAuthenticationChallenge` to reject the fingerprint, or reply with a credential for acceptance, constructed using: +[NSURLCredential ck2_credentialForKnownHostWithPersistence:] The default behaviour (`CK2AuthChallengePerformDefaultHandling`) accepts new fingerprints, adding them to the `known_hosts` file, and causes the operation to fail with an error for mismatched fingerprints. After checking the host fingerprint, SFTP moves on to actually authenticating the client. ## Resource Properties/Attributes ConnectionKit's API is a little asymmetric for handling resource properties: When creating a file or directory, *opening* attributes may be specified. Generally only `NSFilePosixPermissions` is respected. This **only** applies to protocols where permissions can be specified at creation time (i.e. SFTP). But even then there are some servers in my experience that sometimes ignore this value anyway. So: To set the properties of an existing item, use `-[CK2FileManager setAttributes:ofItemAtURL:completionHandler:]`. Again this only applies to certain protocols/servers; see `CK2FileManager.h` for up-to-date information on this. Many protocols do not have an efficient mechanism for retrieving the attributes of an individual item. Instead, you should get a listing of the *parent* directory, and pull out the properties of whichever resources you're interested in. ## Open Panel ConnectionKit also offers a companion framework for OS X: `ConnectionKitUI`. If you build this framework into your app as well as ConnectionKit itself, it exposes `CK2OpenPanel`, an `NSOpenPanel` workalike for browsing and selecting files on remote servers. Legacy ====== For anyone relying on one of the old branches, they have been archived to be tags: * master => v1.x * release-1.2 => v1.2.x * BrianWorkInProgress => brian-work-in-progress * CKFTPResponse => ckftpresponse * release-2.0 => experiment-2.0 ================================================ FILE: Resources/Framework-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType FMWK CFBundleSignature ???? CFBundleVersion 1.0 ================================================ FILE: Resources/da.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Resources/da.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nHvad vil du gøre?"; /* ConnectionOpenPanel */ "A Connection Error Occurred" = "Der opstod en forbindelsesfejl"; /* FTP Abort */ "Action Aborted. Local Error" = "Handling afbrudt. Lokal fejl"; /* FTP no data stream types available */ "All data connection modes have been exhausted. Check with the server administrator." = "Alle dataforbindelsesmuligheder er udtømte. Spørg serveradministratoren."; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An intermediate directory does not exist and needs to be created before the current directory" = "Et midlertidigt bibliotek findes ikke og skal oprettes før det aktuelle bibliotek"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An unknown error occurred" = "Der opstod en ukendt fejl"; /* multiple connection joiner */ "and" = "og"; /* close window */ "Are you sure you want to stop the upload?" = "Er du sikker på du vil stoppe din upload?"; /* authorise */ "Authorize" = "Godkend"; /* authorise */ "Authorize Connection?" = "Godkend forbindelse?"; /* connection type */ "auto" = "auto"; /* config */ "Bad Configuration" = "Fejlkonfiguration"; /* error */ "Bad Droplet" = "Ugyldig Droplet"; /* context menu tree controller action gear */ "Browse Packages" = "Gennemse pakker"; /* filesize: bytes */ "bytes" = "bytes"; /* authorise */ "Cancel" = "Annuller"; /* FTP upload error */ "Cannot Upload File. Storage quota on server exceeded" = "Kan ikke uploade arkiv. Opbevaringskvota på serveren er overskredet"; /* connected message transfer controller */ "Connected to %@" = "Forbundet til %@"; /* file transcript */ "Connected to File System" = "Forbundet til denne Mac"; /* connection string */ "Connecting to %@" = "Forbinder til %@"; /* file transcript */ "Connecting…" = "Forbinder…"; /* close window */ "Continue Upload" = "Fortsæt upload"; /* file transcript */ "Copying %@ to %@" = "Kopierer %1$@ til %2$@"; /* FileConnection set permissions error */ "Could not change file permissions" = "Kunne ikke ændre arkivtilladelser"; /* FileConnection create directory error */ "Could not create directory" = "Kunne ikke oprette mappe"; /* config */ "Couldn't find configuration file specified\n %@" = "Kunne ikke finde det angivne konfigurationsarkiv\n %@"; /* file transcript */ "Create Directory %@ (%lo)" = "Opret mappe %1$@ (%2$lo)"; /* FTP Create directory error Create directory operation failed */ "Create directory operation failed" = "Opret mappe-handling mislykkedes"; /* Failed to open a data stream connection */ "Data Stream Timed Out" = "Datastrøm udløb"; /* outline view column header name outline view column context menu item */ "Date Modified" = "Ændringsdato"; /* file transcript */ "Deleting File %@" = "Sletter arkiv %@"; /* status */ "Disconnected" = "Ikke forbundet"; /* transfer controller */ "Disconnected from %@" = "Lukker forbindelse til %@"; /* unknown UTI name */ "Document" = "Dokument"; /* filesize: exabytes */ "EB" = "EB"; /* Directory Parsing Error */ "Error parsing directory listing" = "Fejl ved undersøgelse af mappeindhold"; /* FTP no data streams available */ "Exhausted all connection types to server. Please contact server administrator" = "Har udtømt alle forbindelsestyper til serveren. Kontakt venligst serveradministratoren"; /* Failed to change to directory Bad FTP command */ "Failed to change to directory" = "Kunne ikke skifte mappe"; /* WebDAV Directory Deletion Error couldn't delete the file MobileMe Directory Deletion Error */ "Failed to delete directory" = "Kunne ikke slette mappe"; /* error for deleting a directory */ "Failed to delete directory: %@" = "Kunne ikke slette mappen: %@"; /* WebDAV File Deletion Error couldn't delete the file MobileMe file deletion error */ "Failed to delete file" = "Kunne ikke slette arkiv"; /* error for deleting a file */ "Failed to delete file: %@" = "Kunne ikke slette arkiv: %@"; /* Failed to download file. */ "Failed to download file." = "Kunne ikke downloade arkiv."; /* Failed to rename file. */ "Failed to rename file." = "Kunne ikke omdøbe arkiv."; /* FTP Upload error SFTP Upload error */ "Failed to set permissions for path %@" = "Kunne ikke indstille tilladelser for stien %@"; /* FileConnection copy data error */ "Failed to upload data" = "Kunne ikke oploade data"; /* Failed to upload file. */ "Failed to upload file." = "Kunne ikke uploade arkiv."; /* error transferring */ "Failed to verify file transferred successfully" = "Kunne ikke bekræfte at arkivoverførsel lykkedes"; /* FTP file download error */ "File %@ does not exist on server" = "Arkiver %@ findes ikke på serveren"; /* FileConnection error */ "File Already Exists" = "Arkivet findes allerede"; /* FTP file in use */ "File in Use" = "Arkivet er i brug"; /* directory kind */ "Folder" = "Mappe"; /* FTP service timed out */ "FTP service not available; Remote server has closed connection" = "FTP-service ikke tilgængelig. Serveren har lukket forbindelsen"; /* FTP no service */ "FTP Service Unavailable" = "FTP-service ikke tilgængelig"; /* filesize: gigabytes */ "GB" = "GB"; /* transfer controller */ "Hide Files" = "Skjul arkiver"; /* Couldn't open the port to the host Host Unavailable */ "Host Unavailable" = "Vært ikke tilgængelig"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "Insufficient storage space available" = "Ikke nok plads tilgængelig"; /* FTP Error */ "Invalid Account name" = "Ugyldigt kontonavn"; /* Stream Error before opening */ "Is the service running on the server" = "Kører denne service på serveren"; /* filesize: kilobytes */ "KB" = "KB"; /* outline view column header name outline view column context menu item */ "Kind" = "Type"; /* File doesn't exist */ "Local file %@ doesn't exist" = "Lokalt arkiv %@ findes ikke"; /* FTP download error */ "Local File already exists" = "Lokalt arkiv findes allerede"; /* filesize: megabytes */ "MB" = "MB"; /* outline view column header name outline view column context menu item */ "Name" = "Navn"; /* tree controller action gear */ "New Folder" = "Ny mappe"; /* config */ "No configuration file specified" = "Intet konfigurationsarkiv angivet"; /* failed to find a connection class */ "No connection available for requested connection type" = "Ingen forbindelse tilgængelig for den ønskede forbindelsestype"; /* FTP Error */ "No Storage Space Available" = "Ingen ledig plads tilgængelig"; /* No such file */ "No such file" = "Arkivet findes ikke"; /* FTP Error */ "Not Logged In" = "Ikke logget ind"; /* OK */ "OK" = "OK"; /* WebDAV Uploading Error MobileMe File Uploading Error */ "Parent Folder does not exist" = "Overliggende mappe findes ikke"; /* filesize: petabytes */ "PB" = "PB"; /* Permission Denied */ "Permission Denied" = "Tilladelse nægtet"; /* mimic Finder naming conventions */ "Plain text document" = "Normalt tekstdokument"; /* config error */ "Quit" = "Slut"; /* tree controller action gear */ "Refresh" = "Opdater"; /* file transcript */ "Renaming %@ to %@" = "Omdøber %1$@ til %2$@"; /* abbreviation for seconds, e.g. 12 MB/s */ "s" = "s"; /* SFTP authenticaton error */ "SFTP connections require some form of authentication." = "SFTP-forbindelser kræve en form for godkendelse."; /* transfer controller */ "Show Files" = "Vis arkiver"; /* context menu tree controller action gear */ "Show Hidden Files" = "Vis skjulte arkiver"; /* context menu tree controller action gear */ "Show Package Extensions" = "Vis pakke-endelser"; /* outline view column header name outline view column context menu item */ "Size" = "Str."; /* close window */ "Stop Upload" = "Stop upload"; /* close window */ "Stop Upload?" = "Stop upload?"; /* Error creating stream */ "Stream Unavailable" = "Stream ikke tilgængelig"; /* filesize: terabytes */ "TB" = "TB"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The body of the request is not supported" = "Indholdet af anmodningen understøttes ikke"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The directory already exists" = "Mappen findes allerede"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The server does not allow the creation of directories at the current location" = "Serveren tillader ikke oprettelse af mapper på dette sted"; /* error transferring */ "The transferred file has a size of 0." = "Det overførte arkivs størrelse er 0."; /* MobileMe Directory Contents Error */ "There is no MobileMe access to the directory" = "Der er ikke MobileMe-adgang til mappen"; /* No WebDAV access to the specified path */ "There is no WebDAV access to the directory" = "Der er ikke WebDAV-adgang til mappen"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "Denne droplet mangler det originale program der oprettede den. Du er nød til at geninstallere det originale program."; /* time out */ "Timed Out waiting for remote host." = "Time out under venten på vært."; /* Transfer Controller */ "Too many files had transfer problems" = "For mange arkiver havde overførselsproblemer"; /* FileConnection failed to copy file */ "Unable to store data in file" = "Kunne ikke opbevare data i arkivet"; /* WebDAV Error */ "Unknown Error Occurred" = "Der opstod en ukendt fejl"; /* status message */ "Uploading" = "Uploader"; /* status */ "Uploading %@" = "Uploader %@"; /* file transcript */ "Writing data to %@" = "Skriver data til %@"; /* Bonjour Error */ "You can not add a child collection to the Bonjour category." = "Du kan ikke tilføje en underliggende samling til Bonjour-kategorien."; /* Bonjour Error */ "You can not add a new server to the Bonjour category." = "Du kan ikke tilføje en ny server til Bonjour-kategorien."; /* FTP file upload error */ "You do not have access to write file %@" = "Du har ikke tilladelse til at skrive arkivet %@"; /* FTP Error */ "You need an Account to Upload Files" = "Du skal have en konto for et kunne uploade arkiver"; ================================================ FILE: Resources/de.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Resources/de.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nWas möchten Sie tun?"; /* ConnectionOpenPanel */ "An unknown error occurred." = "Ein unbekannter Fehler ist aufgetreten. "; /* close window */ "Are you sure you want to stop the upload?" = "Wollen Sie die Übertragung wirklich stoppen?"; /* authorise */ "Authorize" = "Autorisieren"; /* authorise */ "Authorize Connection?" = "Verbindung autorisieren?"; /* config */ "Bad Configuration" = "Falsche Konfiguration"; /* error */ "Bad Droplet" = "Falsches Droplet"; /* filesize: bytes */ "bytes" = "Byte"; /* authorise */ "Cancel" = "Abbrechen"; /* connected message */ "Connected to %@" = "Verbunden mit %@"; /* connection string */ "Connecting to %@" = "Mit %@ verbinden"; /* close window */ "Continue Upload" = "Übertragung fortsetzen"; /* config */ "Couldn't find configuration file specified\n %@" = "Konnte angegebene Konfigurationsdatei nicht finden\n %@"; /* status */ "Disconnected" = "Getrennt"; /* filesize: exabytes */ "EB" = "EB"; /* filesize: gigabytes */ "GB" = "GB"; /* filesize: kilobytes */ "KB" = "KB"; /* FTP download error */ "Local File already exists" = "Lokale Datei existiert bereits"; /* filesize: megabytes */ "MB" = "MB"; /* config */ "No configuration file specified" = "Keine Konfigurationsdatei angegeben"; /* failed to find a connection class */ "No connection available for requested connection type" = "Keine verfügbare Verbindung für gewählte Verbindungsart"; /* filesize: petabytes */ "PB" = "PB"; /* ConnectionOpenPanel */ "Please check your settings." = "Bitte prüfen Sie Ihre Einstellungen."; /* config error */ "Quit" = "Beenden"; /* close window */ "Stop Upload" = "Übertragung stoppen"; /* close window */ "Stop Upload?" = "Übertragung stoppen?"; /* filesize: terabytes */ "TB" = "TB"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "Diesem Droplet fehlt das Originalprogramm mit welchem es erstellt wurden. Bitte reinstallieren Sie das Originalprogramm."; /* status */ "Uploading %@" = "Hochladen von %@"; ================================================ FILE: Resources/en.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nWhat would you like to do?"; /* ConnectionOpenPanel */ "An unknown error occurred." = "An unknown error occurred."; /* close window */ "Are you sure you want to stop the upload?" = "Are you sure you want to stop the upload?"; /* authorise */ "Authorize" = "Authorize"; /* authorise */ "Authorize Connection?" = "Authorize Connection?"; /* config */ "Bad Configuration" = "Bad Configuration"; /* error */ "Bad Droplet" = "Bad Droplet"; /* filesize: bytes */ "bytes" = "bytes"; /* authorise */ "Cancel" = "Cancel"; /* connected message */ "Connected to %@" = "Connected to %@"; /* connection string */ "Connecting to %@" = "Connecting to %@"; /* close window */ "Continue Upload" = "Continue Upload"; /* config */ "Couldn't find configuration file specified\n %@" = "Couldn't find configuration file specified\n %@"; /* status */ "Disconnected" = "Disconnected"; /* filesize: exabytes */ "EB" = "EB"; /* filesize: gigabytes */ "GB" = "GB"; /* filesize: kilobytes */ "KB" = "KB"; /* FTP download error */ "Local File already exists" = "Local File already exists"; /* filesize: megabytes */ "MB" = "MB"; /* config */ "No configuration file specified" = "No configuration file specified"; /* failed to find a connection class */ "No connection available for requested connection type" = "No connection available for requested connection type"; /* filesize: petabytes */ "PB" = "PB"; /* ConnectionOpenPanel */ "Please check your settings." = "Please check your settings."; /* config error */ "Quit" = "Quit"; /* close window */ "Stop Upload" = "Stop Upload"; /* close window */ "Stop Upload?" = "Stop Upload?"; /* filesize: terabytes */ "TB" = "TB"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "This droplet is missing the original application that created it. You will need to reinstall the original application."; /* status */ "Uploading %@" = "Uploading %@"; ================================================ FILE: Resources/es.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\n¿Qué desea hacer?"; /* ConnectionOpenPanel */ "An unknown error occurred." = "Se ha producido un error desconocido."; /* close window */ "Are you sure you want to stop the upload?" = "¿Está seguro que desea detener la carga del archivo en el servidor?"; /* authorise */ "Authorize" = "Autorizar"; /* authorise */ "Authorize Connection?" = "¿Autorizar conexión?"; /* config */ "Bad Configuration" = "Error de configuración"; /* error */ "Bad Droplet" = "Gotero dañado"; /* filesize: bytes */ "bytes" = "bytes"; /* authorise */ "Cancel" = "Cancelar"; /* connected message */ "Connected to %@" = "Conectado a %@"; /* connection string */ "Connecting to %@" = "Conectado a %@"; /* close window */ "Continue Upload" = "Continuar la carga del archivo."; /* config */ "Couldn't find configuration file specified\n %@" = "No se pudo encontrar el archivo de configuración especificado\n %@"; /* status */ "Disconnected" = "Desconectado"; /* filesize: exabytes */ "EB" = "EB"; /* filesize: gigabytes */ "GB" = "GB"; /* filesize: kilobytes */ "KB" = "KB"; /* FTP download error */ "Local File already exists" = "El archivo local ya existe"; /* filesize: megabytes */ "MB" = "MB"; /* config */ "No configuration file specified" = "No se especificó un archivo de configuración"; /* failed to find a connection class */ "No connection available for requested connection type" = "No hay una conexión disponible para el tipo requerido"; /* filesize: petabytes */ "PB" = "PB"; /* ConnectionOpenPanel */ "Please check your settings." = "Por favor, revise su configuración."; /* config error */ "Quit" = "Salir"; /* close window */ "Stop Upload" = "Detener la carga del archivo"; /* close window */ "Stop Upload?" = "¿Detener la carga?"; /* filesize: terabytes */ "TB" = "TB"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "Este gotero no encuentra la aplicación original con el que fue creado; necesitará reinstalarla."; /* status */ "Uploading %@" = "Cargando %@"; ================================================ FILE: Resources/fr.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Resources/fr.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nQue désirez-vous faire ?"; /* ConnectionOpenPanel */ "An unknown error occurred." = "Une erreur inconnue s'est produite."; /* close window */ "Are you sure you want to stop the upload?" = "Êtes-vous sûr de vouloir arrêter le chargement ?"; /* authorise */ "Authorize" = "Autoriser"; /* authorise */ "Authorize Connection?" = "Autoriser la connexion ?"; /* config */ "Bad Configuration" = "Mauvaise configuration"; /* error */ "Bad Droplet" = "Mauvaise 'droplet'"; /* filesize: bytes */ "bytes" = "octets"; /* authorise */ "Cancel" = "Annuler"; /* connected message */ "Connected to %@" = "Connecté à %@"; /* connection string */ "Connecting to %@" = "Se connecte à %@"; /* close window */ "Continue Upload" = "Continuer le chargement"; /* config */ "Couldn't find configuration file specified\n %@" = "N'a pu trouver le fichier de configuration indiqué\n %@"; /* status */ "Disconnected" = "Déconnecté"; /* filesize: exabytes */ "EB" = "Eo"; /* filesize: gigabytes */ "GB" = "Go"; /* filesize: kilobytes */ "KB" = "ko"; /* FTP download error */ "Local File already exists" = "Le fichier local existe déjà"; /* filesize: megabytes */ "MB" = "Mo"; /* config */ "No configuration file specified" = "Aucun fichier de configuration indiqué"; /* failed to find a connection class */ "No connection available for requested connection type" = "Aucune connexion disponible pour le type de connexion demandé"; /* filesize: petabytes */ "PB" = "Po"; /* ConnectionOpenPanel */ "Please check your settings." = "Veuillez vérifiez vos réglages."; /* config error */ "Quit" = "Quitter"; /* close window */ "Stop Upload" = "Arrêter le chargement"; /* close window */ "Stop Upload?" = "Arrêter le chargement ?"; /* filesize: terabytes */ "TB" = "To"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "Il manque à cette droplet l'application originale qui l'a créée. Vous devrez réinstaller l'application originale."; /* status */ "Uploading %@" = "Chargement de %@"; ================================================ FILE: Resources/it.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Resources/it.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nChe cosa ti piacerebbe fare?"; /* ConnectionOpenPanel */ "An unknown error occurred." = "Si è verificato un errore sconosciuto."; /* close window */ "Are you sure you want to stop the upload?" = "Sei sicuro di voler interrompere il caricamento?"; /* authorise */ "Authorize" = "Autorizza"; /* authorise */ "Authorize Connection?" = "Autorizza connessione?"; /* config */ "Bad Configuration" = "Configurazione errata"; /* error */ "Bad Droplet" = "Droplet errato"; /* filesize: bytes */ "bytes" = "byte"; /* authorise */ "Cancel" = "Annulla"; /* connected message */ "Connected to %@" = "Connesso a %@"; /* connection string */ "Connecting to %@" = "Connessione a %@"; /* close window */ "Continue Upload" = "Continua caricamento"; /* config */ "Couldn't find configuration file specified\n %@" = "Impossibile trovare il file di configurazione specificato\n %@"; /* status */ "Disconnected" = "Disconnesso"; /* filesize: exabytes */ "EB" = "EB"; /* filesize: gigabytes */ "GB" = "GB"; /* filesize: kilobytes */ "KB" = "KB"; /* FTP download error */ "Local File already exists" = "Il file locale esiste già"; /* filesize: megabytes */ "MB" = "MB"; /* config */ "No configuration file specified" = "Non è stato specificato nessun file di configurazione"; /* failed to find a connection class */ "No connection available for requested connection type" = "Nessuna connessione è disponibile per il tipo di connessione richiesto"; /* filesize: petabytes */ "PB" = "PB"; /* ConnectionOpenPanel */ "Please check your settings." = "Verifica le tue impostazioni."; /* config error */ "Quit" = "Esci"; /* close window */ "Stop Upload" = "Interrompi caricamento"; /* close window */ "Stop Upload?" = "Interrompere caricamento?"; /* filesize: terabytes */ "TB" = "TB"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "Il droplet ha perduto la connessione con l'applicazione che l'ha creato. Dovrai reinstallare l'applicazione originale."; /* status */ "Uploading %@" = "Sto caricando %@"; ================================================ FILE: Resources/ja.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Resources/ja.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nどうしますか?"; /* ConnectionOpenPanel */ "An unknown error occurred." = "不明なエラーが発生しました。"; /* close window */ "Are you sure you want to stop the upload?" = "アップロードを中断してもよろしいですか?"; /* authorise */ "Authorize" = "認証"; /* authorise */ "Authorize Connection?" = "接続を承認しますか?"; /* config */ "Bad Configuration" = "設定が悪い"; /* error */ "Bad Droplet" = "ドロップレットが悪い"; /* filesize: bytes */ "bytes" = "バイト"; /* authorise */ "Cancel" = "キャンセル"; /* connected message */ "Connected to %@" = "%@ に接続"; /* connection string */ "Connecting to %@" = "%@ に接続中"; /* close window */ "Continue Upload" = "アップロードを継続"; /* config */ "Couldn't find configuration file specified\n %@" = "指定した設定ファイルが見つかりませんでした\n %@"; /* status */ "Disconnected" = "切断"; /* filesize: exabytes */ "EB" = "EB"; /* filesize: gigabytes */ "GB" = "GB"; /* filesize: kilobytes */ "KB" = "KB"; /* FTP download error */ "Local File already exists" = "このローカルファイルはすでに存在します"; /* filesize: megabytes */ "MB" = "MB"; /* config */ "No configuration file specified" = "設定ファイルが指定されていません"; /* failed to find a connection class */ "No connection available for requested connection type" = "この接続タイプは有効ではありません"; /* filesize: petabytes */ "PB" = "PB"; /* ConnectionOpenPanel */ "Please check your settings." = "設定を確認して下さい。"; /* config error */ "Quit" = "終了"; /* close window */ "Stop Upload" = "アップロードを停止"; /* close window */ "Stop Upload?" = "アップロードを停止しますか?"; /* filesize: terabytes */ "TB" = "TB"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "ドロップレットを作成したときのアプリケーションがありません。そのアプリケーションを再インストールする必要があります。"; /* status */ "Uploading %@" = "%@ をアップロード中"; ================================================ FILE: Resources/pt_BR.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\nO que você gostaria de fazer?"; /* ConnectionOpenPanel */ "An unknown error occurred." = "Ocorreu um erro desconhecido."; /* close window */ "Are you sure you want to stop the upload?" = "Você deseja realmente suspender o envio?"; /* authorise */ "Authorize" = "Autorizar"; /* authorise */ "Authorize Connection?" = "Autorizar Conexão?"; /* config */ "Bad Configuration" = "Configuração Errada"; /* error */ "Bad Droplet" = "Droplet Danificado"; /* filesize: bytes */ "bytes" = "bytes"; /* authorise */ "Cancel" = "Cancelar"; /* connected message */ "Connected to %@" = "Conectado a %@"; /* connection string */ "Connecting to %@" = "Conectando a %@"; /* close window */ "Continue Upload" = "Continuar Enviando"; /* config */ "Couldn't find configuration file specified\n %@" = "Não foi possível encontrar a configuração do arquivo especificado\n %@"; /* status */ "Disconnected" = "Desconectado"; /* filesize: exabytes */ "EB" = "EB"; /* filesize: gigabytes */ "GB" = "GB"; /* filesize: kilobytes */ "KB" = "KB"; /* FTP download error */ "Local File already exists" = "O Arquivo Local já existe"; /* filesize: megabytes */ "MB" = "MB"; /* config */ "No configuration file specified" = "Nenhum arquivo de configuração especificado"; /* failed to find a connection class */ "No connection available for requested connection type" = "Nenhuma conexão disponível para o tipo de conexão exigida"; /* filesize: petabytes */ "PB" = "PB"; /* ConnectionOpenPanel */ "Please check your settings." = "Por favor, verifique suas configurações."; /* config error */ "Quit" = "Encerrar"; /* close window */ "Stop Upload" = "Suspender o Envio"; /* close window */ "Stop Upload?" = "Suspender o Envio?"; /* filesize: terabytes */ "TB" = "TB"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "Este droplet está faltando ao aplicativo original no qual foi criado. Você precisa reinstalar o aplicativo original."; /* status */ "Uploading %@" = "Enviando %@"; ================================================ FILE: Resources/zh_CN.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Resources/zh_CN.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\n您想要怎么做?"; /* ConnectionOpenPanel */ "An unknown error occurred." = "发生了未知错误。"; /* close window */ "Are you sure you want to stop the upload?" = "你确定要停止上传吗?"; /* authorise */ "Authorize" = "批准"; /* authorise */ "Authorize Connection?" = "批准连接?"; /* config */ "Bad Configuration" = "不良配置"; /* error */ "Bad Droplet" = "不良 Droplet"; /* filesize: bytes */ "bytes" = "字节"; /* authorise */ "Cancel" = "取消"; /* connected message */ "Connected to %@" = "已连接到 %@"; /* connection string */ "Connecting to %@" = "正在连接到 %@"; /* close window */ "Continue Upload" = "继续上传"; /* config */ "Couldn't find configuration file specified\n %@" = "无法找到指定的配置文件\n %@"; /* status */ "Disconnected" = "已断开"; /* filesize: exabytes */ "EB" = "EB"; /* filesize: gigabytes */ "GB" = "GB"; /* filesize: kilobytes */ "KB" = "KB"; /* FTP download error */ "Local File already exists" = "本机文件已存在"; /* filesize: megabytes */ "MB" = "MB"; /* config */ "No configuration file specified" = "没有指定的配置文件"; /* failed to find a connection class */ "No connection available for requested connection type" = "请求的连接类型没有可用的连接"; /* filesize: petabytes */ "PB" = "PB"; /* ConnectionOpenPanel */ "Please check your settings." = "请检查你的设置。"; /* config error */ "Quit" = "退出"; /* close window */ "Stop Upload" = "停止上传"; /* close window */ "Stop Upload?" = "停止上传?"; /* filesize: terabytes */ "TB" = "TB"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "该 Droplet 丢失了创建它的原应用程序。你需要重新安装该应用程序。"; /* status */ "Uploading %@" = "正在上传 %@"; ================================================ FILE: Resources/zh_TW.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ CFBundleName = "FTPConnection"; NSHumanReadableCopyright = "© __MyCompanyName__, 2004"; ================================================ FILE: Resources/zh_TW.lproj/Localizable.strings ================================================ /* authorise */ "%@\nWhat would you like to do?" = "%@\n您打算怎麼做?"; /* ConnectionOpenPanel */ "A Connection Error Occurred" = "發生連線錯誤"; /* FTP Abort */ "Action Aborted. Local Error" = "放棄動作。本機發生錯誤"; /* FTP no data stream types available */ "All data connection modes have been exhausted. Check with the server administrator." = "已嘗試所有資料連線模式。請與伺服器管理員確認。"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An intermediate directory does not exist and needs to be created before the current directory" = "中間目錄並不存在,您必須在目前目錄前製作該目錄。"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "An unknown error occurred" = "發生未知的錯誤"; /* multiple connection joiner */ "and" = "和"; /* close window */ "Are you sure you want to stop the upload?" = "您是否確定要停止上傳?"; /* authorise */ "Authorize" = "認證"; /* authorise */ "Authorize Connection?" = "認證連線?"; /* connection type */ "auto" = "自動"; /* config */ "Bad Configuration" = "設定不正確"; /* error */ "Bad Droplet" = "Droplet 不正確"; /* context menu tree controller action gear */ "Browse Packages" = "瀏覽套件"; /* filesize: bytes */ "bytes" = "bytes"; /* authorise */ "Cancel" = "取消"; /* FTP upload error */ "Cannot Upload File. Storage quota on server exceeded" = "無法上傳檔案。已超過伺服器的儲存空間"; /* connected message transfer controller */ "Connected to %@" = "已連接至 %@"; /* file transcript */ "Connected to File System" = "已連線至檔案系統"; /* connection string */ "Connecting to %@" = "正在連接至 %@"; /* file transcript */ "Connecting…" = "連線中⋯"; /* close window */ "Continue Upload" = "繼續上傳"; /* file transcript */ "Copying %@ to %@" = "正在拷貝 %1$@ 至 %2$@"; /* FileConnection set permissions error */ "Could not change file permissions" = "無法更改檔案權限"; /* FileConnection create directory error */ "Could not create directory" = "無法製作目錄"; /* config */ "Couldn't find configuration file specified\n %@" = "找不到指定的設定檔\n %@"; /* file transcript */ "Create Directory %@ (%lo)" = "製作目錄 %1$@ (%2$lo)"; /* FTP Create directory error Create directory operation failed */ "Create directory operation failed" = "製作目錄作業失敗"; /* Failed to open a data stream connection */ "Data Stream Timed Out" = "資料流逾時"; /* outline view column header name outline view column context menu item */ "Date Modified" = "修改日期"; /* file transcript */ "Deleting File %@" = "正在刪除檔案 %@"; /* status */ "Disconnected" = "已中斷連線"; /* transfer controller */ "Disconnected from %@" = "已從 %@ 中斷連線"; /* unknown UTI name */ "Document" = "文件"; /* filesize: exabytes */ "EB" = "EB"; /* Directory Parsing Error */ "Error parsing directory listing" = "解析目錄列表時發生錯誤"; /* FTP no data streams available */ "Exhausted all connection types to server. Please contact server administrator" = "已嘗試與此伺服器的所有連線類型。請與伺服器管理員聯繫。"; /* Failed to change to directory Bad FTP command */ "Failed to change to directory" = "無法更改目錄"; /* WebDAV Directory Deletion Error couldn't delete the file MobileMe Directory Deletion Error */ "Failed to delete directory" = "無法刪除目錄"; /* error for deleting a directory */ "Failed to delete directory: %@" = "無法刪除目錄:%@"; /* WebDAV File Deletion Error couldn't delete the file MobileMe file deletion error */ "Failed to delete file" = "無法刪除檔案"; /* error for deleting a file */ "Failed to delete file: %@" = "無法刪除檔案:%@"; /* Failed to download file. */ "Failed to download file." = "無法下載檔案。"; /* Failed to rename file. */ "Failed to rename file." = "無法重新命名檔案。"; /* FTP Upload error SFTP Upload error */ "Failed to set permissions for path %@" = "無法設定路徑 %@ 的權限"; /* FileConnection copy data error */ "Failed to upload data" = "無法載入資料"; /* Failed to upload file. */ "Failed to upload file." = "無法上傳檔案。"; /* error transferring */ "Failed to verify file transferred successfully" = "無法順利驗證傳輸的檔案"; /* FTP file download error */ "File %@ does not exist on server" = "伺服器上並沒有檔案 %@"; /* FileConnection error */ "File Already Exists" = "檔案已存在"; /* FTP file in use */ "File in Use" = "檔案正在使用中"; /* directory kind */ "Folder" = "檔案夾"; /* FTP service timed out */ "FTP service not available; Remote server has closed connection" = "FTP 服務無法取得;遠端伺服器已關閉連線"; /* FTP no service */ "FTP Service Unavailable" = "無法取得 FTP 服務"; /* filesize: gigabytes */ "GB" = "GB"; /* transfer controller */ "Hide Files" = "隱藏檔案"; /* Couldn't open the port to the host Host Unavailable */ "Host Unavailable" = "無法取得主機"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "Insufficient storage space available" = "可用的儲存空間不足"; /* FTP Error */ "Invalid Account name" = "帳號名稱無效"; /* Stream Error before opening */ "Is the service running on the server" = "是在此伺服器上執行的服務"; /* filesize: kilobytes */ "KB" = "KB"; /* outline view column header name outline view column context menu item */ "Kind" = "種類"; /* File doesn't exist */ "Local file %@ doesn't exist" = "本機檔案 %@ 不存在"; /* FTP download error */ "Local File already exists" = "本機檔案已存在"; /* filesize: megabytes */ "MB" = "MB"; /* outline view column header name outline view column context menu item */ "Name" = "名稱"; /* tree controller action gear */ "New Folder" = "新增檔案夾"; /* config */ "No configuration file specified" = "未指定設定檔"; /* failed to find a connection class */ "No connection available for requested connection type" = "請求的連線類型沒有可用的連線"; /* FTP Error */ "No Storage Space Available" = "沒有可用的儲存空間"; /* No such file */ "No such file" = "無此檔案"; /* FTP Error */ "Not Logged In" = "尚未登入"; /* OK */ "OK" = "好的"; /* WebDAV Uploading Error MobileMe File Uploading Error */ "Parent Folder does not exist" = "主體檔案夾不存在"; /* filesize: petabytes */ "PB" = "PB"; /* Permission Denied */ "Permission Denied" = "權限遭拒絕"; /* mimic Finder naming conventions */ "Plain text document" = "純文字文件"; /* config error */ "Quit" = "結束"; /* tree controller action gear */ "Refresh" = "重新整理"; /* file transcript */ "Renaming %@ to %@" = "正在將 %1$@ 重新命名為 %2$@"; /* abbreviation for seconds, e.g. 12 MB/s */ "s" = "s"; /* SFTP authenticaton error */ "SFTP connections require some form of authentication." = "SFTP 連線需要進行某種形式的認證。"; /* transfer controller */ "Show Files" = "顯示檔案"; /* context menu tree controller action gear */ "Show Hidden Files" = "顯示隱藏檔案"; /* context menu tree controller action gear */ "Show Package Extensions" = "顯示套件擴充內容"; /* outline view column header name outline view column context menu item */ "Size" = "大小"; /* close window */ "Stop Upload" = "停止上傳"; /* close window */ "Stop Upload?" = "停止上傳?"; /* Error creating stream */ "Stream Unavailable" = "資料流無法取得"; /* filesize: terabytes */ "TB" = "TB"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The body of the request is not supported" = "不支援此請求的內容"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The directory already exists" = "目錄已存在"; /* MobileMe Create Directory Error WebDAV Create Directory Error */ "The server does not allow the creation of directories at the current location" = "此伺服器不允許在目前的位置製作目錄"; /* error transferring */ "The transferred file has a size of 0." = "傳輸的檔案大小為 0。"; /* MobileMe Directory Contents Error */ "There is no MobileMe access to the directory" = "此目錄無法使用 MobileMe 存取"; /* No WebDAV access to the specified path */ "There is no WebDAV access to the directory" = "此目錄無法使用 WebDAV 存取"; /* error */ "This droplet is missing the original application that created it. You will need to reinstall the original application." = "此 droplet 遺失原始的製作應用程式。您需要重新安裝其原始的應用程式。"; /* time out */ "Timed Out waiting for remote host." = "等待遠端主機逾時。"; /* Transfer Controller */ "Too many files had transfer problems" = "太多檔案發生傳輸問題"; /* FileConnection failed to copy file */ "Unable to store data in file" = "無法儲存檔案資料"; /* WebDAV Error */ "Unknown Error Occurred" = "發生未知的錯誤"; /* status message */ "Uploading" = "上傳"; /* status */ "Uploading %@" = "正在上傳 %@"; /* file transcript */ "Writing data to %@" = "正在寫入資料至 %@"; /* Bonjour Error */ "You can not add a child collection to the Bonjour category." = "您無法將子頁面集加到 Bonjour 類別中。"; /* Bonjour Error */ "You can not add a new server to the Bonjour category." = "您無法將新伺服器加到 Bonjour 類別中。"; /* FTP file upload error */ "You do not have access to write file %@" = "您沒有存取權限可寫入檔案 %@"; /* FTP Error */ "You need an Account to Upload Files" = "您需要“上傳檔案”的帳號"; ================================================ FILE: UKQueue/UKKQueue_Symbols ================================================ _UKFileWatcherRenameNotification _UKFileWatcherLinkCountChangeNotification _UKFileWatcherDeleteNotification _UKFileWatcherAccessRevocationNotification _UKFileWatcherWriteNotification _UKFileWatcherAttributeChangeNotification _UKFileWatcherSizeIncreaseNotification .objc_class_name_UKFNSubscribeFileWatcher _UKFileSubscriptionProc .objc_class_name_UKKQueue .objc_category_name_NSObject_UKMainThreadProxy .objc_class_name_UKMainThreadProxy ================================================ FILE: UnitTests/BaseCKProtocolTests.h ================================================ // // Created by Sam Deane on 06/11/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "BaseCKTests.h" @interface BaseCKProtocolTests : BaseCKTests @end ================================================ FILE: UnitTests/BaseCKProtocolTests.m ================================================ // // Created by Sam Deane on 06/11/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "BaseCKProtocolTests.h" #import "KMSServer.h" #import "KMSTranscriptEntry.h" #import "CK2FileManagerWithTestSupport.h" #import "CK2Authentication.h" #import #import @implementation BaseCKProtocolTests - (BOOL)setupTest { BOOL result; if ([self isMemberOfClass:[BaseCKProtocolTests class]]) { XCTFail(@"Are you trying to run the tests on BaseCKProtocolTests? They should be run by subclasses."); result = NO; } else { result = [super setupTest]; } return result; } - (void)tearDown { if ([self isSetup]) { [self removeTestDirectory]; } [super tearDown]; } - (void)enumerateWithBadURLS:(void (^)(NSURL* url))block { if ([self setupTest]) { NSArray* badURLS = @[@"idontexist-noreally.com/nonexistantfolder", @"127.0.0.1/nonexistantfolder"]; for (NSString* urlPart in badURLS) { NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@", [self.protocol lowercaseString], urlPart]]; block(url); } } } #pragma mark - Tests - (void)testMakeRemoveOnly { if ([self setupTest]) { [self makeTestDirectoryWithFiles:YES]; [self removeTestDirectory]; } } - (void)testContentsOfDirectoryAtURL { if ([self setupTest]) { [self makeTestDirectoryWithFiles:YES]; if (self.useMockServer) { self.server.data = [self mockServerDirectoryListingData]; } NSURL* url = [self URLForTestFolder]; NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsSubdirectoryDescendants; [self.manager contentsOfDirectoryAtURL:url includingPropertiesForKeys:nil options:options completionHandler:^(NSArray *contents, NSError *error) { if (error) { XCTFail(@"got error %@", error); } else { NSUInteger count = [contents count]; XCTAssertTrue(count == 2, @"should have two results, had %ld", count); if (count == 2) { [self checkURL:contents[0] isNamed:[[self URLForTestFile1] lastPathComponent]]; [self checkURL:contents[1] isNamed:[[self URLForTestFile2] lastPathComponent]]; } } [self pause]; }]; [self runUntilPaused]; } } - (void)testContentsOfDirectoryAtURLBadURL { [self enumerateWithBadURLS:^(NSURL *url) { NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsSubdirectoryDescendants; [self.manager contentsOfDirectoryAtURL:url includingPropertiesForKeys:nil options:options completionHandler:^(NSArray *contents, NSError *error) { XCTAssertNotNil(error, @"expected an error"); [self pause]; }]; [self runUntilPaused]; }]; } - (void)testContentsOfDirectoryAtURLBadLogin { if ([self setupTest] && [self protocolUsesAuthentication]) { [self useBadLogin]; NSURL* url = [self URLForTestFolder]; NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsSubdirectoryDescendants; [self.manager contentsOfDirectoryAtURL:url includingPropertiesForKeys:nil options:options completionHandler:^(NSArray *contents, NSError *error) { XCTAssertTrue([self checkIsAuthenticationError:error], @"was expecting authentication error, got %@", error); XCTAssertTrue([contents count] == 0, @"shouldn't get content"); [self pause]; }]; [self runUntilPaused]; } } - (void)testEnumerateContentsOfURL { if ([self setupTest]) { [self makeTestDirectoryWithFiles:YES]; if (self.useMockServer) { self.server.data = [self mockServerDirectoryListingData]; } NSURL* url = [self URLForTestFolder]; NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsSubdirectoryDescendants|CK2DirectoryEnumerationIncludesDirectory; NSMutableArray* expectedURLS = [NSMutableArray arrayWithArray:@[ url, [self URLForTestFile1], [self URLForTestFile2] ]]; [self.manager enumerateContentsOfURL:url includingPropertiesForKeys:nil options:options usingBlock:^(NSURL *item) { NSLog(@"got item %@", item); [self checkURLs:expectedURLS containItemNamed:[item lastPathComponent]]; } completionHandler:^(NSError *error) { if (error) { XCTFail(@"got error %@", error); } [self pause]; }]; [self runUntilPaused]; } } - (void)testEnumerateContentsOfURLBadURL { [self enumerateWithBadURLS:^(NSURL *url) { NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsSubdirectoryDescendants|CK2DirectoryEnumerationIncludesDirectory; [self.manager enumerateContentsOfURL:url includingPropertiesForKeys:nil options:options usingBlock:^(NSURL *item) { } completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error"); [self pause]; }]; [self runUntilPaused]; }]; } - (void)testEnumerateContentsOfURLBadLogin { if ([self setupTest] && [self protocolUsesAuthentication]) { [self useBadLogin]; NSURL* url = [self URLForTestFolder]; NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsSubdirectoryDescendants|CK2DirectoryEnumerationIncludesDirectory; [self.manager enumerateContentsOfURL:url includingPropertiesForKeys:nil options:options usingBlock:^(NSURL *item) { XCTFail(@"shouldn't get any items"); } completionHandler:^(NSError *error) { XCTAssertTrue([self checkIsAuthenticationError:error], @"was expecting authentication error, got %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testCreateDirectoryAtURL { if ([self setupTest]) { [self removeTestDirectory]; NSURL* url = [self URLForTestFolder]; [self.manager createDirectoryAtURL:url withIntermediateDirectories:YES openingAttributes:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testCreateDirectoryAtURLBadURL { [self enumerateWithBadURLS:^(NSURL *url) { [self.manager createDirectoryAtURL:url withIntermediateDirectories:YES openingAttributes:nil completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error"); [self pause]; }]; [self runUntilPaused]; }]; } - (void)testCreateDirectoryAtURLAlreadyExists { if ([self setupTest]) { [self makeTestDirectoryWithFiles:NO]; [self useResponseSet:@"make fails"]; NSURL* url = [self URLForTestFolder]; [self.manager createDirectoryAtURL:url withIntermediateDirectories:YES openingAttributes:nil completionHandler:^(NSError *error) { BOOL errorCanBeNil = [self usingProtocol:@"file"]; // the file protocol doesn't report an error in this situation XCTAssertTrue([self checkIsCreationError:error nilAllowed:errorCanBeNil], @"expected file can't write error, got %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testCreateDirectoryAtURLBadLogin { if ([self setupTest] && [self protocolUsesAuthentication]) { [self useBadLogin]; NSURL* url = [self URLForTestFolder]; [self.manager createDirectoryAtURL:url withIntermediateDirectories:YES openingAttributes:nil completionHandler:^(NSError *error) { XCTAssertTrue([self checkIsAuthenticationError:error], @"was expecting authentication error, got %@ underlying %@", error, error.userInfo[NSUnderlyingErrorKey]); [self pause]; }]; [self runUntilPaused]; } } - (void)testCreateFileAtURL { if ([self setupTest]) { [self makeTestDirectoryWithFiles:NO]; NSURL* url = [self URLForTestFile1]; NSData* data = [@"Some test text" dataUsingEncoding:NSUTF8StringEncoding]; [self.manager createFileAtURL:url contents:data withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testCreateFileAtURLBadURL { NSData* data = [@"Some test text" dataUsingEncoding:NSUTF8StringEncoding]; [self enumerateWithBadURLS:^(NSURL *url) { [self.manager createFileAtURL:url contents:data withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error"); [self pause]; }]; [self runUntilPaused]; }]; } - (void)testCreateFileAtURL2 { if ([self setupTest]) { [self makeTestDirectoryWithFiles:NO]; NSURL* temp = [NSURL fileURLWithPath:NSTemporaryDirectory()]; NSURL* source = [temp URLByAppendingPathComponent:@"test.txt"]; NSError* error = nil; XCTAssertTrue([@"Some test text" writeToURL:source atomically:YES encoding:NSUTF8StringEncoding error:&error], @"failed to write temporary file with error %@", error); NSURL* url = [self URLForTestFile1]; [self.manager createFileAtURL:url withContentsOfURL:source withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; [self runUntilPaused]; XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:source error:&error], @"failed to remove temporary file with error %@", error); } } - (void)testCreateFileAtURLBadURL2 { NSURL* temp = [NSURL fileURLWithPath:NSTemporaryDirectory()]; NSURL* source = [temp URLByAppendingPathComponent:@"test.txt"]; NSError* error = nil; XCTAssertTrue([@"Some test text" writeToURL:source atomically:YES encoding:NSUTF8StringEncoding error:&error], @"failed to write temporary file with error %@", error); [self enumerateWithBadURLS:^(NSURL *url) { [self.manager createFileAtURL:url withContentsOfURL:source withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error"); [self pause]; }]; [self runUntilPaused]; }]; XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:source error:&error], @"failed to remove temporary file with error %@", error); } - (void)testCreateFileAtURLSourceDoesntExist { if ([self setupTest]) { NSURL* source = [NSURL fileURLWithPath:@"/tmp/i-dont-exist.txt"]; NSURL* url = [self URLForTestFile1]; [self.manager createFileAtURL:url withContentsOfURL:source withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error"); [self pause]; }]; [self runUntilPaused]; } } - (void)testCreateFileAtURLSourceIsntLocal { if ([self setupTest]) { NSURL* source = [NSURL URLWithString:@"http://karelia.com/tmp/i-dont-exist.txt"]; NSURL* url = [self URLForTestFile1]; [self.manager createFileAtURL:url withContentsOfURL:source withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error"); [self pause]; }]; [self runUntilPaused]; } } - (void)testUploadOver16k { if ([self setupTest]) { [self makeTestDirectoryWithFiles:NO]; NSURL* url = [self URLForTestFile1]; // make a block of data and fill it with stuff in an attempt to avoid any sneaky compression speeding things up NSUInteger length = 32768; NSMutableData* data = [NSMutableData dataWithCapacity:length * sizeof(UInt16)]; UInt16* bytes = (UInt16*)data.bytes; for (NSUInteger n = 0; n < length; ++n) { bytes[n] = n; } [self.manager createFileAtURL:url contents:data withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testCreateFileDenied { if ([self setupTest]) { [self useResponseSet:@"stor denied"]; NSURL* url = [self URLForPath:@"/BaseCKProtocolTests/test.txt"]; // should fail as it's at the root - we put it in a subfolder just in case NSData* data = [@"Some test text" dataUsingEncoding:NSUTF8StringEncoding]; [self.manager createFileAtURL:url contents:data withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNotNil(error); // TODO: Test for specific error [self pause]; }]; [self runUntilPaused]; } } - (void)testCreateFileAtRootReallyGoesIntoRoot { // I found we were constructing URLs wrong for the paths like: /example.txt // Such files were ending up in the home folder, rather than root if ([self setupTest] && [self usingMockServerWithProtocol:@"ftp"]) // only perform this test for FTP using MockServer { [self useResponseSet:@"chroot fail"]; NSURL* url = [self URLForPath:@"/test.txt"]; NSData* data = [@"Some test text" dataUsingEncoding:NSUTF8StringEncoding]; [self.manager createFileAtURL:url contents:data withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); // Make sure the file went into root, rather than home // This could be done by changing directory to /, or storing directly to /test.txt [self.server.transcript enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(KMSTranscriptEntry *aTranscriptEntry, NSUInteger idx, BOOL *stop) { // Search back for the STOR command if (aTranscriptEntry.type == KMSTranscriptInput && [aTranscriptEntry.value hasPrefix:@"STOR "]) { *stop = YES; // Was the STOR command itself valid? if ([aTranscriptEntry.value hasPrefix:@"STOR /test.txt"]) { } else { // Search back for the preceeding CWD command NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, idx)]; __block BOOL haveChangedDirectory = NO; [self.server.transcript enumerateObjectsAtIndexes:indexes options:NSEnumerationReverse usingBlock:^(KMSTranscriptEntry *aTranscriptEntry, NSUInteger idx, BOOL *stop) { if (aTranscriptEntry.type == KMSTranscriptInput && [aTranscriptEntry.value hasPrefix:@"CWD "]) { *stop = YES; haveChangedDirectory = YES; XCTAssertTrue([aTranscriptEntry.value isEqualToString:@"CWD /\r\n"], @"libcurl changed to the wrong directory: %@", aTranscriptEntry.value); } }]; XCTAssertTrue(haveChangedDirectory, @"libcurl never changed directory"); } } }]; [self pause]; }]; [self runUntilPaused]; } } - (void)testCreateFileSerialThrash { // Create the same file multiple times in a row. This has been tending to fail weirdly when testing CURLTransfer directly if ([self setupTest]) { [self makeTestDirectoryWithFiles:NO]; NSURL* url = [self URLForTestFile1]; NSData* data = [@"Some test text" dataUsingEncoding:NSUTF8StringEncoding]; [self.manager createFileAtURL:url contents:data withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self.manager createFileAtURL:url contents:data withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self.manager createFileAtURL:url contents:data withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self.manager createFileAtURL:url contents:data withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; }]; }]; }]; [self runUntilPaused]; } } - (void)testRenameFileAtURL { if ([self setupTest]) { [self makeTestDirectoryWithFiles:YES]; NSURL* url = [self URLForTestFile1]; NSString* extension = [url pathExtension]; NSString* newName = [@"renamed" stringByAppendingPathExtension:extension]; NSURL* renamed = [[url URLByDeletingLastPathComponent] URLByAppendingPathComponent:newName]; // rename file [self.manager renameItemAtURL:url toFilename:newName completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); if (!self.useMockServer) { // try to remove original file - if we don't get an error here it's a hint that the move didn't work (although sadly for SFTP we won't get an error currently, so it's not conclusive) [self.manager removeItemAtURL:url completionHandler:^(NSError *error) { BOOL errorCanBeNil = [self usingMockServerWithProtocol:@"sftp"]; // SFTP is a bit crap at reporting errors XCTAssertTrue([self checkIsRemovalError:error nilAllowed:errorCanBeNil], @"expected removal error, got %@", error); // try to remove renamed file - again, if we get an error here it's a big hint that the move didn't work [self.manager removeItemAtURL:renamed completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; }]; } else { [self pause]; } }]; [self runUntilPaused]; } } - (void)testRemoveFileAtURL { if ([self setupTest]) { [self makeTestDirectoryWithFiles:YES]; NSURL* url = [self URLForTestFile1]; [self.manager removeItemAtURL:url completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testRemoveFileAtURLBadURL { [self enumerateWithBadURLS:^(NSURL *url) { [self.manager removeItemAtURL:url completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error"); [self pause]; }]; [self runUntilPaused]; }]; } - (void)testRemoveFileAtURLFileDoesnExist { if ([self setupTest]) { [self makeTestDirectoryWithFiles:NO]; [self useResponseSet:@"delete fails"]; NSURL* url = [self URLForTestFile1]; [self.manager removeItemAtURL:url completionHandler:^(NSError *error) { BOOL errorCanBeNil = [self usingProtocol:@"sftp"]; // SFTP is a bit crap at reporting errors XCTAssertTrue([self checkIsRemovalError:error nilAllowed:errorCanBeNil], @"expected removal error, got %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testRemoveFileAtURLContainingFolderDoesnExist { if ([self setupTest]) { [self removeTestDirectory]; [self useResponseSet:@"delete fails missing directory"]; NSURL* url = [self URLForTestFile1]; [self.manager removeItemAtURL:url completionHandler:^(NSError *error) { BOOL errorCanBeNil = [self usingMockServerWithProtocol:@"sftp"]; // SFTP is a bit crap at reporting errors XCTAssertTrue([self checkIsRemovalError:error nilAllowed:errorCanBeNil], @"expected removal error, got %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testRemoveFileAtURLBadLogin { if ([self setupTest] && [self protocolUsesAuthentication]) { [self useBadLogin]; NSURL* url = [self URLForTestFile1]; [self.manager removeItemAtURL:url completionHandler:^(NSError *error) { XCTAssertTrue([self checkIsAuthenticationError:error], @"was expecting authentication error, got %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testSetUnknownAttributes { if ([self setupTest]) { [self makeTestDirectoryWithFiles:YES]; NSURL* url = [self URLForTestFile1]; NSDictionary* values = @{ @"test" : @"test" }; [self.manager setAttributes:values ofItemAtURL:url completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testSetAttributesOnFile { if ([self setupTest]) { [self makeTestDirectoryWithFiles:YES]; NSURL* url = [self URLForTestFile1]; NSDictionary* values = @{ NSFilePosixPermissions : @(0744)}; [self.manager setAttributes:values ofItemAtURL:url completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testSetAttributesOnFileBadURL { NSDictionary* values = @{ NSFilePosixPermissions : @(0744)}; [self enumerateWithBadURLS:^(NSURL *url) { [self.manager setAttributes:values ofItemAtURL:url completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error"); [self pause]; }]; [self runUntilPaused]; }]; } - (void)testSetAttributesOnFolder { if ([self setupTest]) { [self makeTestDirectoryWithFiles:NO]; NSURL* url = [self URLForTestFolder]; NSDictionary* values = @{ NSFilePosixPermissions : @(0777)}; [self.manager setAttributes:values ofItemAtURL:url completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testSetAttributesOnFileDoesntExist { if ([self setupTest]) { [self makeTestDirectoryWithFiles:NO]; [self useResponseSet:@"chmod not permitted"]; NSURL* url = [self URLForTestFile1]; NSDictionary* values = @{ NSFilePosixPermissions : @(0744)}; [self.manager setAttributes:values ofItemAtURL:url completionHandler:^(NSError *error) { BOOL errorCanBeNil = [self usingMockServerWithProtocol:@"webdav"]; // no errors because it's not supported in WebDAV XCTAssertTrue([self checkIsUpdateError:error nilAllowed:errorCanBeNil], @"expected file can't write error, got %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testSetAttributesOnFolderDoesntExist { if ([self setupTest]) { [self removeTestDirectory]; [self useResponseSet:@"chmod not permitted"]; NSURL* url = [self URLForTestFolder]; NSDictionary* values = @{ NSFilePosixPermissions : @(0744)}; [self.manager setAttributes:values ofItemAtURL:url completionHandler:^(NSError *error) { BOOL errorCanBeNil = [self usingMockServerWithProtocol:@"webdav"]; // no errors because it's not supported in WebDAV XCTAssertTrue([self checkIsUpdateError:error nilAllowed:errorCanBeNil], @"expected file can't write error, got %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testSetAttributesCHMODNotUnderstood { if (self.useMockServer && [self setupTest]) // no way to test this on a real server (unless it actually doesn't understand CHMOD of course...) { [self useResponseSet:@"chmod not understood"]; NSURL* url = [self URLForPath:@"/directory/intermediate/test.txt"]; NSDictionary* values = @{ NSFilePosixPermissions : @(0744)}; [self.manager setAttributes:values ofItemAtURL:url completionHandler:^(NSError *error) { // For servers which don't understand or support CHMOD, treat as success, like -[NSURL setResourceValue:forKey:error:] does XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testSetAttributesCHMODUnsupported { if (self.useMockServer && [self setupTest]) // no way to test this on a real server (unless it actually doesn't support CHMOD of course...) { [self useResponseSet:@"chmod unsupported"]; NSURL* url = [self URLForPath:@"/directory/intermediate/test.txt"]; NSDictionary* values = @{ NSFilePosixPermissions : @(0744)}; [self.manager setAttributes:values ofItemAtURL:url completionHandler:^(NSError *error) { // For servers which don't understand or support CHMOD, treat as success, like -[NSURL setResourceValue:forKey:error:] does XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; [self runUntilPaused]; } } - (void)testSetAttributesOperationNotPermitted { if (self.useMockServer && [self setupTest]) // can't reliably target a file that we don't have permission to change on a real server, since we don't know what it has { [self useResponseSet:@"chmod not permitted"]; NSURL* url = [self URLForTestFile1]; NSDictionary* values = @{ NSFilePosixPermissions : @(0744)}; [self.manager setAttributes:values ofItemAtURL:url completionHandler:^(NSError *error) { NSString* domain = error.domain; if ([domain isEqualToString:NSURLErrorDomain]) { // get NSURLErrorNoPermissionsToReadFile if the path doesn't exist or isn't readable on the server XCTAssertTrue(error.code == NSURLErrorNoPermissionsToReadFile, @"unexpected error %@", error); } else if ([domain isEqualToString:NSCocoaErrorDomain]) { XCTAssertTrue((error.code == NSFileWriteUnknownError || // FTP has no hard way to know it was a permissions error error.code == NSFileWriteNoPermissionError), @"unexpected error %@", error); } else { XCTFail(@"unexpected error %@", error); } [self pause]; }]; [self runUntilPaused]; } } - (void)testBadLoginThenGoodLogin { if ([self setupTest] && [self protocolUsesAuthentication]) { [self removeTestDirectory]; [self useBadLogin]; NSURL* url = [self URLForTestFolder]; [self.manager createDirectoryAtURL:url withIntermediateDirectories:YES openingAttributes:nil completionHandler:^(NSError *error) { XCTAssertTrue([self checkIsAuthenticationError:error], @"was expecting authentication error, got %@", error); self.user = self.originalUser; [self useResponseSet:@"default"]; [self.manager createDirectoryAtURL:url withIntermediateDirectories:YES openingAttributes:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; }]; [self runUntilPaused]; } } @end ================================================ FILE: UnitTests/BaseCKTests.h ================================================ // // Created by Sam Deane on 06/11/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "KMSTestCase.h" #import "KMSState.h" #import "CK2FileManager.h" @interface BaseCKTests : KMSTestCase @property (strong, nonatomic) CK2FileManager* manager; @property (assign, atomic) KMSState state; @property (strong, nonatomic) NSMutableString* transcript; @property (readonly, nonatomic) NSString* protocol; @property (assign, nonatomic) BOOL useMockServer; @property (strong, nonatomic) NSString* originalUser; @property (strong, nonatomic) NSString* originalPassword; - (BOOL)setupTest; - (BOOL)isSetup; - (BOOL)protocolUsesAuthentication; - (NSURL*)temporaryFolder; - (BOOL)usingProtocol:(NSString*)type; - (BOOL)usingMockServerWithProtocol:(NSString*)type; - (void)useBadLogin; - (NSData*)mockServerDirectoryListingData; #pragma mark - Test File Support - (NSURL*)URLForTestFolder; - (NSURL*)URLForTestFile1; - (NSURL*)URLForTestFile2; - (void)makeTestDirectoryWithFiles:(BOOL)withFiles; - (void)removeTestDirectory; #pragma mark - Checking - (void)checkURL:(NSURL*)url isNamed:(NSString*)name; - (void)checkURLs:(NSMutableArray*)urls containItemNamed:(NSString*)name; - (BOOL)checkIsAuthenticationError:(NSError*)error; - (BOOL)checkIsCreationError:(NSError*)error nilAllowed:(BOOL)nilAllowed; - (BOOL)checkIsRemovalError:(NSError*)error nilAllowed:(BOOL)nilAllowed; - (BOOL)checkIsUpdateError:(NSError*)error nilAllowed:(BOOL)nilAllowed; - (BOOL)checkIsMissingError:(NSError*)error nilAllowed:(BOOL)nilAllowed; @end ================================================ FILE: UnitTests/BaseCKTests.m ================================================ // // Created by Sam Deane on 06/11/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "BaseCKTests.h" #import "CK2Authentication.h" #import "CK2FileManagerWithTestSupport.h" #import "KMSServer.h" static const BOOL kMakeRemoveTestFilesOnMockServer = YES; @interface TestFileDelegate : CK2FileManager @property (strong, nonatomic) BaseCKTests* tests; @end /** This delegate is used instead of the test itself for operations which are either creating or removing the test files and folders on the server. This helps to prevent those operations from interfering with the state of the actual tests themselves. */ @implementation TestFileDelegate //#define LogHousekeeping NSLog // macro to use for logging "housekeeping" output - ie stuff related to making/removing test files, rather than the tests themselves #define LogHousekeeping(...) + (TestFileDelegate*)delegateWithTest:(BaseCKTests*)tests { TestFileDelegate* result = [[TestFileDelegate alloc] init]; result.tests = tests; return [result autorelease]; } - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential *))completionHandler; { if (challenge.previousFailureCount > 0) { completionHandler(CK2AuthChallengeCancelAuthenticationChallenge, nil); } else { NSURLCredential* credential; if ([challenge.protectionSpace.authenticationMethod isEqualToString:CK2AuthenticationMethodHostFingerprint]) { credential = [NSURLCredential ck2_credentialForKnownHostWithPersistence:NSURLCredentialPersistenceNone]; } else { credential = [NSURLCredential credentialWithUser:self.tests.user password:self.tests.password persistence:NSURLCredentialPersistenceNone]; } completionHandler(CK2AuthChallengeUseCredential, credential); } } - (void)fileManager:(CK2FileManager *)manager appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcriptType { switch (transcriptType) { case CK2TranscriptHeaderIn: case CK2TranscriptHeaderOut: LogHousekeeping(@"housekeeping %d: %@", transcriptType, info); break; default: break; } } @end @implementation BaseCKTests - (void)dealloc { [_manager release]; [_originalPassword release]; [_originalUser release]; [_transcript release]; [super dealloc]; } - (NSURL*)temporaryFolder { NSURL* result = [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@Tests", self.protocol]]; NSError* error = nil; BOOL ok = [[NSFileManager defaultManager] createDirectoryAtURL:result withIntermediateDirectories:YES attributes:nil error:&error]; XCTAssertTrue(ok, @"failed to make temporary folder with error %@", error); return result; } - (void)removeTemporaryFolder { NSError* error = nil; NSURL* tempFolder = [self temporaryFolder]; NSFileManager* fm = [NSFileManager defaultManager]; [fm removeItemAtURL:tempFolder error:&error]; } - (BOOL)makeTemporaryFolder { NSError* error = nil; NSFileManager* fm = [NSFileManager defaultManager]; NSURL* tempFolder = [self temporaryFolder]; BOOL ok = [fm createDirectoryAtURL:tempFolder withIntermediateDirectories:YES attributes:nil error:&error]; XCTAssertTrue(ok, @"couldn't make temporary directory: %@", error); return ok; } - (BOOL)setupManager { CK2FileManagerWithTestSupport* fm = [[CK2FileManagerWithTestSupport alloc] init]; fm.dontShareConnections = YES; fm.delegate = self; self.manager = fm; self.transcript = [[[NSMutableString alloc] init] autorelease]; [fm release]; return self.manager != nil; } - (BOOL)isSetup { return self.manager != nil; } - (NSString*)protocol { return @"Unknown"; } - (BOOL)protocolUsesAuthentication { return NO; } - (BOOL)usingProtocol:(NSString*)type { return [[self.protocol lowercaseString] isEqualToString:type]; } - (BOOL)usingMockServerWithProtocol:(NSString*)type { return self.useMockServer && [self usingProtocol:type]; } - (void)useBadLogin { self.user = @"bad"; [self useResponseSet:@"bad login"]; } - (NSData*)mockServerDirectoryListingData { return nil; } - (BOOL)setupFromSettings { NSString* setting = nil; NSString* protocol = self.protocol; if (protocol) { NSString* key = [NSString stringWithFormat:@"CK%@TestURL", protocol]; setting = [[NSUserDefaults standardUserDefaults] objectForKey:key]; XCTAssertNotNil(setting, @"You need to set a test server address for %@ tests. Use the defaults command on the command line: defaults write otest %@ \"server-url-here\". Use \"MockServer\" instead of a url to use a mock server instead. Use \"Off\" instead of a url to disable %@ tests", protocol, key, key, protocol); } BOOL ok; if (!setting || [setting isEqualToString:@"Off"]) { NSLog(@"Tests turned off for %@", protocol); ok = NO; } else if ([setting isEqualToString:@"MockServer"]) { NSLog(@"Tests using MockServer for %@", protocol); self.useMockServer = YES; ok = [super setupServerWithResponseFileNamed:[self.protocol lowercaseString]]; [KMSServer setLoggingLevel:KMSLoggingOff]; } else { NSURL* url = [NSURL URLWithString:setting]; self.user = url.user; self.password = url.password; self.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@%@/", url.scheme, url.host, url.path]]; ok = YES; } return ok; } - (NSString*)testName { NSString* name = [[[self.name substringToIndex:[self.name length] - 1] componentsSeparatedByString:@" "] objectAtIndex:1]; return name; } - (BOOL)setupTest { BOOL ok = [self setupFromSettings]; if (ok) { self.originalUser = self.user; self.originalPassword = self.password; [self setupManager]; ok = self.manager != nil; } if (ok) { NSLog(@"Tests setup for %@, user:%@, password:%@ url:%@", self.protocol, self.user, self.password, self.url); } else { NSLog(@"Tests not setup for %@", self.protocol); } NSLog(@"===================================================================="); return ok; } - (void)useResponseSet:(NSString*)name { if (self.useMockServer) { [super useResponseSet:name]; } } #pragma mark - Delegate - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential *))completionHandler { NSString *authMethod = [[challenge protectionSpace] authenticationMethod]; if (challenge.previousFailureCount > 0) { NSLog(@"cancelling authentication"); completionHandler(CK2AuthChallengeCancelAuthenticationChallenge, nil); } else { if ([authMethod isEqualToString:NSURLAuthenticationMethodDefault] || [authMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest] || [authMethod isEqualToString:NSURLAuthenticationMethodHTMLForm] || [authMethod isEqualToString:NSURLAuthenticationMethodNTLM] || [authMethod isEqualToString:NSURLAuthenticationMethodNegotiate]) { NSLog(@"authenticating as %@ %@", self.user, self.password); NSURLCredential* credential = [NSURLCredential credentialWithUser:self.user password:self.password persistence:NSURLCredentialPersistenceNone]; completionHandler(CK2AuthChallengeUseCredential, credential); } else if ([authMethod isEqualToString:CK2AuthenticationMethodHostFingerprint]) { NSLog(@"checking fingerprint"); NSURLCredential* credential = [NSURLCredential ck2_credentialForKnownHostWithPersistence:NSURLCredentialPersistenceNone]; completionHandler(CK2AuthChallengeUseCredential, credential); } else { NSLog(@"performing default authentication"); completionHandler(CK2AuthChallengePerformDefaultHandling, nil); } } } - (void)fileManager:(CK2FileManager *)manager appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcriptType { NSString* prefix; switch (transcriptType) { case CK2TranscriptHeaderOut: prefix = @"-->"; break; case CK2TranscriptHeaderIn: prefix = @"<--"; break; /*case CKTranscriptData: prefix = @"(d)"; break; */ case CK2TranscriptText: prefix = @"(i)"; break; default: prefix = @"(?)"; } @synchronized(self.transcript) { [self.transcript appendFormat:@"%@ %@", prefix, info]; UniChar lastChar = [info characterAtIndex:[info length] - 1]; if ((lastChar != '\n') && (lastChar != '\r')) [self.transcript appendString:@"\n"]; } } - (NSURL*)URLForPath:(NSString*)path { NSURL* url = [CK2FileManager URLWithPath:path relativeToURL:self.url]; return [url absoluteURL]; // account for relative URLs } - (void)runUntilPaused { if (self.useMockServer) { [super runUntilPaused]; } else { while (self.state != KMSPauseRequested) { @autoreleasepool { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]]; } } self.state = KMSPaused; } } - (void)pause { if (self.useMockServer) { [super pause]; } else { self.state = KMSPauseRequested; } } - (void)setUp { [self removeTemporaryFolder]; [self makeTemporaryFolder]; } - (void)tearDown { [super tearDown]; if ([self.transcript length] > 0) { NSLog(@"\n\nSession transcript:\n%@\n\n", self.transcript); } [self removeTemporaryFolder]; } #pragma mark - Test File Support - (NSURL*)URLForTestFolder { return [self URLForPath:[[@"CK2FileManagerTests" stringByAppendingPathComponent:self.protocol] stringByAppendingPathComponent:[self testName]]]; } - (NSURL*)URLForTestFile1 { return [[self URLForTestFolder] URLByAppendingPathComponent:@"file1.txt"]; } - (NSURL*)URLForTestFile2 { return [[self URLForTestFolder] URLByAppendingPathComponent:@"file2.txt"]; } - (void)makeTestDirectoryWithFiles:(BOOL)withFiles { // we do report errors from here, since something going wrong is likely to affect the result of the test that called us if (kMakeRemoveTestFilesOnMockServer || !self.useMockServer) { // if we don't want the test files, remove everything first if (!withFiles) { [self removeTestDirectory]; } LogHousekeeping(@"<<<< Making Test Directory"); CK2FileManagerWithTestSupport* session = [[CK2FileManagerWithTestSupport alloc] init]; session.dontShareConnections = YES; session.delegate = [TestFileDelegate delegateWithTest:self]; // make the folder if necessary NSURL* url = [self URLForTestFolder]; [session createDirectoryAtURL:url withIntermediateDirectories:YES openingAttributes:nil completionHandler:^(NSError *error) { XCTAssertTrue([self checkIsCreationError:error nilAllowed:YES], @"expected no error or file exists error, got %@", error); // if we want the files, make them too if (withFiles) { NSData* contents = [@"This is a test file" dataUsingEncoding:NSUTF8StringEncoding]; [session createFileAtURL:[self URLForTestFile1] contents:contents withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertTrue([self checkIsCreationError:error nilAllowed:YES], @"expected no error or file exists error, got %@", error); [session createFileAtURL:[self URLForTestFile2] contents:contents withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertTrue([self checkIsCreationError:error nilAllowed:YES], @"expected no error or file exists error, got %@", error); [self pause]; LogHousekeeping(@"<<<< Made Test Files"); }]; }]; } else { [self pause]; } LogHousekeeping(@"<<<< Made Test Directory"); }]; [self runUntilPaused]; [session release]; } } - (void)removeTestDirectory { if (kMakeRemoveTestFilesOnMockServer || !self.useMockServer) { LogHousekeeping(@"<<<< Removing Test Files"); CK2FileManagerWithTestSupport* session = [[CK2FileManagerWithTestSupport alloc] init]; session.dontShareConnections = YES; session.delegate = [TestFileDelegate delegateWithTest:self]; // we don't care about errors here, we just want to do our best to clean up after any tests [session removeItemAtURL:[self URLForTestFile2] completionHandler:^(NSError *error) { if (error) LogHousekeeping(@"housekeeping error : %@", error); [session removeItemAtURL:[self URLForTestFile1] completionHandler:^(NSError *error) { if (error) LogHousekeeping(@"housekeeping error : %@", error); [session removeItemAtURL:[self URLForTestFolder] completionHandler:^(NSError *error) { if (error) LogHousekeeping(@"housekeeping error : %@", error); [self pause]; LogHousekeeping(@"<<<< Removed Test Files"); }]; }]; }]; [self runUntilPaused]; [session release]; } } #pragma mark - Checking Helpers - (void)checkURL:(NSURL*)url isNamed:(NSString*)name { XCTAssertTrue([[url lastPathComponent] isEqualToString:name], @"URL %@ name was wrong, expected %@", url, name); } - (void)checkURLs:(NSMutableArray*)urls containItemNamed:(NSString*)name { BOOL found = NO; NSUInteger count = [urls count]; for (NSUInteger n = 0; n < count; ++n) { NSURL* url = urls[n]; if ([[url lastPathComponent] isEqualToString:name]) { [urls removeObjectAtIndex:n]; found = YES; break; } } XCTAssertTrue(found, @"unexpected item with name %@", name); } - (BOOL)checkIsAuthenticationError:(NSError*)error log:(BOOL)log { BOOL domainOK = [error.domain isEqualToString:NSURLErrorDomain]; BOOL codeOK = error.code == NSURLErrorUserAuthenticationRequired || error.code == NSURLErrorUserCancelledAuthentication; BOOL result = domainOK && codeOK; if (log && !result) { NSLog(@"expecting authentication error, got %@", error); } return result; } - (BOOL)checkIsAuthenticationError:(NSError*)error { return [self checkIsAuthenticationError:error log:YES]; } - (BOOL)checkIsFileCantWriteError:(NSError*)error log:(BOOL)log { BOOL domainOK = [error.domain isEqualToString:NSCocoaErrorDomain]; BOOL codeOK = error.code == NSFileWriteUnknownError; BOOL result = domainOK && codeOK; if (log && !result) { NSLog(@"expecting cant write error, got %@", error); } return result; } - (BOOL)checkIsFileCantReadError:(NSError*)error log:(BOOL)log { BOOL domainOK = [error.domain isEqualToString:NSCocoaErrorDomain]; BOOL codeOK = error.code == NSFileReadUnknownError; BOOL result = domainOK && codeOK; if (log && !result) { NSLog(@"expecting file not found error, got %@", error); } return result; } - (BOOL)checkIsFileNotFoundError:(NSError*)error log:(BOOL)log { BOOL domainOK = [error.domain isEqualToString:NSCocoaErrorDomain]; BOOL codeOK = error.code == NSFileNoSuchFileError; BOOL result = domainOK && codeOK; if (log && !result) { NSLog(@"expecting file not found error, got %@", error); } return result; } - (BOOL)checkIsFileExistsError:(NSError*)error log:(BOOL)log { BOOL domainOK = [error.domain isEqualToString:NSCocoaErrorDomain]; BOOL codeOK = error.code == NSFileWriteFileExistsError; BOOL result = domainOK && codeOK; if (log && !result) { NSLog(@"expecting file exists error, got %@", error); } return result; } - (BOOL)checkIsRemovalError:(NSError*)error nilAllowed:(BOOL)nilAllowed { BOOL result = nilAllowed && error == nil; if (!result) { result = [self checkIsFileNotFoundError:error log:NO]; // file wasn't there? } if (!result) { result = [self checkIsFileCantWriteError:error log:NO]; // file was there but locked - or protocol is bad at reporting file not found } if (!result) { result = [self checkIsFileCantReadError:error log:NO]; // file wasn't there, but protocol isn't good a reporting it? } if (!result) { NSLog(@"expecting file not found or can't read or write errors, got %@", error); } return result; } - (BOOL)checkIsCreationError:(NSError*)error nilAllowed:(BOOL)nilAllowed { BOOL result = nilAllowed && error == nil; if (!result) { result = [self checkIsFileExistsError:error log:NO]; } if (!result) { result = [self checkIsFileCantWriteError:error log:NO]; } if (!result) { NSLog(@"expecting file exists or can't write errors, got %@", error); } return result; } - (BOOL)checkIsUpdateError:(NSError*)error nilAllowed:(BOOL)nilAllowed { BOOL result = nilAllowed && error == nil; if (!result) { result = [self checkIsFileNotFoundError:error log:NO]; } if (!result) { result = [self checkIsFileCantWriteError:error log:NO]; } if (!result) { NSLog(@"expecting file not found or file can't write error, got %@", error); } return result; } - (BOOL)checkIsMissingError:(NSError*)error nilAllowed:(BOOL)nilAllowed { BOOL result = nilAllowed && error == nil; if (!result) { result = [self checkIsFileNotFoundError:error log:NO]; } if (!result) { result = [self checkIsFileCantReadError:error log:NO]; } if (!result) { NSLog(@"expecting file not found or file can't read errors, got %@", error); } return result; } @end ================================================ FILE: UnitTests/CKUploaderTests.m ================================================ // // Created by Sam Deane on 06/11/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "BaseCKTests.h" #import "KMSServer.h" #import "CKUploader.h" #import #import @interface CKUploaderTests : BaseCKTests @property (strong, nonatomic) NSError* error; @property (assign, nonatomic) BOOL finished; @property (assign, nonatomic) BOOL uploading; @property (assign, nonatomic) BOOL failAuthentication; @end @implementation CKUploaderTests - (void)dealloc { [_error release]; [super dealloc]; } - (CKUploader*)setupUploader { CKUploader* result = nil; if ([self setupTest]) { NSURL* url = [self URLForPath:@"/"]; NSURLRequest* request = [NSURLRequest requestWithURL:url]; CKUploadingOptions options = 0; result = [CKUploader uploaderWithRequest:request options:options delegate:self]; } return result; } - (NSString *)protocol; { return @"WebDAV"; } #pragma mark - Upload Delegate Methods - (void)uploaderDidBecomeInvalid:(CKUploader *)uploader { self.finished = YES; [self pause]; } - (void)uploader:(CKUploader *)uploader transferRecord:(CKTransferRecord *)record didCompleteWithError:(NSError *)error; { self.error = error; } - (void)uploader:(CKUploader *)uploader didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential *))completionHandler { if (challenge.previousFailureCount > 0) { NSLog(@"cancelling authentication"); completionHandler(CK2AuthChallengeCancelAuthenticationChallenge, nil); } else { NSString* user = self.failAuthentication ? @"wrong" : @"user"; NSString* pass = self.failAuthentication ? @"wrong" : @"pass"; NSURLCredential* credential = [NSURLCredential credentialWithUser:user password:pass persistence:NSURLCredentialPersistenceNone]; completionHandler(CK2AuthChallengeUseCredential, credential); } } - (void)uploader:(CKUploader *)uploader didBeginUploadToPath:(NSString *)path { self.uploading = YES; NSLog(@"uploading"); } - (void)uploader:(CKUploader *)uploader appendString:(NSString *)string toTranscript:(CK2TranscriptType)transcript { NSLog(@"%d: %@", (int)transcript, string); } #pragma mark - Utilities - (void)checkResultForRecord:(CKTransferRecord*)record uploading:(BOOL)uploading { if (self.failAuthentication) { XCTAssertTrue([self.error.domain isEqualToString:NSURLErrorDomain], @"unexpected error %@", self.error); XCTAssertTrue(self.error.code == kCFURLErrorUserCancelledAuthentication, @"unexpected error %@", self.error); XCTAssertTrue(self.finished, @"should be finished"); if (record) { XCTAssertTrue([record.error.domain isEqualToString:NSURLErrorDomain], @"unexpected error %@", self.error); XCTAssertTrue(record.error.code == kCFURLErrorUserCancelledAuthentication, @"unexpected error %@", self.error); } } else { XCTAssertTrue(self.finished, @"should be finished"); XCTAssertTrue(self.error == nil, @"unexpected error %@", self.error); XCTAssertNil(record.error, @"unexpected error %@", record.error); } XCTAssertTrue(self.uploading == uploading, @"uploading method %@ have been called", uploading ? @"should" : @"shouldn't"); } #pragma mark - Tests - (void)testUploadFile { CKUploader* uploader = [self setupUploader]; if (uploader) { NSURL* folder = [self temporaryFolder]; NSURL* url = [folder URLByAppendingPathComponent:@"test.txt"]; NSString* testData = @"Some test content"; NSError* error = nil; BOOL ok = [testData writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:&error]; XCTAssertTrue(ok, @"failed to write test file with error %@", error); CKTransferRecord *record = [uploader uploadToURL:[CK2FileManager URLWithPath:@"test/test.txt" relativeToURL:uploader.baseRequest.URL] fromFile:url]; XCTAssertNotNil(record, @"got a transfer record"); XCTAssertTrue(record.size == [[testData dataUsingEncoding:NSUTF8StringEncoding] length], @"unexpected size %ld", record.size); [uploader finishOperationsAndInvalidate]; [self runUntilPaused]; [self checkResultForRecord:record uploading:YES]; } } - (void)testUploadFileNoAuthentication { self.failAuthentication = YES; [self testUploadFile]; } - (void)testUploadData { CKUploader* uploader = [self setupUploader]; if (uploader) { NSData* testData = [@"Some test content" dataUsingEncoding:NSUTF8StringEncoding]; CKTransferRecord *record = [uploader uploadToURL:[CK2FileManager URLWithPath:@"test/test.txt" relativeToURL:uploader.baseRequest.URL] fromData:testData]; XCTAssertNotNil(record, @"got a transfer record"); XCTAssertTrue(record.size == [testData length], @"unexpected size %ld", record.size); [uploader finishOperationsAndInvalidate]; [self runUntilPaused]; [self checkResultForRecord:record uploading:YES]; } } - (void)testUploadDataNoAuthentication { self.failAuthentication = YES; [self testUploadData]; } - (void)testRemoveFileAtPath { CKUploader* uploader = [self setupUploader]; if (uploader) { NSURL *url = [CK2FileManager URLWithPath:@"test/test.txt" relativeToURL:uploader.baseRequest.URL]; [uploader removeItemAtURL:url completionHandler:NULL]; [uploader finishOperationsAndInvalidate]; [self runUntilPaused]; [self checkResultForRecord:nil uploading:NO]; } } - (void)testRemoveFileAtPathNoAuthentication { self.failAuthentication = YES; [self testRemoveFileAtPath]; } - (void)testCancel { CKUploader* uploader = [self setupUploader]; if (uploader) { NSData* testData = [@"Some test content" dataUsingEncoding:NSUTF8StringEncoding]; CKTransferRecord *record = [uploader uploadToURL:[CK2FileManager URLWithPath:@"test/test.txt" relativeToURL:uploader.baseRequest.URL] fromData:testData]; XCTAssertNotNil(record, @"got a transfer record"); XCTAssertTrue(record.size == [testData length], @"unexpected size %ld", record.size); [uploader finishOperationsAndInvalidate]; XCTAssertFalse(self.finished, @"should not be finished"); [uploader invalidateAndCancel]; [self runUntilPaused]; } } @end ================================================ FILE: UnitTests/ErrorTests.m ================================================ // // CK2FileManagerErrorTests.m // Connection // // Created by Sam Deane on 10/04/2013. // Copyright (c) 2012 Karelia Software. All rights reserved. // #import "CK2FileManager.h" #import @interface CK2FileManagerErrorTests : XCTestCase @end @implementation CK2FileManagerErrorTests #pragma mark FTP - (void)testUnsupportedScheme { CK2FileManager* fm = [[CK2FileManager alloc] init]; NSURL* url = [NSURL URLWithString:@"bogus://example.com"]; [fm contentsOfDirectoryAtURL:url includingPropertiesForKeys:@[] options:NSDirectoryEnumerationSkipsHiddenFiles completionHandler:^(NSArray *contents, NSError *error) { XCTAssertNotNil(error, @"expecting an error"); }]; [fm removeItemAtURL:url completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expecting an error"); }]; [fm enumerateContentsOfURL:url includingPropertiesForKeys:@[] options:NSDirectoryEnumerationSkipsHiddenFiles usingBlock:^(NSURL *url) { } completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expecting an error"); }]; [fm createDirectoryAtURL:url withIntermediateDirectories:YES openingAttributes:@{} completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expecting an error"); }]; [fm createFileAtURL:url contents:[NSData data] withIntermediateDirectories:YES openingAttributes:@{} progressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToSend) { } completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expecting an error"); }]; [fm createFileAtURL:url withContentsOfURL:url withIntermediateDirectories:YES openingAttributes:@{} progressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToSend) { } completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expecting an error"); }]; [fm release]; } @end ================================================ FILE: UnitTests/FTPAuthenticationTests.m ================================================ // // Created by Sam Deane on 06/11/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "BaseCKTests.h" #import "CK2FileManager.h" #import @interface FTPAuthenticationTests : BaseCKTests @end @implementation FTPAuthenticationTests - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential *))completionHandler; { NSString* user; NSString* password; if (challenge.previousFailureCount > 0) { user = self.originalUser; password = self.originalPassword; [self useResponseSet:@"default"]; } else { user = @"bad"; password = @"bad"; } NSLog(@"authenticating as %@ %@", self.user, self.password); NSURLCredential* credential = [NSURLCredential credentialWithUser:user password:password persistence:NSURLCredentialPersistenceNone]; completionHandler(CK2AuthChallengeUseCredential, credential); } - (NSString*)protocol { return @"FTP"; } #pragma mark - Tests - (void)testBadLoginThenGoodLogin { // the server starts by rejecting the password // after the first challenge though, we switch to the "normal" responses so that it accepts it if ([self setupTest]) { [self removeTestDirectory]; [self useResponseSet:@"bad login"]; NSURL* url = [self URLForTestFolder]; [self.manager createDirectoryAtURL:url withIntermediateDirectories:YES openingAttributes:nil completionHandler:^(NSError *error) { XCTAssertNil(error, @"got unexpected error %@", error); [self pause]; }]; } [self runUntilPaused]; } @end ================================================ FILE: UnitTests/FTPTests.m ================================================ // // Created by Sam Deane on 06/11/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "BaseCKProtocolTests.h" @interface FTPTests : BaseCKProtocolTests @end @implementation FTPTests - (NSString*)protocol { return @"FTP"; } - (BOOL)protocolUsesAuthentication { return YES; } - (NSData*)mockServerDirectoryListingData { NSString* listing = [NSString stringWithFormat: @"total 1\r\n-rw------- 1 user staff 3 Mar 6 2012 %@\r\n-rw------- 1 user staff 3 Mar 6 2012 %@\r\n\r\n", [[self URLForTestFile1] lastPathComponent], [[self URLForTestFile2] lastPathComponent] ]; return [listing dataUsingEncoding:NSUTF8StringEncoding]; } @end ================================================ FILE: UnitTests/FileTests.m ================================================ // // Created by Sam Deane on 06/11/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "BaseCKProtocolTests.h" @interface FileTests : BaseCKProtocolTests @end @implementation FileTests - (NSString*)protocol { return @"File"; } - (BOOL)setupFromSettings { // for the file tests, we always want to use a URL to a temporary folder self.url = [self temporaryFolder]; return YES; } - (NSURL*)makeTestContents { BOOL ok; NSError* error = nil; NSFileManager* fm = [NSFileManager defaultManager]; NSURL* tempFolder = [self temporaryFolder]; NSURL* testSubfolder = [tempFolder URLByAppendingPathComponent:@"subfolder"]; NSURL* testFile = [tempFolder URLByAppendingPathComponent:@"test.txt"]; ok = [@"Some test text" writeToURL:testFile atomically:YES encoding:NSUTF8StringEncoding error:&error]; XCTAssertTrue(ok, @"couldn't make test file: %@", error); if (ok) { ok = [fm createDirectoryAtURL:testSubfolder withIntermediateDirectories:YES attributes:nil error:&error]; XCTAssertTrue(ok, @"couldn't make test subdirectory: %@", error); } if (ok) { NSURL* otherFile = [testSubfolder URLByAppendingPathComponent:@"another.txt"]; ok = [@"Some more text" writeToURL:otherFile atomically:YES encoding:NSUTF8StringEncoding error:&error]; XCTAssertTrue(ok, @"couldn't make other test file: %@", error); } if (!ok) { tempFolder = nil; } return tempFolder; } #pragma mark - Extra File-Only Tests - (void)testCreateDirectoryAtURLNoPermission { if ([self setupTest]) { NSFileManager* fm = [NSFileManager defaultManager]; NSURL* url = [NSURL fileURLWithPath:@"/System/Test Directory"]; // try to make subdirectory in /System - this really ought to fail [self.manager createDirectoryAtURL:url withIntermediateDirectories:NO openingAttributes:nil completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error here"); XCTAssertTrue([[error domain] isEqualToString:NSCocoaErrorDomain], @"unexpected error domain %@", [error domain]); XCTAssertEqual([error code], (NSInteger) NSFileWriteNoPermissionError, @"unexpected error code %ld", [error code]); [self pause]; }]; [self runUntilPaused]; XCTAssertFalse([fm fileExistsAtPath:[url path]], @"directory shouldn't exist"); } } - (void)testCreateFileAtURLNoPermission { if ([self setupTest]) { NSData* data = [@"Some test text" dataUsingEncoding:NSUTF8StringEncoding]; // try to make file - should fail because we don't have permission NSURL* url = [NSURL fileURLWithPath:@"/System/test.txt"]; [self.manager createFileAtURL:url contents:data withIntermediateDirectories:NO openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error here"); XCTAssertTrue([[error domain] isEqualToString:NSCocoaErrorDomain], @"unexpected error domain %@", [error domain]); XCTAssertEqual([error code], (NSInteger) NSFileWriteNoPermissionError, @"unexpected error code %ld", [error code]); [self pause]; }]; [self runUntilPaused]; // try again, should fail again, but this time because we can't make the intermediate directory url = [NSURL fileURLWithPath:@"/System/Test Directory/test.txt"]; [self.manager createFileAtURL:url contents:data withIntermediateDirectories:YES openingAttributes:nil progressBlock:nil completionHandler:^(NSError *error) { XCTAssertNotNil(error, @"expected an error here"); XCTAssertTrue([[error domain] isEqualToString:NSCocoaErrorDomain], @"unexpected error domain %@", [error domain]); XCTAssertEqual([error code], (NSInteger) NSFileWriteNoPermissionError, @"unexpected error code %ld", [error code]); [self pause]; }]; [self runUntilPaused]; } } // NSFileManager will happily delete a directory that contains stuff, so // currently CK2FileManager is doing the same thing. // If we ever change that, set the following variable to 1 to test for it #define DELETING_DIRECTORY_WITH_ITEMS_FAILS 0 - (void)testRemoveFileAtURLDirectoryContainingItems { if ([self setupTest]) { NSURL* temp = [self makeTestContents]; NSURL* subdirectory = [temp URLByAppendingPathComponent:@"subfolder"]; // remove subdirectory that has something in it - should fail [self.manager removeItemAtURL:subdirectory completionHandler:^(NSError *error) { #if DELETING_DIRECTORY_WITH_ITEMS_FAILS STAssertNotNil(error, @"expected error"); STAssertTrue([[error domain] isEqualToString:NSCocoaErrorDomain], @"unexpected error domain %@", [error domain]); STAssertEquals([error code], (NSInteger) NSFileNoSuchFileError, @"unexpected error code %ld", [error code]); #else XCTAssertNil(error, @"got unexpected error %@", error); #endif [self pause]; }]; [self runUntilPaused]; #if DELETING_DIRECTORY_WITH_ITEMS_FAILS NSFileManager* fm = [NSFileManager defaultManager]; STAssertTrue([fm fileExistsAtPath:[subdirectory path]], @"removal should have failed"); #endif } } @end ================================================ FILE: UnitTests/PathTests.m ================================================ // // PathTests.m // Connection // // Created by Sam Deane on 08/03/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "CK2FileManager.h" #import @interface PathTests : XCTestCase @end @implementation PathTests - (void)testNilURL { NSString *path; XCTAssertNoThrow(path = [CK2FileManager pathOfURL:nil]); XCTAssertNil(path); } - (void)testFTPEmptyNoTrailingSlash { NSURL* testURL = [NSURL URLWithString:@"ftp://user:pass@test.ftp.com"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path length] == 0, @"path should be empty"); } - (void)testFTPEmptyTrailingSlash { NSURL* testURL = [NSURL URLWithString:@"ftp://user:pass@test.ftp.com/"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path length] == 0, @"path should be empty"); } - (void)testFTPRelative { NSURL* testURL = [NSURL URLWithString:@"ftp://user:pass@test.ftp.com/relative/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"relative/path/file.txt"], @"path shouldn't start with slash"); } - (void)testFTPAbsolute { NSURL* testURL = [NSURL URLWithString:@"ftp://user:pass@test.ftp.com//absolute/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/absolute/path/file.txt"], @"path should start with slash"); } - (void)testFTPAbsolutePercentEncoded { NSURL* testURL = [NSURL URLWithString:@"ftp://user:pass@test.ftp.com/%2Fabsolute/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/absolute/path/file.txt"], @"path should start with slash"); } - (void)testFTPAbsolutePercentEncodedExtraSlash { NSURL* testURL = [NSURL URLWithString:@"ftp://user:pass@test.ftp.com/%2F/absolute/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"//absolute/path/file.txt"], @"path should start with slash"); } - (void)testFTPRoot { NSURL* testURL = [NSURL URLWithString:@"ftp://user:pass@test.ftp.com//"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/"], @"path should start with slash"); } - (void)testFTPRootPercentEncoded { NSURL* testURL = [NSURL URLWithString:@"ftp://user:pass@test.ftp.com/%2F"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/"], @"path should start with slash"); } - (void)testFTPRootPercentEncodedExtraSlash { NSURL* testURL = [NSURL URLWithString:@"ftp://user:pass@test.ftp.com/%2F/"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/"], @"trailing slashes should be trimmed"); } - (void)testHTTPEmptyNoTrailingSlash { NSURL* testURL = [NSURL URLWithString:@"http://www.test.com:8080"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path length] == 0, @"path should be empty"); } - (void)testHTTPEmptyTrailingSlash { NSURL* testURL = [NSURL URLWithString:@"http://www.test.com:8080/"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/"], @"path should be /"); } - (void)testHTTPRelative { NSURL* testURL = [NSURL URLWithString:@"http://www.test.com:8080/relative/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/relative/path/file.txt"], @"path should be absolute from root"); } - (void)testHTTPAbsolute { NSURL* testURL = [NSURL URLWithString:@"http://www.test.com:8080//absolute/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"//absolute/path/file.txt"], @"path should start with double slash"); } - (void)testSFTPEmptyNoTrailingSlash { NSURL* testURL = [NSURL URLWithString:@"sftp://user@test.sftp.com"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path length] == 0, @"path should be empty"); } - (void)testSFTPEmptyTrailingSlash { NSURL* testURL = [NSURL URLWithString:@"sftp://user@test.sftp.com/"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/"], @"path should be /"); } - (void)testSFTPRelative { NSURL* testURL = [NSURL URLWithString:@"sftp://user@test.sftp.com/relative/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/relative/path/file.txt"], @"path should be absolute from root"); } - (void)testSFTPAbsolute { NSURL* testURL = [NSURL URLWithString:@"sftp://user@test.sftp.com//absolute/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"//absolute/path/file.txt"], @"path should start with double slash"); } - (void)testSFTPUser { NSURL* testURL = [NSURL URLWithString:@"sftp://user@test.sftp.com/~/absolute/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"absolute/path/file.txt"], @"path should strip the /~"); } - (void)testSCPEmptyNoTrailingSlash { NSURL* testURL = [NSURL URLWithString:@"scp://user@test.scp.com"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path length] == 0, @"path should be empty"); } - (void)testSCPEmptyTrailingSlash { NSURL* testURL = [NSURL URLWithString:@"scp://user@test.scp.com/"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/"], @"path should be /"); } - (void)testSCPRelative { NSURL* testURL = [NSURL URLWithString:@"scp://user@test.scp.com/relative/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"/relative/path/file.txt"], @"path should be absolute from root"); } - (void)testSCPAbsolute { NSURL* testURL = [NSURL URLWithString:@"scp://user@test.scp.com//absolute/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"//absolute/path/file.txt"], @"path should start with double slash"); } - (void)testSCPUser { NSURL* testURL = [NSURL URLWithString:@"scp://user@test.scp.com/~/absolute/path/file.txt"]; NSString* path = [CK2FileManager pathOfURL:testURL]; XCTAssertTrue([path isEqualToString:@"absolute/path/file.txt"], @"path should strip the /~"); } @end ================================================ FILE: UnitTests/README.md ================================================ A quick overview of the unit tests, since some of them are fairly involved. # Server Configuration Since some of the tests talk to servers, the tests can be configured using the defaults command: defaults write otest with a key of CKWebDAVTest, CKFTPTest, or CKSFTPTest. Setting the value to the url of a server will use that server for the tests. Setting the value to "MockServer" will use MockServer (where possible). Setting the value to "Off" will disable those tests. # Tests ## CK2FileManagerPathTests These test that [CK2FileManager pathOfURL] works correctly. ## CK2FileManagerFileTests These test the file manager's support for file operations on the local machine (ie using file:// urls). ## CK2FileManagerFTPTests These test the file manager's support for FTP and SFTP. When the test suite is constructed, it actually makes two test suites containing the same tests, one using ftp and one sftp. The underpinning inherited from CK2FileManagerBaseTests is used to ensure that the correct server value is read from the defaults, and the correct MockServer responses file is loaded if appropriate. ## CK2FileManagerFTPAuthenticationTests This tests FTP support, in the specific situation where the first authentication attempt is bad, but the second one is good. ## CK2FileManagerURLTests These test the CK2FileManager routines for creating URLs: - [CK2FileManager URLWithPath:relativeToURL:] - [CK2FileManager URLWithPath:hostURL:] ## CK2CURLProtocolURLManipulationTests These perform various URL manipulation tests. - Some tests check that NSURL is behaving as expected. - Some check that CFURLHasDirectoryPath() hasn't changed behaviour. - Some check that [CK2FTPProtocol newRequestWithRequest:isDirectory:] is working. ## CK2FileManagerBaseTests This is a base class used by the other tests. ================================================ FILE: UnitTests/SFTPTests.m ================================================ // // Created by Sam Deane on 06/11/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "BaseCKProtocolTests.h" @interface SFTPTests : BaseCKProtocolTests @end @implementation SFTPTests - (NSString*)protocol { return @"SFTP"; } - (BOOL)protocolUsesAuthentication { return YES; } @end ================================================ FILE: UnitTests/URLAppendingTests.m ================================================ // // URLAppendingTests.m // Connection // // Created by Mike on 31/01/2013. // // #import @interface URLAppendingTests : XCTestCase @end @implementation URLAppendingTests - (void)testFTPAppendToRoot; { XCTAssertEqualObjects([[NSURL URLWithString:@"ftp://example.com/%2F"] URLByAppendingPathComponent:@"test1.txt"], [NSURL URLWithString:@"ftp://example.com/%2F/test1.txt"]); XCTAssertEqualObjects([[NSURL URLWithString:@"ftp://example.com//"] URLByAppendingPathComponent:@"test1.txt"], [NSURL URLWithString:@"ftp://example.com//test1.txt"]); } - (void)testFTPAppendToAbsoluteDirectory; { XCTAssertEqualObjects([[NSURL URLWithString:@"ftp://example.com/%2Ftest/"] URLByAppendingPathComponent:@"test1.txt"], [NSURL URLWithString:@"ftp://example.com/%2Ftest/test1.txt"]); XCTAssertEqualObjects([[NSURL URLWithString:@"ftp://example.com//test/"] URLByAppendingPathComponent:@"test1.txt"], [NSURL URLWithString:@"ftp://example.com//test/test1.txt"]); } - (void)testFTPAppendToHome; { XCTAssertEqualObjects([[NSURL URLWithString:@"ftp://example.com/"] URLByAppendingPathComponent:@"test1.txt"], [NSURL URLWithString:@"ftp://example.com/test1.txt"]); } - (void)testFTPAppendToHomeSubdirectory; { XCTAssertEqualObjects([[NSURL URLWithString:@"ftp://example.com/test/"] URLByAppendingPathComponent:@"test1.txt"], [NSURL URLWithString:@"ftp://example.com/test/test1.txt"]); } - (void)testSFTPAppendToRoot; { XCTAssertEqualObjects([[NSURL URLWithString:@"sftp://example.com/"] URLByAppendingPathComponent:@"test1.txt"], [NSURL URLWithString:@"sftp://example.com/test1.txt"]); } - (void)testSFTPAppendToAbsoluteDirectory; { XCTAssertEqualObjects([[NSURL URLWithString:@"sftp://example.com/test/"] URLByAppendingPathComponent:@"test1.txt"], [NSURL URLWithString:@"sftp://example.com/test/test1.txt"]); } - (void)testSFTPAppendToHome; { XCTAssertEqualObjects([[NSURL URLWithString:@"sftp://example.com/~/"] URLByAppendingPathComponent:@"test1.txt"], [NSURL URLWithString:@"sftp://example.com/~/test1.txt"]); } - (void)testSFTPAppendToHomeSubdirectory; { XCTAssertEqualObjects([[NSURL URLWithString:@"sftp://example.com/~/test/"] URLByAppendingPathComponent:@"test1.txt"], [NSURL URLWithString:@"sftp://example.com/~/test/test1.txt"]); } - (void)testFTPRootIsDirectory; { BOOL isDirectory = CFURLHasDirectoryPath((CFURLRef)[NSURL URLWithString:@"ftp://example.com/%2F"]); XCTAssertFalse(isDirectory); } - (void)testFTPRootWithTrailingSlashIsDirectory; { BOOL isDirectory = CFURLHasDirectoryPath((CFURLRef)[NSURL URLWithString:@"ftp://example.com/%2F/"]); XCTAssertTrue(isDirectory); } - (void)testFTPHomeIsDirectory; { BOOL isDirectory = CFURLHasDirectoryPath((CFURLRef)[NSURL URLWithString:@"ftp://example.com/"]); XCTAssertTrue(isDirectory); } - (void)testFTPHomeWithoutTrailingSlashIsDirectory; { BOOL isDirectory = CFURLHasDirectoryPath((CFURLRef)[NSURL URLWithString:@"ftp://example.com"]); XCTAssertFalse(isDirectory); } - (void)testFTPAbsoluteFileIsDirectory; { BOOL isDirectory = CFURLHasDirectoryPath((CFURLRef)[NSURL URLWithString:@"ftp://example.com/%2F/test.txt"]); XCTAssertFalse(isDirectory); } - (void)testFTPRelativeFileIsDirectory; { BOOL isDirectory = CFURLHasDirectoryPath((CFURLRef)[NSURL URLWithString:@"ftp://example.com/test.txt"]); XCTAssertFalse(isDirectory); } @end ================================================ FILE: UnitTests/URLCanonicalizationTests.m ================================================ // // URLAppendingTests.m // Connection // // Created by Mike on 31/01/2013. // // #import #import "CK2CURLBasedProtocol.h" @interface URLCanonicalizationTests : XCTestCase @end @implementation URLCanonicalizationTests - (void)testIncludingUser; { // Test first with no user set. e.g. anonymous login NSURL *url = [CK2CURLBasedProtocol URLByReplacingUserInfoInURL:[NSURL URLWithString:@"ftp://example.com/image.png"] withUser:nil]; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com/image.png"); // Adding user into the URL url = [CK2CURLBasedProtocol URLByReplacingUserInfoInURL:[NSURL URLWithString:@"ftp://example.com/image.png"] withUser:@"user"]; XCTAssertEqualObjects(url.absoluteString, @"ftp://user@example.com/image.png"); // Replacing existing user url = [CK2CURLBasedProtocol URLByReplacingUserInfoInURL:[NSURL URLWithString:@"ftp://test@example.com/image.png"] withUser:@"user"]; XCTAssertEqualObjects(url.absoluteString, @"ftp://user@example.com/image.png"); // Replacing existing user + password url = [CK2CURLBasedProtocol URLByReplacingUserInfoInURL:[NSURL URLWithString:@"ftp://test:sekret@example.com/image.png"] withUser:@"user"]; XCTAssertEqualObjects(url.absoluteString, @"ftp://user@example.com/image.png"); // Escaping of unusual characters url = [CK2CURLBasedProtocol URLByReplacingUserInfoInURL:[NSURL URLWithString:@"ftp://example.com/image.png"] withUser:@"user/:1@example.com"]; XCTAssertEqualObjects(url.absoluteString, @"ftp://user%2F%3A1%40example.com@example.com/image.png"); } @end ================================================ FILE: UnitTests/URLDirectoryTests.m ================================================ // // URLAppendingTests.m // Connection // // Created by Mike on 31/01/2013. // // #import #import "CK2FTPProtocol.h" @interface CK2CURLBasedProtocol (UnitTesting) + (NSURLRequest *)newRequestWithRequest:(NSURLRequest *)request isDirectory:(BOOL)directory; @end @interface URLDirectoryTests : XCTestCase @end @implementation URLDirectoryTests /* We test with all absolute FTP URL forms to make sure they don't get mangled */ - (void)testFileURL; { NSURL *url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com//test"]] isDirectory:NO].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com//test"); url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com/%2Ftest"]] isDirectory:NO].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com/%2Ftest"); url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com/%2F/test"]] isDirectory:NO].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com/%2F/test"); } - (void)testMakingFileIntoFolder; { NSURL *url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com//test"]] isDirectory:YES].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com//test/"); url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com/%2Ftest"]] isDirectory:YES].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com/%2Ftest/"); url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com/%2F/test"]] isDirectory:YES].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com/%2F/test/"); // This has been giving some problems where Cocoa normally recognises the %2F as a folder, I think url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com/%2F"]] isDirectory:YES].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com/%2F/"); } - (void)testFolderURL; { NSURL *url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com//test/"]] isDirectory:YES].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com//test/"); url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com/%2Ftest/"]] isDirectory:YES].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com/%2Ftest/"); url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com/%2F/test/"]] isDirectory:YES].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com/%2F/test/"); url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com/%2F/"]] isDirectory:YES].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com/%2F/"); } - (void)testMakingFolderIntoFile; { NSURL *url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com//test/"]] isDirectory:NO].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com//test"); url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com/%2Ftest/"]] isDirectory:NO].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com//test"); url = [CK2FTPProtocol newRequestWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://example.com/%2F/test/"]] isDirectory:NO].URL; XCTAssertEqualObjects(url.absoluteString, @"ftp://example.com/%2F/test"); } @end ================================================ FILE: UnitTests/URLTests.m ================================================ // // URLTests.m // Connection // // Created by Mike Abdullah on 09/03/2012. // Copyright (c) 2012 Karelia Software. All rights reserved. // #import "CK2FileManager.h" #import @interface URLTests : XCTestCase @end @implementation URLTests - (void)testURLs { NSURL *suiteURL = [[NSBundle bundleForClass:self.class] URLForResource:@"URLs" withExtension:@"testdata"]; NSArray *suite = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:suiteURL] options:0 error:NULL]; XCTAssertTrue(suite.count >= 1); for (NSDictionary *values in suite) { NSString *path = values[@"path"]; BOOL directory = [values[@"isDirectory"] boolValue]; NSURL *base = [NSURL URLWithString:values[@"hostURL"]]; NSURL *url = [CK2FileManager URLWithPath:path isDirectory:directory hostURL:base]; XCTAssertEqualObjects(url.absoluteString, values[@"Output"]); XCTAssertNil(url.baseURL, @"+URLWithPath:isDirectory:hostURL: should always return an absolute URL"); } } #pragma mark FTP - (void)testFTPRelative { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/file.txt" relativeToURL:[NSURL URLWithString:@"ftp://user:pass@test.ftp.com"]]; XCTAssertTrue([[url absoluteString] isEqualToString:@"ftp://user:pass@test.ftp.com/relative/path/file.txt"], @"path should start with slash"); } - (void)testFTPRelativeDirectory { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/directory" relativeToURL:[NSURL URLWithString:@"ftp://user:pass@test.ftp.com"]]; XCTAssertTrue([[url absoluteString] isEqualToString:@"ftp://user:pass@test.ftp.com/relative/path/directory"], @"path should start with slash"); } - (void)testFTPAbsolute { NSURL *url = [CK2FileManager URLWithPath:@"/absolute/path/file.txt" relativeToURL:[NSURL URLWithString:@"ftp://user:pass@test.ftp.com"]]; XCTAssertEqualObjects([url absoluteString], @"ftp://user:pass@test.ftp.com//absolute/path/file.txt"); } - (void)testFTPAbsoluteNonRootURL { NSURL *url = [CK2FileManager URLWithPath:@"/absolute/path/file.txt" relativeToURL:[NSURL URLWithString:@"ftp://user:pass@test.ftp.com/example/path/"]]; XCTAssertEqualObjects([url absoluteString], @"ftp://user:pass@test.ftp.com//absolute/path/file.txt"); } - (void)testFTPRelativeNonRootFolderURL { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/file.txt" relativeToURL:[NSURL URLWithString:@"ftp://user:pass@test.ftp.com/example/path/"]]; XCTAssertEqualObjects(url.absoluteString, @"ftp://user:pass@test.ftp.com/example/path/relative/path/file.txt"); } - (void)testFTPRelativeNonRootFileURL { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/file.txt" relativeToURL:[NSURL URLWithString:@"ftp://user:pass@test.ftp.com/example/file"]]; XCTAssertEqualObjects(url.absoluteString, @"ftp://user:pass@test.ftp.com/example/relative/path/file.txt"); } - (void)testFTPRoot { NSURL *url = [CK2FileManager URLWithPath:@"/" relativeToURL:[NSURL URLWithString:@"ftp://user:pass@test.ftp.com"]]; XCTAssertTrue([[url absoluteString] isEqualToString:@"ftp://user:pass@test.ftp.com//"]); } - (void)testFTPRootNonRootURL { NSURL *url = [CK2FileManager URLWithPath:@"/" relativeToURL:[NSURL URLWithString:@"ftp://user:pass@test.ftp.com/example/path/"]]; XCTAssertTrue([[url absoluteString] isEqualToString:@"ftp://user:pass@test.ftp.com//"]); } #pragma mark WebDAV - (void)testHTTPRelative { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/file.txt" relativeToURL:[NSURL URLWithString:@"http://www.test.com:8080"]]; XCTAssertEqualObjects([url absoluteString], @"http://www.test.com:8080/relative/path/file.txt", @"path should be normal"); } - (void)testHTTPRelativeDirectory { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/directory" relativeToURL:[NSURL URLWithString:@"http://www.test.com:8080"]]; XCTAssertEqualObjects([url absoluteString], @"http://www.test.com:8080/relative/path/directory", @"path should be normal"); } - (void)testHTTPAbsolute { NSURL *url = [CK2FileManager URLWithPath:@"/absolute/path/file.txt" relativeToURL:[NSURL URLWithString:@"http://www.test.com:8080"]]; XCTAssertEqualObjects([url absoluteString], @"http://www.test.com:8080/absolute/path/file.txt", @"path should be normal"); } - (void)testHTTPRelativeNonRootFolderURL { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/file.txt" relativeToURL:[NSURL URLWithString:@"http://www.test.com:8080/example/path/"]]; XCTAssertEqualObjects([url absoluteString], @"http://www.test.com:8080/example/path/relative/path/file.txt"); } - (void)testHTTPRelativeNonRootFileURL { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/file.txt" relativeToURL:[NSURL URLWithString:@"http://www.test.com:8080/example/file"]]; XCTAssertEqualObjects([url absoluteString], @"http://www.test.com:8080/example/relative/path/file.txt"); } #pragma mark SSH - (void)testSFTPRelative { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/file.txt" relativeToURL:[NSURL URLWithString:@"sftp://user@test.sftp.com"]]; XCTAssertEqualObjects([url absoluteString], @"sftp://user@test.sftp.com/~/relative/path/file.txt", @"path should start with ~"); } - (void)testSFTPRelativeTrailingSlash { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/file.txt" relativeToURL:[NSURL URLWithString:@"sftp://user@test.sftp.com/"]]; XCTAssertEqualObjects([url absoluteString], @"sftp://user@test.sftp.com/~/relative/path/file.txt", @"path should start with ~"); } - (void)testSFTPRelativeDirectory { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/directory" relativeToURL:[NSURL URLWithString:@"sftp://user@test.sftp.com"]]; XCTAssertEqualObjects([url absoluteString], @"sftp://user@test.sftp.com/~/relative/path/directory", @"path should start with ~"); } - (void)testSFTPAbsolute { NSURL *url = [CK2FileManager URLWithPath:@"/absolute/path/file.txt" relativeToURL:[NSURL URLWithString:@"sftp://user@test.sftp.com"]]; XCTAssertEqualObjects([url absoluteString], @"sftp://user@test.sftp.com/absolute/path/file.txt", @"path should be normal"); } - (void)testSFTPAbsoluteTrailingSlash { NSURL *url = [CK2FileManager URLWithPath:@"/absolute/path/file.txt" relativeToURL:[NSURL URLWithString:@"sftp://user@test.sftp.com/"]]; XCTAssertEqualObjects([url absoluteString], @"sftp://user@test.sftp.com/absolute/path/file.txt", @"path should be normal"); } - (void)testSCPRelative { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/file.txt" relativeToURL:[NSURL URLWithString:@"scp://user@test.scp.com"]]; XCTAssertEqualObjects([url absoluteString], @"scp://user@test.scp.com/~/relative/path/file.txt", @"path should start with ~"); } - (void)testSCPRelativeTrailingSlash { NSURL *url = [CK2FileManager URLWithPath:@"relative/path/file.txt" relativeToURL:[NSURL URLWithString:@"scp://user@test.scp.com/"]]; XCTAssertEqualObjects([url absoluteString], @"scp://user@test.scp.com/~/relative/path/file.txt", @"path should start with ~"); } - (void)testSCPAbsolute { NSURL *url = [CK2FileManager URLWithPath:@"/absolute/path/file.txt" relativeToURL:[NSURL URLWithString:@"scp://user@test.scp.com"]]; XCTAssertEqualObjects([url absoluteString], @"scp://user@test.scp.com/absolute/path/file.txt", @"path should be normal"); } - (void)testSCPAbsoluteTrailingSlash { NSURL *url = [CK2FileManager URLWithPath:@"/absolute/path/file.txt" relativeToURL:[NSURL URLWithString:@"scp://user@test.scp.com/"]]; XCTAssertEqualObjects([url absoluteString], @"scp://user@test.scp.com/absolute/path/file.txt", @"path should be normal"); } @end ================================================ FILE: UnitTests/URLs.testdata ================================================ [ { "path" : "relative\/path to\/file.txt", "hostURL" : "ftp:\/\/user:pass@test.ftp.com", "Output" : "ftp:\/\/user:pass@test.ftp.com\/relative\/path%20to\/file.txt", "isDirectory" : 0, "Notes" : "Where possible, test with a space in the path to ensure percent encoding is doing its job properly" }, { "path" : "relative\/path to\/directory", "hostURL" : "ftp:\/\/user:pass@test.ftp.com", "Output" : "ftp:\/\/user:pass@test.ftp.com\/relative\/path%20to\/directory\/", "isDirectory" : 1 }, { "path" : "", "hostURL" : "ftp:\/\/user:pass@test.ftp.com:21", "Output" : "ftp:\/\/user:pass@test.ftp.com:21\/", "isDirectory" : 1 }, { "path" : "\/absolute\/path to\/file.txt", "hostURL" : "ftp:\/\/user:pass@test.ftp.com", "Output" : "ftp:\/\/user:pass@test.ftp.com\/\/absolute\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "\/absolute\/path to\/file.txt", "hostURL" : "ftp:\/\/user:pass@test.ftp.com\/example\/path\/", "Output" : "ftp:\/\/user:pass@test.ftp.com\/\/absolute\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/file.txt", "hostURL" : "ftp:\/\/user:pass@test.ftp.com\/example\/path\/", "Output" : "ftp:\/\/user:pass@test.ftp.com\/relative\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/file.txt", "hostURL" : "ftp:\/\/user:pass@test.ftp.com\/example\/file", "Output" : "ftp:\/\/user:pass@test.ftp.com\/relative\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "\/", "hostURL" : "ftp:\/\/user:pass@test.ftp.com", "Output" : "ftp:\/\/user:pass@test.ftp.com\/\/", "isDirectory" : 1 }, { "path" : "\/", "hostURL" : "ftp:\/\/user:pass@test.ftp.com", "Output" : "ftp:\/\/user:pass@test.ftp.com\/\/", "isDirectory" : 0 }, { "path" : "\/", "hostURL" : "ftp:\/\/user:pass@test.ftp.com\/example\/path\/", "Output" : "ftp:\/\/user:pass@test.ftp.com\/\/", "isDirectory" : 1 }, { "path" : "\/", "hostURL" : "ftp:\/\/user:pass@test.ftp.com\/example\/path\/", "Output" : "ftp:\/\/user:pass@test.ftp.com\/\/", "isDirectory" : 0 }, { "path" : "relative\/path to\/file.txt", "hostURL" : "http:\/\/www.test.com:8080", "Output" : "http:\/\/www.test.com:8080\/relative\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/directory", "Output" : "http:\/\/www.test.com:8080\/relative\/path%20to\/directory\/", "hostURL" : "http:\/\/www.test.com:8080", "isDirectory" : 1 }, { "path" : "\/absolute\/path to\/file.txt", "hostURL" : "http:\/\/www.test.com:8080", "Output" : "http:\/\/www.test.com:8080\/absolute\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/file.txt", "hostURL" : "http:\/\/www.test.com:8080\/example\/path\/", "Output" : "http:\/\/www.test.com:8080\/relative\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/file.txt", "hostURL" : "http:\/\/www.test.com:8080\/example\/file", "Output" : "http:\/\/www.test.com:8080\/relative\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/file.txt", "hostURL" : "sftp:\/\/user@test.sftp.com", "Output" : "sftp:\/\/user@test.sftp.com\/~\/relative\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/file.txt", "hostURL" : "sftp:\/\/user@test.sftp.com\/", "Output" : "sftp:\/\/user@test.sftp.com\/~\/relative\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/file.txt", "hostURL" : "sftp:\/\/user:pass@test.ftp.com\/example\/path\/", "Output" : "sftp:\/\/user:pass@test.ftp.com\/~\/relative\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/file.txt", "hostURL" : "sftp:\/\/user:pass@test.ftp.com\/example\/file", "Output" : "sftp:\/\/user:pass@test.ftp.com\/~\/relative\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/directory", "hostURL" : "sftp:\/\/user@test.sftp.com", "Output" : "sftp:\/\/user@test.sftp.com\/~\/relative\/path%20to\/directory\/", "isDirectory" : 1 }, { "path" : "\/absolute\/path to\/file.txt", "hostURL" : "sftp:\/\/user@test.sftp.com", "Output" : "sftp:\/\/user@test.sftp.com\/absolute\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "relative\/path to\/file.txt", "hostURL" : "scp:\/\/user@test.scp.com", "Output" : "scp:\/\/user@test.scp.com\/~\/relative\/path%20to\/file.txt", "isDirectory" : 0 }, { "path" : "\/Users\/Shared", "hostURL" : "file:\/\/\/foo\/bar", "Output" : "file:\/\/\/Users\/Shared\/", "isDirectory" : 1, "Notes" : "My standard trick of making a directory by appending path component of @\"\" turns out to have a caveat: http:\/\/www.mikeabdullah.net\/guaranteeing-directory-urls.html. This isn't too good for our clients as an inconsistency" } ] ================================================ FILE: UnitTests/UnitTest-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleSignature ???? CFBundleVersion 1.0 ================================================ FILE: UnitTests/UnitTests_Prefix.pch ================================================ //#define MockServerLogDetail(...) ================================================ FILE: UnitTests/WebDAVTests.m ================================================ // // Created by Sam Deane on 06/11/2012. // Copyright 2012 Karelia Software. All rights reserved. // #import "BaseCKProtocolTests.h" @interface WebDAVTests : BaseCKProtocolTests @end @implementation WebDAVTests - (NSString*)protocol { return @"WebDAV"; } - (BOOL)protocolUsesAuthentication { return YES; } - (NSData*)mockServerDirectoryListingData { NSString* xml = [NSString stringWithFormat: @"" "" "%@" "" "" "" "" "HTTP/1.1 200 OK" "" "" "" "%@" "" "" "" "" "HTTP/1.1 200 OK" "" "" "" "%@" "" "" "" "" "HTTP/1.1 200 OK" "" "" "\r\n", [self URLForTestFolder], [self URLForTestFile1], [self URLForTestFile2]]; NSData* data = [xml dataUsingEncoding:NSUTF8StringEncoding]; return data; } @end ================================================ FILE: UnitTests/test.sh ================================================ #!/usr/bin/env bash # This script builds and runs the unit tests and produces output in a format that is compatible with Jenkins. base=`dirname $0` echo "$base" pushd "$base/.." > /dev/null build="$PWD/test-build" ocunit2junit="$base/../CurlHandle/CURLHandleSource/Tests/MockServer/UnitTests/OCUnit2JUnit/bin/ocunit2junit" popd > /dev/null sym="$build/sym" obj="$build/obj" testout="$build/output.log" testerr="$build/error.log" rm -rf "$build" mkdir -p "$build" echo Building 32-bit xcodebuild -workspace "ConnectionKit.xcworkspace" -scheme "ConnectionKit" -sdk "macosx" -config "Debug" -arch i386 build OBJROOT="$obj" SYMROOT="$sym" > "$testout" 2> "$testerr" if [ $? != 0 ]; then echo "32-bit build failed" cat "$testerr" fi echo Building and Testing 64-bit xcodebuild -workspace "ConnectionKit.xcworkspace" -scheme "ConnectionKit" -sdk "macosx" -config "Debug" -arch x86_64 test OBJROOT="$obj" SYMROOT="$sym" > "$testout" 2> "$testerr" if [ $? != 0 ]; then echo "64-bit build failed" cat "$testerr" else cd "$build" "../$ocunit2junit" < "$testout" fi ================================================ FILE: UnitTests/use-ftp-server.sh ================================================ #!/usr/bin/env bash defaults write otest CKFTPTestURL $1 ================================================ FILE: UnitTests/use-mockserver.sh ================================================ #!/usr/bin/env bash defaults write otest CKFTPTestURL MockServer defaults write otest CKWebDAVTestURL MockServer defaults write otest CKSFTPTestURL MockServer ================================================ FILE: UnitTests/use-sftp-server.sh ================================================ #!/usr/bin/env bash defaults write otest CKSFTPTestURL $1 ================================================ FILE: UnitTests/use-webdav-server.sh ================================================ #!/usr/bin/env bash defaults write otest CKWebDAVTestURL $1 ================================================ FILE: version.plist ================================================ BuildVersion 92 CFBundleVersion 1.0 ProductBuildVersion 7K571 ProjectName NibPBTemplates SourceVersion 1200000