Repository: alamaison/swish Branch: develop Commit: 2a2140d9b521 Files: 460 Total size: 2.6 MB Directory structure: gitextract_02n81_nz/ ├── .clang-format ├── .gitattributes ├── .tx/ │ └── config ├── CMakeLists.txt ├── COPYING.rtf ├── LICENSE.txt ├── NEWS ├── README.md ├── appcast.xml.in ├── build/ │ ├── making_a_release.txt │ └── news_html.py ├── cmake/ │ ├── GetGitRevisionDescription.cmake │ ├── GetGitRevisionDescription.cmake.in │ ├── Hunter/ │ │ └── config.cmake │ └── HunterGate.cmake ├── cpack_wix_patch.xml ├── ezel/ │ ├── CMakeLists.txt │ ├── control.hpp │ ├── control_parent_impl.hpp │ ├── controls/ │ │ ├── button.hpp │ │ ├── checkbox.hpp │ │ ├── edit.hpp │ │ ├── icon.hpp │ │ ├── label.hpp │ │ ├── line.hpp │ │ └── spinner.hpp │ ├── detail/ │ │ ├── command_dispatch.hpp │ │ ├── dialog_template.hpp │ │ ├── hooks.hpp │ │ ├── hwnd_linking.hpp │ │ ├── message_dispatch.hpp │ │ ├── window_impl.hpp │ │ ├── window_link.hpp │ │ ├── window_proc.hpp │ │ └── window_proxy.hpp │ ├── form.hpp │ └── window.hpp ├── guids.txt ├── po/ │ ├── CMakeLists.txt │ ├── bg/ │ │ ├── swish.mo │ │ └── swish.po │ ├── ca/ │ │ ├── swish.mo │ │ └── swish.po │ ├── compile_mo.sh │ ├── cs/ │ │ ├── swish.mo │ │ └── swish.po │ ├── cy/ │ │ ├── swish.mo │ │ └── swish.po │ ├── da_DK/ │ │ ├── swish.mo │ │ └── swish.po │ ├── de/ │ │ ├── swish.mo │ │ └── swish.po │ ├── el_GR/ │ │ ├── swish.mo │ │ └── swish.po │ ├── es/ │ │ ├── swish.mo │ │ └── swish.po │ ├── et/ │ │ ├── swish.mo │ │ └── swish.po │ ├── fi/ │ │ ├── swish.mo │ │ └── swish.po │ ├── fr/ │ │ ├── swish.mo │ │ └── swish.po │ ├── he/ │ │ ├── swish.mo │ │ └── swish.po │ ├── hi/ │ │ ├── swish.mo │ │ └── swish.po │ ├── hu/ │ │ ├── swish.mo │ │ └── swish.po │ ├── it/ │ │ ├── swish.mo │ │ └── swish.po │ ├── ja/ │ │ ├── swish.mo │ │ └── swish.po │ ├── ko/ │ │ ├── swish.mo │ │ └── swish.po │ ├── lv/ │ │ ├── swish.mo │ │ └── swish.po │ ├── nl/ │ │ ├── swish.mo │ │ └── swish.po │ ├── pl/ │ │ ├── swish.mo │ │ └── swish.po │ ├── pt/ │ │ ├── swish.mo │ │ └── swish.po │ ├── pt_BR/ │ │ ├── swish.mo │ │ └── swish.po │ ├── ro/ │ │ ├── swish.mo │ │ └── swish.po │ ├── ru/ │ │ ├── swish.mo │ │ └── swish.po │ ├── sk/ │ │ ├── swish.mo │ │ └── swish.po │ ├── sv/ │ │ ├── swish.mo │ │ └── swish.po │ ├── template/ │ │ └── swish.pot │ ├── tr/ │ │ ├── swish.mo │ │ └── swish.po │ ├── zh_CN/ │ │ ├── swish.mo │ │ └── swish.po │ └── zh_TW/ │ ├── swish.mo │ └── swish.po ├── setup_conf.xml.in ├── ssh/ │ ├── .clang-format │ ├── CMakeLists.txt │ ├── agent.hpp │ ├── detail/ │ │ ├── agent_state.hpp │ │ ├── file_handle_state.hpp │ │ ├── libssh2/ │ │ │ ├── agent.hpp │ │ │ ├── knownhost.hpp │ │ │ ├── libssh2.hpp │ │ │ ├── session.hpp │ │ │ ├── sftp.hpp │ │ │ └── userauth.hpp │ │ ├── session_state.hpp │ │ └── sftp_channel_state.hpp │ ├── filesystem/ │ │ └── path.hpp │ ├── filesystem.hpp │ ├── host_key.hpp │ ├── knownhost.hpp │ ├── session.hpp │ ├── sftp_error.hpp │ ├── ssh_error.hpp │ └── stream.hpp ├── swish/ │ ├── CMakeLists.txt │ ├── CoFactory.hpp │ ├── atl.hpp │ ├── connection/ │ │ ├── CMakeLists.txt │ │ ├── authenticated_session.cpp │ │ ├── authenticated_session.hpp │ │ ├── connection.vcproj │ │ ├── connection_spec.cpp │ │ ├── connection_spec.hpp │ │ ├── interruptable_session.hpp │ │ ├── running_session.cpp │ │ ├── running_session.hpp │ │ ├── session_manager.cpp │ │ ├── session_manager.hpp │ │ ├── session_pool.cpp │ │ └── session_pool.hpp │ ├── debug.hpp │ ├── drop_target/ │ │ ├── CMakeLists.txt │ │ ├── CopyFileOperation.cpp │ │ ├── CopyFileOperation.hpp │ │ ├── CreateDirectoryOperation.cpp │ │ ├── CreateDirectoryOperation.hpp │ │ ├── DropActionCallback.hpp │ │ ├── DropTarget.cpp │ │ ├── DropTarget.hpp │ │ ├── DropUI.cpp │ │ ├── DropUI.hpp │ │ ├── Operation.hpp │ │ ├── PidlCopyPlan.cpp │ │ ├── PidlCopyPlan.hpp │ │ ├── Plan.hpp │ │ ├── Progress.hpp │ │ ├── RootedSource.hpp │ │ ├── SequentialPlan.cpp │ │ ├── SequentialPlan.hpp │ │ └── SftpDestination.hpp │ ├── forms/ │ │ ├── CMakeLists.txt │ │ ├── add_host.cpp │ │ ├── add_host.hpp │ │ ├── password.cpp │ │ └── password.hpp │ ├── frontend/ │ │ ├── CMakeLists.txt │ │ ├── UserInteraction.cpp │ │ ├── UserInteraction.hpp │ │ ├── announce_error.cpp │ │ ├── announce_error.hpp │ │ ├── bind_best_taskdialog.cpp │ │ ├── bind_best_taskdialog.hpp │ │ ├── commands/ │ │ │ ├── About.cpp │ │ │ └── About.hpp │ │ ├── winsparkle_shower.cpp │ │ └── winsparkle_shower.hpp │ ├── host_folder/ │ │ ├── CMakeLists.txt │ │ ├── ViewCallback.cpp │ │ ├── ViewCallback.hpp │ │ ├── columns.cpp │ │ ├── columns.hpp │ │ ├── commands/ │ │ │ ├── Add.cpp │ │ │ ├── Add.hpp │ │ │ ├── CloseSession.cpp │ │ │ ├── CloseSession.hpp │ │ │ ├── LaunchAgent.cpp │ │ │ ├── LaunchAgent.hpp │ │ │ ├── Remove.cpp │ │ │ ├── Remove.hpp │ │ │ ├── Rename.cpp │ │ │ ├── Rename.hpp │ │ │ ├── commands.cpp │ │ │ └── commands.hpp │ │ ├── context_menu_callback.cpp │ │ ├── context_menu_callback.hpp │ │ ├── extract_icon.hpp │ │ ├── host_itemid_connection.cpp │ │ ├── host_itemid_connection.hpp │ │ ├── host_management.cpp │ │ ├── host_management.hpp │ │ ├── host_pidl.hpp │ │ ├── menu_command_manager.cpp │ │ ├── menu_command_manager.hpp │ │ ├── overlay_icon.hpp │ │ ├── properties.cpp │ │ └── properties.hpp │ ├── nse/ │ │ ├── CMakeLists.txt │ │ ├── Command.cpp │ │ ├── Command.hpp │ │ ├── StaticColumn.hpp │ │ ├── UICommand.cpp │ │ ├── UICommand.hpp │ │ ├── command_site.cpp │ │ ├── command_site.hpp │ │ ├── data_object_util.cpp │ │ ├── data_object_util.hpp │ │ ├── default_context_menu_callback.cpp │ │ ├── default_context_menu_callback.hpp │ │ ├── detail/ │ │ │ └── command_state_conversion.hpp │ │ ├── explorer_command.cpp │ │ ├── explorer_command.hpp │ │ ├── task_pane.hpp │ │ ├── view_callback.cpp │ │ └── view_callback.hpp │ ├── port_conversion.hpp │ ├── provider/ │ │ ├── CMakeLists.txt │ │ ├── Provider.cpp │ │ ├── Provider.hpp │ │ ├── libssh2_sftp_filesystem_item.cpp │ │ ├── libssh2_sftp_filesystem_item.hpp │ │ ├── sftp_filesystem_item.hpp │ │ ├── sftp_provider.hpp │ │ └── ticketed_stream.hpp │ ├── remote_folder/ │ │ ├── CMakeLists.txt │ │ ├── Mode.cpp │ │ ├── Mode.h │ │ ├── ViewCallback.cpp │ │ ├── ViewCallback.hpp │ │ ├── columns.cpp │ │ ├── columns.hpp │ │ ├── commands/ │ │ │ ├── NewFolder.cpp │ │ │ ├── NewFolder.hpp │ │ │ ├── commands.cpp │ │ │ ├── commands.hpp │ │ │ ├── delete.cpp │ │ │ └── delete.hpp │ │ ├── context_menu_callback.cpp │ │ ├── context_menu_callback.hpp │ │ ├── filemode.c │ │ ├── filemode.h │ │ ├── pidl_connection.cpp │ │ ├── pidl_connection.hpp │ │ ├── properties.cpp │ │ ├── properties.hpp │ │ ├── remote_pidl.hpp │ │ └── swish_pidl.hpp │ ├── remotelimits.h │ ├── shell/ │ │ ├── CMakeLists.txt │ │ ├── parent_and_item.hpp │ │ ├── shell.cpp │ │ ├── shell.hpp │ │ ├── shell_item.hpp │ │ └── shell_item_array.hpp │ ├── shell_folder/ │ │ ├── CMakeLists.txt │ │ ├── DataObject.cpp │ │ ├── DataObject.h │ │ ├── Folder.h │ │ ├── HostFolder.cpp │ │ ├── HostFolder.h │ │ ├── IconExtractor.cpp │ │ ├── IconExtractor.h │ │ ├── KbdInteractiveDialog.cpp │ │ ├── KbdInteractiveDialog.h │ │ ├── Pidl.h │ │ ├── Registry.cpp │ │ ├── Registry.h │ │ ├── RemoteFolder.cpp │ │ ├── RemoteFolder.h │ │ ├── SftpDataObject.cpp │ │ ├── SftpDataObject.h │ │ ├── SftpDirectory.cpp │ │ ├── SftpDirectory.h │ │ ├── SnitchingDataObject.hpp │ │ ├── Swish.idl │ │ ├── SwishFolder.hpp │ │ ├── com_dll/ │ │ │ ├── CMakeLists.txt │ │ │ ├── HostFolder.rgs │ │ │ ├── RemoteFolder.rgs │ │ │ ├── Swish.rgs │ │ │ ├── SwishCoClasses.cpp │ │ │ ├── SwishModule.cpp │ │ │ ├── com_dll.def │ │ │ ├── com_dll.rc │ │ │ └── resource.h │ │ ├── data_object/ │ │ │ ├── FileGroupDescriptor.hpp │ │ │ ├── GlobalLocker.hpp │ │ │ ├── ShellDataObject.cpp │ │ │ ├── ShellDataObject.hpp │ │ │ └── StorageMedium.hpp │ │ ├── locale_setup.hpp │ │ ├── resource.h │ │ ├── shell_folder.rc │ │ └── wtl.hpp │ ├── trace.hpp │ ├── utils.hpp │ ├── versions/ │ │ ├── CMakeLists.txt │ │ ├── git_version.h.in │ │ ├── metadata.h.in │ │ ├── version.cpp │ │ └── version.hpp │ └── windows_api.hpp ├── test/ │ ├── CMakeLists.txt │ ├── common_boost/ │ │ ├── CMakeLists.txt │ │ ├── ConsumerStub.hpp │ │ ├── MockConsumer.hpp │ │ ├── MockProvider.hpp │ │ ├── SwishPidlFixture.hpp │ │ ├── data_object_utils.cpp │ │ ├── data_object_utils.hpp │ │ ├── fixtures.hpp │ │ ├── helpers.hpp │ │ ├── stream_utils.cpp │ │ ├── stream_utils.hpp │ │ ├── tree.hh │ │ └── tree.hpp │ ├── connection/ │ │ ├── CMakeLists.txt │ │ ├── authenticated_session_test.cpp │ │ ├── connection_spec_create_session_test.cpp │ │ ├── connection_spec_test.cpp │ │ ├── running_session_test.cpp │ │ ├── session_manager_test.cpp │ │ └── session_pool_test.cpp │ ├── drop_target/ │ │ ├── CMakeLists.txt │ │ ├── drop_target_test.cpp │ │ └── rooted_source_test.cpp │ ├── ezel/ │ │ ├── CMakeLists.txt │ │ └── form_test.cpp │ ├── fix_key_permissions.sh │ ├── fixtures/ │ │ ├── CMakeLists.txt │ │ ├── com_stream_fixture.cpp │ │ ├── com_stream_fixture.hpp │ │ ├── fixture_dsakey │ │ ├── fixture_dsakey.pub │ │ ├── fixture_hostkey │ │ ├── fixture_hostkey.pub │ │ ├── fixture_rsakey │ │ ├── fixture_rsakey.pub │ │ ├── fixture_wrong_dsakey │ │ ├── fixture_wrong_dsakey.pub │ │ ├── local_sandbox_fixture.cpp │ │ ├── local_sandbox_fixture.hpp │ │ ├── openssh_fixture.cpp │ │ ├── openssh_fixture.hpp │ │ ├── provider_fixture.cpp │ │ ├── provider_fixture.hpp │ │ ├── session_fixture.cpp │ │ ├── session_fixture.hpp │ │ ├── sftp_fixture.cpp │ │ ├── sftp_fixture.hpp │ │ ├── ssh_server/ │ │ │ ├── Dockerfile │ │ │ ├── authorized_keys │ │ │ └── ssh_host_rsa_key │ │ ├── test_known_hosts │ │ ├── test_known_hosts_hashed │ │ └── test_known_hosts_out │ ├── forms/ │ │ ├── CMakeLists.txt │ │ ├── add_host_test.cpp │ │ └── password_test.cpp │ ├── host_folder/ │ │ ├── CMakeLists.txt │ │ ├── columns_test.cpp │ │ ├── host_management_test.cpp │ │ ├── host_pidl_test.cpp │ │ ├── properties_test.cpp │ │ └── view_callback_test.cpp │ ├── module.cpp.in │ ├── nse/ │ │ ├── CMakeLists.txt │ │ ├── default_context_menu_callback_tests.cpp │ │ └── explorer_command_tests.cpp │ ├── provider-integration/ │ │ ├── CMakeLists.txt │ │ ├── auth_test.cpp │ │ ├── provider_test.cpp │ │ ├── stream_create_test.cpp │ │ ├── stream_read_test.cpp │ │ ├── stream_test.cpp │ │ └── stream_write_test.cpp │ ├── remote_folder/ │ │ ├── CMakeLists.txt │ │ ├── columns_test.cpp │ │ ├── properties_test.cpp │ │ ├── remote_commands_test.cpp │ │ ├── remote_pidl_test.cpp │ │ └── swish_pidl_test.cpp │ ├── shell/ │ │ ├── CMakeLists.txt │ │ └── shell_test.cpp │ ├── shell_folder/ │ │ ├── CMakeLists.txt │ │ ├── atl.cpp │ │ ├── data_object_test.cpp │ │ ├── exercise_data_object.h │ │ ├── file_group_descriptor_test.cpp │ │ ├── global_lock_test.cpp │ │ ├── remote_folder_test.cpp │ │ ├── sftp_data_object_nasty_old_test.cpp │ │ ├── sftp_data_object_test.cpp │ │ ├── sftp_directory_test.cpp │ │ ├── shell_data_object_test.cpp │ │ └── utils_test.cpp │ ├── shell_folder-com_dll/ │ │ ├── CMakeLists.txt │ │ ├── CppUnitExtensions.h │ │ ├── HostFolder_test.cpp │ │ ├── Module.cpp │ │ ├── RemoteFolder_test.cpp │ │ ├── main.cpp │ │ ├── pidl.cpp │ │ └── pidl.hpp │ ├── shell_folder-dialogue/ │ │ ├── CMakeLists.txt │ │ └── KbdInteractiveDialog_test.cpp │ ├── ssh/ │ │ ├── CMakeLists.txt │ │ ├── auth_test.cpp │ │ ├── filesystem_construction_test.cpp │ │ ├── filesystem_test.cpp │ │ ├── fixture_dsakey │ │ ├── fixture_dsakey.pub │ │ ├── fixture_hostkey │ │ ├── fixture_hostkey.pub │ │ ├── fixture_rsakey │ │ ├── fixture_rsakey.pub │ │ ├── fixture_wrong_dsakey │ │ ├── fixture_wrong_dsakey.pub │ │ ├── host_key_test.cpp │ │ ├── input_stream_test.cpp │ │ ├── io_stream_test.cpp │ │ ├── knownhost_test.cpp │ │ ├── module.cpp │ │ ├── openssh_fixture.cpp │ │ ├── openssh_fixture.hpp │ │ ├── output_stream_test.cpp │ │ ├── path_test.cpp │ │ ├── session_fixture.cpp │ │ ├── session_fixture.hpp │ │ ├── session_test.cpp │ │ ├── sftp_fixture.cpp │ │ ├── sftp_fixture.hpp │ │ ├── ssh_server/ │ │ │ ├── Dockerfile │ │ │ ├── authorized_keys │ │ │ └── ssh_host_rsa_key │ │ ├── stream_threading_test.cpp │ │ ├── test_known_hosts │ │ ├── test_known_hosts_hashed │ │ └── test_known_hosts_out │ └── versions/ │ ├── CMakeLists.txt │ └── version_test.cpp └── thirdparty/ └── taskdialog98/ ├── TaskDialog.h ├── TaskDialogTest.cpp ├── TaskDialogTest.dsp ├── TaskDialogTest.dsw ├── TaskDialogTest.h ├── TaskDialogTest.rc ├── icons.h ├── maindlg.h ├── res/ │ ├── TaskDialogTest.exe.manifest │ └── make_icons_include.bat ├── resource.h ├── stdafx.cpp └── stdafx.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- AccessModifierOffset: '-4' AllowShortFunctionsOnASingleLine: Empty AlwaysBreakTemplateDeclarations: 'true' BreakBeforeBraces: Allman ColumnLimit: '80' ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' ForEachMacros: [ foreach, BOOST_FOREACH ] IndentWidth: '4' MacroBlockBegin: "^[A-Z_]+_BEGIN$" MacroBlockEnd: "^[A-Z_]+_END(?)?$" MaxEmptyLinesToKeep: '1' NamespaceIndentation: None PointerAlignment: Left SpaceAfterControlStatementKeyword: true SpaceBeforeAssignmentOperators: true SpacesInAngles: false SpacesInParentheses: false UseTab: Never ... ================================================ FILE: .gitattributes ================================================ *.sh crlf=input ================================================ FILE: .tx/config ================================================ [main] host = https://www.transifex.com [swish.swish] file_filter = po\\swish.po source_file = po\template\swish.pot source_lang = en_GB type = PO ================================================ FILE: CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin # Street, Fifth Floor, Boston, MA 02110-1301 USA. cmake_minimum_required(VERSION 3.1) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) # Package management ########################################################### include(HunterGate) HunterGate( URL "https://github.com/alamaison/hunter/archive/ee83a15960b5d74098d8704bc9cf82ad8ec1734f.tar.gz" SHA1 "d02301fee68f00198e7941129da446f80189017e" LOCAL ) ################################################################################ project(swish VERSION 0.8.3) set(SWISH_FRIENDLY_NAME "Swish") set(SWISH_VENDOR "swish-sftp.org") set(SWISH_DESCRIPTION "Easy SFTP for Windows Explorer") set(SWISH_COPYRIGHT "Copyright (C) 2006-2015 Alexander Lamaison and contributors") #include(max_warnings) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/taskdialog98) # Prevent Winsock errors and gives quicker builds add_definitions(-DWIN32_LEAN_AND_MEAN) # Silence bogus MSVC warnings add_definitions(-D_SCL_SECURE_NO_WARNINGS) # Currently required because not all Win32 calls use A or W form explicitly add_definitions(-DUNICODE -D_UNICODE) hunter_add_package(Boost COMPONENTS filesystem system test date_time regex signals locale) set(Boost_USE_STATIC_LIBS ON) find_package(Boost 1.49 REQUIRED COMPONENTS filesystem date_time system regex signals thread locale) if(MSVC) add_definitions(-DBOOST_ALL_NO_LIB=1) endif() include_directories(${Boost_INCLUDE_DIRS}) add_subdirectory(ezel) add_subdirectory(ssh) add_subdirectory(swish) add_subdirectory(po) option(BUILD_TESTING "Build test suite" ON) if(BUILD_TESTING) enable_testing() add_subdirectory(test) endif() set(INSTALLER_URL "https://sourceforge.net/projects/swish/files/swish/swish-${swish_VERSION}/swish-${swish_VERSION}.exe") string(TIMESTAMP PUBLICATION_DATE UTC) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/appcast.xml.in ${CMAKE_CURRENT_BINARY_DIR}/appcast.xml @ONLY) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/setup_conf.xml.in ${CMAKE_CURRENT_BINARY_DIR}/setup_conf.xml @ONLY) file(DOWNLOAD "http://the.earth.li/~sgtatham/putty/0.64/x86/pageant.exe" "${CMAKE_CURRENT_BINARY_DIR}/pageant.exe" SHOW_PROGRESS EXPECTED_HASH SHA1=4f7ec7e53b7dd557603c2447fd177d85f14006ad) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pageant.exe" NEWS LICENSE.txt README.md DESTINATION .) include(InstallRequiredSystemLibraries) set(CPACK_GENERATOR WIX) set(CPACK_PACKAGE_NAME "${SWISH_FRIENDLY_NAME}") set(CPACK_PACKAGE_VENDOR "${SWISH_VENDOR}") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${SWISH_DESCRIPTION}") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set(CPACK_WIX_UPGRADE_GUID "97CF376F-FFDE-472A-946B-E3F5D45229DA") set(CPACK_WIX_PATCH_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cpack_wix_patch.xml") set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") set(CPACK_PACKAGE_INSTALL_DIRECTORY "${SWISH_FRIENDLY_NAME}") set(CMAKE_WIX_PROPERTY_ARPCONTACT swish@lammy.co.uk) set(CMAKE_WIX_PROPERTY_ARPHELPLINK http://www.swish-sftp.org) set(CMAKE_WIX_PROPERTY_ARPURLINFOABOUT http://www.swish-sftp.org) set(CMAKE_WIX_PROPERTY_ARPURLUPDATEINFO http://sourceforge.net/projects/swish/) #set(CPACK_WIX_UI_REF WixUI_Minimal) include(CPack) ================================================ FILE: COPYING.rtf ================================================ {\rtf1\ansi\ansicpg1252\deff0{\fonttbl{\f0\fmodern\fprq1\fcharset0 Courier New;}} {\colortbl ;\red0\green0\blue0;} {\*\generator Msftedit 5.41.15.1507;}\viewkind4\uc1\pard\cf1\lang2057\f0\fs18\tab\tab GNU GENERAL PUBLIC LICENSE\par \tab\tab Version 2, June 1991\par \par Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\par 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\par Everyone is permitted to copy and distribute verbatim copies\par of this license document, but changing it is not allowed.\par \par \tab\tab\tab Preamble\par \par The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too.\par \par When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.\par \par To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.\par \par For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.\par \par We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.\par \par Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.\par \par Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.\par \par The precise terms and conditions for copying, distribution and modification follow. \par \par \tab\tab GNU GENERAL PUBLIC LICENSE\par TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\par \par 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".\par \par Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.\par \par 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.\par \par You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.\par \par 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:\par \par a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. \par b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. \par c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) \par \par These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.\par \par Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.\par \par In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.\par \par 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:\par \par a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, \par b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, \par c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) \par \par The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.\par \par If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.\par \par 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.\par \par 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.\par \par 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.\par \par 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.\par \par If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.\par \par It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.\par \par This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.\par \par 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.\par \par 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.\par \par Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.\par \par 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. \par \par \tab\tab\tab NO WARRANTY\par \par 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\par \par 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. \par \par \tab\tab END OF TERMS AND CONDITIONS\par \par \tab How to Apply These Terms to Your New Programs\par \par If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.\par \par To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. \par \par \par Copyright (C) \par \par This program is free software; you can redistribute it and/or modify\par it under the terms of the GNU General Public License as published by\par the Free Software Foundation; either version 2 of the License, or\par (at your option) any later version.\par \par This program is distributed in the hope that it will be useful,\par but WITHOUT ANY WARRANTY; without even the implied warranty of\par MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\par GNU General Public License for more details.\par \par You should have received a copy of the GNU General Public License along\par with this program; if not, write to the Free Software Foundation, Inc.,\par 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\par \par Also add information on how to contact you by electronic and paper mail.\par \par If the program is interactive, make it output a short notice like this when it starts in an interactive mode: \par \par Gnomovision version 69, Copyright (C) year name of author\par Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\par This is free software, and you are welcome to redistribute it\par under certain conditions; type `show c' for details.\par \par The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.\par \par You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: \par \par Yoyodyne, Inc., hereby disclaims all copyright interest in the program\par `Gnomovision' (which makes passes at compilers) written by James Hacker.\par \par , 1 April 1989\par Ty Coon, President of Vice\par \par This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License.\par \par } ================================================ FILE: LICENSE.txt ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ================================================ FILE: NEWS ================================================ swish-0.8.2 -Fixed error when known_hosts contained unrecognised key types. swish-0.8.1 -Fix crash in 32-bit version when showing dialogues. -Updated OpenSSL to 1.0.0l. -Updated Hebrew translation. swish-0.8.0 -Added option to log out of server manually. -Fix file extension behaviour to match Windows folder options. -Fixed bug where timestamps were adjusted to local time twice. -New Latvian and Catalan translations. -Updated Hungarian, Japanese and Portuguese translations. swish-0.7.4 -Restored copy progress bar which had disappeared in 0.7.2. -Updated Welsh translation. swish-0.7.3 -Fixed drag-and-drop crash on Windows 8. -Fixed timestamps so they are in user's timezone. -New Romanian translation. -Updated Korean translation. swish-0.7.2 -Added direct downloading and installation of updates. -More detailed error messages. -Fixed bug with non-ASCII characters in usernames and passwords. -New Welsh, Danish, Greek, Estonian, Finnish and Korean translations. -Updated Bulgarian, Czech, German, Spanish, French, Hebrew, Italian, Japanese, Dutch, Polish, Russian, Swedish and Chinese (Simplified) translations. swish-0.7.0 -Added support for public key authentication through Pageant key agent. -Updated Czech, Spanish, Japanese, Dutch and Portuguese (Brazil) translations. swish-0.6.3 -Fixed errors with Windows 8. -Disabled compression to support a greater range of SSH servers. -Updated German, Italian, Japanese, Portuguese (Brazil), Chinese (Simplified), Chinese (Traditional) and French translations. swish-0.6.2 -Fixed performance problems with small files. -Fixed bug causing ZIP files to be unpacked as the were copied. -New Brazilian Portuguese translation. -Updated Dutch, German, Chinese (Simplified), Italian and Portuguese translations. swish-0.6.1 -Significant speed improvement. -Installer supports Windows 8. -Better error reporting when copying to a remote server. -Fixed bug where renaming a folder did not change the name displayed. -Fixed bug where files did not appear after copying to remote server. -Hindi, Bulgarian, Portuguese, Swedish and Chinese (Simplified) translations. swish-0.6.0 -Added double-click file opening. -Downgraded to libssh2 1.2.7 to fix incomplete file transfers. swish-0.5.4 -Added support for symlinks. -Display error message if drag-and-drop fails. -Japanese, Polish, Slovak, Hungarian and Chinese translations. swish-0.4.6 -Fixed major installer problem which broke all new installations as it failed to register an interface. -Fixed problem that left assertion checks in release builds. swish-0.4.5 -Added ability to create new folders. -Added auto-updater. -More useful error messages. -Better error dialogues. -Fixed pasting files to the server. This may have been broken for a long time! -Fixed problem with port numbers above 999 in some locales. -Fixed bug where pressing Cancel showed an error message. -Fixed bug causing Explorer Window to flash when navigating between folders. -Italian translation. swish-0.4.4 -64-bit support. -Windows XP 'webtask' pane. -Hebrew, Czech, Spanish, French and Turkish translations. swish-0.4.2 -Explicitly prevent 64-bit installation. -Fix bug where sessions couldn't be restored until Explorer restarted. -Fix bug with non-ASCII Windows usernames. swish-0.4.1 -Write speeds 4x faster. -Huge reduction in GUI memory usage. -Confirmation before overwriting existing files. -Improved write progress indicator. -Better error reporting. -Fix bug where 'Remove' option missing from host menu. -German translation. swish-0.4.0 -Full support for files and directories with non-Latin Unicode names. swish-0.3.5 -Display Unicode filenames. swish-0.3.4 -Fixed hostname resolution for Windows versions earlier than Vista swish-0.3.3 -IPv6 support. -Window 7 fixes. -Fix overwriting larger file with smaller one. -Error messages when negotiating a connection fails. -Support for most characters (e.g. @) in user names. swish-0.3.2.0 -Internationalisation (English, Dutch, Russian) swish-0.3.1.0 -Added host-key verification. -New WIX-based installer swish-0.3.0.1 -Added command buttons to add and remove hosts (Windows Vista and above). -Allow the user to cancel a file transfer mid-file rather than only between files. -Fix bug where password dialogues appear behind the Explorer window. -Fix missing menu options bug in Windows 98. swish-0.3.0.0 -Full drag-and-drop support in Windows XP and above. Drag-and-drop files and folders to and from the remote server. swish-0.2.1.13 -Enable removal of hosts from Swish folder. swish-0.2.1.12 -Added 'Type' property to Explorer view. -Fixed incorrect column witdths (ticket #2). -Display user name and group name rather than the UID and GID. -Added 'Owner ID' and 'Group ID' columns to Explorer view to display the numerical UID and GID. -Added 'Date Accessed' column to Explorer view. swish-0.2.1.11 -Read-only file transfer support added. Files and folders can be copied from a remote server to the local computer using cut&paste or drag&drop. swish-0.2.1.10 -Now supports Windows Vista. swish-0.2.1.9 -Added support for the keyboard-interactive authentication scheme: (http://www.ietf.org/rfc/rfc4256.txt). -Improved robustness of the low-level backend session handling in the face of errors. swish-0.2.1.8 -Fixed bug when overwriting existing directories. swish-0.2.1.7 -Files and folders can be renamed. If an item with the chosen name already exists, we offer to overwrite it. Due to the limitations of SFTP versions 4 and below, we do this non-atomically which is not ideal. Time will tell whether this is more trouble than it's worth. swish-0.2.1.6 -Files and folders can be deleted from the Explorer window. If a directory contains other items, we deleted these recursively using separate calls to the server. This is a bit clunky but we are limited by what SFTP supports. swish-0.2.1.5 -SFTP sessions (connections) are now pooled in the COM ROT so that subsequent requests can reuse them without prompting the user for a password or having to renegotiate with the server. swish-0.2.1.4 -Replace the PuTTY-based dataprovider component with one based in libssh2. This is vastly more stable. The new backend has all the abilities of the of the old one with a few exceptions, namely, host-verification (aka known_hosts), key-interactive authentication and public-key authentication. Replacing these is a priority. -The frontend has been integrate much more closely with Explorer. -Connections are added with a dialog invoked from the Tools menu and are stored in the registry. -Explorer displays the default icon for files based on their extension (this doesn't work in Windows 98 yet). -Subfolder can be navigated. -The default explorer context menu is displayed when right-clicking a file. swish-0.2.1 -Created a data provider component to wrap PuTTY's SFTP client (psftp.exe) permitting listing of files in a directory -Linked data provider to front end using simple dialog box for requesting host information from the user swish-0.2 -All previous prototype code discarded -Created Explorer namespace extension with window explorer window showing three dummy host connection items -Used SHCreateShellFolderView to create default shell view window giving best possible Explorer integration -Created PIDL Manager to handle host (connection) folder item -Added conection item details to Details and Tiles view -Created Remote Folder displaying dummy file listing which is displayed by double-clicking (or otherwise opening) host connection in initial folder swish-0.1.1 -Experimentation with Visual Studio and linking with gcc-compiled libssh swish-0.1-gcc -Experimentation using gcc, wxWidgets and libssh-0.11 ================================================ FILE: README.md ================================================ Swish ===== What is Swish? -------------- Swish is a plugin for Microsoft Windows Explorer that adds support for SFTP. If you've used Explorer's built-in FTP support, Swish is that but for SFTP over SSH. Supported Operating Systems --------------------------- * Windows 8 (most tested) * Windows 7 (occasionally tested) * Windows Vista (rarely tested) Swish may also work on Windows XP, but we haven't tested that in a while. Binaries -------- Binary installers are [on our website](http://www.swish-sftp.org). Getting involved ---------------- We welcome patches to help improve Swish. Swish fetches almost all its dependencies when you configure it with [CMake]. That magic happens thanks to the [Hunter package manager]. However, you will need Perl installed. [Strawberry Perl] is good, and available on [Chocolatey]. You'll also need a compiler (obviously), a recent version of the Windows SDK and CMake. [CMake]: https://cmake.org/ [Hunter package manager]: https://github.com/ruslo/hunter/ [Strawberry Perl]: http://strawberryperl.com/ [Chocolatey]: https://chocolatey.org/packages/StrawberryPerl Licensing --------- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . If you modify this Program, or any covered work, by linking or combining it with the OpenSSL project's OpenSSL library (or a modified version of that library), containing parts covered by the terms of the OpenSSL or SSLeay licenses, the licensors of this Program grant you additional permission to convey the resulting work. ### Why have an exception for OpenSSL? The [OpenSSL] library is incompatible with the GPL license because it contains an advertising clause. However lots of useful, open source software (including our own projects) need to use it and currently the alternatives aren't quite up to scratch. As we want these projects to be able to reuse Washer, we have added this exception to the GPL - a common technique used by other projects such as [wget]. If [GnuTLS] improves to the point where OpenSSL is no longer necessary, we may remove this exception. [OpenSSL]: http://www.openssl.org/ [wget]: http://www.gnu.org/software/wget/ [GnuTLS]: http://www.gnu.org/software/gnutls/ ================================================ FILE: appcast.xml.in ================================================ Swish updates http://www.swish-sftp.org/autoupdate/appcast.xml Appcast for Swish updates. en Version @swish_VERSION@ http://www.swish-sftp.org/autoupdate/NEWS-@swish_VERSION@.html @PUBLICATION_DATE@ ================================================ FILE: build/making_a_release.txt ================================================ Update version number in: - top-level CMakeLists.txt - wix/swish.wxs - wix/wix.wixproj Add notable changes to NEWS. Make sure .po files are compiled to .mo files (po/compile_mo.sh). To build the final multi-architecture EXE, run this command in the top level: "C:\Program Files (x86)\dotNetInstaller\bin\InstallerLinker.exe" /Output:swish.exe /Template:"C:\Program Files (x86)\dotNetInstaller\bin\dotNetInstaller.exe" /Configuration:setup_conf.xml Add version to Trac. ================================================ FILE: build/news_html.py ================================================ """Convert Swish NEWS file to HTML. """ _NEWS = '../NEWS' _VERSIONED_NEWS = '../NEWS-%s.html' def main(): with open(_NEWS, 'r') as news_in: html = (''' %s ''') % read_news(news_in, 1) with open(_NEWS, 'r') as news_in: version = extract_version(news_in) with open(_VERSIONED_NEWS % version, 'w') as html_open: html_open.write(html) def extract_version(news): return news.readline().strip('\n\r').replace('swish-', '') def read_news(news, indent): entry_html = '''

%s

    %s

''' html = [] while True: entry = read_entry(news) if not entry: break lines = [] for item in entry['items']: lines.append(create_list_item(item, indent + 1)) html.append(entry_html % (entry['heading'], '\n'.join(lines))) return '\n'.join(html) def create_list_item(item, indent): return '%s
  • %s
  • ' % (('\t' * indent), item) def read_entry(news): entry = { 'heading' : news.readline().strip(), 'items' : [] } while True: line = news.readline().strip('\n\r') print line if not line: break if line[0] == ' ': entry['items'][-1] += line continue if line[0] != '-': raise Exception('Incorrect format: %s' % line) entry['items'].append(line[1:]) if not entry['heading']: return None return entry if __name__ == '__main__': main() ================================================ FILE: cmake/GetGitRevisionDescription.cmake ================================================ # - Returns a version string from Git # # These functions force a re-configure on each git commit so that you can # trust the values of the variables in your build system. # # get_git_head_revision( [ ...]) # # Returns the refspec and sha hash of the current head revision # # git_describe( [ ...]) # # Returns the results of git describe on the source tree, and adjusting # the output so that it tests false if an error occurs. # # git_get_exact_tag( [ ...]) # # Returns the results of git describe --exact-match on the source tree, # and adjusting the output so that it tests false if there was no exact # matching tag. # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) if(__get_git_revision_description) return() endif() set(__get_git_revision_description YES) # We must run the following at "include" time, not at function call time, # to find the path to this module rather than the path to a calling list file get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) function(get_git_head_revision _refspecvar _hashvar) set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(GIT_DIR "${GIT_PARENT_DIR}/.git") while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) # We have reached the root directory, we are not in git set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) return() endif() set(GIT_DIR "${GIT_PARENT_DIR}/.git") endwhile() # check if this is a submodule if(NOT IS_DIRECTORY ${GIT_DIR}) file(READ ${GIT_DIR} submodule) string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) endif() set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") if(NOT EXISTS "${GIT_DATA}") file(MAKE_DIRECTORY "${GIT_DATA}") endif() if(NOT EXISTS "${GIT_DIR}/HEAD") return() endif() set(HEAD_FILE "${GIT_DATA}/HEAD") configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY) include("${GIT_DATA}/grabRef.cmake") set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) endfunction() function(git_describe _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() get_git_head_revision(refspec hash) if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() if(NOT hash) set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) return() endif() # TODO sanitize #if((${ARGN}" MATCHES "&&") OR # (ARGN MATCHES "||") OR # (ARGN MATCHES "\\;")) # message("Please report the following error to the project!") # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") #endif() #message(STATUS "Arguments to execute_process: ${ARGN}") execute_process(COMMAND "${GIT_EXECUTABLE}" describe ${hash} ${ARGN} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_get_exact_tag _var) git_describe(out --exact-match ${ARGN}) set(${_var} "${out}" PARENT_SCOPE) endfunction() ================================================ FILE: cmake/GetGitRevisionDescription.cmake.in ================================================ # # Internal file for GetGitRevisionDescription.cmake # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) set(HEAD_HASH) file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) if(HEAD_CONTENTS MATCHES "ref") # named branch string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) else() configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") set(HEAD_HASH "${CMAKE_MATCH_1}") endif() endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() ================================================ FILE: cmake/Hunter/config.cmake ================================================ hunter_config(Boost VERSION 1.49.0) ================================================ FILE: cmake/HunterGate.cmake ================================================ # Copyright (c) 2013-2015, Ruslan Baratov # 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. # # 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. # This is a gate file to Hunter package manager. # Include this file using `include` command and add package you need, example: # # cmake_minimum_required(VERSION 3.0) # # include("cmake/HunterGate.cmake") # HunterGate( # URL "https://github.com/path/to/hunter/archive.tar.gz" # SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" # ) # # project(MyProject) # # hunter_add_package(Foo) # hunter_add_package(Boo COMPONENTS Bar Baz) # # Projects: # * https://github.com/hunter-packages/gate/ # * https://github.com/ruslo/hunter cmake_minimum_required(VERSION 3.0) # Minimum for Hunter include(CMakeParseArguments) # cmake_parse_arguments option(HUNTER_ENABLED "Enable Hunter package manager support" ON) option(HUNTER_STATUS_PRINT "Print working status" ON) option(HUNTER_STATUS_DEBUG "Print a lot info" OFF) set(HUNTER_WIKI "https://github.com/ruslo/hunter/wiki") function(hunter_gate_status_print) foreach(print_message ${ARGV}) if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG) message(STATUS "[hunter] ${print_message}") endif() endforeach() endfunction() function(hunter_gate_status_debug) foreach(print_message ${ARGV}) if(HUNTER_STATUS_DEBUG) string(TIMESTAMP timestamp) message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}") endif() endforeach() endfunction() function(hunter_gate_wiki wiki_page) message("------------------------------ WIKI -------------------------------") message(" ${HUNTER_WIKI}/${wiki_page}") message("-------------------------------------------------------------------") message("") message(FATAL_ERROR "") endfunction() function(hunter_gate_internal_error) message("") foreach(print_message ${ARGV}) message("[hunter ** INTERNAL **] ${print_message}") endforeach() message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") message("") hunter_gate_wiki("error.internal") endfunction() function(hunter_gate_fatal_error) cmake_parse_arguments(hunter "" "WIKI" "" "${ARGV}") string(COMPARE EQUAL "${hunter_WIKI}" "" have_no_wiki) if(have_no_wiki) hunter_gate_internal_error("Expected wiki") endif() message("") foreach(x ${hunter_UNPARSED_ARGUMENTS}) message("[hunter ** FATAL ERROR **] ${x}") endforeach() message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") message("") hunter_gate_wiki("${hunter_WIKI}") endfunction() function(hunter_gate_user_error) hunter_gate_fatal_error(${ARGV} WIKI "error.incorrect.input.data") endfunction() function(hunter_gate_self root version sha1 result) string(COMPARE EQUAL "${root}" "" is_bad) if(is_bad) hunter_gate_internal_error("root is empty") endif() string(COMPARE EQUAL "${version}" "" is_bad) if(is_bad) hunter_gate_internal_error("version is empty") endif() string(COMPARE EQUAL "${sha1}" "" is_bad) if(is_bad) hunter_gate_internal_error("sha1 is empty") endif() string(SUBSTRING "${sha1}" 0 7 archive_id) if(EXISTS "${root}/cmake/Hunter") set(hunter_self "${root}") else() set( hunter_self "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" ) endif() set("${result}" "${hunter_self}" PARENT_SCOPE) endfunction() # Set HUNTER_GATE_ROOT cmake variable to suitable value. function(hunter_gate_detect_root) # Check CMake variable string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty) if(not_empty) set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE) hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable") return() endif() # Check environment variable string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty) if(not_empty) set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE) hunter_gate_status_debug("HUNTER_ROOT detected by environment variable") return() endif() # Check HOME environment variable string(COMPARE NOTEQUAL "$ENV{HOME}" "" result) if(result) set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE) hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable") return() endif() # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only) if(WIN32) string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result) if(result) set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE) hunter_gate_status_debug( "HUNTER_ROOT set using SYSTEMDRIVE environment variable" ) return() endif() string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result) if(result) set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE) hunter_gate_status_debug( "HUNTER_ROOT set using USERPROFILE environment variable" ) return() endif() endif() hunter_gate_fatal_error( "Can't detect HUNTER_ROOT" WIKI "error.detect.hunter.root" ) endfunction() macro(hunter_gate_lock dir) if(NOT HUNTER_SKIP_LOCK) if("${CMAKE_VERSION}" VERSION_LESS "3.2") hunter_gate_fatal_error( "Can't lock, upgrade to CMake 3.2 or use HUNTER_SKIP_LOCK" WIKI "error.can.not.lock" ) endif() hunter_gate_status_debug("Locking directory: ${dir}") file(LOCK "${dir}" DIRECTORY GUARD FUNCTION) hunter_gate_status_debug("Lock done") endif() endmacro() function(hunter_gate_download dir) string( COMPARE NOTEQUAL "$ENV{HUNTER_DISABLE_AUTOINSTALL}" "" disable_autoinstall ) if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL) hunter_gate_fatal_error( "Hunter not found in '${dir}'" "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" "Settings:" " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" WIKI "error.run.install" ) endif() string(COMPARE EQUAL "${dir}" "" is_bad) if(is_bad) hunter_gate_internal_error("Empty 'dir' argument") endif() string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad) if(is_bad) hunter_gate_internal_error("HUNTER_GATE_SHA1 empty") endif() string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad) if(is_bad) hunter_gate_internal_error("HUNTER_GATE_URL empty") endif() set(done_location "${dir}/DONE") set(sha1_location "${dir}/SHA1") set(build_dir "${dir}/Build") set(cmakelists "${dir}/CMakeLists.txt") hunter_gate_lock("${dir}") if(EXISTS "${done_location}") # while waiting for lock other instance can do all the job hunter_gate_status_debug("File '${done_location}' found, skip install") return() endif() file(REMOVE_RECURSE "${build_dir}") file(REMOVE_RECURSE "${cmakelists}") file(MAKE_DIRECTORY "${build_dir}") # check directory permissions # Disabling languages speeds up a little bit, reduces noise in the output # and avoids path too long windows error file( WRITE "${cmakelists}" "cmake_minimum_required(VERSION 3.0)\n" "project(HunterDownload LANGUAGES NONE)\n" "include(ExternalProject)\n" "ExternalProject_Add(\n" " Hunter\n" " URL\n" " \"${HUNTER_GATE_URL}\"\n" " URL_HASH\n" " SHA1=${HUNTER_GATE_SHA1}\n" " DOWNLOAD_DIR\n" " \"${dir}\"\n" " SOURCE_DIR\n" " \"${dir}/Unpacked\"\n" " CONFIGURE_COMMAND\n" " \"\"\n" " BUILD_COMMAND\n" " \"\"\n" " INSTALL_COMMAND\n" " \"\"\n" ")\n" ) if(HUNTER_STATUS_DEBUG) set(logging_params "") else() set(logging_params OUTPUT_QUIET) endif() hunter_gate_status_debug("Run generate") execute_process( COMMAND "${CMAKE_COMMAND}" "-H${dir}" "-B${build_dir}" WORKING_DIRECTORY "${dir}" RESULT_VARIABLE download_result ${logging_params} ) if(NOT download_result EQUAL 0) hunter_gate_internal_error("Configure project failed") endif() hunter_gate_status_print( "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" " ${HUNTER_GATE_URL}" " -> ${dir}" ) execute_process( COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" WORKING_DIRECTORY "${dir}" RESULT_VARIABLE download_result ${logging_params} ) if(NOT download_result EQUAL 0) hunter_gate_internal_error("Build project failed") endif() file(REMOVE_RECURSE "${build_dir}") file(REMOVE_RECURSE "${cmakelists}") file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}") file(WRITE "${done_location}" "DONE") hunter_gate_status_debug("Finished") endfunction() # Must be a macro so master file 'cmake/Hunter' can # apply all variables easily just by 'include' command # (otherwise PARENT_SCOPE magic needed) macro(HunterGate) if(HUNTER_GATE_DONE) # variable HUNTER_GATE_DONE set explicitly for external project # (see `hunter_download`) set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) endif() # First HunterGate command will init Hunter, others will be ignored get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET) if(NOT HUNTER_ENABLED) # Empty function to avoid error "unknown function" function(hunter_add_package) endfunction() elseif(_hunter_gate_done) hunter_gate_status_debug("Secondary HunterGate (use old settings)") hunter_gate_self( "${HUNTER_CACHED_ROOT}" "${HUNTER_VERSION}" "${HUNTER_SHA1}" _hunter_self ) include("${_hunter_self}/cmake/Hunter") else() set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_LIST_DIR}") string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name) if(_have_project_name) hunter_gate_fatal_error( "Please set HunterGate *before* 'project' command. " "Detected project: ${PROJECT_NAME}" WIKI "error.huntergate.before.project" ) endif() cmake_parse_arguments( HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} ) string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1) string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url) string( COMPARE NOTEQUAL "${HUNTER_GATE_UNPARSED_ARGUMENTS}" "" _have_unparsed ) string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global) string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath) if(_empty_sha1) hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory") endif() if(_empty_url) hunter_gate_user_error("URL suboption of HunterGate is mandatory") endif() if(_have_unparsed) hunter_gate_user_error( "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" ) endif() if(_have_global) if(HUNTER_GATE_LOCAL) hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)") endif() if(_have_filepath) hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)") endif() endif() if(HUNTER_GATE_LOCAL) if(_have_global) hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)") endif() if(_have_filepath) hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)") endif() endif() if(_have_filepath) if(_have_global) hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)") endif() if(HUNTER_GATE_LOCAL) hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)") endif() endif() hunter_gate_detect_root() # set HUNTER_GATE_ROOT # Beautify path, fix probable problems with windows path slashes get_filename_component( HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE ) hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}") if(NOT HUNTER_ALLOW_SPACES_IN_PATH) string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces) if(NOT _contain_spaces EQUAL -1) hunter_gate_fatal_error( "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" "(Use at your own risk!)" WIKI "error.spaces.in.hunter.root" ) endif() endif() string( REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" HUNTER_GATE_VERSION "${HUNTER_GATE_URL}" ) string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty) if(_is_empty) set(HUNTER_GATE_VERSION "unknown") endif() hunter_gate_self( "${HUNTER_GATE_ROOT}" "${HUNTER_GATE_VERSION}" "${HUNTER_GATE_SHA1}" hunter_self_ ) set(_master_location "${hunter_self_}/cmake/Hunter") if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter") # Hunter downloaded manually (e.g. by 'git clone') set(_unused "xxxxxxxxxx") set(HUNTER_GATE_SHA1 "${_unused}") set(HUNTER_GATE_VERSION "${_unused}") else() get_filename_component(_archive_id_location "${hunter_self_}/.." ABSOLUTE) set(_done_location "${_archive_id_location}/DONE") set(_sha1_location "${_archive_id_location}/SHA1") # Check Hunter already downloaded by HunterGate if(NOT EXISTS "${_done_location}") hunter_gate_download("${_archive_id_location}") endif() if(NOT EXISTS "${_done_location}") hunter_gate_internal_error("hunter_gate_download failed") endif() if(NOT EXISTS "${_sha1_location}") hunter_gate_internal_error("${_sha1_location} not found") endif() file(READ "${_sha1_location}" _sha1_value) string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) if(NOT _is_equal) hunter_gate_internal_error( "Short SHA1 collision:" " ${_sha1_value} (from ${_sha1_location})" " ${HUNTER_GATE_SHA1} (HunterGate)" ) endif() if(NOT EXISTS "${_master_location}") hunter_gate_user_error( "Master file not found:" " ${_master_location}" "try to update Hunter/HunterGate" ) endif() endif() include("${_master_location}") set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) endif() endmacro() ================================================ FILE: cpack_wix_patch.xml ================================================ ================================================ FILE: ezel/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES detail/command_dispatch.hpp detail/dialog_template.hpp detail/hooks.hpp detail/hwnd_linking.hpp detail/message_dispatch.hpp detail/window_impl.hpp detail/window_link.hpp detail/window_proc.hpp detail/window_proxy.hpp controls/button.hpp controls/checkbox.hpp controls/edit.hpp controls/icon.hpp controls/label.hpp controls/line.hpp controls/spinner.hpp control.hpp control_parent_impl.hpp form.hpp window.hpp) add_custom_target(ezel-src SOURCES ${SOURCES}) add_library(ezel INTERFACE) target_include_directories(ezel INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(ezel INTERFACE ${Boost_LIBRARIES}) ================================================ FILE: ezel/control.hpp ================================================ /** @file GUI control base. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_CONTROL_HPP #define EZEL_CONTROL_HPP #pragma once #include // window #include // shared_ptr #include namespace ezel { class form; /** * Base-class for form control facades. * * All controls that can be added to forms are added as an instance of a * subclass of this this template. * This allows form to access the impl pointer but nothing else. * * @param T Type of implementation class (pimpl) */ template class control : public window { public: control(boost::shared_ptr impl) : window(impl) {} virtual ~control() {} protected: friend class form; // form need a p-impl from controls }; } // namespace ezel #endif ================================================ FILE: ezel/control_parent_impl.hpp ================================================ /** @file Compound window parent. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_CONTROL_PARENT_IMPL_HPP #define EZEL_CONTROL_PARENT_IMPL_HPP #pragma once #include // window_impl #include // message #include // assert namespace ezel { namespace detail { /** * Parent of any window that receive WM_COMMAND message from one or more * children. */ class control_parent_impl : public window_impl { public: typedef window_impl super; typedef message_map messages; virtual LRESULT handle_message( UINT message, WPARAM wparam, LPARAM lparam) { return dispatch_message(this, message, wparam, lparam); } /** * What to do if this window is sent a command message by a child window. * * We reflect the command back to the control that sent it in case * wants to react to it. */ LRESULT on(message m) { window_impl* w = window_from_hwnd(m.control_hwnd()); assert(w != this); w->handle_command(m.command_code(), m.wparam(), m.lparam()); return default_message_handler(m); } protected: control_parent_impl( const std::wstring& title, short left, short top, short width, short height) : window_impl(title, left, top, width, height) {} }; }} // namespace ezel::detail #endif ================================================ FILE: ezel/controls/button.hpp ================================================ /** @file GUI button control. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_CONTROLS_BUTTON_HPP #define EZEL_CONTROLS_BUTTON_HPP #pragma once #include // control base class #include // command_map #include // window_impl #include // command #include // shared_ptr #include // signal #include namespace ezel { namespace controls { class button_impl : public ezel::detail::window_impl { public: typedef ezel::detail::window_impl super; typedef ezel::detail::command_map commands; virtual void handle_command( WORD command_id, WPARAM wparam, LPARAM lparam) { dispatch_command(this, command_id, wparam, lparam); } button_impl( const std::wstring& title, short left, short top, short width, short height, bool default) : ezel::detail::window_impl(title, left, top, width, height), m_default(default) {} std::wstring window_class() const { return L"button"; } DWORD style() const { DWORD style = WS_CHILD | WS_VISIBLE | WS_TABSTOP; style |= (m_default) ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON; return style; } boost::signal& on_click() { return m_on_click; } void on(command) { m_on_click(); } private: boost::signal m_on_click; bool m_default; }; class button : public ezel::control { public: button( const std::wstring& title, short left, short top, short width, short height, bool default=false) : control( boost::shared_ptr( new button_impl(title, left, top, width, height, default))) {} boost::signal& on_click() { return impl()->on_click(); } short left() const { return impl()->left(); } short top() const { return impl()->top(); } short width() const { return impl()->width(); } short height() const { return impl()->height(); } }; }} // namespace ezel::controls #endif ================================================ FILE: ezel/controls/checkbox.hpp ================================================ /** @file GUI check-box control. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_CONTROLS_CHECKBOX_HPP #define EZEL_CONTROLS_CHECKBOX_HPP #pragma once #include // control base class #include // window_impl #include // function #include // shared_ptr #include namespace ezel { namespace controls { typedef boost::function on_click_callback; class checkbox_impl : public ezel::detail::window_impl { public: typedef ezel::detail::window_impl super; typedef ezel::detail::command_map commands; virtual void handle_command( WORD command_id, WPARAM wparam, LPARAM lparam) { dispatch_command(this, command_id, wparam, lparam); } checkbox_impl( const std::wstring& text, short left, short top, short width, short height) : ezel::detail::window_impl(text, left, top, width, height) {} std::wstring window_class() const { return L"button"; } DWORD style() const { return WS_CHILD | WS_VISIBLE | BS_CHECKBOX | WS_TABSTOP; } boost::signal& on_click() { return m_on_click; } void on(command) { m_on_click(); } private: boost::signal m_on_click; bool m_default; }; class checkbox : public ezel::control { public: checkbox( const std::wstring& text, short left, short top, short width, short height) : control( boost::shared_ptr( new checkbox_impl(text, left, top, width, height))) {} std::wstring text() const { return impl()->text(); } short left() const { return impl()->left(); } short top() const { return impl()->top(); } short width() const { return impl()->width(); } short height() const { return impl()->height(); } }; }} // namespace ezel::controls #endif ================================================ FILE: ezel/controls/edit.hpp ================================================ /** @file GUI edit (text) control. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_CONTROLS_EDIT_HPP #define EZEL_CONTROLS_EDIT_HPP #pragma once #include // control base class #include // window_impl #include // shared_ptr #include // signal #include namespace ezel { namespace controls { class edit_impl : public ezel::detail::window_impl { public: typedef ezel::detail::window_impl super; typedef ezel::detail::command_map commands; virtual void handle_command( WORD command_id, WPARAM wparam, LPARAM lparam) { dispatch_command(this, command_id, wparam, lparam); } edit_impl( const std::wstring& text, short left, short top, short width, short height, DWORD custom_style) : ezel::detail::window_impl(text, left, top, width, height), m_custom_style(custom_style) {} std::wstring window_class() const { return L"Edit"; } DWORD style() const { DWORD style = ezel::detail::window_impl::style() | WS_CHILD | ES_LEFT | WS_BORDER | ES_AUTOHSCROLL; style |= m_custom_style; return style; } boost::signal& on_change() { return m_on_change; } boost::signal& on_update() { return m_on_update; } void on(command) { m_on_change(); } void on(command) { m_on_update(); } private: boost::signal m_on_change; boost::signal m_on_update; DWORD m_custom_style; }; class edit : public ezel::control { public: struct style { enum value { default = 0, password = ES_PASSWORD, force_lowercase = ES_LOWERCASE, only_allow_numbers = ES_NUMBER }; }; edit( const std::wstring& text, short left, short top, short width, short height, style::value custom_style=style::default) : control( boost::shared_ptr( new edit_impl( text, left, top, width, height, custom_style))) {} boost::signal& on_change() { return impl()->on_change(); } boost::signal& on_update() { return impl()->on_update(); } boost::signal& on_text_change() { return impl()->on_text_change(); } boost::signal& on_text_changed() { return impl()->on_text_changed(); } short left() const { return impl()->left(); } short top() const { return impl()->top(); } short width() const { return impl()->width(); } short height() const { return impl()->height(); } }; }} // namespace ezel::controls #endif ================================================ FILE: ezel/controls/icon.hpp ================================================ /** @file GUI icon control. @if license Copyright (C) 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_CONTROLS_ICON_HPP #define EZEL_CONTROLS_ICON_HPP #pragma once #include // control base class #include // window_impl #include // icon_window #include // shared_ptr #include namespace ezel { namespace controls { class icon_impl : public ezel::detail::window_impl { protected: typedef ezel::detail::window_impl super; public: icon_impl(short left, short top, short width, short height) : ezel::detail::window_impl(L"", left, top, width, height), m_icon(NULL) {} std::wstring window_class() const { return L"static"; } DWORD style() const { DWORD style = ezel::detail::window_impl::style() | WS_CHILD | SS_ICON | WS_GROUP | SS_NOTIFY | SS_CENTERIMAGE; return style; } HICON change_icon(HICON new_icon) { if (!is_active()) { HICON previous = m_icon; m_icon = new_icon; return previous; } else { washer::window::icon_window icon( washer::window::window_handle::foster_handle(hwnd())); return icon.change_icon(new_icon); } } protected: /** * Set the source of the icon to whatever the user set via change_icon. */ virtual void push() { super::push(); washer::send_message( hwnd(), STM_SETIMAGE, IMAGE_ICON, m_icon); } private: HICON m_icon; }; class icon : public ezel::control { public: struct style { enum value { default = 0, ampersand_not_special = SS_NOPREFIX }; }; icon(short left, short top, short width, short height) : control( boost::shared_ptr( new icon_impl(left, top, width, height))) {} HICON change_icon(HICON new_icon) { return impl()->change_icon(new_icon); } short left() const { return impl()->left(); } short top() const { return impl()->top(); } short width() const { return impl()->width(); } short height() const { return impl()->height(); } }; }} // namespace ezel::controls #endif ================================================ FILE: ezel/controls/label.hpp ================================================ /** @file GUI label (static text) control. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_CONTROLS_LABEL_HPP #define EZEL_CONTROLS_LABEL_HPP #pragma once #include // control base class #include // window_impl #include // shared_ptr #include namespace ezel { namespace controls { class label_impl : public ezel::detail::window_impl { public: label_impl( const std::wstring& text, short left, short top, short width, short height, DWORD custom_style) : ezel::detail::window_impl(text, left, top, width, height), m_custom_style(custom_style) {} std::wstring window_class() const { return L"static"; } DWORD style() const { DWORD style = ezel::detail::window_impl::style() | WS_CHILD | SS_LEFT | WS_GROUP | SS_NOTIFY; style &= ~WS_TABSTOP; style |= m_custom_style; return style; } private: DWORD m_custom_style; }; class label : public ezel::control { public: struct style { enum value { default = 0, ampersand_not_special = SS_NOPREFIX }; }; label( const std::wstring& text, short left, short top, short width, short height, style::value custom_style=style::default) : control( boost::shared_ptr( new label_impl( text, left, top, width, height, custom_style))) {} short left() const { return impl()->left(); } short top() const { return impl()->top(); } short width() const { return impl()->width(); } short height() const { return impl()->height(); } }; }} // namespace ezel::controls #endif ================================================ FILE: ezel/controls/line.hpp ================================================ /** @file GUI horizontal line control. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_CONTROLS_LINE_HPP #define EZEL_CONTROLS_LINE_HPP #pragma once #include // control base class #include // window_impl #include // shared_ptr #include namespace ezel { namespace controls { class line_impl : public ezel::detail::window_impl { public: line_impl(short left, short top, short width) : ezel::detail::window_impl(L"", left, top, width, 1) {} std::wstring window_class() const { return L"static"; } DWORD style() const { return WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ | WS_GROUP | SS_NOTIFY; } }; class line : public ezel::control { public: line( short left, short top, short width) : control( boost::shared_ptr(new line_impl(left, top, width))) {} short left() const { return impl()->left(); } short top() const { return impl()->top(); } short width() const { return impl()->width(); } short height() const { return impl()->height(); } }; }} // namespace ezel::controls #endif ================================================ FILE: ezel/controls/spinner.hpp ================================================ /** @file GUI spinner control. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_CONTROLS_SPINNER_HPP #define EZEL_CONTROLS_SPINNER_HPP #pragma once #include // control base class #include // window_impl #include // send_message #include // shared_ptr #include // int32_t #include #include // UDS_*, UDM_* namespace ezel { namespace controls { class spinner_impl : public ezel::detail::window_impl { protected: typedef ezel::detail::window_impl super; public: spinner_impl( short left, short top, short width, short height, boost::int32_t minimum, boost::int32_t maximum, boost::int32_t initial_value, DWORD custom_style) : ezel::detail::window_impl(L"", left, top, width, height), m_min(minimum), m_max(maximum), m_value(initial_value), m_custom_style(custom_style) {} std::wstring window_class() const { return L"msctls_updown32"; } DWORD style() const { DWORD style = ezel::detail::window_impl::style() | WS_CHILD | UDS_ARROWKEYS | UDS_SETBUDDYINT | UDS_AUTOBUDDY; style |= m_custom_style; return style; } void range(boost::int32_t minimum, boost::int32_t maximum) { if (!is_active()) { m_min = minimum; m_max = maximum; } else { washer::send_message( hwnd(), UDM_SETRANGE32, minimum, maximum); } } boost::int32_t value(boost::int32_t v) { if (!is_active()) m_value = v; else return washer::send_message_return( hwnd(), UDM_SETPOS32, 0, v); } protected: /** * Set the initial range of the spinner. */ virtual void push() { super::push(); washer::send_message(hwnd(), UDM_SETRANGE32, m_min, m_max); washer::send_message(hwnd(), UDM_SETPOS32, 0, m_value); } private: boost::int32_t m_min; boost::int32_t m_max; boost::int32_t m_value; DWORD m_custom_style; }; class spinner : public ezel::control { public: struct style { enum value { default = 0, no_thousand_separator = UDS_NOTHOUSANDS, wrap_sequence = UDS_WRAP }; }; spinner( short left, short top, short width, short height, boost::int32_t minimum, boost::int32_t maximum, boost::int32_t initial_value, style::value custom_style=style::default) : control( boost::shared_ptr( new spinner_impl( left, top, width, height, minimum, maximum, initial_value, custom_style))) {} void range(boost::int32_t minimum, boost::int32_t maximum) { impl()->range(minimum, maximum); } boost::int32_t value(boost::int32_t new_value) { return impl()->value(new_value); } short left() const { return impl()->left(); } short top() const { return impl()->top(); } short width() const { return impl()->width(); } short height() const { return impl()->height(); } }; }} // namespace ezel::controls #endif ================================================ FILE: ezel/detail/command_dispatch.hpp ================================================ /** @file Command message dispatch. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_COMMAND_DISPATCH_HPP #define EZEL_COMMAND_DISPATCH_HPP #pragma once #include // command #ifndef BOOST_MPL_LIMIT_VECTOR_SIZE #define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS #endif #ifndef BOOST_MPL_LIMIT_VECTOR_SIZE #define BOOST_MPL_LIMIT_VECTOR_SIZE 50 #endif #include // begin, end #include // copy_if #include // not_equal_to #include // vector_c #include // is_same #define EZEL_COMMAND_MAP_TEMPLATE \ int EZC0=-1, int EZC1=-1, int EZC2=-1, int EZC3=-1, int EZC4=-1, \ int EZC5=-1, int EZC6=-1, int EZC7=-1, int EZC8=-1, int EZC9=-1, \ int EZC10=-1, int EZC11=-1, int EZC12=-1, int EZC13=-1, int EZC14=-1, \ int EZC15=-1, int EZC16=-1, int EZC17=-1, int EZC18=-1, int EZC19=-1, \ int EZC20=-1, int EZC21=-1, int EZC22=-1, int EZC23=-1, int EZC24=-1, \ int EZC25=-1, int EZC26=-1, int EZC27=-1, int EZC28=-1, int EZC29=-1, \ int EZC30=-1, int EZC31=-1, int EZC32=-1, int EZC33=-1, int EZC34=-1, \ int EZC35=-1, int EZC36=-1, int EZC37=-1, int EZC38=-1, int EZC39=-1, \ int EZC40=-1, int EZC41=-1, int EZC42=-1, int EZC43=-1, int EZC44=-1, \ int EZC45=-1, int EZC46=-1, int EZC47=-1, int EZC48=-1, int EZC49=-1 #define EZEL_COMMAND_ARG \ EZC0, EZC1, EZC2, EZC3, EZC4, \ EZC5, EZC6, EZC7, EZC8, EZC9, \ EZC10, EZC11, EZC12, EZC13, EZC14, \ EZC15, EZC16, EZC17, EZC18, EZC19, \ EZC20, EZC21, EZC22, EZC23, EZC24, \ EZC25, EZC26, EZC27, EZC28, EZC29, \ EZC30, EZC31, EZC32, EZC33, EZC34, \ EZC35, EZC36, EZC37, EZC38, EZC39, \ EZC40, EZC41, EZC42, EZC43, EZC44, \ EZC45, EZC46, EZC47, EZC48, EZC49 namespace ezel { namespace detail { template inline void dispatch_command(T* obj, WORD id, WPARAM wparam, LPARAM lparam); template inline void dispatch_command_next( T* obj, WORD /*id*/, WPARAM wparam, LPARAM lparam, boost::mpl::true_) { obj->on(washer::gui::command_base(wparam, lparam)); } template inline void dispatch_command_next( T* obj, WORD id, WPARAM wparam, LPARAM lparam, boost::mpl::false_) { dispatch_command(obj, id, wparam, lparam); } /** * Dispatch conditionally based on whether we're at end of superclass chain. * * If we are, this command goes to the default command handler. Otherwise, * we create a dispatch for the superclasses command map and delegate * dispatch there. */ template inline void dispatch_command( T* obj, WORD command_id, WPARAM wparam, LPARAM lparam, boost::mpl::true_) { return dispatch_command_next( obj, command_id, wparam, lparam, boost::is_same::type()); } template inline void dispatch_command( T* obj, WORD command_id, WPARAM wparam, LPARAM lparam, boost::mpl::false_) { typedef boost::mpl::deref::type Front; typedef boost::mpl::next::type Next; if(command_id == Front::value) { return obj->on(command(wparam, lparam)); } else { return dispatch_command( obj, command_id, wparam, lparam, typename boost::is_same::type()); } } /** * Command dispatcher. * * Messages are dispatched to the superclasses of T one at a time until one is * found whose command map contains the current command. Then its handler * for that meassage is invoked. * If we reach the end of the chain without finding a matching map entry, * the command is delivered to the default command handler. */ template inline void dispatch_command( T* obj, WORD command_id, WPARAM wparam, LPARAM lparam) { typedef boost::mpl::begin::type Begin; typedef boost::mpl::end::type End; dispatch_command( obj, command_id, wparam, lparam, typename boost::is_same::type()); } template class command_map { public: // -1 is used to indicate an unused template parameter. To // prevent us having to treat -1 as a special case, we filter it out of // the command map immediately here typedef typename boost::mpl::copy_if< boost::mpl::vector_c, boost::mpl::not_equal_to< boost::mpl::_1, boost::mpl::int_<-1> >, boost::mpl::back_inserter< boost::mpl::vector_c > >::type commands; }; }} // namespace washer::gui #endif ================================================ FILE: ezel/detail/dialog_template.hpp ================================================ /** @file Windows dialog in-memory template helpers @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_DETAIL_DIALOG_TEMPLATE_HPP #define EZEL_DETAIL_DIALOG_TEMPLATE_HPP #pragma once #include // window_impl #include // BOOST_FOREACH #include // numeric_cast #include // shared_ptr #include // BOOST_STATIC_ASSERT #include // copy #include #include namespace ezel { namespace detail { #pragma warning(push) #pragma warning(disable: 4996) // std::copy: Function call that may be unsafe // the calculations rely on pointer-arithmetic on wchar_t pointers // skipping 16-bit (WORD-sized) fields BOOST_STATIC_ASSERT(sizeof(wchar_t) == 2); // 16-bits BOOST_STATIC_ASSERT(sizeof(wchar_t) == sizeof(WORD)); /** * If the pointer is already DWORD-aligned return it unchanged otherwise * advance to the next DWORD boundary. */ template inline T* next_double_word(T* p) { return reinterpret_cast((reinterpret_cast(p) + 3) & ~3); } /** * If the pointer is already WORD-aligned return it unchanged otherwise * advance to the next WORD boundary. */ template inline T* next_word(T* p) { return reinterpret_cast((reinterpret_cast(p) + 1) & ~1); } /** * Calculate the necessary buffer size for a dialog template. * * Apparently all the fields are naturally WORD-aligned so we don't need to * force it explicitly * (@see http://msdn.microsoft.com/en-us/library/ms644996%28VS.85%29.aspx in * the user contrib comments) but we do so anyway just to be on the safe side. */ inline size_t calculate_template_size( const std::wstring& title, const std::wstring& font) { DLGTEMPLATE* dlg = 0; // template wchar_t* pos = reinterpret_cast(dlg + 1); pos = next_word(pos) + 1; // menu (1) pos = next_word(pos) + 1; // window class (1) // title (?) + terminator (1) wchar_t* title_array = next_word(pos); title_array = title_array + title.size() + 1; // fontsize (1) pos = next_word(title_array); pos = pos + 1; // font (?) + terminator (1) wchar_t* font_array = next_word(pos); font_array = next_word(font_array) + font.size() + 1; // padding return reinterpret_cast(next_double_word(font_array)); } /** * Write the DLGTEMPLATE part of a Dialog template. * * Assumes the buffer is big enough to fit the data. Use * calculate_template_size to calculate the nescessary size. */ inline DLGITEMTEMPLATE* write_template_to_buffer( const std::wstring& title, short font_size, const std::wstring& font, short left, short top, short width, short height, size_t control_count, DLGTEMPLATE* dlg) { // dlg buffer must already be DWORD aligned assert((((int)dlg) % sizeof(DWORD)) == 0); dlg->style = DS_SETFONT | WS_VISIBLE | WS_POPUPWINDOW | DS_MODALFRAME; if (!title.empty()) dlg->style |= WS_CAPTION; dlg->cx = width; dlg->cy = height; dlg->x = left; dlg->y = top; dlg->cdit = boost::numeric_cast(control_count); wchar_t* pos = reinterpret_cast(dlg + 1); pos = next_word(pos); *pos++ = 0; // no menu pos = next_word(pos); *pos++ = 0; // default dialog window class // caption wchar_t* title_array = next_word(pos); title_array = std::copy(title.begin(), title.end(), title_array); *title_array++ = 0; // null-terminate // font size pos = next_word(title_array); *pos++ = font_size; // font name wchar_t* font_array = next_word(pos); font_array = std::copy(font.begin(), font.end(), font_array); *font_array++ = 0; // null-terminate return reinterpret_cast(next_double_word(font_array)); } /** * Calculate the necessary buffer size for an item template. * * Apparently all the fields are naturally WORD-aligned so we don't need to * force it explicitly * (@see http://msdn.microsoft.com/en-us/library/ms644996%28VS.85%29.aspx in * the user contrib comments) but we do so anyway just to be on the safe side. * * After the custom data field we have to add en extra WORD of buffer *not* * including any extra needed for DWORD-alignment. This does not match the * MSDN documentation but is required. */ template inline size_t calculate_control_template_size( const std::wstring& window_class, const std::wstring& title, size_t current_buffer_size) { // item template DLGITEMTEMPLATE* item = reinterpret_cast(current_buffer_size); item = next_double_word(item) + 1; // class (?) + terminator (1) wchar_t* class_array = reinterpret_cast(item); class_array = next_word(class_array) + window_class.size() + 1; // title (?) + terminator (1) wchar_t* title_array = class_array + 1; title_array = next_word(title_array) + title.size() + 1; // custom data size (1) + custom data (?) + extra WORD (1) wchar_t* custom_data_size = title_array + 1; custom_data_size = next_word(custom_data_size); Customdata* data = reinterpret_cast(custom_data_size + 1); data++; wchar_t* mystery_extra = reinterpret_cast(data); mystery_extra++; return reinterpret_cast(next_double_word(mystery_extra)); } /** * Write a DLGITEMTEMPLATE entry for a dialog control. * * Assumes the buffer is big enough to fit the data. Use * calculate_item_template_size to calculate the nescessary size. */ template inline DLGITEMTEMPLATE* write_control_to_buffer( const std::wstring& window_class, const std::wstring& title, unsigned short id, DWORD style, short width, short height, short left, short top, const CustomData& custom_data, DLGITEMTEMPLATE* buffer) { DLGITEMTEMPLATE* item = next_double_word(buffer); item->style = style; item->id = id; item->cx = width; item->cy = height; item->x = left; item->y = top; item = item + 1; // skip over template // control window class name wchar_t* class_array = reinterpret_cast(next_word(item)); class_array = std::copy( window_class.begin(), window_class.end(), class_array); *class_array++ = 0; // null-terminate // title wchar_t* title_array = next_word(class_array); title_array = std::copy(title.begin(), title.end(), title_array); *title_array++ = 0; // null-terminate // custom data size must include its own memory in the size value wchar_t* custom_data_size = next_word(title_array); *custom_data_size++ = sizeof(wchar_t) + sizeof(CustomData); CustomData* data = reinterpret_cast(custom_data_size); *data++ = custom_data; wchar_t* mystery_extra = reinterpret_cast(data); mystery_extra++; return reinterpret_cast(next_double_word(mystery_extra)); } /** * The required buffer size to store the window as a control in a * dialog template. */ inline size_t increment_required_buffer_size( const window_impl* w, size_t current_buffer_size) { return calculate_control_template_size( w->window_class(), w->text(), current_buffer_size); } /** * Write the window to a byte buffer as a control in a dialog template. */ inline DLGITEMTEMPLATE* to_buffer( window_impl* w, unsigned short id, DLGITEMTEMPLATE* buffer) { return write_control_to_buffer( w->window_class(), w->text(), id, w->style(), w->width(), w->height(), w->left(), w->top(), w, buffer); } const unsigned short BUTTON_ID_OFFSET = 100; /** * Build a dialog resource template in memory. */ inline std::vector build_dialog_template_in_memory( const std::wstring& font, short font_size, const std::wstring& title, short width, short height, short left, short top, const std::vector >& controls) { size_t buffer_len = calculate_template_size(title, font); BOOST_FOREACH(boost::shared_ptr w, controls) { buffer_len = increment_required_buffer_size(w.get(), buffer_len); } std::vector buffer(buffer_len, 0); DLGITEMTEMPLATE* pos = write_template_to_buffer( title, font_size, font, width, height, left, top, controls.size(), reinterpret_cast(&buffer[0])); for (size_t i = 0; i < controls.size(); ++i) { window_impl* w = controls[i].get(); // offset ID to avoid collision with dialog manager's // 'special' button IDs unsigned short id = boost::numeric_cast( i + BUTTON_ID_OFFSET); pos = to_buffer( w, id, reinterpret_cast(pos)); } return buffer; } #pragma warning(pop) }} // namespace ezel::detail #endif ================================================ FILE: ezel/detail/hooks.hpp ================================================ /** @file Window creation hooks @if license Copyright (C) 2010, 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_HOOKS_HPP #define EZEL_HOOKS_HPP #pragma once #include // window_impl #include // windows_hook #include // cerr #include // CallNextHookEx namespace ezel { namespace detail { namespace native { /// @name CREATESTRUCT // @{ template struct create_struct; template<> struct create_struct { typedef CREATESTRUCTA type; }; template<> struct create_struct { typedef CREATESTRUCTW type; }; // @} /// @name CBT_CREATEWND // @{ template struct cbt_createwnd; template<> struct cbt_createwnd { typedef CBT_CREATEWNDA type; }; template<> struct cbt_createwnd { typedef CBT_CREATEWNDW type; }; // @} } template inline void handle_create( HWND hwnd, HWND /*insert_after*/, typename native::create_struct::type& create_info) { UNALIGNED wchar_t* data = static_cast(create_info.lpCreateParams); if (data) { assert(*data == sizeof(wchar_t) + sizeof(window_impl*)); data++; // skip size window_impl* w = *reinterpret_cast(data); w->attach(hwnd); } } template inline void handle_destroy(HWND hwnd) { window_impl* this_window = fetch_user_window_data(hwnd); this_window->detach(); } /** * Hooking procedure called by Windows any time a GUI event happens. * * This function captures window creation and establishes a two-way link * between the Win32 window object and the C++ wrapper object. * * @todo Pass *this* hook as first param for Windows 9x. */ template inline LRESULT CALLBACK cbt_hook_function( int code, WPARAM wparam, LPARAM lparam) { try { if (code == HCBT_CREATEWND) { HWND hwnd = reinterpret_cast(wparam); typedef typename native::cbt_createwnd::type* cbt_param; cbt_param cbt_info = reinterpret_cast(lparam); handle_create( hwnd, cbt_info->hwndInsertAfter, *(cbt_info->lpcs)); } else if (code == HCBT_DESTROYWND) { //handle_destroy(reinterpret_cast(wparam)); } } catch (std::exception& e) { std::cerr << e.what(); } // TODO: pass *this* hook as first param for Windows 9x LRESULT rc = ::CallNextHookEx(NULL, code, wparam, lparam); return rc; } /** * Sets up and tears down window event hooks. * * Declare one static instance of this class in order to register window * creation. */ template class creation_hooks { public: creation_hooks() : m_cbt_hook(washer::windows_hook(WH_CBT, &cbt_hook_function)) {} private: washer::hhook m_cbt_hook; }; }} // namespace ezel::detail #endif ================================================ FILE: ezel/detail/hwnd_linking.hpp ================================================ /** @file Low-level HWND manipulation. @if license Copyright (C) 2010, 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_DETAIL_WINDOW_HPP #define EZEL_DETAIL_WINDOW_HPP #pragma once #include // last_error #include // set_window_field, window_field namespace ezel { namespace detail { /** * Store a value in the GWLP_USERDATA segment of the window descriptor. * * The value type must be no bigger than a LONG_PTR. */ template inline void store_user_window_data(HWND hwnd, const U& data) { washer::gui::set_window_field(hwnd, GWLP_USERDATA, data); } /** * Store a value in the DWLP_USER segment of the window descriptor. * * The value type must be no bigger than a LONG_PTR. */ template inline void store_dialog_window_data(HWND hwnd, const U& data) { washer::gui::set_window_field(hwnd, DWLP_USER, data); } /** * Get a value previously stored in the GWLP_USERDATA segment of the * window descriptor. * * The value type must be no bigger than a LONG_PTR. * * @throws system_error if there is an error or if no value has yet been * stored. * @note Storing 0 will count as not having a previous value so will * throw an exception. */ template inline U fetch_user_window_data(HWND hwnd) { return washer::gui::window_field(hwnd, GWLP_USERDATA); } /** * Get a value previously stored in the DWLP_USER segment of the * window descriptor. * * The value type must be no bigger than a LONG_PTR. * * @throws system_error if there is an error or if no value has yet been * stored. * @note Storing 0 will count as not having a previous value so will * throw an exception. */ template inline U fetch_dialog_window_data(HWND hwnd) { return washer::gui::window_field(hwnd, DWLP_USER); } }} // namespace ezel::detail #endif ================================================ FILE: ezel/detail/message_dispatch.hpp ================================================ /** @file Message dispatch. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_MESSAGE_DISPATCH_HPP #define EZEL_MESSAGE_DISPATCH_HPP #pragma once #include // message #ifndef BOOST_MPL_LIMIT_VECTOR_SIZE #define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS #endif #ifndef BOOST_MPL_LIMIT_VECTOR_SIZE #define BOOST_MPL_LIMIT_VECTOR_SIZE 50 #endif #include // begin, end #include // copy_if #include // not_equal_to #include // vector_c #include // is_same #define EZEL_MESSAGE_MAP_TEMPLATE \ UINT EZM0=0, UINT EZM1=0, UINT EZM2=0, UINT EZM3=0, UINT EZM4=0, \ UINT EZM5=0, UINT EZM6=0, UINT EZM7=0, UINT EZM8=0, UINT EZM9=0, \ UINT EZM10=0, UINT EZM11=0, UINT EZM12=0, UINT EZM13=0, UINT EZM14=0, \ UINT EZM15=0, UINT EZM16=0, UINT EZM17=0, UINT EZM18=0, UINT EZM19=0, \ UINT EZM20=0, UINT EZM21=0, UINT EZM22=0, UINT EZM23=0, UINT EZM24=0, \ UINT EZM25=0, UINT EZM26=0, UINT EZM27=0, UINT EZM28=0, UINT EZM29=0, \ UINT EZM30=0, UINT EZM31=0, UINT EZM32=0, UINT EZM33=0, UINT EZM34=0, \ UINT EZM35=0, UINT EZM36=0, UINT EZM37=0, UINT EZM38=0, UINT EZM39=0, \ UINT EZM40=0, UINT EZM41=0, UINT EZM42=0, UINT EZM43=0, UINT EZM44=0, \ UINT EZM45=0, UINT EZM46=0, UINT EZM47=0, UINT EZM48=0, UINT EZM49=0 #define EZEL_MESSAGE_ARG \ EZM0, EZM1, EZM2, EZM3, EZM4, \ EZM5, EZM6, EZM7, EZM8, EZM9, \ EZM10, EZM11, EZM12, EZM13, EZM14, \ EZM15, EZM16, EZM17, EZM18, EZM19, \ EZM20, EZM21, EZM22, EZM23, EZM24, \ EZM25, EZM26, EZM27, EZM28, EZM29, \ EZM30, EZM31, EZM32, EZM33, EZM34, \ EZM35, EZM36, EZM37, EZM38, EZM39, \ EZM40, EZM41, EZM42, EZM43, EZM44, \ EZM45, EZM46, EZM47, EZM48, EZM49 namespace ezel { namespace detail { template inline LRESULT dispatch_message_next( T* obj, UINT message_id, WPARAM wparam, LPARAM lparam); template inline LRESULT dispatch_message_next( T* obj, UINT message_id, WPARAM wparam, LPARAM lparam, boost::mpl::true_) { return obj->default_message_handler(message_id, wparam, lparam); } template inline LRESULT dispatch_message_next( T* obj, UINT message_id, WPARAM wparam, LPARAM lparam, boost::mpl::false_) { return dispatch_message(obj, message_id, wparam, lparam); } /** * Dispatch conditionally based on whether we're at end of superclass chain. * * If we are, this message goes to the default message handler. Otherwise, * we create a dispatch for the superclasses message map and delegate * dispatch there. */ template inline LRESULT dispatch_message( T* obj, UINT message_id, WPARAM wparam, LPARAM lparam, boost::mpl::true_) { return dispatch_message_next( obj, message_id, wparam, lparam, boost::is_same::type()); } template inline LRESULT dispatch_message( T* obj, UINT message_id, WPARAM wparam, LPARAM lparam, boost::mpl::false_) { typedef boost::mpl::deref::type Front; typedef boost::mpl::next::type Next; if(message_id == Front::value) { return obj->on(message(wparam, lparam)); } else { return dispatch_message( obj, message_id, wparam, lparam, typename boost::is_same::type()); } } /** * Main message handler. * * Messages are dispatched to the superclasses of T one at a time until one is * found whose message map contains the current message. Then its handler for * that meassage is invoked. If we reach the end of the chain without finding * a matching map entry, the message is delivered to the default message * handler. */ template inline LRESULT dispatch_message( T* obj, UINT message_id, WPARAM wparam, LPARAM lparam) { typedef boost::mpl::begin::type Begin; typedef boost::mpl::end::type End; return dispatch_message( obj, message_id, wparam, lparam, typename boost::is_same::type()); } template class message_map { public: // Zero (0) is used to indicate an unused template parameter. To // prevent us having to treat 0 as a special case, we filter it out of // the message map immediately here typedef typename boost::mpl::copy_if< boost::mpl::vector_c, boost::mpl::not_equal_to< boost::mpl::_1, boost::mpl::int_<0> >, boost::mpl::back_inserter< boost::mpl::vector_c > >::type messages; }; }} // namespace washer::gui #endif ================================================ FILE: ezel/detail/window_impl.hpp ================================================ /** @file HWND wrapper implementation. @if license Copyright (C) 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_DETAIL_WINDOW_IMPL_HPP #define EZEL_DETAIL_WINDOW_IMPL_HPP #pragma once #include // command_map #include // message_map #include // window_link #include // window_proc_base, window_proc #include // window_proxy #include // message #include // command #include // trace #include #include // diagnostic_information #include // make_shared #include // noncopyable #include // signal #include // assert #include namespace ezel { /** * We are EXPLICITLY bringing the washer::gui::message/command classes into * the ezel namespace. */ using washer::gui::message; using washer::gui::command; namespace detail { void catch_form_creation(HWND hwnd, unsigned int msg, LPARAM lparam); void catch_form_destruction(HWND hwnd, unsigned int msg); LRESULT CALLBACK window_impl_proc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); class window_impl; /** * Fetch C++ wrapper pointer embedded in WND data. */ inline window_impl* window_from_hwnd(HWND hwnd) { return fetch_user_window_data(hwnd); } /** * Interface for window wrappers used internally by window_impl. * * There are two implementations of this interface. One wraps a real Win32 * window which window_impl uses once it's been attached to an HWND. The * other only simulates a window to holds the properties that a window_impl * in initialised with as well as to reflect any property changes made before * the real window is created. */ template class internal_window { public: virtual std::basic_string text() const = 0; virtual void text(const std::basic_string& new_text) = 0; virtual bool is_visible() = 0; virtual bool is_enabled() = 0; virtual void visible(bool state) = 0; virtual void enable(bool state) = 0; virtual short left() const = 0; virtual short top() const = 0; virtual short width() const = 0; virtual short height() const = 0; }; /** * Fake window holding properties before real window is attached. * * The purpose of this class is to maintain any properties which are set on * a wrapper before it has been attached to a real Win32 window. It * simulates the fields in the real window. */ template class fake_window : public internal_window { public: fake_window( bool is_enabled, bool is_visible, const std::basic_string& text, short left, short top, short width, short height) : m_enabled(is_enabled), m_visible(is_visible), m_text(text), m_left(left), m_top(top), m_width(width), m_height(height) {} std::basic_string text() const { return m_text; } void text(const std::basic_string& new_text) { m_text = new_text; } bool is_visible() { return m_visible; } bool is_enabled() { return m_enabled; } void visible(bool state) { m_visible = state; } void enable(bool state) { m_enabled = state; } short left() const { return m_left; } short top() const { return m_top; } short width() const { return m_width; } short height() const { return m_height; } private: std::basic_string m_text; bool m_enabled; bool m_visible; short m_left; short m_top; short m_width; short m_height; }; /** * Wrapper around a real Win32 window. */ template class real_window : public internal_window { public: real_window(HWND hwnd) : m_window(washer::window::window_handle::foster_handle(hwnd)) { if (hwnd == NULL) BOOST_THROW_EXCEPTION(std::logic_error("Invalid window handle")); } /** * Window text. * * @note We could allow the caller to specify that they require narrow or * wide string irrespective of whether this window is narrow or * wide. However, the fake_window doesn't support this and * this class must match its interface. */ std::basic_string text() const { return window().text(); } /** * Window text. * * @note We could allow the caller to pass this method a narrow or wide * string irrespective of whether this window is narrow or wide. * However, the fake_window doesn't support this and this * class must match its interface. */ void text(const std::basic_string& new_text) { window().text(new_text); } bool is_visible() { return window().is_visible(); } bool is_enabled() { return window().is_enabled(); } void visible(bool state) { window().visible(state); } void enable(bool state) { window().enable(state); } short left() const { return window().position().left(); } short top() const { return window().position().top(); } short width() const { return window().position().width(); } short height() const { return window().position().height(); } private: washer::window::window& window() { return m_window; } const washer::window::window& window() const { return m_window; } washer::window::window m_window; }; template void copy_fields(internal_window& source, internal_window& target) { target.text(source.text()); target.enable(source.is_enabled()); target.visible(source.is_visible()); } /** * Window handle (HWND) wrapper class. * * Only one instance must exist per HWND so this class in non-copyable. * Clients use it via facade classes that reference the single instances * via a shared pointer. * * The lifetime of the class has three phases: * * - before it is connected to an HWND. * The data in the fields of this class are the data that the Win32 window * will be initialised with (via a dialog template) when the Window dialog * manager calls CreateWindow. * * - while connected to an HWND. * Method of this class fetch their data directly from the Win32 object. * Member fields are ignored. * * - after detaching from an HWND (when the Win32 window is destroyed) * The Win32 data is pulled in just before destruction and stored in the * member fields. Subsequent method calls use this data. */ class window_impl : private boost::noncopyable { public: typedef window_impl super; // end of dispatch chain typedef message_map< WM_CREATE, WM_DESTROY, WM_NCDESTROY, WM_SETTEXT, WM_SHOWWINDOW> messages; typedef command_map<-1> commands; virtual LRESULT handle_message( UINT message_id, WPARAM wparam, LPARAM lparam) { return dispatch_message(this, message_id, wparam, lparam); } virtual void handle_command( WORD command_id, WPARAM wparam, LPARAM lparam) { dispatch_command(this, command_id, wparam, lparam); } window_impl( const std::wstring& text, short left, short top, short width, short height) : m_window( boost::make_shared< fake_window >( true, true, text, left, top, width, height)) {} virtual ~window_impl() { assert(!m_link.attached()); // why have we not detached? } bool is_active() const { return m_link.attached(); } virtual std::wstring window_class() const = 0; virtual DWORD style() const { return WS_VISIBLE | WS_TABSTOP; } short left() const { return window().left(); } short top() const { return window().top(); } short width() const { return window().width(); } short height() const { return window().height(); } std::wstring text() const { return window().text(); } void text(const std::wstring& new_text) { window().text(new_text); } void visible(bool state) { window().visible(state); } void enable(bool state) { window().enable(state); } /// @name Event handlers // @{ /** * @name Lifetime events * * The main purpose of these handlers is to synchronise C++ wrapper * and the real Win32 window. The wrapper's fields can be set before the * real window is created and callers need access to the fields after the * real window is destroyed. Therefore we push the data out to the * window when it's created and pull it back just before it's destroyed. * * We do this with the @c WM_CREATE and @c WM_DESTROY messages (rather * than @c WM_NCCREATE and @c WM_NCDESTROY) as we can't be sure of the * fields' integrity outside of this 'safe zone'. For example, when * common controls 6 is enabled setting an icon before @c WM_CREATE * fails to show the icon. */ LRESULT on(message m) { LRESULT res = default_message_handler(m); push(); return res; } LRESULT on(message m) { pull(); return default_message_handler(m); } /** * To prevent capturing creation of windows not directly part of our * dialog template, such as the system menu, we engage our CBT hook for * as short a period as possible. Therefore we have to detach ourselves * in this function rather than from the CBT hook. * * @see ezel::detail::cbt_hook_function. */ LRESULT on(message m) { LRESULT res = default_message_handler(m); detach(); return res; } // @} LRESULT on(message m) { m_on_text_change(m.text()); LRESULT res = default_message_handler(m); m_on_text_changed(); return res; } LRESULT on(message m) { m_on_showing(m.state()); LRESULT res = default_message_handler(m); m_on_show(m.state()); return res; } // @} /** * Perform default processing for a message. * * This method must call the window procedure of the wrapped window. * Here it's done through a call to CallWindowProc but dialog windows * must do this differently so should override this method. */ virtual LRESULT default_message_handler( UINT message_id, WPARAM wparam, LPARAM lparam) { return m_window_proc->do_default_handling(message_id, wparam, lparam); } template LRESULT default_message_handler(const message& m) { return default_message_handler(N, m.wparam(), m.lparam()); } /** * Default command handler. * * Command message that aren't handled elsewhere are dispatched to this * function. By default, it does nothing. Override if you want to * to handle unhandled command messages. */ virtual void on(washer::gui::command_base unknown) { (void)unknown; #ifdef _DEBUG window_impl* w = window_from_hwnd(unknown.control_hwnd()); washer::trace( "Unhandled command (code 0x%x) from window with title '%s'") % unknown.command_code() % w->text(); #endif } /** * Establish a two-way link between this C++ wrapper object and the * Win32 window object. * * Also replace the Win32 window's message handling procedure (WNDPROC) * with our own so that we can intercept any messages it is sent. This * is otherwise known as subclassing. * * We don't push the wrapper fields out to the Win32 window yet as it's * much too early. This is called by the CBT hook and at this point the * window hasn't even recieved an WM_NCCREATE message yet. * * @todo What if we get a failure partway through? */ void attach(HWND hwnd) { assert(!m_link.attached()); // an instance should only be attached once m_link = window_link(hwnd, this); m_window.attach(hwnd); install_window_procedure(); } /// @name Event delegates // @{ boost::signal& on_text_change() { return m_on_text_change; } boost::signal& on_text_changed() { return m_on_text_changed; } boost::signal& on_showing() { return m_on_showing; } boost::signal& on_show() { return m_on_show; } // @} protected: HWND hwnd() const { return m_link.hwnd(); } /** * Suck data from real Win32 window object into the wrapper class. * * This method exists so that properties of the window are still available * after the real window has been destroyed. * * Override this method when subclasses have other fields that need to * be sucked out of the window. In most cases the overriding method must * call this method of the base class to synchronise all the fields. */ virtual void pull() { m_window.pull(); } /** * Update Win32 window object from fields in this wrapper class. * * Fields can be set in the wrapper before the Win32 window is created. * This window pushes those values out to the real window once it is * created. * * Override this method when subclasses have other fields that need to * be pushed out to the window. In most cases the overriding method must * call this method of the base class to synchronise all the fields. * * @todo Some of this pushing is redundant as the values are set in * the dialogue template. Not necessarily harmful but worth * further thought. */ virtual void push() { m_window.push(); } boost::shared_ptr& window_procedure() { return m_window_proc; } private: /** * Break the two-way link between this C++ wrapper object and the * Win32 window object. * * The fields of the Win32 must have been pulled in by our window * proc when it recieved WM_DESTROY. That message is the last point at * which we can be sure of the fields' integrity. * * @bug If someone has subclassed us but not removed their hook by the * time they pass us the the WM_NCDESTROY message (bad!) then we * never remove our hooks as we're not at the bottom of the * subclass chain. The UpDown control seems to do this when * it subclasses its buddy control. * * @todo Investigate SetWindowSubclass/RemoveWindowSubclass and * whether it might fix the unsubclassing bug. Unfortunately, * it may not work on earlier Windows versions. */ void detach() { assert(m_link.attached()); // why are we detaching a detached wrapper? remove_window_procedure(); m_window.detach(); m_link = window_link(); } /** * Replace the window's own Window proc with ours. */ virtual void install_window_procedure() { window_procedure() = boost::make_shared(hwnd(), window_impl_proc); } /** * Remove our window proc and put back the one it came with. */ virtual void remove_window_procedure() { window_procedure().reset(); } internal_window& window() { return *m_window; } const internal_window& window() const { return *m_window; } window_link m_link; window_proxy< internal_window, fake_window, real_window > m_window; boost::shared_ptr m_window_proc; /// @name Events // @{ boost::signal m_on_text_change; boost::signal m_on_text_changed; boost::signal m_on_showing; boost::signal m_on_show; // @} }; /** * Custom window procedure for wrapping HWNDs and intercepting their * messages. */ inline LRESULT CALLBACK window_impl_proc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { try { window_impl* w = window_from_hwnd(hwnd); return w->handle_message(message, wparam, lparam); } catch (const std::exception& e) { // We should always be able to get our window. If we were able to // replace the window proc with this one then we must have hooked // it correctly so why can't we find it now? washer::trace("window_impl_proc exception: %s") % boost::diagnostic_information(e); assert(!"Something went very wrong here - we couldn't get our window"); return ::DefWindowProcW(hwnd, message, wparam, lparam); } } }} // namespace ezel::detail #endif ================================================ FILE: ezel/detail/window_link.hpp ================================================ /** @file HWND/wrapper linking. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_DETAIL_WINDOW_LINK_HPP #define EZEL_DETAIL_WINDOW_LINK_HPP #pragma once #include // store_user_window_data #include // trace #include // diagnostic_information #include // noncopyable #include // shared_ptr, make_shared namespace ezel { namespace detail { /** * Link between a real Win32 window handle and a pointer to a window wrapper. * * The purpose of this class is to establish, maintain and then destroy a * two-way link between an HWND and a C++ wrapper instance. The link * is broken when the instance is destroyed. To explicitly break the link, * assign a broken link to the existing link: * @code link = window_link() @endcode * * Clients can query the status of the linked by calling attached(). * * Stores the instance pointer in the HWND's user data field. * @todo Store the pointer somewhere else to prevent issues including other * uses accidentally overwriting our pointer. */ template class window_link_helper : private boost::noncopyable { public: typedef T* pointer_type; /// Link HWND to wrapper. window_link_helper(HWND hwnd, pointer_type wrapper) : m_hwnd(hwnd) { store_user_window_data(hwnd, wrapper); } /// Create a broken link. window_link_helper() : m_hwnd(NULL) {} /// Break link. ~window_link_helper() { try { if (attached()) store_user_window_data(m_hwnd, pointer_type()); } catch (const std::exception& e) { washer::trace("Unlinking window threw exception: %s") % boost::diagnostic_information(e); } } HWND hwnd() const { return m_hwnd; } bool attached() const { return m_hwnd != NULL; } private: HWND m_hwnd; }; /** * Copyable link between a real Win32 window handle and a pointer to a window * wrapper. * * The purpose of this class make the link implementation in window_link_helper * break the link only when the last copy goes out-of-scope. * * The methods are the same as window_link_helper. */ template class window_link { public: typedef T* pointer_type; /// Link HWND to wrapper. window_link(HWND hwnd, pointer_type wrapper) : m_link(boost::make_shared >(hwnd, wrapper)) {} /// Create a broken link. window_link() : m_link(boost::make_shared >()) {} HWND hwnd() const { return m_link->hwnd(); } bool attached() const { return m_link->attached(); } private: boost::shared_ptr > m_link; }; }} // namespace ezel::detail #endif ================================================ FILE: ezel/detail/window_proc.hpp ================================================ /** @file Window procedures. @if license Copyright (C) 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_WINDOW_PROC_HPP #define EZEL_WINDOW_PROC_HPP #pragma once #include // trace #include // dialog #include // window #include // assert #include namespace ezel { namespace detail { class window_proc_base { public: virtual ~window_proc_base() {} virtual LRESULT do_default_handling( UINT message_id, WPARAM wparam, LPARAM lparam) = 0; }; /** * Subclass a window with a standard window procdure (WNDPROC). */ class window_proc : public window_proc_base { public: /** * Subclass window. */ window_proc(HWND hwnd, WNDPROC new_proc) : m_window(washer::window::window_handle::foster_handle(hwnd)), m_proc(new_proc), m_sub_proc(m_window.change_window_procedure(m_proc)) {} /** * Unsubclass window. */ ~window_proc() { try { WNDPROC current_wndproc = m_window.window_procedure(); if (current_wndproc == m_proc) { WNDPROC proc = m_window.change_window_procedure(m_sub_proc); (void)proc; assert(proc == m_proc); // mustn't remove someone else's // window procedure } } catch (const std::exception& e) { washer::trace("window_proc destructor threw exception: %s") % boost::diagnostic_information(e); } } virtual LRESULT do_default_handling( UINT message_id, WPARAM wparam, LPARAM lparam) { return ::CallWindowProcW( m_sub_proc, m_window.hwnd(), message_id, wparam, lparam); } protected: washer::window::window& window() { return m_window; } private: washer::window::window m_window; WNDPROC m_proc; WNDPROC m_sub_proc; ///< Subclassed window's default message handler }; /** * Window proc for dialog window. * * Delegates default processing to DefDlgProc as in the alternative * dialog handling method explained by Raymond Chen. * * Your dialog loop will still be called but only if default processing * is invoked. Generally you should just return FALSE to allow the * dialog manager to handle the message. * * @see http://blogs.msdn.com/b/oldnewthing/archive/2003/11/13/55662.aspx */ class dialog_proc : public window_proc { public: dialog_proc(HWND hwnd, WNDPROC new_proc) : window_proc(hwnd, new_proc) {} virtual LRESULT do_default_handling( UINT message_id, WPARAM wparam, LPARAM lparam) { return ::DefDlgProc(window().hwnd(), message_id, wparam, lparam); } }; }} // namespace ezel::detail #endif ================================================ FILE: ezel/detail/window_proxy.hpp ================================================ /** @file Window implementation switcher. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_DETAIL_WINDOW_PROXY_HPP #define EZEL_DETAIL_WINDOW_PROXY_HPP #pragma once #include // make_shared #include // noncopyable #include // shared_ptr #include // assert namespace ezel { namespace detail { /** * Switch between two window implementations. * * One implementation is a real wrapper round an HWND, the other just pretends. * This class serves up a window wrapper that works correctly whether or not * it is attached to an real Win32 window object. * * The purpose of this class is to manage the change from an artificial * window to an attached window and vice versa. The idea is that an instance * of this class will responds correctly to property setting/getting * regardless of whether the window is real or fake. * * As the fake window type may have an arbitrary contructor signature, the * client must pass an instance to our constructor. The real window type * we instantiate ourselves and must have the constructor: * @code RealType(HWND hwnd) @endcode * * Field data is synchronised between the real and fake windows by calling * @c copy_fields. For any type you use as the @c Interface template * parameter you must implement @c copy_fields in the same namespace with this * signature: * @code copy_fields(Interface& source, Interface& target) @endcode * It will be found by ADL. */ template class window_proxy { public: window_proxy(boost::shared_ptr fake) : m_fake_window(fake), m_active_window(m_fake_window) {} Interface* operator->() { return m_active_window; } Interface& operator*() { return *m_active_window; } const Interface* operator->() const { return m_active_window; } const Interface& operator*() const { return *m_active_window; } /** * Switch from fake to real window. */ void attach(HWND hwnd) { assert(!m_real_window); // why are we attaching twice? assert(m_active_window == m_fake_window); // fake window not active one m_real_window = boost::make_shared(hwnd); m_active_window = m_real_window; } /** * Switch back to the fake window. */ void detach() { assert(m_real_window); // why are not attached? assert(m_active_window == m_real_window); // real window not active one m_real_window.reset(); m_active_window = m_fake_window; } /** * Suck data from real Win32 window object into the fake window. * * This method exists so that properties of the window are still available * after the real window has been destroyed. * * Call this method after receiving the WM_CREATE message so that * @c copy_fields() can rely on the integrity of the window fields. */ void pull() { assert(m_real_window); // must not call this method unless attached copy_fields(real(), fake()); } /** * Update Win32 window object from fields in the fake window. * * Fields can be set in the wrapper before the Win32 window is created. * This window pushes those values out to the real window once it is * created. * * Call this method before receiving the WM_DESTROY message so that * @c copy_fields() can rely on the integrity of the window fields. */ void push() { assert(m_real_window); // must not call this method unless attached copy_fields(fake(), real()); } private: Interface& real() { return *m_real_window; } Interface& fake() { return *m_fake_window; } boost::shared_ptr m_fake_window; boost::shared_ptr m_real_window; boost::shared_ptr m_active_window; }; }} // namespace ezel::detail #endif ================================================ FILE: ezel/form.hpp ================================================ /** @file GUI forms (aka dialogs) @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_FORM_HPP #define EZEL_FORM_HPP #pragma once #include // control #include // control_parent_impl #include // build_in_memory_dialog_template #include // creation_hooks #include // fetch_user_window_data #include // window_impl #include // dialog_proc #include // window #include // module_handle #include // command #include // message #include // bind #include // errinfo_api_function #include // errinfo #include // function #include // make_shared #include // shared_ptr #include // signal #include // BOOST_THROW_EXCEPTION #include // weak_ptr #include // assert #include #include #include // DialogBoxIndirectParam namespace ezel { namespace detail { INT_PTR CALLBACK dialog_creation_handler( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); /** * Real form class implementation. */ class form_impl : public control_parent_impl { public: typedef control_parent_impl super; virtual LRESULT handle_message( UINT message, WPARAM wparam, LPARAM lparam) { return dispatch_message(this, message, wparam, lparam); } typedef message_map messages; form_impl( const std::wstring& title, short left, short top, short width, short height) : control_parent_impl(title, left, top, width, height) {} std::wstring window_class() const { return L"#32770"; } DWORD style() const { return DS_SETFONT | WS_VISIBLE | WS_POPUPWINDOW | DS_MODALFRAME; } void add_control(boost::shared_ptr control) { m_controls.push_back(control); } void show(HWND hwnd_owner) { std::vector buffer = build_dialog_template_in_memory( L"MS Shell Dlg", 8, text(), left(), top(), width(), height(), m_controls); hook_window_creation(); try { INT_PTR rc = ::DialogBoxIndirectParamW( washer::module_handle(), (buffer.empty()) ? NULL : reinterpret_cast(&buffer[0]), hwnd_owner, dialog_creation_handler, reinterpret_cast(this)); if (rc < 1) BOOST_THROW_EXCEPTION( boost::enable_error_info(washer::last_error()) << boost::errinfo_api_function( "DialogBoxIndirectParamW")); } catch (...) { unhook_window_creation(); throw; } } void end() { // set the 2nd parameter to > 0 so we can detect error case from // return value of DialogBoxIndirectParam if(!::EndDialog(hwnd(), 1)) BOOST_THROW_EXCEPTION( boost::enable_error_info(washer::last_error()) << boost::errinfo_api_function("EndDialog")); } /// @name Event delegates // @{ boost::signal& on_create() { return m_on_create; } boost::signal& on_activating() { return m_on_activating; } boost::signal& on_activate() { return m_on_activate; } boost::signal& on_deactivating() { return m_on_deactivating; } boost::signal& on_deactivate() { return m_on_deactivate; } // @} /// @name Message handlers // @{ LRESULT on(message m) { end(); return default_message_handler(m); } LRESULT on(message m) { if (m.active()) m_on_activating(m.by_mouse()); else if (m.deactive()) m_on_deactivating(); else assert(!"Inconsistent message state"); LRESULT res = default_message_handler(m); if (m.active()) m_on_activate(m.by_mouse()); else if (m.deactive()) m_on_deactivate(); else assert(!"Inconsistent message state"); return res; } LRESULT on(message /*message*/) { // All our controls should have been created by now so stop // monitoring window creation. This prevents problems with // the system menu which is created later. unhook_window_creation(); if (!m_on_create.empty()) return m_on_create() == TRUE; else return TRUE; // give default control focus } // @} private: /** * Replace the window's own window proc with ours. */ virtual void install_window_procedure() { window_procedure() = boost::make_shared(hwnd(), window_impl_proc); } friend INT_PTR CALLBACK dialog_creation_handler( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); void hook_window_creation() { m_hooks = boost::make_shared >(); } void unhook_window_creation() { m_hooks.reset(); } /** * The collection of controls on this form. * They are held as smart pointers to ensure they stay alive as long * as the form, regardless of how they are passed to add_control. */ std::vector > m_controls; boost::shared_ptr > m_hooks; /// @name Events // @{ boost::signal m_on_create; boost::signal m_on_activating; boost::signal m_on_activate; boost::signal m_on_deactivating; boost::signal m_on_deactivate; // @} }; /** * Dialog proc to capture WM_INITDIALOG. * * The dialog proc boostraps all the rest of the window and message * capture. It is the way we associate the dialog's HWND with a form_impl * instance. We can't do it with the window creation hooks used to * capture the rest of the windows as that relies on a pointer stuffed * in the WM_CREATE CREATESTRUCT. DialogBoxIndirectParam doesn't give * us a way to do that but it does allow us to stuff the pointer into * the WM_INITDIALOG lparam which is why we cature that here. * * The moment we attach the instance to the HWND, our regular Ezel window * procedure takes over and any subsequent message are dispatched as * normal. Therefore we ignore any other types of message. However, * we have to dispatch the WM_INITDIALOG message here as it won't be * sent again. */ inline INT_PTR CALLBACK dialog_creation_handler( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { assert(msg != WM_CREATE); // apparently a dialog should never get this if (msg != WM_INITDIALOG) return FALSE; try { // we stashed a pointer to our C++ form object in the creation // data in the dialog template // now we extract it here and use it to set up a two-way link // between the C++ form object and the Win32 dialog object form_impl* this_form = reinterpret_cast(lparam); this_form->attach(hwnd); return this_form->handle_message(msg, wparam, lparam); } catch (const std::exception& e) { (void)e; /* ignore */ washer::trace("threw when trying to link to dialog window: %s") % e.what(); return FALSE; } } } class form : public window { public: form( const std::wstring& title, short left, short top, short width, short height) : window( boost::make_shared( title, left, top, width, height)) {} template void add_control(const control& control) { impl()->add_control(control.impl()); } void show(HWND hwnd_owner=NULL) { impl()->show(hwnd_owner); } void end() { impl()->end(); } /** * A functor that can be called to destroy the form instance. * * This allows users to write : * @code btn.on_click().connect(frm.killer()) @endcode * instead of * @code btn.on_click().connect(bind(&form::end, ref(frm))) @endcode * * The functor holds a *weak* reference to the form to prevent circular * references. If it held a strong reference, when the functor is passed * to a control owned by the form, the form would indirectly hold a * reference and would never be destroyed. */ boost::function killer() { typedef boost::weak_ptr weak_form_reference; weak_form_reference weak_ref = impl(); return boost::bind( &detail::form_impl::end, boost::bind( &weak_form_reference::lock, weak_ref)); } /// @name Event delegates. // @{ boost::signal& on_create() { return impl()->on_create(); } boost::signal& on_activating() { return impl()->on_activating(); } boost::signal& on_activate() { return impl()->on_activate(); } boost::signal& on_deactivating() { return impl()->on_deactivating(); } boost::signal& on_deactivate() { return impl()->on_deactivate(); } // @} }; } // namespace ezel #endif ================================================ FILE: ezel/window.hpp ================================================ /** @file Ezel window. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef EZEL_WINDOW_HPP #define EZEL_WINDOW_HPP #pragma once #include // shared_ptr #include // signal #include namespace ezel { /** * Base-class for window facades. * * All Ezel windows are an instance of a subclass of this this template. * This provides access to the methods and properties common to all windows. * * @param T Type of implementation class (pimpl) */ template class window { public: window(boost::shared_ptr impl) : m_impl(impl) {} virtual ~window() {} std::wstring text() const { return impl()->text(); } void text(const std::wstring& new_text) const { return impl()->text(new_text); } void visible(bool visibility) { impl()->visible(visibility); } void enable(bool enablement) { impl()->enable(enablement); } /// @name Events // @{ boost::signal& on_showing() { return impl()->on_showing(); } boost::signal& on_show() { return impl()->on_show(); } boost::signal& on_text_change() { return impl()->on_text_change(); } boost::signal& on_text_changed() { return impl()->on_text_changed(); } // @} protected: boost::shared_ptr impl() const { return m_impl; } private: boost::shared_ptr m_impl; // pimpl }; } #endif ================================================ FILE: guids.txt ================================================ b816a838-5022-11dc-9153-0090f5284f85 Swish Type Library (DLL module UUID) b816a839-5022-11dc-9153-0090f5284f85 *REMOVED* IHostFolder Interface b816a83a-5022-11dc-9153-0090f5284f85 CHostFolder Class b816a83b-5022-11dc-9153-0090f5284f85 *REMOVED* IRemoteFolder Interface b816a83c-5022-11dc-9153-0090f5284f85 CRemoteFolder Class b816a83d-5022-11dc-9153-0090f5284f85 *REMOVED* IRemoteEnumIDList Interface b816a83e-5022-11dc-9153-0090f5284f85 *REMOVED* CRemoteEnumIDList Class b816a83f-5022-11dc-9153-0090f5284f85 *REMOVED* IHostContextMenu Interface b816a840-5022-11dc-9153-0090f5284f85 *REMOVED* CHostContextMenu Class b816a841-5022-11dc-9153-0090f5284f85 *REMOVED* ISftpProvider Interface b816a842-5022-11dc-9153-0090f5284f85 *REMOVED* CPuttyProvider Class b816a843-5022-11dc-9153-0090f5284f85 *CHANGED* IEnumListing Interface b816a844-5022-11dc-9153-0090f5284f85 *REMOVED* ISftpConsumer Interface b816a845-5022-11dc-9153-0090f5284f85 *REMOVED* PuttyProvider Type Library b816a846-5022-11dc-9153-0090f5284f85 *CHANGED* Libssh2Provider Type Library (DLL module UUID) b816a847-5022-11dc-9153-0090f5284f85 *CHANGED* CLibssh2Provider Class b816a848-5022-11dc-9153-0090f5284f85 *REMOVED* CExplorerCallback Class b816a849-5022-11dc-9153-0090f5284f85 *REMOVED* CIconExtractor Class b816a84a-5022-11dc-9153-0090f5284f85 *REMOVED* CUserInteraction Class b816a84b-5022-11dc-9153-0090f5284f85 *REMOVED* CSftpDirectory Class b816a84c-5022-11dc-9153-0090f5284f85 b816a84d-5022-11dc-9153-0090f5284f85 b816a84e-5022-11dc-9153-0090f5284f85 b816a84f-5022-11dc-9153-0090f5284f85 b816a850-5022-11dc-9153-0090f5284f85 FMTID Swish host b816a851-5022-11dc-9153-0090f5284f85 FMTID Swish remote folder b816a852-5022-11dc-9153-0090f5284f85 b816a853-5022-11dc-9153-0090f5284f85 b816a854-5022-11dc-9153-0090f5284f85 b816a855-5022-11dc-9153-0090f5284f85 b816a856-5022-11dc-9153-0090f5284f85 b816a857-5022-11dc-9153-0090f5284f85 b816a858-5022-11dc-9153-0090f5284f85 b816a859-5022-11dc-9153-0090f5284f85 b816a85a-5022-11dc-9153-0090f5284f85 b816a85b-5022-11dc-9153-0090f5284f85 b816a85c-5022-11dc-9153-0090f5284f85 b816a85d-5022-11dc-9153-0090f5284f85 b816a85e-5022-11dc-9153-0090f5284f85 b816a85f-5022-11dc-9153-0090f5284f85 b816a860-5022-11dc-9153-0090f5284f85 *REMOVED* Provider backend APPID b816a861-5022-11dc-9153-0090f5284f85 *REMOVED* Provider backend Type Library b816a862-5022-11dc-9153-0090f5284f85 *REMOVED* Provider Component b816a863-5022-11dc-9153-0090f5284f85 *REMOVED* RealDispenser Component b816a864-5022-11dc-9153-0090f5284f85 *REMOVED* Dispenser Component b816a865-5022-11dc-9153-0090f5284f85 b816a866-5022-11dc-9153-0090f5284f85 b816a867-5022-11dc-9153-0090f5284f85 b816a868-5022-11dc-9153-0090f5284f85 b816a869-5022-11dc-9153-0090f5284f85 b816a86a-5022-11dc-9153-0090f5284f85 b816a86b-5022-11dc-9153-0090f5284f85 b816a86c-5022-11dc-9153-0090f5284f85 b816a86d-5022-11dc-9153-0090f5284f85 b816a86e-5022-11dc-9153-0090f5284f85 b816a86f-5022-11dc-9153-0090f5284f85 b816a870-5022-11dc-9153-0090f5284f85 b816a871-5022-11dc-9153-0090f5284f85 b816a872-5022-11dc-9153-0090f5284f85 b816a873-5022-11dc-9153-0090f5284f85 b816a874-5022-11dc-9153-0090f5284f85 b816a875-5022-11dc-9153-0090f5284f85 b816a876-5022-11dc-9153-0090f5284f85 b816a877-5022-11dc-9153-0090f5284f85 b816a878-5022-11dc-9153-0090f5284f85 b816a879-5022-11dc-9153-0090f5284f85 b816a87a-5022-11dc-9153-0090f5284f85 b816a87b-5022-11dc-9153-0090f5284f85 b816a87c-5022-11dc-9153-0090f5284f85 b816a87d-5022-11dc-9153-0090f5284f85 b816a87e-5022-11dc-9153-0090f5284f85 b816a87f-5022-11dc-9153-0090f5284f85 b816a880-5022-11dc-9153-0090f5284f85 Add SFTP command ID b816a881-5022-11dc-9153-0090f5284f85 Remove SFTP command ID b816a882-5022-11dc-9153-0090f5284f85 New folder commmand ID b816a883-5022-11dc-9153-0090f5284f85 Rename SFTP host command ID b816a884-5022-11dc-9153-0090f5284f85 Launch key agent command ID b816a885-5022-11dc-9153-0090f5284f85 About box command ID b816a886-5022-11dc-9153-0090f5284f85 Close session command ID b816a887-5022-11dc-9153-0090f5284f85 b816a888-5022-11dc-9153-0090f5284f85 b816a889-5022-11dc-9153-0090f5284f85 b816a88a-5022-11dc-9153-0090f5284f85 b816a88b-5022-11dc-9153-0090f5284f85 b816a88c-5022-11dc-9153-0090f5284f85 b816a88d-5022-11dc-9153-0090f5284f85 b816a88e-5022-11dc-9153-0090f5284f85 b816a88f-5022-11dc-9153-0090f5284f85 b816a890-5022-11dc-9153-0090f5284f85 b816a891-5022-11dc-9153-0090f5284f85 b816a892-5022-11dc-9153-0090f5284f85 b816a893-5022-11dc-9153-0090f5284f85 b816a894-5022-11dc-9153-0090f5284f85 b816a895-5022-11dc-9153-0090f5284f85 b816a896-5022-11dc-9153-0090f5284f85 b816a897-5022-11dc-9153-0090f5284f85 b816a898-5022-11dc-9153-0090f5284f85 b816a899-5022-11dc-9153-0090f5284f85 b816a89a-5022-11dc-9153-0090f5284f85 b816a89b-5022-11dc-9153-0090f5284f85 b816a89c-5022-11dc-9153-0090f5284f85 b816a89d-5022-11dc-9153-0090f5284f85 b816a89e-5022-11dc-9153-0090f5284f85 b816a89f-5022-11dc-9153-0090f5284f85 b816a8a0-5022-11dc-9153-0090f5284f85 b816a8a1-5022-11dc-9153-0090f5284f85 b816a8a2-5022-11dc-9153-0090f5284f85 b816a8a3-5022-11dc-9153-0090f5284f85 b816a8a4-5022-11dc-9153-0090f5284f85 b816a8a5-5022-11dc-9153-0090f5284f85 b816a8a6-5022-11dc-9153-0090f5284f85 b816a8a7-5022-11dc-9153-0090f5284f85 b816a8a8-5022-11dc-9153-0090f5284f85 b816a8a9-5022-11dc-9153-0090f5284f85 b816a8aa-5022-11dc-9153-0090f5284f85 b816a8ab-5022-11dc-9153-0090f5284f85 b816a8ac-5022-11dc-9153-0090f5284f85 b816a8ad-5022-11dc-9153-0090f5284f85 b816a8ae-5022-11dc-9153-0090f5284f85 b816a8af-5022-11dc-9153-0090f5284f85 b816a8b0-5022-11dc-9153-0090f5284f85 b816a8b1-5022-11dc-9153-0090f5284f85 b816a8b2-5022-11dc-9153-0090f5284f85 b816a8b3-5022-11dc-9153-0090f5284f85 b816a8b4-5022-11dc-9153-0090f5284f85 b816a8b5-5022-11dc-9153-0090f5284f85 b816a8b6-5022-11dc-9153-0090f5284f85 b816a8b7-5022-11dc-9153-0090f5284f85 b816a8b8-5022-11dc-9153-0090f5284f85 b816a8b9-5022-11dc-9153-0090f5284f85 b816a8ba-5022-11dc-9153-0090f5284f85 b816a8bb-5022-11dc-9153-0090f5284f85 b816a8bc-5022-11dc-9153-0090f5284f85 b816a8bd-5022-11dc-9153-0090f5284f85 b816a8be-5022-11dc-9153-0090f5284f85 b816a8bf-5022-11dc-9153-0090f5284f85 b816a8c0-5022-11dc-9153-0090f5284f85 b816a8c1-5022-11dc-9153-0090f5284f85 b816a8c2-5022-11dc-9153-0090f5284f85 b816a8c3-5022-11dc-9153-0090f5284f85 b816a8c4-5022-11dc-9153-0090f5284f85 b816a8c5-5022-11dc-9153-0090f5284f85 b816a8c6-5022-11dc-9153-0090f5284f85 b816a8c7-5022-11dc-9153-0090f5284f85 b816a8c8-5022-11dc-9153-0090f5284f85 b816a8c9-5022-11dc-9153-0090f5284f85 b816a8ca-5022-11dc-9153-0090f5284f85 b816a8cb-5022-11dc-9153-0090f5284f85 b816a8cc-5022-11dc-9153-0090f5284f85 b816a8cd-5022-11dc-9153-0090f5284f85 b816a8ce-5022-11dc-9153-0090f5284f85 b816a8cf-5022-11dc-9153-0090f5284f85 b816a8d0-5022-11dc-9153-0090f5284f85 b816a8d1-5022-11dc-9153-0090f5284f85 b816a8d2-5022-11dc-9153-0090f5284f85 b816a8d3-5022-11dc-9153-0090f5284f85 b816a8d4-5022-11dc-9153-0090f5284f85 b816a8d5-5022-11dc-9153-0090f5284f85 b816a8d6-5022-11dc-9153-0090f5284f85 b816a8d7-5022-11dc-9153-0090f5284f85 b816a8d8-5022-11dc-9153-0090f5284f85 b816a8d9-5022-11dc-9153-0090f5284f85 b816a8da-5022-11dc-9153-0090f5284f85 b816a8db-5022-11dc-9153-0090f5284f85 b816a8dc-5022-11dc-9153-0090f5284f85 b816a8dd-5022-11dc-9153-0090f5284f85 b816a8de-5022-11dc-9153-0090f5284f85 b816a8df-5022-11dc-9153-0090f5284f85 b816a8e0-5022-11dc-9153-0090f5284f85 b816a8e1-5022-11dc-9153-0090f5284f85 b816a8e2-5022-11dc-9153-0090f5284f85 b816a8e3-5022-11dc-9153-0090f5284f85 b816a8e4-5022-11dc-9153-0090f5284f85 b816a8e5-5022-11dc-9153-0090f5284f85 b816a8e6-5022-11dc-9153-0090f5284f85 b816a8e7-5022-11dc-9153-0090f5284f85 b816a8e8-5022-11dc-9153-0090f5284f85 b816a8e9-5022-11dc-9153-0090f5284f85 b816a8ea-5022-11dc-9153-0090f5284f85 b816a8eb-5022-11dc-9153-0090f5284f85 b816a8ec-5022-11dc-9153-0090f5284f85 b816a8ed-5022-11dc-9153-0090f5284f85 b816a8ee-5022-11dc-9153-0090f5284f85 b816a8ef-5022-11dc-9153-0090f5284f85 b816a8f0-5022-11dc-9153-0090f5284f85 b816a8f1-5022-11dc-9153-0090f5284f85 b816a8f2-5022-11dc-9153-0090f5284f85 b816a8f3-5022-11dc-9153-0090f5284f85 b816a8f4-5022-11dc-9153-0090f5284f85 b816a8f5-5022-11dc-9153-0090f5284f85 b816a8f6-5022-11dc-9153-0090f5284f85 b816a8f7-5022-11dc-9153-0090f5284f85 b816a8f8-5022-11dc-9153-0090f5284f85 b816a8f9-5022-11dc-9153-0090f5284f85 b816a8fa-5022-11dc-9153-0090f5284f85 b816a8fb-5022-11dc-9153-0090f5284f85 b816a8fc-5022-11dc-9153-0090f5284f85 b816a8fd-5022-11dc-9153-0090f5284f85 b816a8fe-5022-11dc-9153-0090f5284f85 b816a8ff-5022-11dc-9153-0090f5284f85 b816a900-5022-11dc-9153-0090f5284f85 b816a901-5022-11dc-9153-0090f5284f85 b816a902-5022-11dc-9153-0090f5284f85 b816a903-5022-11dc-9153-0090f5284f85 b816a904-5022-11dc-9153-0090f5284f85 b816a905-5022-11dc-9153-0090f5284f85 b816a906-5022-11dc-9153-0090f5284f85 b816a907-5022-11dc-9153-0090f5284f85 b816a908-5022-11dc-9153-0090f5284f85 b816a909-5022-11dc-9153-0090f5284f85 b816a90a-5022-11dc-9153-0090f5284f85 b816a90b-5022-11dc-9153-0090f5284f85 b816a90c-5022-11dc-9153-0090f5284f85 b816a90d-5022-11dc-9153-0090f5284f85 b816a90e-5022-11dc-9153-0090f5284f85 b816a90f-5022-11dc-9153-0090f5284f85 b816a910-5022-11dc-9153-0090f5284f85 b816a911-5022-11dc-9153-0090f5284f85 b816a912-5022-11dc-9153-0090f5284f85 b816a913-5022-11dc-9153-0090f5284f85 b816a914-5022-11dc-9153-0090f5284f85 b816a915-5022-11dc-9153-0090f5284f85 b816a916-5022-11dc-9153-0090f5284f85 b816a917-5022-11dc-9153-0090f5284f85 b816a918-5022-11dc-9153-0090f5284f85 b816a919-5022-11dc-9153-0090f5284f85 b816a91a-5022-11dc-9153-0090f5284f85 b816a91b-5022-11dc-9153-0090f5284f85 b816a91c-5022-11dc-9153-0090f5284f85 b816a91d-5022-11dc-9153-0090f5284f85 b816a91e-5022-11dc-9153-0090f5284f85 b816a91f-5022-11dc-9153-0090f5284f85 b816a920-5022-11dc-9153-0090f5284f85 b816a921-5022-11dc-9153-0090f5284f85 b816a922-5022-11dc-9153-0090f5284f85 b816a923-5022-11dc-9153-0090f5284f85 b816a924-5022-11dc-9153-0090f5284f85 b816a925-5022-11dc-9153-0090f5284f85 b816a926-5022-11dc-9153-0090f5284f85 b816a927-5022-11dc-9153-0090f5284f85 b816a928-5022-11dc-9153-0090f5284f85 b816a929-5022-11dc-9153-0090f5284f85 b816a92a-5022-11dc-9153-0090f5284f85 b816a92b-5022-11dc-9153-0090f5284f85 b816a92c-5022-11dc-9153-0090f5284f85 b816a92d-5022-11dc-9153-0090f5284f85 b816a92e-5022-11dc-9153-0090f5284f85 b816a92f-5022-11dc-9153-0090f5284f85 b816a930-5022-11dc-9153-0090f5284f85 b816a931-5022-11dc-9153-0090f5284f85 b816a932-5022-11dc-9153-0090f5284f85 b816a933-5022-11dc-9153-0090f5284f85 b816a934-5022-11dc-9153-0090f5284f85 b816a935-5022-11dc-9153-0090f5284f85 b816a936-5022-11dc-9153-0090f5284f85 b816a937-5022-11dc-9153-0090f5284f85 b816a938-5022-11dc-9153-0090f5284f85 b816a939-5022-11dc-9153-0090f5284f85 b816a93a-5022-11dc-9153-0090f5284f85 b816a93b-5022-11dc-9153-0090f5284f85 b816a93c-5022-11dc-9153-0090f5284f85 b816a93d-5022-11dc-9153-0090f5284f85 b816a93e-5022-11dc-9153-0090f5284f85 b816a93f-5022-11dc-9153-0090f5284f85 b816a940-5022-11dc-9153-0090f5284f85 b816a941-5022-11dc-9153-0090f5284f85 b816a942-5022-11dc-9153-0090f5284f85 b816a943-5022-11dc-9153-0090f5284f85 b816a944-5022-11dc-9153-0090f5284f85 b816a945-5022-11dc-9153-0090f5284f85 b816a946-5022-11dc-9153-0090f5284f85 b816a947-5022-11dc-9153-0090f5284f85 b816a948-5022-11dc-9153-0090f5284f85 b816a949-5022-11dc-9153-0090f5284f85 b816a94a-5022-11dc-9153-0090f5284f85 b816a94b-5022-11dc-9153-0090f5284f85 b816a94c-5022-11dc-9153-0090f5284f85 b816a94d-5022-11dc-9153-0090f5284f85 b816a94e-5022-11dc-9153-0090f5284f85 b816a94f-5022-11dc-9153-0090f5284f85 b816a950-5022-11dc-9153-0090f5284f85 b816a951-5022-11dc-9153-0090f5284f85 b816a952-5022-11dc-9153-0090f5284f85 b816a953-5022-11dc-9153-0090f5284f85 b816a954-5022-11dc-9153-0090f5284f85 b816a955-5022-11dc-9153-0090f5284f85 b816a956-5022-11dc-9153-0090f5284f85 b816a957-5022-11dc-9153-0090f5284f85 b816a958-5022-11dc-9153-0090f5284f85 b816a959-5022-11dc-9153-0090f5284f85 b816a95a-5022-11dc-9153-0090f5284f85 b816a95b-5022-11dc-9153-0090f5284f85 b816a95c-5022-11dc-9153-0090f5284f85 b816a95d-5022-11dc-9153-0090f5284f85 b816a95e-5022-11dc-9153-0090f5284f85 b816a95f-5022-11dc-9153-0090f5284f85 b816a960-5022-11dc-9153-0090f5284f85 b816a961-5022-11dc-9153-0090f5284f85 b816a962-5022-11dc-9153-0090f5284f85 b816a963-5022-11dc-9153-0090f5284f85 b816a964-5022-11dc-9153-0090f5284f85 b816a965-5022-11dc-9153-0090f5284f85 b816a966-5022-11dc-9153-0090f5284f85 b816a967-5022-11dc-9153-0090f5284f85 b816a968-5022-11dc-9153-0090f5284f85 b816a969-5022-11dc-9153-0090f5284f85 b816a96a-5022-11dc-9153-0090f5284f85 b816a96b-5022-11dc-9153-0090f5284f85 b816a96c-5022-11dc-9153-0090f5284f85 b816a96d-5022-11dc-9153-0090f5284f85 b816a96e-5022-11dc-9153-0090f5284f85 b816a96f-5022-11dc-9153-0090f5284f85 b816a970-5022-11dc-9153-0090f5284f85 b816a971-5022-11dc-9153-0090f5284f85 b816a972-5022-11dc-9153-0090f5284f85 b816a973-5022-11dc-9153-0090f5284f85 b816a974-5022-11dc-9153-0090f5284f85 b816a975-5022-11dc-9153-0090f5284f85 b816a976-5022-11dc-9153-0090f5284f85 b816a977-5022-11dc-9153-0090f5284f85 b816a978-5022-11dc-9153-0090f5284f85 b816a979-5022-11dc-9153-0090f5284f85 b816a97a-5022-11dc-9153-0090f5284f85 b816a97b-5022-11dc-9153-0090f5284f85 b816a97c-5022-11dc-9153-0090f5284f85 b816a97d-5022-11dc-9153-0090f5284f85 b816a97e-5022-11dc-9153-0090f5284f85 b816a97f-5022-11dc-9153-0090f5284f85 b816a980-5022-11dc-9153-0090f5284f85 b816a981-5022-11dc-9153-0090f5284f85 b816a982-5022-11dc-9153-0090f5284f85 b816a983-5022-11dc-9153-0090f5284f85 b816a984-5022-11dc-9153-0090f5284f85 b816a985-5022-11dc-9153-0090f5284f85 b816a986-5022-11dc-9153-0090f5284f85 b816a987-5022-11dc-9153-0090f5284f85 b816a988-5022-11dc-9153-0090f5284f85 b816a989-5022-11dc-9153-0090f5284f85 b816a98a-5022-11dc-9153-0090f5284f85 b816a98b-5022-11dc-9153-0090f5284f85 b816a98c-5022-11dc-9153-0090f5284f85 b816a98d-5022-11dc-9153-0090f5284f85 b816a98e-5022-11dc-9153-0090f5284f85 b816a98f-5022-11dc-9153-0090f5284f85 b816a990-5022-11dc-9153-0090f5284f85 b816a991-5022-11dc-9153-0090f5284f85 b816a992-5022-11dc-9153-0090f5284f85 b816a993-5022-11dc-9153-0090f5284f85 b816a994-5022-11dc-9153-0090f5284f85 b816a995-5022-11dc-9153-0090f5284f85 b816a996-5022-11dc-9153-0090f5284f85 b816a997-5022-11dc-9153-0090f5284f85 b816a998-5022-11dc-9153-0090f5284f85 b816a999-5022-11dc-9153-0090f5284f85 b816a99a-5022-11dc-9153-0090f5284f85 b816a99b-5022-11dc-9153-0090f5284f85 b816a99c-5022-11dc-9153-0090f5284f85 b816a99d-5022-11dc-9153-0090f5284f85 b816a99e-5022-11dc-9153-0090f5284f85 b816a99f-5022-11dc-9153-0090f5284f85 b816a9a0-5022-11dc-9153-0090f5284f85 b816a9a1-5022-11dc-9153-0090f5284f85 b816a9a2-5022-11dc-9153-0090f5284f85 b816a9a3-5022-11dc-9153-0090f5284f85 b816a9a4-5022-11dc-9153-0090f5284f85 b816a9a5-5022-11dc-9153-0090f5284f85 b816a9a6-5022-11dc-9153-0090f5284f85 b816a9a7-5022-11dc-9153-0090f5284f85 b816a9a8-5022-11dc-9153-0090f5284f85 b816a9a9-5022-11dc-9153-0090f5284f85 b816a9aa-5022-11dc-9153-0090f5284f85 b816a9ab-5022-11dc-9153-0090f5284f85 b816a9ac-5022-11dc-9153-0090f5284f85 b816a9ad-5022-11dc-9153-0090f5284f85 b816a9ae-5022-11dc-9153-0090f5284f85 b816a9af-5022-11dc-9153-0090f5284f85 b816a9b0-5022-11dc-9153-0090f5284f85 b816a9b1-5022-11dc-9153-0090f5284f85 b816a9b2-5022-11dc-9153-0090f5284f85 b816a9b3-5022-11dc-9153-0090f5284f85 b816a9b4-5022-11dc-9153-0090f5284f85 b816a9b5-5022-11dc-9153-0090f5284f85 b816a9b6-5022-11dc-9153-0090f5284f85 b816a9b7-5022-11dc-9153-0090f5284f85 b816a9b8-5022-11dc-9153-0090f5284f85 b816a9b9-5022-11dc-9153-0090f5284f85 b816a9ba-5022-11dc-9153-0090f5284f85 b816a9bb-5022-11dc-9153-0090f5284f85 b816a9bc-5022-11dc-9153-0090f5284f85 b816a9bd-5022-11dc-9153-0090f5284f85 b816a9be-5022-11dc-9153-0090f5284f85 b816a9bf-5022-11dc-9153-0090f5284f85 b816a9c0-5022-11dc-9153-0090f5284f85 b816a9c1-5022-11dc-9153-0090f5284f85 b816a9c2-5022-11dc-9153-0090f5284f85 b816a9c3-5022-11dc-9153-0090f5284f85 b816a9c4-5022-11dc-9153-0090f5284f85 b816a9c5-5022-11dc-9153-0090f5284f85 b816a9c6-5022-11dc-9153-0090f5284f85 b816a9c7-5022-11dc-9153-0090f5284f85 b816a9c8-5022-11dc-9153-0090f5284f85 b816a9c9-5022-11dc-9153-0090f5284f85 b816a9ca-5022-11dc-9153-0090f5284f85 b816a9cb-5022-11dc-9153-0090f5284f85 b816a9cc-5022-11dc-9153-0090f5284f85 b816a9cd-5022-11dc-9153-0090f5284f85 b816a9ce-5022-11dc-9153-0090f5284f85 b816a9cf-5022-11dc-9153-0090f5284f85 b816a9d0-5022-11dc-9153-0090f5284f85 b816a9d1-5022-11dc-9153-0090f5284f85 b816a9d2-5022-11dc-9153-0090f5284f85 b816a9d3-5022-11dc-9153-0090f5284f85 b816a9d4-5022-11dc-9153-0090f5284f85 b816a9d5-5022-11dc-9153-0090f5284f85 b816a9d6-5022-11dc-9153-0090f5284f85 b816a9d7-5022-11dc-9153-0090f5284f85 b816a9d8-5022-11dc-9153-0090f5284f85 b816a9d9-5022-11dc-9153-0090f5284f85 b816a9da-5022-11dc-9153-0090f5284f85 b816a9db-5022-11dc-9153-0090f5284f85 b816a9dc-5022-11dc-9153-0090f5284f85 b816a9dd-5022-11dc-9153-0090f5284f85 b816a9de-5022-11dc-9153-0090f5284f85 b816a9df-5022-11dc-9153-0090f5284f85 b816a9e0-5022-11dc-9153-0090f5284f85 b816a9e1-5022-11dc-9153-0090f5284f85 b816a9e2-5022-11dc-9153-0090f5284f85 b816a9e3-5022-11dc-9153-0090f5284f85 b816a9e4-5022-11dc-9153-0090f5284f85 b816a9e5-5022-11dc-9153-0090f5284f85 b816a9e6-5022-11dc-9153-0090f5284f85 b816a9e7-5022-11dc-9153-0090f5284f85 b816a9e8-5022-11dc-9153-0090f5284f85 b816a9e9-5022-11dc-9153-0090f5284f85 b816a9ea-5022-11dc-9153-0090f5284f85 b816a9eb-5022-11dc-9153-0090f5284f85 b816a9ec-5022-11dc-9153-0090f5284f85 b816a9ed-5022-11dc-9153-0090f5284f85 b816a9ee-5022-11dc-9153-0090f5284f85 b816a9ef-5022-11dc-9153-0090f5284f85 b816a9f0-5022-11dc-9153-0090f5284f85 b816a9f1-5022-11dc-9153-0090f5284f85 b816a9f2-5022-11dc-9153-0090f5284f85 b816a9f3-5022-11dc-9153-0090f5284f85 b816a9f4-5022-11dc-9153-0090f5284f85 b816a9f5-5022-11dc-9153-0090f5284f85 b816a9f6-5022-11dc-9153-0090f5284f85 b816a9f7-5022-11dc-9153-0090f5284f85 b816a9f8-5022-11dc-9153-0090f5284f85 b816a9f9-5022-11dc-9153-0090f5284f85 b816a9fa-5022-11dc-9153-0090f5284f85 b816a9fb-5022-11dc-9153-0090f5284f85 b816a9fc-5022-11dc-9153-0090f5284f85 b816a9fd-5022-11dc-9153-0090f5284f85 b816a9fe-5022-11dc-9153-0090f5284f85 b816a9ff-5022-11dc-9153-0090f5284f85 b816aa00-5022-11dc-9153-0090f5284f85 b816aa01-5022-11dc-9153-0090f5284f85 b816aa02-5022-11dc-9153-0090f5284f85 b816aa03-5022-11dc-9153-0090f5284f85 b816aa04-5022-11dc-9153-0090f5284f85 b816aa05-5022-11dc-9153-0090f5284f85 b816aa06-5022-11dc-9153-0090f5284f85 b816aa07-5022-11dc-9153-0090f5284f85 b816aa08-5022-11dc-9153-0090f5284f85 b816aa09-5022-11dc-9153-0090f5284f85 b816aa0a-5022-11dc-9153-0090f5284f85 b816aa0b-5022-11dc-9153-0090f5284f85 b816aa0c-5022-11dc-9153-0090f5284f85 b816aa0d-5022-11dc-9153-0090f5284f85 b816aa0e-5022-11dc-9153-0090f5284f85 b816aa0f-5022-11dc-9153-0090f5284f85 b816aa10-5022-11dc-9153-0090f5284f85 b816aa11-5022-11dc-9153-0090f5284f85 b816aa12-5022-11dc-9153-0090f5284f85 b816aa13-5022-11dc-9153-0090f5284f85 b816aa14-5022-11dc-9153-0090f5284f85 b816aa15-5022-11dc-9153-0090f5284f85 b816aa16-5022-11dc-9153-0090f5284f85 b816aa17-5022-11dc-9153-0090f5284f85 b816aa18-5022-11dc-9153-0090f5284f85 b816aa19-5022-11dc-9153-0090f5284f85 b816aa1a-5022-11dc-9153-0090f5284f85 b816aa1b-5022-11dc-9153-0090f5284f85 b816aa1c-5022-11dc-9153-0090f5284f85 b816aa1d-5022-11dc-9153-0090f5284f85 b816aa1e-5022-11dc-9153-0090f5284f85 b816aa1f-5022-11dc-9153-0090f5284f85 b816aa20-5022-11dc-9153-0090f5284f85 b816aa21-5022-11dc-9153-0090f5284f85 b816aa22-5022-11dc-9153-0090f5284f85 b816aa23-5022-11dc-9153-0090f5284f85 b816aa24-5022-11dc-9153-0090f5284f85 b816aa25-5022-11dc-9153-0090f5284f85 b816aa26-5022-11dc-9153-0090f5284f85 b816aa27-5022-11dc-9153-0090f5284f85 b816aa28-5022-11dc-9153-0090f5284f85 b816aa29-5022-11dc-9153-0090f5284f85 b816aa2a-5022-11dc-9153-0090f5284f85 b816aa2b-5022-11dc-9153-0090f5284f85 ================================================ FILE: po/CMakeLists.txt ================================================ set(LANGUAGES bg ca cs cy da_DK de el_GR es et fi fr he hi hu it ja ko lv nl pl pt pt_BR ro ru sk sv tr zh_CN zh_TW) foreach(lang ${LANGUAGES}) set(COMPILED_TRANSLATION "${lang}/swish.mo") install(FILES "${COMPILED_TRANSLATION}" DESTINATION "${lang}/LC_MESSAGES") endforeach() ================================================ FILE: po/bg/swish.po ================================================ # # Translators: # albertvision , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: albertvision \n" "Language-Team: Bulgarian (http://www.transifex.com/projects/p/swish/language/bg/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: bg\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Не мога да създам файла на сървъра" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Копиране на '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "на '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Папката вече съдържа файл с име '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Искате ли да го презапишете?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Потвърди презаписа" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Копиране..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Не мога да кача файловете" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Нямате права за писане в тази директория" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Нова SFTP връзка" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Създай" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Име:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Например: \"Home Computer\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Посочете подробна информация за компютъра и акаунта, който бихте искали да се свържете:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Хост:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Порт:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Потребител:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Посочете директория на сървъра, в която искате Swish да се логне:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "П&ът:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Например: /home/yourusername" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Отказ" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Името не може да е по дълго от 30 знака." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Името на хоста е грешно." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Портът е невалиден(между 0 и 65535)" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Потр. име е невалидно." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Пътят е невалиден." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Конекция със същото име вече съществува. Моля сменте името." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Попълнете всички полета." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Парола" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "ОК" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Покажи &подробности (може да не е на вашия език)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Скрий &подробности" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Папката вече съдържа файл с името '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Искате ли да презапишете файла\n\n\t%1%\n\nс този?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Файлът вече съществува" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Несъвпадащ host-key" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "ВНИМАНИЕ: SSH host-key е променен!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "SSH host-key изпратен от '%1%' да се идентифицира, несъвпада с вече познатия ключ за този сървър. Това означава, че или трето лице се представя за компютъра с, който се опитвате се свържете или системния администратор може да е сменил ключа." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Важно е да проверите за правилния key fingerprint:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Доверявам се на ключа: &обнови и се свържи\nНяма да има нужда, да проверявате ключа, освен ако не се промени" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Вярвам на ключа: &просто се свържи\nЩе бъдете предупредени отново при следващото свързване" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Отказ\nИзберете тази опция, освен ако не сте сигурни, че ключа е превилен" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Непознат host-key" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Сървър '%1%' се идентифицира с SSH host-key, с отпечтък:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Ако не очаквате този ключ, трето лице може да се представя за компютъра, с който се опитвате да се свържете." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Потвърди непознат SSH host-key" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Доверявам се на ключа: &запази го и се свържи\nНяма да има нужда, да проверявате ключа, освен ако не се промени" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Доверявам се на ключа: &само се свържи\nНяма да има нужда, да проверявате ключа, освен ако не се промени" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Име" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Хост" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Потр. име" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Порт" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Отдалечен път" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Тип" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Мрежово у-во" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Добави SFTP Връзка" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Създай нова SFTP връзка с Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Добави SFTP Връзка..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Добави Взръзка" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP Задачи" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Тези задачи ти помагат да управляваш SFTP връзките на Swish." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Зареди key agent" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Зареди Putty SSH key agent" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Зареди key agent" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Премахни SFTP Връзка" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Премахни SFTP връзката създадена с Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Премахни SFTP Връзката..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Премахни Връзката" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Размер" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Дата на промяна" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Дата на достъп" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Права" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Притежател" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Група" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID на притежателя" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID на групата" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Отвори &линк" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Отвори" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Не мога да отворя линк" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Може би нямате права." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Не мога да отворя айла" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Задачи - папки и файлове" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Тези задачи ти помагат да управляваш отсрещните файлове." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Не може да се изтрие обекта" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Нова &папка" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Създай нова, празна папка в папката, която сте отворили." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Направи нова папка" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Нова папка" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Не може да се създаде нова папка" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Keyboard-interactive request" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Няма достъп до директорията" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Непознат път" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Проверете дали пътя е въведен вярно." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Не може да се преименува обекта" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Няма достъп до обекта" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Няма достъп до обектите" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Няма достъп до папката" ================================================ FILE: po/ca/swish.po ================================================ # # Translators: # lluis.dalmau , 2013 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: lluis.dalmau \n" "Language-Team: Catalan (http://www.transifex.com/projects/p/swish/language/ca/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ca\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Impossible crear l'arxiu al servidor" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Copiant '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "A '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "La carpeta ja conté un arxiu anomenat '{1}'" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Voleu reemplaçar-lo?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Confirmar sobrescriptura" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Copiant..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Impossible transferir arxius" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "No teniu permisos per escriure al directori." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Nova connexió SFTP" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Crear" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Etiqueta:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Per exemple: \"El meu ordinador\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Descriure els detalls de l'ordinador i el compte al qual voleu connectar:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Amfitrió:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Usuari" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Descriure el directori del servidor on voleu que Swish comenci la connexió:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "Rut&a:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Exemple: /home/elteunom" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Cancel·la" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "L'etiqueta no pot tenir més de 30 caràcters." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "El nom d'amfitrió no és vàlid." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "El port no és vàlid (entre 0 i 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Nom d'usuari invàlid." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Ruta invàlida." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Existeix una connexió amb la mateixa etiqueta. Prova'n una altra." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Completa tots els camps." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Contrasenya" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "D'acord" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Mostra &detalls (poden no ser en la teva llengua)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Amaga &detalls" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "La carpeta té un arxiu anomenat '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Vols substituir l'arxiu existent⏎\n⏎\n⇥%1%⏎\n⏎\nper aquest altre?⏎\n⏎\n⇥%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "L'arxiu existeix" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Error en la clau" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "PERILL: la clau SSH de l'amfitrió ha canviat!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "La clau SSH de l'anfitrió enviada per '%1%' per a identificar-se no coincideix amb la clau d'aquest servidor. Això pot ser perque algú està intentant falsejar l'ordinador al qual estàs volent accedir, o bé, que l'administrador del sistema ha canviat la clau SSH." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "És importat comprovar que l'empremta sigui correcta:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Confio en aquesta clau: &actualitza-la i connecta\nNo et caldrà tornar-la a verificar mentre no canviï." #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Confio en aquesta clau: &només connecta\nSeràs avisat sobre aquesta clau a la propera connexió" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Cancel·la\nEscull aquesta opció només si estàs segur que la clau és correcta" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Clau desconeguda" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "El servidor '%1%' s'ha identificat amb la clau d'amfitrió SSH, la qual té l'empremta:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Si no esperaves aquesta clau, algú podria intentat fer-se passar per l'ordinador amfitrió al que volies connectar." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Verifica la clau SSH desconeguda" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Confio en la clau: %desa i connecta\nNo hauràs de tornar a verificar la clau mentre no canviï." #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Confio en la clau: &només connecta\nHauràs de tornar a verificar la clau a la propera connexió" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Nom" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Amfitrió" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Nom d'usuari" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Ruta remota" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Tipus" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Unitat de xarxa" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Afegir connexió SFTP" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Crear una nova connexió SFTP amb Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Afegir una connexió SFTP..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Afegir Connexió" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Accions SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Aquestes accions t'ajuden a administrar les connexions SFTP." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "Activa el gestor de claus" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Activa l'agent de claus Putty SSH, Pageant." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Activa l'agent de claus" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Esborra Connexió SFTP" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Esborra una connexió SFTP creada per Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Esborra Connexió SFTP..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Esborra Connexió" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Mida" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Data de modificació" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Data d'accés" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Permisos" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Propietari" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grup" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID propietari" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID grup" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Obrir enllaç" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Obrir" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Impossible obrir l'enllaç" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Pot ser que no tinguis permisos." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Impossible obrir l'arxiu" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Accions sobre arxius i carpetes" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Aquestes accions t'ajuden a administrar els teus arxius remots." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Impossible d'esborrar" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Nova &carpeta" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Crear nova carpeta i buida a la carpeta que hagi obert." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Crea nova carpeta" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Nova carpeta" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Impossible crear carpeta" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Petició de paraula clau" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Accés impossible al directori" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Ruta no reconeguda" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Comprova que la ruta sigui correcta." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Canvi de nom impossible" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Accés impossible" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Accés impossible" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Accés a la carpeta impossible" ================================================ FILE: po/compile_mo.sh ================================================ #!/bin/sh for lang in *; do if [ -d $lang ]; then msgfmt -c --statistics --verbose -o $lang/swish.mo $lang/swish.po fi done ================================================ FILE: po/cs/swish.po ================================================ # # Translators: # Karol Kružel , 2012 # mishak , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: mishak \n" "Language-Team: Czech (http://www.transifex.com/projects/p/swish/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: cs\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Soubor nelze na serveru vytvořit:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Kopíruje se '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "Do '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Tato složka již obsahuje soubor se jménem '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Chcete provést nahrazení?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Potvrzení přepsání souboru" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopíruji..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Soubory nelze přenést" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Zřejmě nemáte oprávnění pro zápis do této složky." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Nové SFTP spojení" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Vytvořit" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Název:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Například: \"Počítač v kanceláři\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Zadejte údaje o počítači a účtu ke kterému se chcete připojit:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Hostitel:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Uživatel:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Zadejte počáteční adresář na serveru, ke kterému se má Switch připojit:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "&Cesta:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Například: /home/vaseJmeno" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Zrušit" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Název nemůže být delší než 30 znaků." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Název hostitele je chybný." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Číslo portu je chybný (musí být mezi 0 a 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Uživatelské jméno je chybné." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Cesta je chybná." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Spojení s tímto názvem už existuje. Zkuste prosím jiný název." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Vyplňte všechny pole." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Heslo" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Zobrazit &detaily (mohou být v jíném jazyce)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Skrýt &detaily" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Složky již obsahuje soubor pojmenovaný '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Chcete přepsat existující soubor\n\n\t%1%\n\ntímto souborem?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Soubor již existuje" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Špatný host-key" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "VAROVÁNÍ: SSH host-key se změnil!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "SSH host-key zaslaný '%1%' k ověření své identity se neshoduje s již známým klíčem tohoto serveru. Může to znamenat, že se třetí strana snaží předstírat, že je počítač, ke kterému se snažíte připojit nebo správce systému právě změnil přístupový klíč." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Je důležité ověřit, že toto je správný otisk (fingerprint):" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Důvěřuji tomuto klíči: &updatovat a připojit se\nNemusíte ověřovat tento klíč znovu, pokud se nezmění." #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Důvěřuji tomuto klíči: &pouze se připojit\nBudete varování na tento klíč, jakmile se znovu připojíte" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Stornol\nVyberte tuto možnost pokud si nejste jisti, zda je klíč správný." #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Neznámý host-key" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Server '%1%' se identifikovat SSH klíčem, ke kterému náleží otisk(fingerprint):" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Pokud je tento klíč neočekávaný, může se jednat o snahu třetí strany předstírat, že je počítačem, ke kterému se snažíte připojit." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Ověřte neznámý SSH host-key" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Důvěřuji tomuto klíči: &uložit a připojit se\nNebudete muset ověřovat tento klíč v budoucnu, pokud se nezmění." #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Důvěřuji tomuto klíči: &pouze připojit\nBudete znovu vyzvání k ověření toho klíče při příštím připojení" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Jméno" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Hostitel" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Uživatelské jméno" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Vzdálená cesta" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Typ" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Síťová jednotka" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Přidat SFTP připojení" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Vytvořit nové SFTP připojení se Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Přidat SFTP připojení..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Přidat připojení" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP úkoly" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Tyto úkoly vám pomůžou s nastavením Swish SFTP připojení." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "Spustit k&líčenku" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Spustit Putty SSH klíčenku, Pageant." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Spustit klíčenku" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Odebrat SFTP připojení" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Odebrat SFTP připojení vytvořené se Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Odebrat SFTP připojení" #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Odebrat připojení" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Velikost" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Datum změny" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Datum přístupu" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Oprávnění" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Vlastník" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Skupina" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID vlastníka" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID skupiny" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Otevřít odk&az" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Otevřít" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Nelze otevřít odkaz" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Zřejmě nemáte potřebná oprávnění." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Nelze otevřít soubor" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Operace se soubory a složkami" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Tyto operace slouží ke správě vzdálených souborů." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Položku nelze smazat" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Nová &složka" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Vytvořit v otevřené složce novou prázdnou podsložku." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Vytvoř novou složku." #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Nová složka" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Nelze vytvořit novou složku" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Požadavek na použití klávesnice" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Nelze získat přístup ke složce" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Cesta nebyla rozpoznána" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Ověřte, zda byla cesta zadána správně." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Položku nelze přejmenovat." #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Položka je nedostupná" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Položky jsou nedostupné" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Složka je nedostupná." ================================================ FILE: po/cy/swish.po ================================================ # # Translators: # Alexander Lamaison , 2013 # rdj , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Alexander Lamaison \n" "Language-Team: Welsh (http://www.transifex.com/projects/p/swish/language/cy/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: cy\n" "Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Copio '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "I '{1}" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Mae'r ffolder yn cynnwys y ffeil '{1}' yn barod" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Hoffech chi eu cyfnewid?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Cadarnhewch y cyfnewidiad" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Adysgrifio..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Methu danfod ty ffeiliau" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Cysylltiad STFP Newydd" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Creu" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Label:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Er engraifft: \"Cyfrifiadur Cartref\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Nodwch y wybodaeth amdano'r cyfrifiadur ac yr cyfrif hoffech chi cysylltu a:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Cynhaliwr" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Porth:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Defnyddiwr:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Nodwch pa cyfarwyddiadur ar y gwasanaethydd hoffwch Swish i ddechrau cysylltiad a" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "P&arth:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Er enghraifft: /Cartref/eichcyfeirenw" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Dirymu" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Ni all y label fod yn fwy na 30 llythyren o hyd." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Mae enw'r cynhaliwr yn annilys." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Dydy'r porth ddim yn ddilys (rhwng 0 a 65535)" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Mae'r cyfrifenw yn annilys." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Mae'r llwybr yn annilys." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Mae cysylltiad gyda'r un label yn bodoli. A wnewch chi trio un arall." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Cwblhewch pob maes." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Cyfrinair" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Dangos &manylion (nid oes angen iddo fod yn eich iaith)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Cuddiwch &gwybodaeth" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Mae'r ffolder yn cynnwy y ffeil o'r enw '%1%' yn barod" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Hoffech chi amnewid y ffeil presennol\n \n»%1%\n\ngyda?\n\n»%2% " #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Ffeil yn bodoli" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "" #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "" #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Enw" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Cynhaliwr" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Cyfeirenw" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Porth" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Llwybr anghysbell" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Math" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Gyriant Rhyngwaith" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Ychwanegu Cysylltiad SFTP" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Creu Cysylltiad SFTP newydd gyda Swish" #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Ychwanegu Cysylltiad SFTP..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Ychwanegwch Cysylltiad" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Tasgau SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Mae'r tasgau yma yn eich helpu rheoli eich cysylltiadau SFTP." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Tynnu'r Cysylltiad SFTP" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Tynnu cysylltiad a greuwyd gyda Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Tynnu'r Cysylltiad SFTP..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Tynnu Cysylltiad" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Maint" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Dyddiad Newidiwyd" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Dyddiad Agorwyd" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Caniataodau" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Perchenog" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grwp" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "Adnabyddiaeth Perchennog" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "Adnabyddiaeth Grwp" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Agor &linc" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Agor" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Methu agor y cysylltiad" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Efallai nid oes caniatad ganddoch chi" #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Methu agor y ffeil" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "" #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Methu dileu'r eitem" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Cais bysyllfwrdd rhyngweithiol" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Methu cyrchu y cyfarwyddiadur" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Llwybr heb eu cydnabod" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Gwiriwch bod y llwybr wedi ei mewnbynnu yn gywir." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Methu ailenwi'r eitem" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Methu ailenwi'r gwrthrych" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Methu agor y gwrthrychau" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Methu cyrchu'r ffolder" ================================================ FILE: po/da_DK/swish.po ================================================ # # Translators: # Martin Finnerup , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Martin Finnerup \n" "Language-Team: Danish (Denmark) (http://www.transifex.com/projects/p/swish/language/da_DK/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: da_DK\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Denne mappe indeholder allerede en fil kaldet '{1}'" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Vil du gerne overskrive den?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Bekræft overskrivning af fil" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopierer..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Ny SFTP Forbindelse" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Opret" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Mærkat:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "For eksempel: \"Hjemme Computer\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Indtast detaljerne for den computer og bruger du gerne vil forbinde til:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Vært:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Bruger:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Specificér den mapper på serveren som du gerne vil have Swish til at starte forbindelsen i:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "S&ti:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Eksempel: /home/ditbrugernavn" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Annuller" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Mærkaten kan ikke være længere end 30 tegn lang." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Værtsnavnet er ugyldigt." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Porten er ikke gyldig (mellem 0 og 65535)" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Brugernavnet er ugyldigt." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Stien er ugyldig." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "En forbindelse med den samme mærkat eksisterer allerede. Prøv en anden." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Udfyld alle felter." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Kodeord:" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Vis &detaljer (hvilket måske ikke er i dit sprog)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Skjul &detaljer" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "" #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "" #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Navn" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Vært" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Brugernavn" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Fjernsti" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Type" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Netværksdrev" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "" #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "" #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Størrelse" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Dato ændret" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Dato brugt" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Tilladelser" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Ejer" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Gruppe" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "Ejer ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "Gruppe ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Du har måske ikke tilladelse." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "" #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Tastatur-interaktiv forespørgsel" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Ikke i stand til at opnå adgang til mappen" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Sti ikke genkendt" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Check om stien blev stavet korrekt." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Ikke i stand til at omdøbe genstand" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "" ================================================ FILE: po/de/swish.po ================================================ # # Translators: # Gottfried von Makhir <>, 2012 # leolabs2 , 2012 # Philippe Käüver <>, 2012 # vr0 , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: vr0 \n" "Language-Team: German (http://www.transifex.com/projects/p/swish/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Die Datei kann auf dem Server nicht erstellt werden:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "kopiert '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "zu '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Der Ordner enthält bereits eine Datei namens '{1}'" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Möchten Sie einen Austausch vornehmen?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Austausch der Datei bestätigen" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopieren..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Die Datei kann nicht übertragen werden" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Du hast keine Rechte um in diesem Verzeichniss zu schreiben." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Neue SFTP-Verbindung" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Anlegen" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Bezeichnung" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Zum Beispiel: \"Mein Computer\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Details des Rechners und Accounts, mit dem Sie sich verbinden wollen:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Rechner:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Benutzer:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Das Verzeichnis auf dem Rechner, mit dem die Verbindung hergestellt werden soll:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "Pf&ad" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Beispiel: /home/benutzername" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Abbrechen" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Die Bezeichnung darf nicht länger als 30 Zeichen sein." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Der Rechnername ist ungültig." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Der Port ist ungültig (nur zwischen 0 und 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Der Benutzername ist ungültig." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Der Pfad ist ungültig" #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Eine Verbindung mit dieser Bezeichnung existiert bereits. Bitte versuchen Sie eine andere." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Füllen Sie alle Felder aus." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Passwort" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "&Details anzeigen (möglicherweise ohne Übersetzung)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "&Details verbergen" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Der Ordner enthält bereits eine Datei namens '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Wollen Sie die bestehende Datei\n\n\t%1%\n\ndurch diese ersetzen?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Die Datei existiert bereits" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Falscher Host-Schlüssel" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "ACHTUNG: Der SSH Host-Schlüssel wurde geändert!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "Der SSH Host-Schlüssel, der von '%1%' gesendet wurde, um sich zu identifizieren, passt nicht zu dem für diesen Rechner gespeicherten Schlüssel. Das kann bedeuten, dass Dritte vorgeben, der Rechner zu sein, mit dem Sie sich verbinden wollen, oder dass der Systemadministrator diesen Schlüssel ausgetauscht hat." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Es ist wichtig, zu überprüfen ob dies der richtige Fingerabdruck ist:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Ich vertraue diesem Schlüssel: &Aktualisieren und Verbinden\nSie müssen diesen Schlüssel nicht erneut verifizieren, außer er wird verändert" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Ich vertraue diesem Schlüssel: &Nur verbinden\nSie werden bei der nächsten Verbindung mit diesem Schlüssel erneut gewarnt" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Abbrechen\nWählen Sie diese Option, außer Sie sind sicher, dass der Schlüssel korrekt ist." #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Unbekannter Host-Schlüssel" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Der Server '%1%' hat sich mit einem SSH Host-Schlüssel identifiziert, dessen Fingerabdruck lautet:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Falls Sie diesen Schlüssel nicht erwarten, kann es sein, dass Dritte vorgeben, der Computer zu sein, mit dem Sie sich verbinden wollen." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Prüfen Sie unbekannten SSH-Host-Schlüssel" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Ich vertraue diesem Schlüssel: &Speichern und Verbinden\nSie müssen den Schlüssel nicht erneut verifizieren, außer er wird verändert" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Ich vertraue diesem Schlüssel: &einfach verbinden\nSie werden bei der nächsten Verbindung erneut aufgefordert, den Schlüssel zu bestätigen" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Name" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Rechner" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Benutzername" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Entfernter Pfad" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Verbindungstyp" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Netzlaufwerk" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "SFTP-Verbindung &Hinzufügen" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Neue SFTP-Verbindung mit Swish anlegen" #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "SFTP-Verbindung &Hinzufügen..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Verbindung Hinzufügen" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Aufgaben für SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Diese Aufgaben helfen Ihnen bei der Verwaltung der Swish SFTP Verbindungen." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Schlüsselverwaltung starten" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Putty SSH Schlüsselverwaltung starten" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Schlüsselverwaltung starten" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "SFTP-Verbindung &entfernen" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Eine mit Swish angelegte SFTP-Verbindung entfernen." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "SFTP-Verbindung &entfernen..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Verbindung entfernen" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Größe" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Datum zuletzt geändert" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Datum letzter Zugriff" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Berechtigungen" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Besitzer" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Gruppe" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "Besitzer-ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "Gruppen-ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Öffnen &link" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Öffnen" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Der link kann nicht geöffnet werden" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Möglicherweise verfügen Sie nicht über die nötigen Berechtigungen." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Die Datei kann nicht geöffnet werden" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Aufgaben für Dateien und Ordner" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Diese Aufgaben helfen Ihnen bei der Verwaltung der Daten auf den Servern." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Das Element kann nicht gelöscht werden" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Neuer &Ordner" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Erstellt einen neuen leeren Ordner." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Neuen Ordner erstellen" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Neuer Ordner" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Neuer Ordner kann nicht angelegt werden" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Interaktive Tastaturabfrage" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Auf das Verzeichnis kann nicht zugegriffen werden" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Pfad nicht erkannt" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Bitte prüfen Sie den Pfad auf mögliche Fehler." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Das Element kann nicht umbenannt werden" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Auf das Element kann nicht zugegriffen werden" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Auf die Elemente kann nicht zugegriffen werden" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Auf das Verzeichnis kann nicht zugegriffen werden" ================================================ FILE: po/el_GR/swish.po ================================================ # # Translators: # Klonos TwinZ , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Klonos TwinZ \n" "Language-Team: Greek (Greece) (http://www.transifex.com/projects/p/swish/language/el_GR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: el_GR\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Αδύνατη η δημιουργία του αρχείου στον εξυπηρετητή:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Αντιγραφή του '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "Σε '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Αυτός ο φάκελος περιέχει ήδη ένα αρχείο με όνομα '{1}'" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Θα θέλατε να το αντικαταστήσετε?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Επιβεβαίωση Αντικατάστασης Αρχείου" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Γίνεται αντιγραφή..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Αδύνατη η μεταφορά των αρχείων" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Μπορεί να μην έχετε δικαιώματα εγγραφής σε αυτόν τον φάκελο." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Νέα Σύνδεση SFTP" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Δημιουργία" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Ετικέτα:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Για παράδειγμα: \"Υπολογιστής στο Σπίτι\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Προσδιορίστε τις λεπτομέρειες του υπολογιστή του λογαριασμού χρήστη με τα οποία θα θέλατε να συνδεθείτε:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Host:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Πόρτα:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Χρήστης:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Προσδιορίστε τον φάκελο στον server στον οποίο θα θέλατε το Swish να ξεκινά την σύνδεση:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "&Διαδρομή:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Παράδειγμα: /home/yourusername" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Ακύρωση" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Η ετικέτα δε μπορεί να είναι μεγαλύτερη από 30 χαρακτήρες" #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Το όνομα του host δεν είναι έγκυρο." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Η πόρτα δεν είναι έγκυρο (μεταξύ 0 και 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Το όνομα χρήστη δεν είναι έγκυρο." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Η διαδρομή δεν είναι έγκυρη." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Υπάρχει ήδη μια σύνδεση με την ίδια ετικέτα. Παρακαλώ δοκιμάστε μια άλλη." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Συμπληρώστε όλα τα πεδία." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Συνθηματικό" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Εμφάνιση &λεπτομερειών (που μπορεί να μην είναι στη γλώσσα σας)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Απόκρυψη &λεπτομερειών" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Ο φάκελος περιέχει ήδη ένα αρχείο με όνομα '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Θα θέλατε να αντικαταστήσετε το ήδη υπάρχον αρχείο\n\n»%1%\n\nμε αυτό?\n\n»%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Το αρχείο υπάρχει ήδη" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Το κλειδί host δεν ταιριάζει" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "ΠΡΟΕΙΔΟΠΟΙΗΣΗ: το κλειδί SSH του host άλλαξε!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "Το κλειδί SSH του host που απεστάλη από '%1%' με σκοπό να το ταυτοποιήσει δεν ταιριάζει το ήδη γνωστό κλειδί γι' αυτόν τον εξυπηρετητή. Αυτό μπορεί να σημαίνει ότι κάποιος τρίτος προσποιείται ότι είναι ο υπολογιστής με τον οποίο προσπαθείτε να συνδεθείτε ή απλά ότι ο διαχειριστής του συστήματος έχει αλλάξει το κλειδί." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Είναι σημαντικό να ελέγξετε ότι αυτό είναι το σωστό αποτύπωμα κλειδιού:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Εμπιστεύομαι αυτό το κλειδί: &ενημέρωση και σύνδεση\nΔεν θα χρειαστεί να επαληθεύσετε το κλειδί αυτό ξανά έκτος και αν αλλάξει" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Εμπιστεύομαι αυτό το κλειδί: &απλώς σύνδεση\nΘα ειδοποιηθείτε γι' αυτό το κλειδί ξανά την επόμενη φορά που θα συνδεθείτε" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Ακύρωση\nΚάνετε αυτή την επιλογή εκτός και αν είστε σίγουροι ποως το κλειδί είναι σωστό" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Άγνωστο κλειδί host" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Ο εξυπηρετητής '%1%' έχει ταυτοποιήσει τον εαυτό του με ένα SSH κλειδί host του οποίου το αποτύπωμα είναι:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Αν δεν περιμένετε αυτό το κλειδί, κάποιος τρίτος ενδέχεται να προσποιείται να είναι ο υπολογιστής με τον οποίο προσπαθείτε να συνδεθείτε." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Επαληθεύστε το άγνωστο SSH κλειδί host" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Εμπιστεύομαι αυτό το κλειδί: &αποθήκευση και σύνδεση\nΔεν θα χρειαστεί να επαληθεύσετε αυτό το κλειδί ξανά εκτός και αν αλλάξει" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Εμπιστεύομαι αυτό το κλειδί: &σύνδεση μόνο\nΘα σας ζητηθεί να επαληθεύσετε το κλειδί ξανά την επόμενη φορά που θα συνδεθείτε" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Όνομα" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Host" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Όνομα χρήστη" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Πόρτα" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Απομακρυσμένη διαδρομή" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Είδος" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Δίσκος δικτύου" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Δημιουργία σύνδεσης SFTP" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Δημιουργία νέας σύνδεσης SFTP με χρήση του Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Δημιουργία σύνδεσης SFTP..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Δημιουργία Σύνδεσης" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Ενέργειες SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Αυτές οι ενέργειες σας βοηθούν να διαχειριστείτε συνδέσεις του Swish SFTP" #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Εκτέλεση του διαχειριστή κλειδιών" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Εκτέλεση του διαχειριστή κλειδιών SSH του Putty, Pageant." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Εκτέλεση του διαχειριστή κλειδιών" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Διαγραφή Σύνδεσης SFTP" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "&Διαγραφή σύνδεσης SFTP που έχει γίνει με χρήση του Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Διαγραφή σύνδεσης SFTP..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Διαγραφή Σύνδεσης" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Μέγεθος" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Ημερομηνία Τροποποίησης" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Ημερομηνία Προσπέλασης" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Άδειες" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Ιδιοκτήτης" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Ομάδα" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID ιδιοκτήτη" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID ομάδας" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Άνοιγμα &συνδέσμου" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Άνοιγμα" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Αδύνατη το άνοιγμα του συνδέσμου" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Μπορεί να μην έχετε τα κατάλληλα δικαιώματα." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Αδύνατο το άνοιγμα του αρχείου" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Ενέργειες Αρχείων και Φακέλων" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Αυτές οι ενέργειες σας βοηθούν να διαχειριστείτε τα απομακρυσμένα αρχεία σας." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Αδύνατη η διαγραφή του αντικειμένου" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Νέος &φάκελος" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Δημιουργία ενός νέου, κενού φακέλου μέσα στον φάκελο που έχετε ανοιχτό." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Δημιουργία νέου φακέλου" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Νέος φάκελος" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Δεν ήταν δυνατή η δημιουργία νέου φακέλου" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Αναζήτηση με διάδραση πληκτρολογίου" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Δεν είναι δυνατή η προσπέλαση του φακέλου" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Η διαδρομή δεν αναγνωρίζεται" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Ελέγξτε αν η διαδρομή έχει πληκτρολογηθεί σωστά." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Αδύνατη η μετονομασία του αντικειμένου" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Αδύνατη η πρόσβαση στο αντικείμενο" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Αδύνατη η πρόσβαση στα αντικείμενα" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Αδύνατη η πρόσβαση στον φάκελο" ================================================ FILE: po/es/swish.po ================================================ # # Translators: # Antonio Rico , 2012 # Tomas Figueroa , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Antonio Rico \n" "Language-Team: Spanish (http://www.transifex.com/projects/p/swish/language/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Imposible crear fichero en el servidor:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Copiando '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "A '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Esta carpeta ya contiene un archivo llamado '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "¿Desea reemplazarlo?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Confirmar reemplazo de archivo" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Copiando..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "No se pueden trasnferir los archivos" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Puede que no tenga permiso para escribir en este directorio." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Nueva conexión SFTP" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Crear" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Nombre:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Por ejemplo: \"Computadora de casa\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Especifique los detalles del equipo y de la cuenta a la que desea conectarse:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Host:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Puerto:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Usuario:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Especifique el directorio en el servidor en que desea que Swish inicie la conexión:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "&Ruta:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Ejemplo: /home/usuario" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Cancelar" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "El nombre no puede contener más de 30 caracteres." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "El nombre del host no es válido." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "El puerto no es válido (entre 0 y 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "El nombre de usuario no es válido." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "La ruta no es válida." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Ya existe una conexión con el mismo nombre. Pruebe con otro." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Llene todos los campos." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Contraseña" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "Aceptar" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Mostrar &detalles (podrían no estar en su idioma)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Ocultar &detalles" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "La carpeta ya contiene un archivo llamado '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "¿Desea reemplazar el archivo existente\n\n\t%1%\n\ncon este otro?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "El archivo ya existe" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Conflicto con la clave del host" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "ADVERTENCIA: la clave del host SSH ha cambiado." #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "La clave del host SSH enviada por '%1%' para identificarse no concuerda con la clave conocida para este servidor. Esto podría significar que algún tercero está pretendiendo ser el equipo al que usted está trantando de conectarse, o que el administrador del sistema podría haber cambiado la clave." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Es importante verificar que ésta es la huella digital correcta:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Confío en esta clave: &actualizar y conectar\nNo tendrá que verificar esta clave otra vez a menos que cambie" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Confío en esta clave: &solo conectar\nSe le avisará respecto a esta clave otra vez la próxima vez que se conecte " #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Cancelar\nElija esta opción a menos que esté seguro de que la clave es correcta" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Clave de host desconocida" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "El servidor '%1%' se ha identificado con una clave de host SSH cuya huella digital es:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Si no está esperando esta clave, un tercero podría estar pretendiendo ser el equipo al que usted trata de conectarse." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Verificar clave de host SSH desconocida" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Confío en esta clave: &almacenar y conectar\nNo tendrá que verificar esta clave otra vez a menos que cambie" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Confío en esta clave: &solo conectar\nSe le pedirá que verifique la clave de nuevo la próxima vez que se conecte" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Nombre" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Host" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Usuario" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Puerto" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Ruta remota" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Tipo" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Dispositivo de red" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Agregar Conexión SFTP" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Crear una nueva conexión SFTP con Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Agregar Conexión SFTP..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Agregar Conexión" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Tareas SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Estas tareas ayudan a administrar las conexiones Swish SFTP." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Invocar agente de claves" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Invocar el agente de claves Putty SSH, Pageant." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Invocar agente de claves" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Eliminar Conexión SFTP" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Eliminar una conexión SFTP creada con Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Eliminar Conexión SFTP..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Eliminar Conexión" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Tamaño" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Fecha de modificación" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Fecha de último acceso" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Permisos" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Propietario" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grupo" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID del propietario" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID del grupo" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Abrir enlace" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "Abrir" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "No se puede abrir el enlace" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Puede que no tenga permiso." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "No se puede abrir el archivo" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Acciones sobre ficheros y carpetas" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Estas acciones le ayudan a gestionar sus ficheros remotos." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Imposible eliminar el elemento" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Nueva &carpeta" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Crear una nueva carpeta vacia en la carpeta actual." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Crear una nueva carpeta" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Nueva carpeta" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "No se pudo crear una nueva carpeta" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Solicitud de teclado interactivo" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "No se puede acceder al directorio" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Ruta no reconocida" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Verifique que la ruta fue introducida correctamente." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Imposible renombrar el elemento" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Imposible acceder al elemento" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Imposible acceder a los elementos" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Imposible acceder a la carpeta" ================================================ FILE: po/et/swish.po ================================================ # # Translators: # pexy , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: pexy \n" "Language-Team: Estonian (http://www.transifex.com/projects/p/swish/language/et/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: et\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Ei saanud faili luua serverisse:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Kopeerin '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "> '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "See kaust juba sisaldab faili nimega '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Kas soovid seda asendada?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Kinnita faili asendamine" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopeerin..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Ei saa faile üle kanda" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Sul ei pruugi olla õigusi, et sellesse kausta kirjutada." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Uus SFTP ühendus" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Loo" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Nimi:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Näiteks: \"Koduarvuti\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Vali arvuti andmed ja konto millega soovid ühendust võtta:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Server:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Kasutajanimi:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Vali kaust serveris mille Swish avab ühenduse loomisel:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "&Failitee" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Näiteks: /home/kasutajanimi" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Katkesta" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Nimi võib olla kuni 30 märki pikk." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Serveri nimi on ebakorrektne." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Pordi number on ebakorrektne (0-65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Kasutajanimi on ebakorrektne." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Failitee on ebakorrektne." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Sellise nimega ühendus on juba olemas. Vali uus nimi." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Täida kõik väljad." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Salasõna" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Näita &andmeid (ei pruugi olla sinu keeles)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Peida &andmed" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "See kaust juba sisaldab faili nimega '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Kas soovid asendada olemasolevat faili\n\n\t%1%\n\nselle failiga\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Fail on juba olemas" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Serveritunnus ei klapi" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "HOIATUS: SSH serveritunnus on muutunud!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "SSH serveritunnus, millega server '%1%' ennast identifitseerib, ei klapi varem salvestatud tunnusega selle serveri kohta. See võib tähendada, et kolmas osapool püüab pahatahtlikult esineda serverina või on serveri hooldaja muutnud servertunnust." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "On oluline kontrollida, kas see serveritunnuse sõrmejälg on õige:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Usalda seda serveritunnust: &uuenda ja ühenda\nSa ei pea enam seda serveritunnust kontrollima" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Usalda seda serveritunnust: &ainult ühenda\nJärgmisel ühendamisel hoiatatakse sind uuesti selle serveritunnuse tõttu" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Katkesta\nKatkesta kui sa pole serveritunnuse õigsuses kindel" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Tundmatu serveritunnus" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Server '%1%' identifitseerib ennast SSH serveritunnusega, mille sõrmejälg on:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Kui see pole korrektne serveritunnus, siis võib kolmas osapool püüd pahatahtlikult esineda serverina, millega sa ühenduda tahad." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Kontrolli tundmatu SSH serveritunnus" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Usalda seda serveritunnust: &salvesta ja ühenda\nSa ei pea enam seda serveritunnust kontrollima" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Usalda seda serveritunnust: &ainult ühenda\nJärgmisel ühendamisel palutakse sul uuesti serveritunnust kontrollida" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Nimi" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Server" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Kasutajanimi" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Kaust serveris" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Tüüp" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Võrguketas" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Lisa SFTP ühendus" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Loo Swish abil uus SFTP ühendus." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Lisa SFTP ühendus..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Lisa ühendus" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP tegevused" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Need tegevused aitavad sul hallata Swish SFTP ühendusi." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Käivita võtmehaldur" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Käivita Putty SSH võtmehaldur, Pageant." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Käivita võtmehaldur" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Kustuta SFTP ühendus" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Kustuta SFTP ühendus, mis on loodud Swish abil." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Kustuta SFTP ühendus..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Kustuta ühendus" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Suurus" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Muutmisaeg" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Vaatamisaeg" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Õigused" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Omanik" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grupp" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "Omaniku ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "Grupi ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Ava &viit" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Ava" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Ei saa avada viita" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Sul ei pruugi õigusi olla." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Ei saa avada faili" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Faili ja kausta tegevused" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Need tegevused aitavad sul hallata faile serveris." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Faili ei saa kustutada" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "&Uus kaust" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Loo uus tühi kaust jooksvasse kausta." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Loo uus kaust" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Uus kaust" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Ei saanud uut kausta luua" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Kausta ei saa avada" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Failiteed ei tuntud ära" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Kontrolli, et kaust sisestati korrektselt." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Faili ei saa ümber nimetada" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Faili ei saa avada" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Faile ei saa avada" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Kausta ei saa avada" ================================================ FILE: po/fi/swish.po ================================================ # # Translators: # Heikki Salko , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Heikki Salko \n" "Language-Team: Finnish (http://www.transifex.com/projects/p/swish/language/fi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Tiedostoa ei voida luoda palvelimella:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Kopioidaan '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "Kohteeseen '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Tässä kansiossa on jo tiedosto nimeltä '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Haluatko korvata sen?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Vahvista tiedoston korvaaminen" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopioidaan..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Tiedostoja ei voida siirtää" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Sinulla ei ehkä ole oikeuksia kirjoittaa tähän hakemistoon." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Uusi SFTP-yhteys" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Luo" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Nimi:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Esimerkiksi \"Kotikone\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Määritä tietokoneen ja käyttäjätunnuksen tiedot, johon haluat yhdistää:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Isäntä:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Portti:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Käyttäjä:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Määritä palvelimen hakemisto, jossa haluat Swish:n aloittavan yhteyden:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "P&olku:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Esimerkki: /home/kayttaja" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Peruuta" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Nimi ei voi olla pidempi kuin 30 merkkiä." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Isännän nimi ei ole kelvollinen." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Portti ei ole kelvollinen (0 ja 65535 välillä)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Käyttäjänimi ei ole kelvollinen." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Polku ei ole kelvollinen." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Tämä nimi on jo toisen yhteyden käytössä. Kokeile toista." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Täytä kaikki kentät." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Salasana" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Näytä &lisätiedot (jotka eivät välttämättä ole kielelläsi)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Piilota &lisätiedot" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Kansiossa on jo tiedosto nimeltä '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Haluatko korvata olemassaolevan tiedoston\n\n\t%1%\n\ntällä?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Tiedosto on jo olemassa." #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Isäntä-avain ei täsmää" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "VAROITUS: SSH:n isäntä-avain on muuttunut!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "Tietokoneen '%1%' tunnistamistaan varten lähettämä SSH:n isäntä-avain ei täsmää tämän palvelimen tunnetun avaimen kanssa. Tämä saattaa tarkoittaa, että jokin kolmas osapuoli esiintyy tietokoneena, johon yrität yhdistää, tai järjestelmän ylläpitäjä on voinut vaihtaa avaimen." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "On tärkeää tarkistaa, että tämä on oikea avaimen sormenjälki:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Luotan tähän avaimeen: pä&ivitä ja yhdistä\\nSinun ei tarvitse vahvistaa tätä avainta uudelleen, ellei se muutu" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Luotan tähän avaimeen: &yhdistä\nSinua varoitetaan tästä avaimesta uudelleen, kun yhdistät seuraavan kerran" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Peruuta\nValitse tämä vaihtoehto, ellet ole varma avaimen oikeellisuudesta" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Tuntematon isäntä-avain" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Palvelin '%1%' on tunnistanut itsensä SSH:n isäntä-avaimella, jonka sormenjälki on:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Jos et odota tätä avainta, jokin kolmas osapuoli saattaa esiintyä tietokoneena, johon yrität yhdistää." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Vahvista tuntematon SSH:n isäntä-avain" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Luotan tähän avaimeen: &tallenna ja yhdistä\nSinun ei tarvitse vahvistaa tätä avainta uudelleen, ellei se muutu" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Luotan tähän avaimeen: &yhdistä\nSinua varoitetaan tästä avaimesta uudelleen, kun yhdistät seuraavan kerran" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Nimi" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Isäntä" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Käyttäjätunnus" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Portti" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Etäpolku" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Tyyppi" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Verkkoasema" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Lisää SFTP-yhteys" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Luo uusi SFTP-yhteys Swish:llä." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Lisää SFTP-yhteys..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Lisää yhteys" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP-tehtävät" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Nämä tehtävät auttavat sinua hallitsemaan Swish:n SFTP-yhteyksiä." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "A&vaa avainagentti" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Avaa PuTTY:n SSH-avainagentti, Pageant" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Avaa avainagentti" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Poista SFTP-yhteys" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Poista Swish:llä luotu SFTP-yhteys." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Poista SFTP-yhteys..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Poista yhteys" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Koko" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Viimeksi muokattu" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Viimeksi käytetty" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Oikeudet" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Omistaja" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Ryhmä" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "Omistajan ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "Ryhmän ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Avaa &linkki" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Avaa" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Linkkiä ei voida avata" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Sinulla ei ehkä ole oikeuksia." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Tiedostoa ei voida avata" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Tiedosto- ja kansiotehtävät" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Nämä tehtävät helpottavat etätiedostojen hallintaa." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Tiedostoa ei voida poistaa" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Uusi &kansio" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Luo avoinna olevaan kansioon uusi, tyhjä kansio." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Luo uusi kansio" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Uusi kansio" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Uutta kansiota ei voida luoda" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Näppäimistö-interaktiivinen pyyntö" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Hakemistoon ei saada pääsyä" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Polkua ei tunnistettu" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Tarkista, että polku on syötetty oikein." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Kohdetta ei voida nimetä uudelleen" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Kohteeseen ei saada pääsyä" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Kohteisiin ei saada pääsyä" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Kansiota ei voida avata" ================================================ FILE: po/fr/swish.po ================================================ # # Translators: # fkhannouf , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: fkhannouf \n" "Language-Team: French (http://www.transifex.com/projects/p/swish/language/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Impossible de créer le fichier sur le serveur" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Copie de '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "Vers '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Ce dossier contient déjà le fichier '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Voulez-vous le remplacer ?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Confirmer le remplacement" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Copie en cour..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Transfert du fichier impossible" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Il semble que vous n'ayez pas la permission d'écrire dans ce répertoire" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Nouvelle connexion SFTP" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Créer" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Libellé" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Par exemple : \"Ordinateur personnel\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Indiquer les éléments de l'ordinateur et du compte sur lequel vous voulez vous connecter" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Hôte:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Identifiant:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Indiquer le répertoire du serveur sur lequel Swish doit démarrer la connexion" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "&Chemin:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Exemple : /home/identifiant" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Annuler" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Le libellé ne peut faire plus de 30 caractères." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Nom d'hôte non valide." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Numéro de port non valide (entre 0 et 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Identifiant non valide." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Chemin non valide." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Une connexion avec le même nom existe déjà. Essayez un autre nom." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Complétez tous les champs." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Mot de passe" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "Valider" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Montrer les détails (qui peuvent ne pas être en français)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Cacher les détails" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Le dossier contient déjà un fichier nommé '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Voulez-vous remplacer le fichier existant\n\n\t%1%\n\npar celui-ci ?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Fichier déjà existant" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Clé d'hôte non reconnue." #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "ATTENTION : la clé d'hôte a changé !" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "La clé d'hôte envoyée par '%1%' pour s'identifier ne correspond pas à la clé connue pour ce serveur. Cela peut signifier qu'un tiers prétend être l'ordinateur sur lequel vous essayez de vous connecter, ou bien que l'administrateur système a simplement changé la clé." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Il est important de vérifier que c'est la bonne empreinte de clé :" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Je valide cette clé: &Mettre à jour et se connecter\nVous n'aurez plus à vérifier cette clé tant qu'elle ne change pas" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Je valide cette clé : &Simple connexion\nVous serez à nouveau averti la prochaine fois que vous vous connectez" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Annuler\nChoisissez cette option seulement si vous êtes certains que la clé est correcte." #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Clé d'hôte inconnue" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Le serveur '%1%' s'est identifié avec une clé d'hôte dont l'empreinte est :" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Si vous n'attendiez pas cette clé, un tiers prétend peut-être être l'ordinateur sur lequel vous voulez vous connecter." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Vérification de clé d'hôte inconnue" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Je valide cette clé: &Enregistrer et se connecter\nVous n'aurez plus à vérifier cette clé tant qu'elle ne change pas." #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Je valide cette clé : &Simple connexion\nIl vous sera demandé de vérifier la clé à nouveau la prochaine fois que vous vous connecterez" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Nom" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Hôte" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Identifiant" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Chemin distant" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Type" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Disque réseau" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Ajouter une connexion SFTP" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Créer une nouvelle connexion SFTP avec Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Ajouter une connexion SFTP..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Ajouter une connexion" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Tâches SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Ces tâches vous aident à gérer vos connexions Swish SFTP." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Lancer l'agent d'authentification" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "&Lancer l'agent d'authentification de Putty SSH, Pageant." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Lancer l'agent d'authentification" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Enlever la connexion SFTP" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Enlève une connexion SFTP créée avec Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Enlever la connexion SFTP..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Enlever la connexion" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Taille" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Date de modification" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Date d'accès" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Permissions" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Propriétaire" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Groupe" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID du propriétaire" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID du groupe" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Ouvrir le lien" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "Ouvrir" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Impossible d'ouvrir le lien" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Il semble que vous n'ayez pas les permissions nécessaires" #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Impossible d'ouvrir le répertoire" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Tâches liées aux fichiers et répertoires" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Ces tâches vous aident à gérer les fichiers distants" #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Impossible de détruire l'article" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Nouveau répertoire" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Crée un nouveau répertoire vide dans le répertoire que vous avez ouvert" #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Créer un nouveau répertoire" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Nouveau répertoire" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Impossible de créer un nouveau répertoire" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Demande interactive du clavier" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Impossible d'atteindre ce répertoire" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Chemin non reconnu" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Vérifiez que le chemin entré est correct" #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Impossible de renommer cet article" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Impossible d'accéder à cet article" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Impossble d'accéder à ces articles" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Impossible d'accéder au répertoire" ================================================ FILE: po/he/swish.po ================================================ # # Translators: # aharonoosh , 2012 # SmileyBarry , 2014 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2014-02-22 19:00+0000\n" "Last-Translator: SmileyBarry \n" "Language-Team: Hebrew (http://www.transifex.com/projects/p/swish/language/he/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: he\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "לא ניתן ליצור קובץ בשרת:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "מעתיק '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "ל '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "'{1}' התיקייה הזו כבר מכילה קובץ בשם" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "האם אתה רוצה להחליף את זה?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "אשר החלפת קבצים" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "מעתיק..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "לא ניתן להעביר קבצים" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "אולי אין לך הרשאה לכתוב לספרייה זו." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "חדש SFTP חיבור" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "צור" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&שם:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "\"Home Computer\" לדוגמא " #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "הגדר את הפרטים של המחשב והחשבון שברצונך להתחבר אליו:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&שרת:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&שער:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&משתמש:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "יתחיל בחיבור אליוSwish הקש את הספריה בשרת שאתה רוצה ש" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "נ&תיב" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "/home/yourusername דוגמא:" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "בטל" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "השם לא יכול להיות באורך יותר מ 30 אותיות" #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "השרת לא תקין" #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "(השער לא תקין (השער התקין הוא בין 0 ל 65535" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "המשתמש לא תקין" #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "הנתיב לא חוקי" #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "חיבור עם אותו שם קיים. בבקשה נסה אחר." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "השלם את כל השדות." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "סיסמא" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "אישור" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "ה&צג פרטים" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "הסתר &פרטים" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "'%1%' התקיה כבר מכילה קובץ בשם " #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "האם אתה רוצה להחליף את הקובץ הקיים/n\t%1%\n\nאם זה?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "קובץ קיים" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "מפתח לא מתאים" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "אזהרה: הפצח השתנה!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "על מנת לזהות את עצמו לא תואם למפתח קיים לשרת זה. '%1%' שנשלח על ידי SSHמפתח הדבר זה יכול להוות אינדיקציה שצד שלישי מנסה להעמיד פנים שהוא המחשב אליו אתה מנסה להתחבר או שהמנהל מערכת שינה את המפתח." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "זה חשוב לבדוק שזה מפתח החתימה הנכון:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "אני בוטח במפתח זה:&עדכן והתחבר\nלא יהיה עליך לוודא את מפתח זה שוב אלא אם הוא משתנה" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "אני בוטח במפתח זה: &רק תתחבר\nאתה תוזהר בפעם הבאה על מפתח זה בפעם הבאה כשתתחבר" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "ביטול&\nבחר אופציה זאת אלא אם אתה בטוח שהמפתח נכון" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "מפתח-שרת לא ידוע" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr ":SSH זיהה את עצמו עם מפתח שרת '%1%' השרת " #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "אם אתה לא מצפה למפתח זה, צד שלישי יכול להיות מנסה להתחזות להיות המחשב אשר אליו אתה מנסה להתחבר." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "בעל מפתח-שרת לא ידוע SSH וודא " #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "אני בוטח במפתח זה: &אחסן והתחבר\nלא תצטרך לוודא מפתח זה שוב אלא אם הוא ישתנה" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr " &just connect\nאני בוטח במפתח זה: אתה תתבקש לוודא את המפתח שוב בפעם הבאה שתתחבר" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "שם" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "שרת" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "משתמש" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "שער" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "נתיב מרוחק" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "סוג" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "כונן רשת" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "SFTP הוסף חיבור&" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Swish עם SFTP צור חיבור " #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "SFTP הוסף חיבור&" #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "הוסף חיבור" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP משימות " #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Swish SFTP משימות אלו עוזרות לך לנהל את חיבורי" #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "הפעל סוכן מפתח" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&SFTP הסר חיבור" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr ".Swish הסר חיבור אשר נוצר עם " #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&...SFTP הסר חיבור " #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "הסר חיבור" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "גודל" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "תאריך שונה" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "תאריך ניגש" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "הרשאות" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "בעלים" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "קבוצה" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "מספר בעלים" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "מספר קבוצה" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "פתח &קישור" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&פתח" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "לא ניתן לפתוח את הקישור" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "אולי אין לך רשות." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "לא ניתן לפתוח את הקובץ" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "משימות קובץ ותיקיה" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "משימות אלה תעזורנה לך לנהל את הקבצים המרוחקים שלך." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "אין אפשרות למחוק את הפריט" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "&תיקייה חדשה" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "צור תיקייה חדשה וריקה בתיקייה שפתחת." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "צור תיקייה חדשה" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "תיקייה חדשה" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "לא ניתן ליצור תיקייה חדשה" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "מקלדת-בקשה אינטראקטיבית" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "אין אפשרות לגשת לספרייה" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "נתיב לא מוכר" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "ודאו כי הנתיב הוזן כהלכה." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "לא ניתן לשנות את שם הפריט" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "אין אפשרות לגשת לפריט" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "לא ניתן לגשת לפריטים" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "לא ניתן לגשת לתיקייה" ================================================ FILE: po/hi/swish.po ================================================ # # Translators: msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: alamaison \n" "Language-Team: Hindi (http://www.transifex.com/projects/p/swish/language/hi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "इस फ़ोल्डर में पहले से ही '{1}' नामक एक फाइल शामिल हैं." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "क्या आप इसे बदलना चाहते हैं?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "फाइल बदलने के लिए पुष्टि" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "प्रतिलिपि कार्य जारी..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "नया SFTP कनेक्शन" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "बनाएँ" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&लेबल:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "उदाहरण के लिए: \"गृह कंप्यूटर\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "कंप्यूटर और खाता जिससे कनेक्ट होना चाहते हैं उसका विवरण बताएं:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&होस्ट:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&पोर्ट:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&यूज़र:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "सर्वर पर डाईरेक्टरी बताएं जिससे आप Swish का कनेक्शन शुरू करना चाहते हैं:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "&पथ:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "उदाहरण: / होम / आपकायूज़रनेम" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "रद्द करें" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "लेबल 30 अक्षरों से अधिक लंबा नहीं हो सकता." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "होस्ट नाम अमान्य है." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "पोर्ट मान्य नहीं है(0 और 65535 के बीच)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "यूज़रनेम अमान्य है." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "पथ अमान्य है." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "इस लेबल के साथ एक कनेक्शन पहले से ही मौजूद है. कृपया अन्य की कोशिश करें." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "सभी फील्ड को पूरा करें." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "पासवर्ड" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "ठीक है" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "विवरण दिखाएँ (जो आपकी भाषा में नहीं हो सकता)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "विवरण छिपाएँ" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "फ़ोल्डर में पहले से ही एक '%1%' नामक फ़ाइल शामिल" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "क्या आप मौजूदा फ़ाइल को बदलना चाहते\n\n%1%\n\nइस के साथ?\n\n%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "फ़ाइल पहले से मौजूद" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "बेमेल होस्ट-कुंजी" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "चेतावनी: SSH होस्ट-कुंजी बदल गई है!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "'%1%' द्वारा पहचान के लिए भेजी गई SSH होस्ट-कुंजी इस सर्वर को ज्ञात की से मेल नहीं खाती है. इसका मतलब हो सकता है कि एक तीसरी पार्टी आपके कंप्यूटर से कनेक्ट करने की कोशिश कर रही है या फिर आपके सिस्टम ऐडमिनिस्ट्रेटर ने कुंजी बदल दी है." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "यह महत्वपूर्ण है कि जांच लें यह सही कुंजी फिंगरप्रिंट है:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "मुझे इस कुंजी पर भरोसा है: नवीनीकरण करें और कनेक्ट करें\nआपको इस कुंजी को फिर से सत्यापित करने की जरूरत नहीं है जब तक यह न बदली जाए" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "मुझे इस कुंजी पर भरोसा है: सिर्फ कनेक्ट करें\nआपको इस कुंजी के बारे में फिर से चेतावनी दी जाएगी जब आप अगली बार कनेक्ट होंगे " #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "रद्द करें\nइस विकल्प को चुनें जब तक आप सुनिश्चित न हो कि कुंजी सही है" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "अज्ञात होस्ट-कुंजी" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "सर्वर '%1%' ने SSH एक होस्ट-कुंजी की पहचान की है जिसका फिंगरप्रिंट है:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "यदि आप इस कुंजी की उम्मीद नहीं कर रहे हैं, आप जिस कंप्यूटर से कनेक्ट करने की कोशिश कर रहें हैं वो तीसरी पार्टी का है." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "अज्ञात SSH होस्ट-कुंजी को सत्यापित करें" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "मुझे इस कुंजी पर भरोसा है: संग्रह करें और कनेक्ट करें\nआपको इस कुंजी को फिर से सत्यापित करने की जरूरत नहीं है जब तक यह न बदली जाए" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "मुझे इस कुंजी पर भरोसा है: सिर्फ कनेक्ट करें\nआपको इस कुंजी को फिर से सत्यापित करने को कहा जाएगा जब आप अगली बार कनेक्ट होंगे " #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "नाम" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "होस्ट" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "यूज़रनेम" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "पोर्ट" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "दूरस्थ पथ" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "टाइप करें " #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "नेटवर्क ड्राइव" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "S&FTP कनेक्शन जोड़ें" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Swish से एक नया SFTP कनेक्शन बनाएँ." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "S&FTP कनेक्शन जोड़ें..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "कनेक्शन जोड़ें" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP के कार्य" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "इन कार्यों की मदद से आप Swish SFTP कनेक्शन का प्रबंधन कर सकते हैं." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&SFTP कनेक्शन हटाएँ" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Swish से बनाया गया SFTP कनेक्शन हटाएँ." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&SFTP कनेक्शन हटाएँ..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "कनेक्शन हटाएँ" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "साइज़" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "संशोधित करने की तिथि" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "उपयोग करने की तिथि" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "अनुमतियाँ" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "मालिक" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "समूह" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "मालिक आईडी" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "समूह आईडी" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "आप के पास अनुमति नहीं है. " #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "फ़ाइल और फ़ोल्डर कार्य" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "इन कार्यों की मदद से आप अपनी दूरस्थ फ़ाइलों का प्रबंधन कर सकते हैं." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "आइटम को हटाने में असमर्थ" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "नया &फ़ोल्डर" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "आप खुले फ़ोल्डर में एक नया, खाली फ़ोल्डर बनाएँ." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "एक नया फ़ोल्डर बनाएँ" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "नया फ़ोल्डर" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "नया फ़ोल्डर नहीं बनाया जा सका" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "कीबोर्ड-इंटरैक्टिव अनुरोध" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "डाईरेक्टरी का उपयोग करने में असमर्थ" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "सही पथ नहीं." #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "जाँच लें कि पथ सही ढंग से दर्ज किया गया है." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "आइटम का नाम बदलने में असमर्थ" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "आइटम तक पहुँचने में असमर्थ" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "आइटम्स तक पहुँचने में असमर्थ" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "फ़ोल्डर तक पहुँचने में असमर्थ" ================================================ FILE: po/hu/swish.po ================================================ # # Translators: # Laszlo Pap , 2013 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Laszlo Pap \n" "Language-Team: Hungarian (http://www.transifex.com/projects/p/swish/language/hu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hu\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Nem lehet létrehozni a fájlt a szerveren" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "'{1}' másolása" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "'{1}'hez" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Ez a mappa már tartalmaz egy '{1}' nevu fájlt." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Szeretné lecserélni?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Fájlcsere megerosítése" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Másolás..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "A. fájlátvitel nem lehetséges" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Lehetséges, hogy nincs írási engedélye a könyvtárra" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Új SFTP kapcsolat" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Létrehozás" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Címke:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Például: \"Otthoni számítógép\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Adja meg a számítógép adatait melyhez csatlakozni kíván:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Gépnév:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "Fel&használó:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Határozza meg a mappát a szerveren melyet a Swish csatlakozás után automatikusan megnyit:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "&Elérési út:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Példa: /home/felhasznaloneve" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Mégse" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "A címke nem lehet hosszabb 30 karakternél." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "A gépnév érvénytelen." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Érvénytelen port (0 és 65535 között)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "A felhasználónév érvénytelen." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Az elérési út érvénytelen." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Egy kapcsolat már létezik az adott címkével. Kérem próbálkozzon másikkal." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Töltse ki az összes mezőt." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Jelszó" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "&Részletek mutatása (nem feltétlenül saját nyelven jelenik meg)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "&Részletek elrejtése" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "A mappa már tartalmaz egy '%1%' nevu fájlt." #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Szeretné lecserélni a létezo fájlt\n\n\t%1%\n\nezzel?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "A fájl már létezik" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Nem egyezo kiszolgáló-kulcs" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "FIGYELEM: Az SSH kiszolgáló-kulcs megváltozott!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "Az SSH kiszolgáló-kulcs melyet a '%1%' kiszolgáló küldött, nem egyezik a kiszolgáló ismert kulcsával. Ez azt jelentheti, hogy egy harmadik-fél tetteti magát a kiszolgálónak, vagy csak az adminisztrátor megváltoztatta a kulcsot." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Fontos, hogy ellenorizze a következo kulcs helyességét:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Megbízom ebben a kulcsban: &frissítés és csatlakozás\nNem kell újra ellenoriznie ezt a kulcsot, csak ha megváltozik" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Megízom ebben a kulcsban: &csak csatlakozás\nFigyelmeztetve lesz mikor újra csatlakozik" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Mégse\nVálassza ezt a lehetoséget ha ön biztos abban, hogy a kulcs nem megfelelo" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Ismeretlen kiszolgáló-kulcs" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "A kiszolgáló '%1%' a következo SSH-kiszolgáló kulccsal azonosította magát:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Ha ön nem ezt a kulcsot várta, egy harmadik-fél tettetheti hogy a számítógép, melyhez ön épp csatlakozni próbál." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Ellenorizze az ismeretlen SSH kiszolgáló-kulcsot" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Megbízom ebben a kulcsban: &eltárolás és csatlakozás\nNem kell újra ellenoriznie ezt a kulcsot, csak ha megváltozik" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Megízom ebben a kulcsban: &csak csatlakozás\nÚjra kell majd ellenoriznie a kulcsot mikor ismét csatlakozik" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Név" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Kiszolgáló" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Felhasználónév" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Távoli elérési út" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Típus" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Hálózati Meghajtó" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&SFTP kapcsolat hozzáadása" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Új SFTP kapcsolat létrehozása Swish-el." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&SFTP kapcsolat hozzáadása..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Kapcsolat hozzáadása" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP Feladatok" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Ezek a feladatok segítenek a Swish SFTP kapcsolatok kezelésében." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "A kulcsügynök &futtatása" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "A Pageant Putty SSH kulcsügynök futtatása" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Kulcsügynök futtatása" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "SFTP kapcsolat &eltávolítása" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Swish által készített SFTP kapcsolat eltávolítása. " #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "SFTP kapcsolat &eltávolítása..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Kapcsolat eltávolítása" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Méret" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Módosítás dátuma" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Hozzáférés dátuma" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Jogosultságok" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Tulajdonos" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Csoport" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "Tulajdonos ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "Csoport ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "&Hivatkozás megnyitása" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Megnyitás" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Nem lehet megnyitni a hivatkozást" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Lehetséges, hogy nincs jogosultsága." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "A fájlt nem lehet megnyitni" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Fájl és Mappa Feladatok" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Ezek a feladatok segítenek a távoli fájlok kezelésében." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Nem lehet törölni az adott elemet" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Új &mappa" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Új, üres mappa létrehozása az éppen megnyitott mappában." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Új mappa létrehozása" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Új mappa" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Nem sikerült létrehozni az új mappát" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Billentyuzet-interaktív kérés" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Nem lehet hozzáférni a mappához" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Elérési út felismerése nem sikerült" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Ellenorizze, hogy az elérési út helyesen lett-e megadva." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Nem sikerült átnevezni az adott elemet" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Nem lehet hozzáférni az elemhez" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Nem lehet hozzáférni az elemekhez" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Nem lehet hozzáférni a mappához" ================================================ FILE: po/it/swish.po ================================================ # # Translators: # Alessandro Calorì , 2012 # gbdenaro , 2012 # pizar , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: pizar \n" "Language-Team: Italian (http://www.transifex.com/projects/p/swish/language/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Impossibile creare il file sul server:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Sto copiando '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "A '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "La cartella contiene già il file '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Vuoi sostituirlo?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Conferma Sostituzione File" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Sto copiando..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Impossibile trasferire i file" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Potresti non avere i permessi di scrittura sulla cartella." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Nuova Connessione SFTP " #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Crea" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Nome:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Per esempio: \"Server Documenti\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Specifica i dettagli del server e l'account a cui vuoi connetterti:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Server:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Porta:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "Nome &Utente:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Specifica la cartella del server con la quale vuoi iniziare la connessione:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "P&ercorso:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Esempio: /home/utente" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Cancella" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "L'etichetta non può essere più lunga di 30 caratteri" #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Nome Server invalido" #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "La porta non è valida ( tra 0 e 65535 )" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Il nome utente è invalido" #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Il percorso è invalido" #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Una connessione con la stessa etichetta è già presente. Riprovare." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Completa tutti i campi." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Password" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Visualizza &Dettagli (potrebbero essere in un'altra lingua)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Nascondi &Dettagli" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "La cartella contiene già il file '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Vuoi sostituire il file esistente\n\n\t%1%\n\ncon?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "File già esistente" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Host-key ssh non corrispondenti" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "ATTENZIONE: la chiave-host SSH è cambiata!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "L' host-key SSH inviata da '%1%' per identificare se stesso non corrisponde alla chiave salvata per questo server. Questo potrebbe significare che qualcuno finge di essere il computer a cui si sta cercando di connettersi o l'amministratore di sistema può avere appena cambiato la chiave." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "E' importante verificare la correttezza dell'impronta:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Confermo questa chiave: $salva e collegati\nNon verificare nuovamente" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Confermo questa chiave: &collegati\nVerifica chiave ad ogni accesso" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Cancella\nScegli questo opzione se non si è certi che la chiave sia corretta" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Host-key sconosciuta" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Il server '%1%' si è identificato con una host-key SSH con impronta:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Se la chiave non è quella prevista, potrebbe significare che qualcuno finge di essere il computer a cui si sta cercando di connettersi o l'amministratore di sistema può avere appena cambiato la chiave." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Verifica host-key SSH sconosciuta" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Confermo questa chiave: &salva e collegati\nNon verificare nuovamente" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Confermo questa chiave: &collegati\nVerifica chiave ad ogni accesso" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Nome" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Server" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Nome Utente" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Porta" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Percorso remoto" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Tipo" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Unità di Rete" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Aggiungi Connessione SFTP" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Crea nuova connessione SFTP con Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Aggiungi Connessione SFTP..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Aggiungi Connessione" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Attività SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Queste attività consentono di gestire connessioni SFTP Swish." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Lancia il gestore delle chiavi" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Lancia il gestore di Putty SSH delle chiavi, Pageant" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Lancia il gestore delle chiavi" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Rimuovi Connessione SFTP" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Rimuovi Connessione SFTP creata con Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Rimuovi Connessione SFTP..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Rimuovi Connessione" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Dimensioni" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Modificato" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Ultimo Accesso" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Permessi" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Proprietario" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Gruppo" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID Proprietario" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID Gruppo" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Apri co&llegamento" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "Apri" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Impossibile aprire il collegamento" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Potresti non avere i permessi." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Impossibile aprire il file" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Operazioni su files e cartelle." #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Queste operazioni ti permettono di gestire i tuoi files remoti." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Non si riesce a cancellare l'oggetto." #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Nuova &cartella" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Crea una nuova cartella vuota nella cartella che hai aperto." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Crea una nuova cartella" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Nuova Cartella" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Non si riesce a creare una nuova cartella" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Richiesto input da tastiera" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Non è possibile accedere alla cartella" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Percorso non riconosciuto" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Verifica che il percorso sia stato inserito correttamente." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Non si riesce a rinominare l'oggetto." #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "L'oggetto non è accessibile." #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Gli oggetti non sono accessibili." #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "La cartella non è accessibile." ================================================ FILE: po/ja/swish.po ================================================ # # Translators: # Satoshi Kikuta , 2012 # Tadashi "ELF" Jokagi , 2012 # takaf , 2013 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: takaf \n" "Language-Team: Japanese (http://www.transifex.com/projects/p/swish/language/ja/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ja\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "サーバーにファイルを作成できませんでした。" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "'{1}'をコピー中" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "'{1}'へ" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "既に '{1}' という名前のファイルが存在します" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "上書きしてよろしいですか?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "ファイルの上書き確認" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "コピー中..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "転送に失敗しました" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "子のディレクトリーに書き込む権限がありません" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "新規接続(SFTP)" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "作成する" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "ラベル(&L):" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "例: \"自宅パソコン\"" #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "接続したいコンピューターとアカウントの詳細を指定してください。" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "ホスト名(&H):" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "ポート番号(&P):" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "ユーザー(&U):" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Swishで接続するサーバーのディレクトリーを指定してください" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "パス(&A):" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "例: /home/yourusername" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "キャンセル" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "ラベルは30文字以内で指定してください。" #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "ホスト名が正しくありません。" #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "ポート番号の指定に誤りがあります(0から65535)。" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "ユーザー名に誤りがあります。" #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "パスの指定に誤りがあります。" #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "その接続名は既に存在しています。別の名前で登録してください。" #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "全項目入力してください。" #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "パスワード" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "詳細表示(日本語ではないかもしれません)(&D)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "詳細を隠す(&D)" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "既に '%1%' という名前のファイルが存在します" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "すでに存在しているファイルを置き換えてよろしいですか?\n\n»%1%\n\n置き換えるファイル\n\n»%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "ファイルは既に存在しています" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "ホスト鍵が一致しません" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "警告:SSHホスト鍵が変更されました" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "'%1%' から送信されてきたサーバー側のSSHホスト鍵が一致しません。管理者が変更しただけの可能性もありますが、何者かが接続先に「なりすまし」ている可能性もあります" #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "この鍵の指紋(fingerprint)が正しい確認してください。" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "この鍵を信頼します: 鍵情報を更新して接続する(&U)\n鍵が変更されるまでこの鍵を確認する必要はありません" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "この鍵を信頼します: このまま接続する(&J)\n次回接続時もこのメッセージが表示されます" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "キャンセル(&C)\nこの鍵が正しいものであると確信できない場合はこちらを選んでください" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "不明なサーバー鍵です" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "サーバー '%1%' は自身の以下の指紋(fingerprint)のSSH鍵で特定されています。" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "あなたが接続しようとしているこの鍵が予期できない鍵である場合、何者かがなりすましている可能性があります。" #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "不明なSSH鍵を確認する" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "この鍵を信頼します: 鍵情報を保管して接続する(&S)\n鍵が変更されるまでこの鍵を確認する必要はありません" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "この鍵を信頼します: このまま接続する(&J)\n次回接続時もこのメッセージが表示されます" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "ファイル名/ラベル" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "ホスト名" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "ユーザー名" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "ポート番号" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "接続先のパス" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "タイプ" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "ネットワークドライブ" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "SFTP接続を追加する(&A)" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "新しくSwithのSFTP接続を作成します。" #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "SFTP接続を追加する(A&)..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "接続を追加する" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTPタスク" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "これらのタスクでSwishのSFTP接続を管理できます" #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "SSH key agentの起動" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Putty SSH key agentのページェントの起動" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "SSH agent programの起動" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "接続を削除する(&R)" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "作成したSwishのSFTP接続を削除します。" #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "SFTP接続を削除する(&R)..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "接続を削除する" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "サイズ" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "変更日付" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "最終アクセス日付" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "権限(パーミッション)" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "オーナー" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "グループ" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "オーナーID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "グループID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "リンクを開く(&L)" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "開く(&O)" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "リンクを開けませんでした" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "権限(パーミッション)がありません。" #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "ファイルを開けませんでした" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "ファイルとフォルダーのタスク" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "これらのタスクで接続先のファイルを管理できます。" #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "削除できません" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "新規フォルダー(&F)" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "現在開いているフォルダーに新規フォルダーを作成します。" #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "新規フォルダーを作成する" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "新規フォルダー" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "新規フォルダーを作成できません" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "対話式でキーボードから入力する" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "ディレクトリーにアクセスできません" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "パスが認識できません。" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "入力したパスが正しいか確認してください。" #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "名前の変更はできません" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "アクセスできません" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "アクセスできません" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "フォルダーにアクセスできません" ================================================ FILE: po/ko/swish.po ================================================ # # Translators: # swkim85 , 2012-2013 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: swkim85 \n" "Language-Team: Korean (http://www.transifex.com/projects/p/swish/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ko\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "서버에 파일을 만들 수 없습니다." #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "복사중 '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "'{1}' 으로" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "폴더에 파일 '{1}' 가 이미 존재합니다." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "덮어 쓸까요?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "파일 대체 확인" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "복사중..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "파일을 전송할 수 없습니다." #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "폴더에 쓰기 권한이 없습니다." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "새로운 SFTP 연결" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "만들기" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "라벨:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "예: \"내 컴퓨터\"" #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "연결하려는 컴퓨터의 계정과 상세정보를 입력하세요." #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "호스트:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "포트:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "사용자:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "접속을 시작할 서버상의 디렉터리 경로를 입력" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "경로" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "예: /home/username" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "취소" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "라벨명은 30글자까지 제한됩니다." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "호스트명이 유효하지 않습니다." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "포트번호가 유효하지 않습니다.(0 에서 65535 까지 숫자)" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "사용자명이 유효하지 않습니다." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "경로가 올바르지 않습니다." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "같은 라벨의 접속이 존재합니다. 다른 라벨을 입력하세요." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "모든 필드를 입력하세요." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "비밀번호" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "확인" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "파일이 이미 존재합니다." #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "일치하지 않는 호스트 키" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "경고: SSH 호스트키가 변경됨" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "" #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "올바른 key fingerprint 인지 확인하세요." #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "알려지지 않은 호스트 키" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "" #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "알려지지 않은 SSH 호스트 키를 확인" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "이름" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "호스트" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "사용자" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "포트" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "원격경로" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "종류" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "네트워크 드라이브" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "SFTP 연결 추가" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Swish로 새로운 SFTP 연결을 생성" #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "SFTP 연결 추가" #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "연결 추가" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP 작업들" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "키 에이전트 실행" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "키 에이전트 실행" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "SFTP 연결을 삭제" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "SFTP 접속 삭제" #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "연결을 삭제" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "크기" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "수정한 날짜" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "접근한 날짜" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "퍼미션" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "사용자" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "그룹" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "사용자ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "그룹ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "링크 열기" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "열기" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "링크를 열 수 없습니다." #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "퍼미션이 없습니다." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "파일을 열 수 없습니다." #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "파일과 폴더 작업" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "" #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "항목을 삭제할 수 없습니다." #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "새로운 폴더" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "새로운 폴더를 생성" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "새 폴더" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "새 폴더를 생성할 수 없습니다." #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Keyboard-interactive request" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "디렉터리에 접근할 수 없습니다." #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "경로가 올바르지 않습니다." #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "경로가 정확한지 확인하세요." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "이름을 변경할 수 없습니다" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "접근할 수 없습니다." #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "항목에 접근할 수 없습니다" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "폴더에 접근할 수 없습니다." ================================================ FILE: po/lv/swish.po ================================================ # # Translators: msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Artūrs Zalužinskis \n" "Language-Team: Latvian (http://www.transifex.com/projects/p/swish/language/lv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: lv\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Nav iespējams izveidot datni uz servera:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "'{1}' kopēšana" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "Uz '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Šī mape jau satur datni ar nosaukumu '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Vai vēlaties aizvietot to?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Apstiprināt datnes aizvietošanu" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopēšana..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Nav iespējams pārnest datnes" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Iespējams Jums nav rakstīšanas tiesību šajā mapē." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Jauns SFTP pieslēgums" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Izveidot" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Etiķete:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Piemēram: \"Mājas dators\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Norādiet papildus informāciju par datoru un kontu kuram vēlaties pieslēgties:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Saimniekdators" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Ports:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Lietotājs:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Norādiet mapi uz servera, kuru vēlaties norādīt kā Swish starta pieslēguma punktu:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "C&eļš:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Piemēram: /home/janis" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "At&celt" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Etiķetes garums nedrīkst pārsniegt 30 rakstzīmes." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Saimniekdatora vārds ir nepareizs." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Ports nav derīgs (no 0 līdz 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Lietotājvārds ir nepareizs." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Ceļš ir nepareizs." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Pieslēgums jau eksistē ar šādu etiķeti. Lūdzu izmantojiet citu." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Aizpildiet visus laukus." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Parole" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "Labi" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Rādīt &detaļas (kuras var nebūt jūsu valodā)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Slēpt &detaļas" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Mape jau satur failu ar nosaukumu '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Vai vēlaties aizvietot eksistējošo datni\n\n\t%1%\n\nar šo?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Datne jau eksistē" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Host-atslēga nesakrīt" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "UZMANīBU: SSH host-atslēga ir mainīta!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "SSH host-atslēga kas nosūtītā no '%1%' lai identificētu sevi neatbislt zināmai atslēgai kas paredzēta šim serverim. Tas varētu nozīmēt kā trešās personas uzdodas par datoru kuram mēģinat pieslēgties vai iespējams sistēmas administrators tikko nomainīja atslēgu." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Ir svarīgi pārbaudīt tā ir pareizā identificējošā zīmju virkne:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Es uzticos šai atslēgai: &atjaunināt un pieslēgt\nJums nav nepieciešams apstiprināt šīs atslēgas pareizību kamēr tā netiks nomainīta." #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Es uzticos šai atslēgai: &tikai pieslēgt\nJūs saņemsiet brīdinājumu par šo atslēgu nākamajā pieslēguma reizē" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "At&celt\nIzvēlaties šo opciju ja neesat pārliecināts ka atslēga ir pareiza" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Nezināmā host-atslēga" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Serveris '%1%' ir identificējis sevi ar SSH host-atslēgu kuras identificējošā zīmju virkne: ir:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Ja jūs neesat sagaidījis šo atslēgu, trešās puses var kļūt par datoru kuram jūs mēģinat pieslēgties." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Apstiprināt nezināmo SSH-host atslēgu" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Es uzticos šai atslēgai: &uzglabāt un pieslēgties\nJums nav jāapstirina šo atslēgu kamēr tā netiks nomainīta" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Es uzticos šai atslegai: &tikai pieslēgties\nJums tiks pieprasīts apstiprinājums par atslēgas pareizību nākamajā pieslēgšanas reizē" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Vārds" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Saimniekdators" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Lietotājvārds" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Ports" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Attālinātais ceļš" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Tips" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Tīkla diskdzinis" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "Pi&evienot SFTP pieslēgumu" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Izveidot jaunu SFTP pieslēgumu ar Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "P&ievienot SFTP pieslēgumu..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Pievienot pieslēgumu" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP uzdevumi" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Šie uzdevumi palīdz jums pārvaldīt Swish SFTP pieslēgumus." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Palaist atslēgu aģentu" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Palaist Putty SSH atslēgu aģentu, Pageant." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Palaist atslēgu aģentu" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Dzest SFTP pieslēgumu" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Dzēst SFTP pieslēgumu kas ir izveidots ar Swish" #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Dzēst SFTP pieslēgumu..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Dzēst pieslēgumu" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Izmērs" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Modificēšanas datums" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Piekļuves datums" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Atļaujas" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Īpašnieks" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grupa" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "Īpašnieka ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "Grupas ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Atvērt &saiti" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Atvērt" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Nav iespējams atvērt saiti" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Iespējams jums nav tiesīnu" #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Nav iespējams atvērt datni" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Datņu un mapju uzdevumi" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Šie uzdevumi palīdz jums pārvaldīt jūsu attalinātas datnes." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Nav iespējams dzēst vienību" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Jauna &Mape" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Izveidot jaunu, tukšu mapi atvērtajā mapē" #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Izveidot jaunu mapi" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Jauna mape" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Nav iespējams izveidot jaunu mapi" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Tastatūras interaktīvais pieprasījums" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Nav iespējams piekļūt mapei" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Ceļš nav atpazīts" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Pārbaudiet vai ceļš ir ievadīts korekti." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Nav iespējams pārdēvēt vienību" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Nav iespējams piekļūt vienībai" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Nav iespējams piekļūt vienībām" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Nav iespējams piekļūt mapei" ================================================ FILE: po/nl/swish.po ================================================ # # Translators: # Jos Lagerweij , 2012 # Stefan Suceveanu , 2013 # Thijs-jan Veldhuizen , 2012 # bericht , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Stefan Suceveanu \n" "Language-Team: Dutch (http://www.transifex.com/projects/p/swish/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Kon geen bestand aanmaken op de server:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "'{1}' wordt gekopieerd" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "Naar '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Deze map bevat al een bestand met de naam '{1}'" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Wil je het vervangen?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Bevestig vervanging" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopiëren..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Kan bestanden niet kopieren" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Mogelijk heeft u geen schrijfrechten voor deze map." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Nieuwe SFTP Verbinding" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Aanmaken" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Titel:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Bijvoorbeeld: \"Computer Thuis\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Geef de details van de computer en de account waarmee u verbinding wilt maken:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Host:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Poort:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Gebruiker:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Geef de map op de server waar Swish de verbinding in op moet starten:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "P&ad:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Voorbeeld: /home/uwgebruikersnaam" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Annuleren" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "De titel mag niet langer dan 30 lettertekens zijn." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "De hostnaam is ongeldig." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "De poort is niet geldig (tussen 0 en 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "De gebruikersnaam is ongeldig." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Het pad is ongeldig." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Een verbinding met deze titel bestaat al. Probeer een ander." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Vul alle velden in." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Wachtwoord" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "&Details weergeven (mogelijk niet in uw eigen taal)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "&Details verbergen" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Deze map bevat al een bestand met de naam '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Wilt u het bestaande bestand\n\n\t%1%\n\nvervangen door dit bestand?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Bestand bestaat al" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Host-sleutel komt niet overeen" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "WAARSCHUWING: de SSH host-sleutel is veranderd!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "De SSH host-sleutel verzonden door '%1%' om zich te identificeren komt niet overeen met de bekende sleutel voor deze server. Mogelijk luistert iemand uw verbinding af, of de systeembeheerder heeft zojuist de sleutel van de host gewijzigd." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Het is belangrijk om te controleren dat dit de juiste sleutel vingerafdruk is:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Ik vertrouw deze sleutel: &werk bij en verbind\nU hoeft deze sleutel niet opnieuw te verifiëren, tenzij deze verandert " #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Ik vertrouw deze sleutel: &verbind\nU wordt opnieuw gewaarschuwd voor deze sleutel, de volgende keer dat u verbindt" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Annuleer\nKies deze optie, tenzij u zeker bent dat de sleutel correct is" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Onbekende host-sleutel" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "De server %1% heeft zich geïdentificeerd met een SSH host-sleutel waarvan de vingerafdruk is:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Als u deze sleutel niet verwacht, kan een derde beweren de computer te zijn waarmee u een verbinding probeert te maken." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Verifieer onbekende SSH host-sleutel" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Ik vertrouw deze sleutel: &opslaan en verbinden\nU hoeft deze sleutel niet opnieuw te verifiëren, tenzij deze verandert " #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Ik vertrouw deze sleutel: &verbinden\nU wordt gevraagd om deze sleutel opnieuw te verifiëren, de volgende keer dat u verbindt" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Naam" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Host" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Gebruikersnaam" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Poort" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Extern pad" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Bestandsformaat" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Netwerkschijf" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "SFTP Verbinding &aanmaken" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Maak een niewe SFTP verbinding aan." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "SFTP Verbinding &aanmaken..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Verbinding &aanmaken" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP taken" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Deze taken helpen je bij het beheren van Swish SFTP-verbindingen" #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Start sleutelbeheer" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Start Putty SSH sleutelbeheer, Pageant" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Start sleutelbeheer" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "SFTP Verbinding &verwijderen" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Verwijder een SFTP verbinding gemaakt met Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "SFTP Verbinding &verwijderen..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Verbinding &verwijderen" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Grootte" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Gewijzigd op" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Geraadpleegd op" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Toegangsrechten" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Eigenaar" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Groep" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "Eigenaar ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "Groep ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Open link" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "Open" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Kon de link niet openen" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Mogelijk heeft u geen toegangsrechten." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Kon het bestand niet openen" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Taken voor bestanden en mappen" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Deze taken helpen je je bestanden op afstand te beheren." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Het is niet mogelijk om dit item te verwijderen" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Nieuwe &map" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Maak een nieuwe, lege, map in de map die je nu open hebt." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Maak een nieuwe map" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Nieuwe map" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Kon geen nieuwe map maken" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Keyboard-interactive aanvraag" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Het is niet mogelijk deze map te benaderen" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Pad niet herkend" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Controleer of het pad juist is ingevoerd." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Het is niet mogelijk dit item te hernoemen" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Het is niet mogelijk dit item te benaderen" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Het is niet mogelijk deze items te benaderen" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Het is niet mogelijk deze map te benaderen" ================================================ FILE: po/pl/swish.po ================================================ # # Translators: # Mr Pyo , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Mr Pyo \n" "Language-Team: Polish (http://www.transifex.com/projects/p/swish/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Nie można utworzyć pliku na serwerze" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Kopiowanie '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "Do '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Ten folder zawiera już plik o nazwie '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Czy chcesz zamienić?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Potwierdź zamianę pliku" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopiowanie..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Nie można przesłać plików" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Możliwe, że nie możesz zapisywać w tym katalogu." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Nowe połączenie SFTP" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Utwórz" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Etykieta:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Na przykład: \"Komputer domowy\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Podaj szczegóły komputera i konta z którym chcesz się połączyć:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Host:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Użytkownik:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Podaj folder na serwerze w którym Swish ma rozpocząć połączenie:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "Ścieżka:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Przykład: /home/twoja_nazwa_uzytkownika" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Anuluj" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Etykieta nie może być dłuższa niż 30 znaków." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Nazwa hosta jest nieprawidłowa." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Podany numer portu jest nieprawidłowy (prawidłowy zakres: 0-65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Nazwa użytkownika jest nieprawidłowa." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Ścieżka jest nieprawidłowa." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Połączenie z podaną etykietą już istnieje. Proszę użyć innej etykiety." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Wypełnij wszystkie pola." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Hasło" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Pokaż szczegóły" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Ukryj szczegóły" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Ten folder zawiera już plik o nazwie '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Czy chcesz zamienić istniejący plik\n\n\t%1%\n\nnastępującym?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Plik już istnieje" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Niepasujący klucz hosta" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "UWAGA: klucz SSH hosta uległ zmianie!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "Klucz SSH hosta wysłany przez '%1%' w celu identyfikacji nie pasuje do znanego klucza dla tego serwera. Może to oznaczać, że ktoś obcy stara się udać komputer z którym chcesz się połączyć lub administrator systemu zmienił klucz." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Ważnym jest sprawdzenie poprawności odcisku palca tego klucza:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Ufam temu kluczowi: &odśwież i połącz\nNie będziesz musiał weryfikować tego klucza ponownie pod warunkiem, że nie ulegnie on zmianie" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Ufam temu kluczowi: &po prostu połącz\nBędziesz ostrzeżony o tym kluczu przy następnym połączeniu" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Anuluj\nWybierz tę opcję jeżeli nie jesteś pewny poprawności klucza " #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Nieznany klucz hosta." #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Serwer '%1%' zidentyfikował się kluczem SSH hosta o odcisku palca:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Jeżeli nie spodziewasz się tego klucza, może to oznaczać, że ktoś obcy stara się udać komputer z którym chcesz się połączyć." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Zweryfikuj nieznany klucz SSH hosta" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Ufam temu kluczowi: &zachowaj i połącz\nNie będziesz musiał weryfikować tego klucza ponownie pod warunkiem, że nie ulegnie on zmianie" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Ufam temu kluczowi: &po prostu połącz\nBędziesz zapytany o ponowną weryfikację tego klucza przy następnym połączeniu" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Nazwa" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Host" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Użytkownik" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Zdalna ścieżka" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Typ" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Dysk sieciowy" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "Dodaj połączenie " #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Utwórz nowe połączenie SFTP przy użyciu Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "Dodaj połączenie SFTP..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Dodaj połączenie" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Zadania SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Te zadania pomagają zarządzać twoimi połączeniami SFTP." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "Uruchom agenta kluczy" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Uruchom Pageant, agenta kluczy Putty." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Uruchom agenta kluczy" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "Usuń połączenie SFTP" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Usuń połączenie utworzone przy użyciu Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "Usuń połączenie SFTP..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Usuń połączenie" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Rozmiar" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Data modyfikacji" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Data dostępu" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Uprawnienia" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Właściciel" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grupa" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID Właściciela" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID Grupy" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Otwórz łącze" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Otwórz" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Nie można otworzyć łącza" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Możliwy brak uprawnień." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Nie można otworzyć pliku" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Zadania pliku i folderu" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Te zadania pomagają zarządzać twoimi zdalnymi plikami." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Niepowodzenie podczas usuwania elementu." #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Nowy folder" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Utwórz nowy, pusty folder." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Utwórz nowy folder" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Nowy folder" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Nie można utworzyć nowego folderu" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Interaktywne żądanie klawiatury" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Brak dostępu do katalogu." #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Ścieżka nierozpoznana." #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Sprawdź czy podana ścieżka jest poprawna." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Niepowodzenie podczas zmiany nazwy elementu." #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Niepowodzenie podczas dostępu do elementu." #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Niepowodzenie podczas dostępu do elementów." #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Niepowodzenie podczas dostępu do folderu." ================================================ FILE: po/pt/swish.po ================================================ # # Translators: # Éliton Claus , 2012 # jmruas , 2013 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: jmruas \n" "Language-Team: Portuguese (http://www.transifex.com/projects/p/swish/language/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Não foi possível criar ficheiro no servidor:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "A copiar '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "Para '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Esta pasta já contém um ficheiro com o nome '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Deseja substituir?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Confirme substituição" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "A copiar..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Não foi possível transferir os ficheiros" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Pode não ter permissão para escrever nesta pasta." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Nova ligação SFTP" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Criar" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "Nome da Ligação:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "For example: \"Computador de casa\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Indique os detalhes do computador e conta a que se pretende ligar:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Máquina:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Porta:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Utilizador:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Indique qual a pasta no servidor onde o Swich deve iniciar a ligação:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "C&aminho:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Exemplo: /home/o_seu_utilizador" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Cancelar" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "O nome da ligação não poderá ter mais do que 30 caracteres." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "O nome da máquina é inválido." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "A porta não é válida (deverá estar no intervalo 0 a 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "O utilizador é inválido." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "O caminho é inválido." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Uma ligação com o mesmo nome já existe. Tente outro nome." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Complete todos os campos." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Palavra-chave" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Mostrar &detalhes (que poderão não estar na sua língua)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Esconder &detalhes" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "A pasta já contém um ficheiro com o nome '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Deseja substituir o ficheiro existente\n\n\t%1%\n\npor este?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "O ficheiro já existe" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Chave de máquina (host-key) não é idêntica" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "ATENÇÃO: a chave de máquina (host-key) SSH mudou!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "Host-key SSH enviada por '%1%' para se identificar não corresponde à chave conhecida para este servidor. Isto pode significar que terceiros poderão estar a fazer-se passar pelo computador a que se está a ligar, ou o administrador poderá ter alterado a chave." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "É importante verificar se esta chave de máquina está correta:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Eu confio nesta chave de máquina: &actualizar e ligar\nNão terá que verificar novamente esta chave a não ser que seja alterada" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Eu confio nesta chave de máquina: &ligar apenas\nSerá notificado sobre esta chave na próxima vez que se ligar" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Cancelar\nEscolha esta opção se não tiver a certeza que a chave de máquina está correta" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Chave de máquina (host-key) desconhecida" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "O servidor '%1%' idenficou-se com a seguinte chave de máquina (host-key) SSH:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Se não está à espera desta chave de máquina, isto pode significar que terceiros poderão estar a fazer-se passar pelo computador a que se está a ligar." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Verifique chave de máquina (host-key) SSH desconhecida" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Eu confio nesta chave de máquina: &armazenar e ligar\nNão terá que verificar novamente esta chave a não ser que seja alterada" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Eu confio nesta chave de máquina: &ligar apenas\nTerá que verificar novamente esta chave na próxima vez que se ligar" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Nome" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Máquina" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Utilizador" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Porta" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Caminho remoto" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Tipo" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Unidade de Rede" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Adicionar ligação SFTP" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Criar uma nova ligação SFTP com o Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Adicionar Ligação SFTP..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Adicionar Ligação" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Tarefas SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Estas tarefas facilitam a gestão das ligações SFTP do Swish." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "La&nçar agente-chave" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Lançar agente-chave SSH Putty, Pageant." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Lançar agente-chave" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Remover Ligação SFTP" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Remover uma ligação SFTP criada com o Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Remover Ligação SFTP..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Remover Ligação" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Tamanho" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Data de Modificação" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Data do Último Acesso" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Permissões" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Proprietário (owner)" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grupo" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID de proprietário (owner)" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID de grupo" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Abrir &ligação" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Abrir" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Não foi possível abrir a ligação" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Possivelmente não tem permissões." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Não foi possível abrir o ficheiro" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Tarefas de ficheiros e pastas" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Estas tarefas falicitam a gestão dos seus ficheiros remotos." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Não foi possível apagar o item" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Nova &pasta" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Criar uma pasta nova (vazia), na pasta que está aberta." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Criar uma nova pasta" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Nova pasta" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Não foi possível criar uma nova pasta" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Autenticação interativa com teclado (keyboard-interactive request)" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Não foi possível ter acesso à pasta" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Caminho não reconhecido" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Verifique se o caminho foi introduzido corretamente." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Não foi possível alterar o nome do item" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Não foi possível ter acesso ao item" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Não foi possível ter acesso aos itens" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Não foi possível ter acesso à pasta" ================================================ FILE: po/pt_BR/swish.po ================================================ # # Translators: # Éliton Claus , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Éliton Claus \n" "Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/swish/language/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt_BR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Não foi possível criar o arquivo no servidor:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Copiando '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "Para '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Esta pasta já contém um arquivo com o nome '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Deseja substituir?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Confirme a substituição" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Copiando..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Não foi possível transferir os arquivos" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Você pode não ter permissão de escrita neste diretório." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Nova conexão SFTP" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Criar" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "Nome da conexão:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Por exemplo: \"Computador de casa\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Indique os detalhes do computador e da conta que gostaria de se conectar:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Servidor:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Porta:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Usuário:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Indique a pasta inicial no servidor para a conexão:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "C&aminho:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Exemplo: /home/o_seu_utilizador" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Cancelar" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "O nome da conexão não pode ter mais do que 30 caracteres." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "O nome do servidor é inválido." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "A porta não é válida (deve estar entre 0 e 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Usuário inválido." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Caminho inválido." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Uma conexão com o mesmo nome já existe. Tente outro nome." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Complete todos os campos." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Senha" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Mostrar &detalhes (que poderão não estar na sua língua)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Esconder &detalhes" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "A pasta já contém um arquivo com o nome '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Deseja substituir o arquivo existente\n\n\t%1%\n\npor este?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "O arquivo já existe" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Host-key não é idêntica" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "ATENÇÃO: Host-key SSH mudou!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "A Host-key SSH enviada por '%1%' para se identificar não é a mesma que a chave conhecida por você. \nIsto pode representar alguém tentando se passar pelo servidor que você está se conectando ou o administrador do servidor pode ter alterado a Host-key SSH do servidor." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "É importante verificar se esta chave de máquina está correta:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Eu confio nesta chave de máquina: &atualizar e conectar\nNão terá que verificar novamente esta chave a não ser que seja alterada" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Eu confio nesta chave de máquina: &conectar apenas\nSerá notificado sobre esta chave na próxima vez que se conectar" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Cancelar\nEscolha esta opção se não tiver a certeza que a chave de máquina está correta" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Host-key desconhecida" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "O servidor '%1%' idenficou-se com a seguinte Host-key SSH:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Se não está esperando esta Host-key, isto pode significar que terceiros poderão estar se passando pelo servidor que está se conectando." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Verifique a Host-key SSH desconhecida" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Eu confio nesta chave de máquina: &armazenar e conectar\nNão terá que verificar novamente esta chave a não ser que seja alterada" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Eu confio nesta chave de máquina: &conectar apenas\nTerá que verificar novamente esta chave na próxima vez que se conectar" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Nome" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Servidor" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Usuário" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Porta" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Caminho remoto" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Tipo" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Unidade de Rede" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Adicionar conexão SFTP" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Criar uma nova conexão SFTP com o Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Adicionar conexão SFTP..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Adicionar conexão" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Tarefas SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Estas tarefas facilitam a gestão das conexões SFTP do Swish." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "E&xecutar o agente de chaves" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Executar o Pageant, agente de chaves SSH do Putty." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Executar o agente de chaves" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Remover conexão SFTP" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Remover uma conexão SFTP criada com o Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Remover conexão SFTP..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Remover conexão" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Tamanho" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Data de Modificação" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Data do Último Acesso" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Permissões" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Proprietário (owner)" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grupo" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID de proprietário (owner)" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID de grupo" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Abrir e criar &link" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Abrir" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Impossível de abrir o link" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Você possivelmente não tem permissões." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Impossível de abrir o arquivo" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Tarefas de arquivos e pastas" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Estas tarefas falicitam a gestão dos seus arquivos remotos." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Não foi possível apagar o item" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Nova &pasta" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Criar uma pasta nova (vazia), dentro da pasta que está aberta." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Criar uma nova pasta" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Nova pasta" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Não foi possível criar uma nova pasta" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Autenticação interativa com teclado (senha digitada)" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Não foi possível ter acesso à pasta" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Caminho não reconhecido" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Verifique se o caminho foi digitado corretamente." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Não foi possível alterar o nome do item" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Não foi possível ter acesso ao item" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Não foi possível ter acesso aos itens" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Não foi possível ter acesso à pasta" ================================================ FILE: po/ro/swish.po ================================================ # # Translators: # Stefan Suceveanu , 2013 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Stefan Suceveanu \n" "Language-Team: Romanian (http://www.transifex.com/projects/p/swish/language/ro/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ro\n" "Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Nu a fost posibilă creerea fișierului pe server:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Copiem '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "în '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Acest director deja conține un fișier cu numele '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Vrei să îl înlocuiești?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Confirmă înlocuire fișierului" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Copiere ..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Fișierul nu a putut fi transferat" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "E posibil ca să nu aveți drepturile necesare de a scrie în acest director." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Conexiune SFTP nouă" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Crează" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "Nume&le conexiunii:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "De exemplu: \"Calculatorul de acasă\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Specifică detaliile contului și a calculatorului la care vrei să te conectezi:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Server:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Utilizator:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Specifică directorul de pe server în care Swish se va poziționa la conectare:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "C&alea:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Exemplu: /home/numeletau" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Abandon" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Numele conexiunii nu poate fi mai mare de 30 de caractere." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Numele serverului este invalid." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Portul nu este corect (valori permise între 0 și 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Numele utilizatorului nu este corect." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Calea nu este corectă." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Mai există o conexiune cu același nume. Încercați un alt nume." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Completați toate câmpurile." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Parola" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Afișează &detaliile (pot fi în altă limbă)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Ascunde &detaliile" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Directorul conține deja un fișier cu numele '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Dorești înlocuire fișierului existent ⏎ ⏎ »%1%⏎ ⏎ cu acest fișier?⏎ ⏎ »%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Fișierul deja există" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Nepotrivire la cheia serverului" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "ATENȚIE: cheia SSH a serverului a fost schimbată!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "Cheia SSH trimisă de serverul '%1%' pentru identificare nu se potrivește cu cheia cunoscută a acestui server. Această poate însemna că un alt sistem poate pretinde ca este calculatorul la care vrei să te conectezi sau administratorul sistemului poate a schimbat cheia de curând." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Este important să verifici că aceasta este cheia amprentă corectă: " #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Am încredere în această cheie: act&ualizare și conectare⏎ Nu mai este necesară verificarea acestei chei atâta timp cât ea nu se va schimba" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Am încredere în această cheie: &numai conectare⏎ Vei fi atenționat din nou la următoarea conectare despre nepotrivirea acestei chei." #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Renunță⏎ Alege această opțiune numai dacă nu ești sigur că această cheie este corectă" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Cheia serverului este necunoscută" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Serverul '%1%' s-a identificat cu cheia SSH a serverului a cărei amprentă este:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Dacă nu recunoști această cheie e posibil ca un alt sistem să pretindă că este calculatorul la care încerci să te conectezi." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Verifică cheia SSH a serverului" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Am încredere în această cheie: &memoare și conectare⏎ Nu mai este necesară verificarea acestei chei atâta timp cât ea nu se va schimba" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Am încredere în această cheie: &numai conectare⏎ La următoarea conectare vei fi rugat din nou să verifici cheia." #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Nume" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Server" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Utilizator" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Calea de pe server" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Tip" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Disk de rețea" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Adaugă o conexiune SFTP" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Crează o nouă conexiune SFTP folosind Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Adaugă o conexiune SFTP ..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Adaugă o conexiune" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Gestiune SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Aici poți gestiona conexiunile SFTP Swish." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Lansare key-agent" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Lansare Pageant, agentul pentru cheile SHH Putty." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Lansare key-agent" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "Ște&rge conexiunea SFTP" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Șterge o conexiunea SFTP creată folosind Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "Ște&rge conexiunea SFTP ..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Șterge conexiunea" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Dimensiune" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Data modificării" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Data accesării" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Permisiuni" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Proprietar" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grup" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID Proprietar" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID Grup" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Deschide &link-ul" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Deschide" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Link-ul este imposibil de deschis" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "E posibil să nu ai drepturile necesare." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Fișierul este imposibil de deschis" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Gestiunea fișierelor și directoarelor" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Te ajută să gestionezi fișierele de pe server." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Obiectul nu poate fi șters" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "&Director nou" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Crează un director nou, gol, în directorul actual." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Crează un director nou" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Director nou" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Nu a fost posibilă crearea unui director nou" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Cerere de introducere manuală de la tastatură" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Nu pot accesa directorul" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Calea nu este recunoscută." #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Verifică dacă calea este introdusă corect." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Redenumirea obiectului nu este posibilă" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Obiectul nu poate fi accesat" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Obiectele nu pot fi accesate" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Directorul nu poate fi accesat" ================================================ FILE: po/ru/swish.po ================================================ # # Translators: # mPolr , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: mPolr \n" "Language-Team: Russian (http://www.transifex.com/projects/p/swish/language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Невозможно создать файл на сервере" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Копирование '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "В '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Каталог уже содержит файл с именем '{1}'" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Вы хотите заменить его?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Подтверждение замены файла" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Копирую..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Невозможно переместить файлы" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Возможно у вас нет прав для записи в эту папку." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Новое SFTP соединение" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Создать" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Имя:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Пример: \"Домашний\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Укажите параметры хоста и аккаунта, к которому Вы соединяетесь:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Хост:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Порт:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Логин:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Укажите каталог удаленного сервера, к которому соединиться:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "П&уть:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Пример: /home/вашлогин" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Отмена" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Имя не может быть длиннее 30 символов." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Имя хоста неверно." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Порт неверен (между 0 и 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Логин неверен." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Путь неверен." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Соединение с таким именем уже существует. Введите другое имя." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Заполните все поля." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Пароль" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "ОК" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Показать &детали (могут быть не на вашем языке)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Спрятать &детали" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Каталог уже содержит файл с именем '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Вы хотите заменить существующий файл\n\n\t%1%\n\nэтим?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Файл уже существует" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Не совпадает ключ хоста" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "ВНИМАНИЕ: ключ SSH сервера изменился!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "Ключ SSH хоста '%1%' не совпадает с известным ключем от этого сервера. Это может означать присутствие третьей стороны в канале передачи данных или смену ключа администратором системы." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Важно проверить отпечаток ключа:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Я доверяю этому ключу: &Обновить и соединить\nВам не надо будет проверять этот ключ, пока он не сменится" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Я доверяю этому ключу: &Просто соединить\nВ следующий раз вы будете снова предупреждены о ключе" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "О&тмена\nВыбирайте эту опцию, пока Вы не уверены в ключе сервера" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Неизвестный ключ хоста" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Сервер '%1%' идентифицировал себя с SSH хост-ключем с отпечатком:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Если вы ожидаете не этого ключа, третья сторона может присутствовать в канале передачи данных." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Проверьте неизвестный SSH хост-ключ" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Я доверяю этому ключу: &Сохранить и соединить\nВам не надо будет проверять этот ключ, пока он не сменится" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Я доверяю этому ключу: &Просто соединить\nВ следующий раз вы будете снова предупреждены о ключе" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Имя" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Хост" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Логин" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Порт" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Удаленный путь" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Тип" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Сетевой диск" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Добавить SFTP-соединение" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Создать новое SFTP соединение с помощью Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Добавить SFTP-соединение..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Добавить соединение" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "Задачи SFTP" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Эти задачи управляют соединениями Swish SFTP" #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "&Запустить агента ключей" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Запустить Putty SSH агента ключей, Pageant." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Запустить агента ключей" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Удалить SFTP-соединение" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Удалить SFTP-соединение, созданное Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Удалить SFTP-соединение..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Удалить соединение" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Размер" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Изменен" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Последний доступ" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Права" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Владелец" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Группа" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID владельца" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID группы" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Открыть &ссылку" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Открыть" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Невозможно открыть ссылку" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "У вас не хватает прав." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Невозможно открыть файл" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Задачи файлов и папок" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Эти задачи управляют вашими удаленными файлами." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Ошибка удаления объекта" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "&Новый каталог" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Создает новый чистый каталог в текущем каталоге." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Создание нового каталога" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Новый каталог" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Ошибка создания каталога" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Ввод с клавиатуры" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Ошибка доступа к каталогу" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Путь не опознан" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Проверьте, чтобы путь был введен правильно." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Невозможно переименовать объект" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Ошибка доступа к объекту" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Ошибка доступа к объектам" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Ошибка доступа к каталогу" ================================================ FILE: po/sk/swish.po ================================================ # # Translators: msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: alamaison \n" "Language-Team: Slovak (http://www.transifex.com/projects/p/swish/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sk\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Táto zložka už obsahuje súbor s názvom '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Chcete súbor prepísať?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Potvrdiť prepis súboru" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopírovanie..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Nové SFTP pripojenie" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Vytvoriť" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Visačka:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Napríklad: \"Domáci počítač\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Vložte údaje o počítač a konte, na ktoré sa chcete pripojiť:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Hostiteľ:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Používateľ:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Vyberte si zložku na serveri, do ktorej sa má Swish pripojiť pri štarte:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "C&esta:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Príklad: /home/yourusername" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Zrušiť" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Visačka nemôže byť dlhšia ako 30 písmen." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Meno hostiteľa nie je správne." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Port nie je správny (medzi 0 a 65535)" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Používateľské meno nie je správne." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Cesta nei je platná." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Pripojenie s rovnakou visačkou už existuje. Skúste inú." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Vyplňte všetky polia." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Heslo" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Zobraziť &podrobnosti (ktoré nemusia byť vo vašom jazyku)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Skryť &podrobnosti" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Táto zložka už obsahuje súbor s názvom '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Chcete prepísať existujúci súbor\n\n\t%1%\n\ntýmto?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Súbor už existuje" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Nezhodný hostiteľsý kľúč" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "POZOR: SSH hostiteľský kľúč bol zmenený!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "SSH hostiteľský kľúč poslaný '%1%' na vlastnú identifikáciu nesúhlasí so známym kľúčom pre tento server. To môže znamenať, že sa tretia strana tvári ako počítač, na ktorý sa snažíte pripojiť, alebo len administrátor tento kľúč zmenil." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Je dôležité skontrolovať, že toto je správny odtlačok kľúča:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Dôverujem tomuto kľúču: &obnoviť a pripojiť\nTento kľúč už nebudete musieť overovať, pokial sa nezmení" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Dôverujem tomuto kľúču: &iba pripojiť\nPri ďalšom pripojení budete znovu vyzvaní na overenie kľúča" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Zrušiť\nAk si nie ste istí, že je kľúč správny, vyberte si túto možnosť" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Neznámy hostiteľský kľúč" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Server '%1%' sa identifikoval SSH hostiteľským kľúčom, ktorého odtlačok je:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Ak ste tento kľúč neočakávali, je možné, že sa tretia strana tvári ako počítač, na ktorý sa snažíte pripojiť." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Overiť neznámy SSH hostiteľský kľúč" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Dôverujem tomuto kľúču: &uložiť a pripojiť\nTento kľúč už nebudete musieť overovať, pokial sa nezmení" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Dôverujem tomuto kľúču: &iba pripojiť\nPri ďalšom pripojení budete znovu vyzvaní na overenie kľúča" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Meno" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Hostiteľ" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Používateľské meno" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Vzdialená cesta" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Typ" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Sieťový disk" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "&Pridať SFTP pripojenie" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Pridať SFTP pripojenie cez Swish" #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "&Pridáva sa SFTP pripojenie..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Pridať pripojenie" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP úlohy" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Tieto úlohy vám pomôžu spravovať Swish SFTP pripojenia." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "&Odstrániť SFTP pripojenie..." #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Odstrániť SFTP pripojenie vytvorené cez Swish." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "&Odstraňovanie SFTP pripojenia..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Odstrániť pripojenie" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Veľkosť" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Dátum zmeny" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Dátum prístupu" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Oprávnenia" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Majiteľ" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Skupina" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "ID majiteľa" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "ID skupiny" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Nemusíte mať oprávnenie" #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Úlohy zložiek a súborov" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Tieto úlohy vám pomôžu spravovať vaše vzdialené súbory." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Položka sa nedá vymazať" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Nová &zložka" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Vytvorí novú, prázdnu zložku v zložke, ktorú máte otvorenú." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Vytvoriť novú zložku" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Nová zložka" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Nová zložka sa nedá vytvoriť" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Zložka nie je prístupná" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Cesta nebola nájdená" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Skontroľujte, či bola cesta zadaná správne." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Položka sa nedá premenovať" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "K položke sa nedá pristúpiť" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "K položkám sa nedá pristúpiť" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Zložka nie je prístupná" ================================================ FILE: po/sv/swish.po ================================================ # # Translators: # Sopor, 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: Sopor\n" "Language-Team: Swedish (http://www.transifex.com/projects/p/swish/language/sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sv\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "Kunde inte skapa någon fil på servern:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "Kopierar '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "Till '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Denna mappen innehåller redan en fil med namnet '{1}'." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Vill du ersätta den?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Bekräfta filersättning" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopierar..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "Kunde inte överföra filerna" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "Du verkar inte ha skrivrättigheter i denna mappen." #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Ny STFP-anslutning" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Skapa" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Etikett:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Till exampel: \"Hemdatorn\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Specificera detaljerna för datorn och kontot du vill ansluta till:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Värd:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Användare:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Ange mappen på servern du vill att Swish skall börja anslutningen i:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "S&ökväg:" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Exempel: /hemma/dittanvändarnamn" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "Avbryt" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Etiketten kan inte vara längre än 30 tecken" #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Värdnamnet är ogiltigt." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Porten är inte giltig (mellan 0 och 65535)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Användarnamnet är ogiltigt." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Sökvägen är ogiltig." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Det finns redan en anslutning med samma namn. Var god använd ett annat namn." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Komplettera alla fält" #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Lösenord" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "OK" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "Visa &detaljer (kan vara på ett annat språk)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "Dölj &detaljer" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Mappen innehåller redan en fil med namnet '%1%'" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Vill du ersätta den befintliga filen\n\n\t%1%\n\nwith this one?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Filen finns redan" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Ogiltig värdnyckel" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "VARNING: SSH-värdnyckel har ändrats!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "SSH-värdnyckel skickad med '%1%' för att identifiera sig stämmer inte överens med nyckel för den här servern. Detta kan innebära en tredje part låtsas vara den dator du försöker ansluta till eller så har systemadministratören nyligen ändrat nyckeln." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Det är viktigt att kontrollera om detta är rätt nyckel:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Jag litar på denna nyckeln: &uppdatera och anslut \nDu kommer inte att behöva verifiera denna nyckel igen om den inte ändras" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Jag litar på denna nyckeln: &bara anslut \nDu kommer att varnas för denna nyckel igen nästa gång du ansluter" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&Avbryt \nVälj detta alternativ om du är säker på att nyckeln är felaktig" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Okänd värdnyckel" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Servern '%1%' har identifierat sig med en SSH-värdnyckel, vars fingeravtryck är:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Om du inte väntar på denna nyckel kan en tredje part som låtsas vara den dator du försöker ansluta till." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Verifiera okända SSH-värdnyckel" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Jag litar på denna nyckel: &spara och anslut\nDu kommer inte att behöva verifiera denna nyckel igen om den inte ändras" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Jag litar på denna nyckeln: &bara anslut\nDu kommer inte att behöva verifiera denna nyckel igen om den inte ändras" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "Namn" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Värd" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Användarnamn" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Fjärrsökväg" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Typ" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Nätverksenhet" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "Lägg till SFTP-anslutning" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Skapa en ny SFTP-anslutning med Swish." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "Skapa en ny SFTP anslutning..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Skapa anslutning" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP-aktiviteter" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Dessa uppgifter hjälper dig att hantera Swish SFTP-anslutningar." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "Starta nyckelagenten" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "Starta Putty SSH-nyckelagent, Pageant." #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "Starta nyckelagenten" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "Ta bort SFTP-anslutning" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Ta bort en SFTP-anslutning skapad med Swish" #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "Ta bort en SFTP-anslutning" #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Ta bort anslutning" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Storlek" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Ändrad datum" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Använd datum" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "Behörigheter" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Ägare" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grupp" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "Ägar-ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "Grupp-ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "Öppna &länk" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "&Öppna" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "Kunde inte öppna länken" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "Du kanske inte har behörighet." #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "Kunde inte öppna filen" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "Fil- och mappaktiviteter" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "Dessa uppgifter hjälper dig att hantera dina fjärrfiler." #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "Det går inte att ta bort objektet" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "Ny %mapp" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "Skapa en ny, tom mapp i den mapp som du har öppen." #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "Skapa en ny mapp" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "Ny mapp" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "Kunde inte skapa en ny mapp" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Begäran om tangentbords-interativitet" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "Det går inte att komma åt katalogen" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "Sökvägen hittas inte" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "Kontrollera att sökvägen angetts korrekt." #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "Det går inte att byta namn på objektet" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "Det går inte att komma åt objektet" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "Det går inte att komma åt objekten" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "Det går inte att komma åt mappen" ================================================ FILE: po/template/swish.pot ================================================ # Alexander Lamaison , 2010. msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: \n" "Last-Translator: Alexander Lamaison \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-Basepath: .\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "Top line of a transfer progress window saying which file is being copied. {1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "Second line of a transfer progress window giving the destination directory. {1} is replaced with the directory path and must be included in your translation." msgid "To '{1}'" msgstr "" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "" #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "" #: ../../swish/forms/add_host.cpp:151 msgid "Specify the details of the computer and account you would like to connect to:" msgstr "" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "" #: ../../swish/forms/add_host.cpp:169 msgid "Specify the directory on the server that you would like Swish to start the connection in:" msgstr "" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "" #: ../../swish/forms/add_host.cpp:185 #: ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "" #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "" #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "" #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "" #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "" #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "" #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "The SSH host-key sent by '%1%' to identify itself doesn't match the known key for this server. This could mean a third-party is pretending to be the computer you're trying to connect to or the system administrator may have just changed the key." msgstr "" #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "The server '%1%' has identified itself with an SSH host-key whose fingerprint is:" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "If you are not expecting this key, a third-party may be pretending to be the computer you're trying to connect to." msgstr "" #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "" #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "" #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "" #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "" ================================================ FILE: po/tr/swish.po ================================================ # # Translators: msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: alamaison \n" "Language-Team: Turkish (http://www.transifex.com/projects/p/swish/language/tr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: tr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "Bu klasör zaten {1} isminde bir dosya içermekte." #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "Üstüne yazmak istiyor musunuz?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "Dosyanın üstüne yazılacak. Emin misiniz?" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "Kopyalanıyor..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "Yeni SFTP Bağlantısı" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "Oluştur" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "&Etiket:" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "Örneğin: \"Bilgisayarım\"." #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "Bağlanmak istediğiniz bilgisayar ve hesabın detaylarını belirtin:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "&Sunucu:" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "&Port:" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "&Kullanıcı:" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "Swish'in bağlantıyı içinde başlatmasını istediğiniz klasörü belirtin:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "Klasör &Yolu" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "Örneğin: /home/" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "İptal" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "Etiket 30 karakterden daha uzun olamaz." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "Sunucu ismi geçersiz." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "Port değeri geçerli bir değer değil (0 ile 65535 arasında seçilmeli)." #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "Kullanıcı ismi geçersiz." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "Klasör Yolu geçersiz." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "Aynı etiketli başka bir bağlantı zaten var. Lütfen başka bir isim verin." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "Tüm alanları tamamlayın." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "Şifre" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "Tamam" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "Bu klasör zaten '%1%' isminde bir dosya içermekte." #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "Var olan\n \n\t%1%'i\n\t%2%\n\nile değiştirmek istiyor musunuz?" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "Dosya zaten mevcut" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "Sunucu anahtarı eşleşmedi" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "UYARI: SSH Sunucu anahtarı değişti!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "%1% tarafından kendini tanıtmak için gönderilen SSH sunucu anahtarı bu sunucu için önceden bilinen anahtar ile uyuşmuyor. Bu, bağlanmaya çalıştığınız bilgisayarı taklit eden 3. partilerin araya girmiş olabileceği yada sistem yöneticisinin sunucu anahtarını değiştirmiş olabileceği anlamına gelmektedir." #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "Bu anahtar parmak izinin (fingerprint), doğru olduğunu kontrol ettiğinizden emin olun: " #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "Bu sunucu anahtarına güveniyorum: &güncelle ve bağlan\nBu sunucu anahtarını değişmediği sürece bir daha doğrulamak zorunda değilsiniz." #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "Bu sunucu anahtarına güveniyorum: &sadece bağlan\nBir dahaki bağlantınızda bu sunucu anahtarı ile ilgili olarak tekrar uyarı alacaksınız" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "&İptal\nSunucu anahtarının doğru olduğundan emin değilseniz bu seçeneği seçin." #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "Bilinmeyen Sunucu Anahtarı" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "Sunucu %1% kendisini şu parmak izine sahip SSH sunucu anahtarı ile tanıtıyor: " #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "Eğer bu anahtar beklenildiği şekilde değilse 3. partiler araya girmiş ve bağlanmaya çalıştığınız makineyi taklit ediyor olabilir." #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "Bilinmeyen SSH sunucu anahtarını doğrulayın" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "Bu sunucu anahtarına güveniyorum: &sakla ve bağlan\nBu sunucu anahtarını değişmediği sürece bir daha doğrulamak zorunda değilsiniz." #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "Bu sunucu anahtarına güveniyorum: &sadece bağlan\nBir dahaki bağlantınızda bu sunucu anahtarını doğrulamak üzere tekrar uyarı alacaksınız" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "İsim" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "Sunucu" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "Kullanıcı İsmi" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "Port" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "Uzak Klasör Yolu" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "Tip" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "Ağ Diski" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "Yeni &SFTP Bağlantısı Ekle" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "Swish kullanarak yeni bir SFTP bağlantısı oluştur." #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "Yeni &SFTP Bağlantısı Ekle..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "Bağlantı Ekle" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP Görevleri" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "Bu görevler, Swish SFTP bağlantılarını yönetmenizde size yardımcı olur." #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "SFTP Bağlantısını &Kaldır" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "Swish kullanılarak oluşturulan SFTP Bağlantısını Kaldır." #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "SFTP Bağlantısını &Kaldır..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "Bağlantıyı Kaldır" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "Boyut" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "Değiştirme Zamanı" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "Erişim Zamanı" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "İzinler" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "Dosya Sahibi" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "Grup" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "Sahip ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "Grup ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "" #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "" #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "Klavye-Etkileşimli İstek" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "" ================================================ FILE: po/zh_CN/swish.po ================================================ # # Translators: # Rob . , 2012 # vibbow , 2012 # ZH CEXO , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: vibbow \n" "Language-Team: Chinese (China) (http://www.transifex.com/projects/p/swish/language/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "无法在伺服器上建立文件" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "正在复制 '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "到 '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "这个文件夹里已经存在[{1}]。" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "是否要取代它?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "确认取代文件" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "正在复制..." #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "无法传输文件" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "你没有权限写入此文件夹。" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "新SFTP连接" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "创建" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "标识(&L):" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "例子:\"家中电脑\"。" #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "指定你将连接的电脑和帐号:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "主机(&H):" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "端口(&P):" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "用户(&U):" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "指定你开启Swish连接主机时的文件夹开始位置:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "路径(&a):" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "例子: /home/yourusername" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "取消" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "标签不可超过30个字." #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "主机名字无效." #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "输入端口无效(必须在0-65535之间)" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "用户名字无效." #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "路径无效." #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "同名连接已存在. 请另试用其他命名." #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "完成全部字域." #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "密码" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "确认" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "显示详细(也许不是以您的语言显示)(&d)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "隐藏详细(&d)" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "文件夹里已经存在[%1%]" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "是否让此已存在文件\n\n\t%1%\n\n被以下文件替代?\n\n\t%2%" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "文件已经存在" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "主机密钥不匹配" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "警告: SSH主机密匙已被更换!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "[%1%]传送过来的SSH主机密匙和这伺服器上已认证的密匙不匹配。这有可能是第三者在伪装着你想要连上的电脑,或者是系统管理员已经更改了密匙" #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "重要!检查是否密匙指纹正确:" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "我信任此密匙:&更新并连接\n如果没有任何改动前提下,你往后不再需要确认此密匙" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "我信任此密匙:&直接连接\n下次再次连接时你将会再被警告关于此密匙事宜" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "取消(&C)\n如果不能确认密匙是正确的话选择此项" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "未知主机密匙" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "伺服器 [%1%] 已认证SSH主机密匙的指纹为:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "如果这不是你所预计的密匙,也许是某人在伪装为你想连接的电脑。" #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "核对不明SSH密匙" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "我信任此密匙:储存并连接(&s)\n如果没有任何改动前提下,你往后不再需要确认此密匙" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "我信任此密匙:直接连接(&j)\n下次再次连接时你将会再要求对此密匙进行核对" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "名字" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "主机" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "用户名" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "端口" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "远程路径" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "种类" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "网络驱动器" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "添加 SFTP 连接(&A)" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "用 Swish 创建新的 SFTP 连接。" #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "添加 SFTP 连接(&A)..." #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "添加连接" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP 任务" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "此任务帮你管理 SFTP 连接。" #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "启动密钥代理程序(&L)" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "启动Putty密钥代理程序Pageant" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "启动密钥代理程序" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "删除 SFTP 连接(&R)" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "删除一个 Swish 创建的 SFTP 连接。" #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "删除 SFTP 连接(&R)..." #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "删除连接" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "大小" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "修改日期" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "上回访问日期" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "权限" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "拥有人" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "组" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "拥有人 ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "组 ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "打开連接(&l)" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "打开(&O)" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "无法打开连接" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "也许您没有权限。" #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "无法打开相关文件" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "文件和文件夹任务" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "此任务帮你管理你的远程文件。" #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "无法删除项目" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "新 &文件夹" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "在你已打开的文件夹内创建一个新的空文件夹。" #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "创建一个新文件夹" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "新文件夹" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "无法创建新文件夹" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "键盘互动请求" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "无法进入目录" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "无法识别路径" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "检查是否路径正确。" #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "不能重命名" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "无法进入此项目" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "无法进入此项目" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "无法进入文件夹" ================================================ FILE: po/zh_TW/swish.po ================================================ # # Translators: # Rob . , 2012 msgid "" msgstr "" "Project-Id-Version: Swish\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-30 14:18-0000\n" "PO-Revision-Date: 2013-11-20 11:36+0000\n" "Last-Translator: alamaison \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/swish/language/zh_TW/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_TW\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: translate:1,1t;translate:1c,2,2t;translate:1,2,3t\n" "X-Poedit-SearchPath-0: ../../swish\n" #: ../../swish/drop_target/CopyFileOperation.cpp:152 msgid "Unable to create file on the server:" msgstr "無法在伺服器上創建文檔:" #: ../../swish/drop_target/CopyFileOperation.cpp:249 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:60 msgctxt "" "Top line of a transfer progress window saying which file is being copied. " "{1} is replaced with the file path and must be included in your translation." msgid "Copying '{1}'" msgstr "正在複製 '{1}'" #: ../../swish/drop_target/CopyFileOperation.cpp:260 #: ../../swish/drop_target/CreateDirectoryOperation.cpp:71 msgctxt "" "Second line of a transfer progress window giving the destination directory. " "{1} is replaced with the directory path and must be included in your " "translation." msgid "To '{1}'" msgstr "到 '{1}'" #: ../../swish/drop_target/DropUI.cpp:316 msgid "This folder already contains a file named '{1}'." msgstr "已有稱為{1}的資料夾" #: ../../swish/drop_target/DropUI.cpp:319 msgid "Would you like to replace it?" msgstr "要覆寫這檔案嗎?" #: ../../swish/drop_target/DropUI.cpp:327 msgid "Confirm File Replace" msgstr "確認覆寫" #: ../../swish/drop_target/DropUI.cpp:366 msgctxt "Progress" msgid "Copying..." msgstr "正在複製…" #: ../../swish/drop_target/SnitchingDropTarget.cpp:125 msgid "Unable to transfer files" msgstr "無法傳輸文檔" #: ../../swish/drop_target/SnitchingDropTarget.cpp:127 msgid "You might not have permission to write to this directory." msgstr "您也許沒有權限寫入此目錄" #: ../../swish/forms/add_host.cpp:102 msgid "New SFTP Connection" msgstr "新SFTP連線" #: ../../swish/forms/add_host.cpp:122 msgid "Create" msgstr "建立" #: ../../swish/forms/add_host.cpp:143 msgctxt "New Host" msgid "&Label:" msgstr "連線名稱(&L):" #: ../../swish/forms/add_host.cpp:148 msgid "For example: \"Home Computer\"." msgstr "例如「家裡的電腦」" #: ../../swish/forms/add_host.cpp:151 msgid "" "Specify the details of the computer and account you would like to connect " "to:" msgstr "輸入要連線的電腦及登入資料:" #: ../../swish/forms/add_host.cpp:155 msgctxt "New Host" msgid "&Host:" msgstr "伺服器(&S):" #: ../../swish/forms/add_host.cpp:159 msgctxt "New Host" msgid "&Port:" msgstr "連接埠(&P):" #: ../../swish/forms/add_host.cpp:164 msgctxt "New Host" msgid "&User:" msgstr "使用者(&U):" #: ../../swish/forms/add_host.cpp:169 msgid "" "Specify the directory on the server that you would like Swish to start the " "connection in:" msgstr "輸入開啟Swish時要瀏灠在伺服器上的目錄:" #: ../../swish/forms/add_host.cpp:173 msgctxt "New Host" msgid "P&ath:" msgstr "路徑(&P);" #: ../../swish/forms/add_host.cpp:177 msgid "Example: /home/yourusername" msgstr "例如:/home/yourusername" #: ../../swish/forms/add_host.cpp:185 ../../swish/forms/password.cpp:68 msgid "Cancel" msgstr "取消" #: ../../swish/forms/add_host.cpp:331 msgid "The label cannot be longer than 30 characters." msgstr "連線名稱不可長過30個字元" #: ../../swish/forms/add_host.cpp:335 msgid "The host name is invalid." msgstr "伺服器地址不正確" #: ../../swish/forms/add_host.cpp:341 msgid "The port is not valid (between 0 and 65535)." msgstr "連接埠不正確 (要在0至65535之間)" #: ../../swish/forms/add_host.cpp:345 msgid "The username is invalid." msgstr "登入名稱不正確" #: ../../swish/forms/add_host.cpp:349 msgid "The path is invalid." msgstr "路徑不正確" #: ../../swish/forms/add_host.cpp:355 msgid "A connection with the same label already exists. Please try another." msgstr "已有同名稱的連線存在,請使用其他名稱" #: ../../swish/forms/add_host.cpp:361 msgid "Complete all fields." msgstr "輸入所有空格" #: ../../swish/forms/password.cpp:57 msgid "Password" msgstr "密碼" #: ../../swish/forms/password.cpp:64 msgid "OK" msgstr "確定" #: ../../swish/frontend/announce_error.cpp:136 msgid "Show &details (which may not be in your language)" msgstr "顯示詳細信息(可能不是以中文表示) (&d)" #: ../../swish/frontend/announce_error.cpp:137 msgid "Hide &details" msgstr "隠藏詳細信息 (&d)" #: ../../swish/frontend/UserInteraction.cpp:195 msgid "The folder already contains a file named '%1%'" msgstr "資料夾內已有名為「%1%」的檔案" #: ../../swish/frontend/UserInteraction.cpp:200 msgid "" "Would you like to replace the existing file\n" "\n" "\t%1%\n" "\n" "with this one?\n" "\n" "\t%2%" msgstr "是否要將現有的檔案\n\n\t%1%\n\n換成\n\n\t%2%\n\n這個檔案?" #: ../../swish/frontend/UserInteraction.cpp:204 msgid "File already exists" msgstr "已有這檔案" #: ../../swish/frontend/UserInteraction.cpp:242 msgid "Mismatched host-key" msgstr "伺服器簽署不一致" #: ../../swish/frontend/UserInteraction.cpp:243 msgid "WARNING: the SSH host-key has changed!" msgstr "警告:SSH伺服器簽署有變!" #: ../../swish/frontend/UserInteraction.cpp:249 msgid "" "The SSH host-key sent by '%1%' to identify itself doesn't match the known " "key for this server. This could mean a third-party is pretending to be the " "computer you're trying to connect to or the system administrator may have " "just changed the key." msgstr "「%1%」剛發出的SSH伺服器簽署跟之前的有異。這可能是有其他人偽冒你要連線到的電腦,又或是伺服器管理理剛剛改變了簽署" #: ../../swish/frontend/UserInteraction.cpp:256 msgid "It is important to check this is the right key fingerprint:" msgstr "必須核實簽署正確" #: ../../swish/frontend/UserInteraction.cpp:265 msgid "" "I trust this key: &update and connect\n" "You won't have to verify this key again unless it changes" msgstr "我相信這個簽署:更新記錄並連線(&u)\n除非簽署改變,不然不用再確認" #: ../../swish/frontend/UserInteraction.cpp:270 msgid "" "I trust this key: &just connect\n" "You will be warned about this key again next time you connect" msgstr "我相信這個簽署:只連線(&j)\n下次連線時將會再次警告" #: ../../swish/frontend/UserInteraction.cpp:276 #: ../../swish/frontend/UserInteraction.cpp:320 msgid "" "&Cancel\n" "Choose this option unless you are sure the key is correct" msgstr "取消(&C)\n除排確定簽署正確,不然選這項" #: ../../swish/frontend/UserInteraction.cpp:289 msgid "Unknown host-key" msgstr "沒有記錄的伺服器簽署" #: ../../swish/frontend/UserInteraction.cpp:295 msgid "" "The server '%1%' has identified itself with an SSH host-key whose " "fingerprint is:" msgstr "伺服器「%1%」用以下的SSH簽署去表示自己:" #: ../../swish/frontend/UserInteraction.cpp:299 msgid "" "If you are not expecting this key, a third-party may be pretending to be the" " computer you're trying to connect to." msgstr "如果這跟你預期的簽署有別,可能是有其他人偽冒你要連線到的電腦" #: ../../swish/frontend/UserInteraction.cpp:302 msgid "Verify unknown SSH host-key" msgstr "確認沒記錄的SSH伺服器簽署" #: ../../swish/frontend/UserInteraction.cpp:309 msgid "" "I trust this key: &store and connect\n" "You won't have to verify this key again unless it changes" msgstr "我相信這個簽署:記錄並連線(&u)\n除非簽署改變,不然不用再確認" #: ../../swish/frontend/UserInteraction.cpp:314 msgid "" "I trust this key: &just connect\n" "You will be asked to verify the key again next time you connect" msgstr "我相信這個簽署:只連線(&j)\n下次連線時將會再次警告" #: ../../swish/host_folder/columns.cpp:57 msgctxt "Property (filename/label)" msgid "Name" msgstr "名稱" #: ../../swish/host_folder/columns.cpp:60 msgctxt "Property" msgid "Host" msgstr "伺服器地址" #: ../../swish/host_folder/columns.cpp:63 msgctxt "Property" msgid "Username" msgstr "登入名稱" #: ../../swish/host_folder/columns.cpp:66 msgctxt "Property" msgid "Port" msgstr "連接埠" #: ../../swish/host_folder/columns.cpp:69 msgctxt "Property" msgid "Remote path" msgstr "伺服器路徑" #: ../../swish/host_folder/columns.cpp:72 msgctxt "Property" msgid "Type" msgstr "種類" #: ../../swish/host_folder/properties.cpp:79 msgctxt "FileType" msgid "Network Drive" msgstr "網路磁碟" #: ../../swish/host_folder/commands/Add.cpp:83 msgid "&Add SFTP Connection" msgstr "建立SFTP連線(&A)" #: ../../swish/host_folder/commands/Add.cpp:84 msgid "Create a new SFTP connection with Swish." msgstr "用Swish去建立新的SFTP連線" #: ../../swish/host_folder/commands/Add.cpp:85 msgid "&Add SFTP Connection..." msgstr "新增SFTP連線…(&A)" #: ../../swish/host_folder/commands/Add.cpp:86 msgid "Add Connection" msgstr "建立連線" #: ../../swish/host_folder/commands/commands.cpp:91 msgid "SFTP Tasks" msgstr "SFTP工作" #: ../../swish/host_folder/commands/commands.cpp:104 msgid "These tasks help you manage Swish SFTP connections." msgstr "這些工作可助你管理Swish的SFTP連線" #: ../../swish/host_folder/commands/LaunchAgent.cpp:98 #: ../../swish/host_folder/commands/LaunchAgent.cpp:103 msgctxt "Title of command used to launch the SSH agent program" msgid "&Launch key agent" msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:99 msgid "Launch Putty SSH key agent, Pageant." msgstr "" #: ../../swish/host_folder/commands/LaunchAgent.cpp:106 msgctxt "Title of command used to launch the SSH agent program" msgid "Launch key agent" msgstr "" #: ../../swish/host_folder/commands/Remove.cpp:83 msgid "&Remove SFTP Connection" msgstr "移除連線(&R)" #: ../../swish/host_folder/commands/Remove.cpp:84 msgid "Remove a SFTP connection created with Swish." msgstr "移除用Swish建立的連線" #: ../../swish/host_folder/commands/Remove.cpp:85 msgid "&Remove SFTP Connection..." msgstr "移除連線…(&R)" #: ../../swish/host_folder/commands/Remove.cpp:86 msgid "Remove Connection" msgstr "移除連線" #: ../../swish/remote_folder/columns.cpp:82 msgctxt "Property" msgid "Size" msgstr "大小" #: ../../swish/remote_folder/columns.cpp:89 msgctxt "Property" msgid "Date Modified" msgstr "更新時間" #: ../../swish/remote_folder/columns.cpp:93 msgctxt "Property" msgid "Date Accessed" msgstr "存取時間" #: ../../swish/remote_folder/columns.cpp:96 msgctxt "Property" msgid "Permissions" msgstr "使用權" #: ../../swish/remote_folder/columns.cpp:99 msgctxt "Property" msgid "Owner" msgstr "擁有者" #: ../../swish/remote_folder/columns.cpp:102 msgctxt "Property" msgid "Group" msgstr "群組" #: ../../swish/remote_folder/columns.cpp:105 msgctxt "Property" msgid "Owner ID" msgstr "擁有者ID" #: ../../swish/remote_folder/columns.cpp:108 msgctxt "Property" msgid "Group ID" msgstr "群組ID" #: ../../swish/remote_folder/context_menu_callback.cpp:157 msgid "Open &link" msgstr "打開連接 (&l)" #: ../../swish/remote_folder/context_menu_callback.cpp:179 msgid "&Open" msgstr "打開(&O)" #: ../../swish/remote_folder/context_menu_callback.cpp:273 msgid "Unable to open the link" msgstr "無法打開連接" #: ../../swish/remote_folder/context_menu_callback.cpp:274 #: ../../swish/remote_folder/context_menu_callback.cpp:379 #: ../../swish/remote_folder/commands/delete.cpp:232 #: ../../swish/remote_folder/commands/NewFolder.cpp:240 msgid "You might not have permission." msgstr "可能沒有所需權限" #: ../../swish/remote_folder/context_menu_callback.cpp:378 msgid "Unable to open the file" msgstr "無法打開文檔" #: ../../swish/remote_folder/commands/commands.cpp:93 msgid "File and Folder Tasks" msgstr "檔案及資料夾工作" #: ../../swish/remote_folder/commands/commands.cpp:105 msgid "These tasks help you manage your remote files." msgstr "這些工作可助你管理伺服器上的檔案" #: ../../swish/remote_folder/commands/delete.cpp:231 msgid "Unable to delete the item" msgstr "不能刪除" #: ../../swish/remote_folder/commands/NewFolder.cpp:168 msgid "New &folder" msgstr "開新目錄(&F)" #: ../../swish/remote_folder/commands/NewFolder.cpp:169 msgid "Create a new, empty folder in the folder you have open." msgstr "在你已開啟的資料夾開再新增一個全新的空資料夾" #: ../../swish/remote_folder/commands/NewFolder.cpp:171 msgid "Make a new folder" msgstr "開新資料夾" #: ../../swish/remote_folder/commands/NewFolder.cpp:211 msgctxt "Initial name" msgid "New folder" msgstr "新資料夾" #: ../../swish/remote_folder/commands/NewFolder.cpp:239 msgid "Could not create a new folder" msgstr "開新資料夾時失敗" #: ../../swish/shell_folder/KbdInteractiveDialog.cpp:67 msgid "Keyboard-interactive request" msgstr "要求以鍵盤輸入" #: ../../swish/shell_folder/RemoteFolder.cpp:182 msgid "Unable to access the directory" msgstr "不能讀取目錄" #: ../../swish/shell_folder/RemoteFolder.cpp:255 msgid "Path not recognised" msgstr "不能辨識連接埠號碼" #: ../../swish/shell_folder/RemoteFolder.cpp:256 msgid "Check that the path was entered correctly." msgstr "檢查路徑是否正確" #: ../../swish/shell_folder/RemoteFolder.cpp:377 msgid "Unable to rename the item" msgstr "不能更名" #: ../../swish/shell_folder/RemoteFolder.cpp:704 msgid "Unable to access the item" msgstr "不能讀取" #: ../../swish/shell_folder/RemoteFolder.cpp:705 msgid "Unable to access the items" msgstr "不能讀取" #: ../../swish/shell_folder/RemoteFolder.cpp:732 msgid "Unable to access the folder" msgstr "不能讀取資料夾" ================================================ FILE: setup_conf.xml.in ================================================ ================================================ FILE: ssh/.clang-format ================================================ --- AccessModifierOffset: '-4' AllowShortFunctionsOnASingleLine: Empty AlwaysBreakTemplateDeclarations: 'true' BreakBeforeBraces: Allman ColumnLimit: '80' ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' ForEachMacros: [ foreach, BOOST_FOREACH ] IndentWidth: '4' MacroBlockBegin: "^[A-Z_]+_BEGIN$" MacroBlockEnd: "^[A-Z_]+_END$" MaxEmptyLinesToKeep: '1' NamespaceIndentation: None PointerAlignment: Left SpaceAfterControlStatementKeyword: true SpaceBeforeAssignmentOperators: true SpacesInAngles: false SpacesInParentheses: false UseTab: Never ... ================================================ FILE: ssh/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES agent.hpp detail/agent_state.hpp detail/file_handle_state.hpp detail/libssh2/agent.hpp detail/libssh2/knownhost.hpp detail/libssh2/libssh2.hpp detail/libssh2/session.hpp detail/libssh2/sftp.hpp detail/libssh2/userauth.hpp detail/session_state.hpp detail/sftp_channel_state.hpp filesystem.hpp filesystem/path.hpp host_key.hpp knownhost.hpp session.hpp sftp_error.hpp ssh_error.hpp stream.hpp) add_custom_target(ssh-src SOURCES ${SOURCES}) add_library(ssh INTERFACE) target_include_directories(ssh INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) hunter_add_package(Libssh2) find_package(Libssh2 REQUIRED CONFIG) target_link_libraries(ssh INTERFACE Libssh2::libssh2 ${Boost_LIBRARIES}) ================================================ FILE: ssh/agent.hpp ================================================ /** @file Key-agent protocol. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_AGENT_HPP #define SSH_AGENT_HPP #include #include #include // ssh::detail::libssh2::agent #include #include #include #include #include // BOOST_THROW_EXCEPTION #include #include // LIBSSH2_AGENT, libssh2_agent_* namespace ssh { class identity { public: identity(boost::shared_ptr agent, libssh2_agent_publickey* identity) : m_agent(agent), m_identity(identity) { } void authenticate(const std::string& user_name) { detail::agent_state::scoped_lock lock = m_agent->aquire_lock(); detail::libssh2::agent::userauth(m_agent->agent_ptr(), m_agent->session_ptr(), user_name.c_str(), m_identity); } private: boost::shared_ptr m_agent; libssh2_agent_publickey* m_identity; }; namespace detail { template class identity_iterator_base : public boost::iterator_facade< identity_iterator_base, IdentityType, boost::forward_traversal_tag, // this tag allows value return IdentityType> { friend class boost::iterator_core_access; // Enables conversion constructor to work: template friend class identity_iterator_base; public: identity_iterator_base(boost::shared_ptr agent) : m_agent(agent), m_pos(NULL) { increment(); } /** * End iterator. */ identity_iterator_base() : m_pos(NULL) { } /** * Copy conversion constructor. * * Purpose: to allow mutable iterators to be converted to const iterators. */ template identity_iterator_base(const identity_iterator_base& other) : m_agent(other.m_agent), m_pos(other.m_pos) { } private: void increment() { if (!m_agent) BOOST_THROW_EXCEPTION(std::logic_error( "Can't increment past the end of a collection")); detail::agent_state::scoped_lock lock = m_agent->aquire_lock(); bool no_more_identities = detail::libssh2::agent::get_identity(m_agent->agent_ptr(), m_agent->session_ptr(), &m_pos, m_pos) == 1; if (no_more_identities) { // Use m_agent as the end marker as a NULL m_pos means start again m_agent.reset(); m_pos = NULL; // To keep equality with the end iterator happy } } bool equal(const identity_iterator_base& other) const { return m_agent == other.m_agent && m_pos == other.m_pos; } value_type dereference() const { if (!m_agent) BOOST_THROW_EXCEPTION( std::logic_error("Can't dereference the end of a collection")); return identity(m_agent, m_pos); } boost::shared_ptr m_agent; libssh2_agent_publickey* m_pos; }; } /** * A connection to an SSH key agent. * * When this object is created, all the identities currently stored in it are * copied out. If you need a fresh list, request a new agent instance. */ class agent_identities { public: typedef detail::identity_iterator_base iterator; typedef detail::identity_iterator_base const_iterator; explicit agent_identities(detail::session_state& session) : m_agent(boost::make_shared(boost::ref(session))) // http://stackoverflow.com/a/1374266/67013 { // We pull the identities out here (AND ONLY HERE) so that all copies // of the agent, iterators and identity objects refer to valid data. // If we called this when creating the iterator it would wipe out all // other iterators. detail::agent_state::scoped_lock lock = m_agent->aquire_lock(); ::ssh::detail::libssh2::agent::list_identities(m_agent->agent_ptr(), m_agent->session_ptr()); } iterator begin() const { return iterator(m_agent); } iterator end() const { return iterator(); } private: boost::shared_ptr m_agent; }; } // namespace ssh #endif ================================================ FILE: ssh/detail/agent_state.hpp ================================================ /** @file RAII lifetime management of libssh2 agent collections. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_DETAIL_AGENT_STATE_HPP #define SSH_DETAIL_AGENT_STATE_HPP #include #include #include #include // LIBSSH2_AGENT namespace ssh { namespace detail { inline LIBSSH2_AGENT* do_agent_init(session_state& session) { detail::session_state::scoped_lock lock = session.aquire_lock(); return detail::libssh2::agent::init(session.session_ptr()); } /** * RAII object managing agent state that must be maintained together. * * Manages the graceful startup/shutdown the agent collection and does so in * a thread-safe manner. */ class agent_state : private boost::noncopyable { // // Intentionally not movable to prevent the public classes that own // this object moving it when they are themselves moved. This object // is referenced by other classes that don't own it so the owning classes // need to leave it where it is when they move so as not to invalidate // the other references. Making this non-copyable, non-movable enforces // that. // public: typedef session_state::scoped_lock scoped_lock; /** * Creates agent collection that closes itself in a thread-safe manner * when it goes out of scope. */ agent_state(session_state& session) : m_session(session), m_agent(do_agent_init(session_ref())) { detail::session_state::scoped_lock lock = session_ref().aquire_lock(); try { detail::libssh2::agent::connect(m_agent, session_ref().session_ptr()); } catch (...) { // If connection fails, the destructor won't be called so we have // to free the agent here as well ::libssh2_agent_disconnect(m_agent); } } ~agent_state() throw() { session_state::scoped_lock lock = session_ref().aquire_lock(); ::libssh2_agent_disconnect(m_agent); ::libssh2_agent_free(m_agent); } scoped_lock aquire_lock() { return session_ref().aquire_lock(); } LIBSSH2_SESSION* session_ptr() { return session_ref().session_ptr(); } LIBSSH2_AGENT* agent_ptr() { return m_agent; } private: session_state& session_ref() { return m_session; } session_state& m_session; LIBSSH2_AGENT* m_agent; }; } } // namespace ssh::detail #endif ================================================ FILE: ssh/detail/file_handle_state.hpp ================================================ /** @file RAII lifetime management of libssh2 file handles. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_DETAIL_FILE_HANDLE_STATE_HPP #define SSH_DETAIL_FILE_HANDLE_STATE_HPP #include // open #include #include #include #include // LIBSSH2_SFTP_HANDLE namespace ssh { namespace detail { inline LIBSSH2_SFTP_HANDLE* do_open(sftp_channel_state& sftp, const char* filename, unsigned int filename_len, unsigned long flags, long mode, int open_type) { session_state::scoped_lock lock = sftp.aquire_lock(); return libssh2::sftp::open(sftp.session_ptr(), sftp.sftp_ptr(), filename, filename_len, flags, mode, open_type); } /** * RAII object managing SFTP file handle state that must be maintained together. * * Manages the graceful opening/closing of file handles. */ class file_handle_state : private boost::noncopyable { // // Intentionally not movable to prevent the public classes that own // this object moving it when they are themselves moved. This object // is referenced by other classes that don't own it so the owning classes // need to leave it where it is when they move so as not to invalidate // the other references. Making this non-copyable, non-movable enforces // that. // public: typedef sftp_channel_state::scoped_lock scoped_lock; /** * Creates a new file handle that closes itself in a thread-safe manner * when it goes out of scope. */ file_handle_state(sftp_channel_state& sftp, const char* filename, unsigned int filename_len, unsigned long flags, long mode, int open_type) : m_sftp(sftp), m_handle(do_open(sftp_ref(), filename, filename_len, flags, mode, open_type)) { } ~file_handle_state() throw() { sftp_channel_state::scoped_lock lock = sftp_ref().aquire_lock(); ::libssh2_sftp_close_handle(m_handle); } scoped_lock aquire_lock() { return sftp_ref().aquire_lock(); } LIBSSH2_SESSION* session_ptr() { return sftp_ref().session_ptr(); } LIBSSH2_SFTP* sftp_ptr() { return sftp_ref().sftp_ptr(); } LIBSSH2_SFTP_HANDLE* file_handle() { return m_handle; } private: sftp_channel_state& sftp_ref() { return m_sftp; } sftp_channel_state& m_sftp; LIBSSH2_SFTP_HANDLE* m_handle; }; } } // namespace ssh::detail #endif ================================================ FILE: ssh/detail/libssh2/agent.hpp ================================================ /** @file Exception wrapper round raw libssh2 agent functions. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_DETAIL_LIBSSH2_AGENT_HPP #define SSH_DETAIL_LIBSSH2_AGENT_HPP #include // last_error_code, SSH_DETAIL_THROW_API_ERROR_CODE #include // errinfo_api_function #include // BOOST_THROW_EXCEPTION #include // assert #include // LIBSSH2_SESSION, LIBSSH2_AGENT, libssh2_agent_* // See ssh/detail/libssh2/libssh2.hpp for rules governing functions in this // namespace namespace ssh { namespace detail { namespace libssh2 { namespace agent { /** * Error-fetching wrapper around libssh2_agent_init. */ inline LIBSSH2_AGENT* init(LIBSSH2_SESSION* session, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { LIBSSH2_AGENT* agent = ::libssh2_agent_init(session); if (!agent) { ec = ssh::detail::last_error_code(session, e_msg); } return agent; } /** * Exception wrapper around libssh2_agent_init. */ inline LIBSSH2_AGENT* init(LIBSSH2_SESSION* session) { boost::system::error_code ec; std::string message; LIBSSH2_AGENT* agent = init(session, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_agent_init"); } return agent; } /** * Error-fetching wrapper around libssh2_agent_connect. */ inline void connect(LIBSSH2_AGENT* agent, LIBSSH2_SESSION* session, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_agent_connect(agent); if (rc < 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_agent_connect. */ inline void connect(LIBSSH2_AGENT* agent, LIBSSH2_SESSION* session) { boost::system::error_code ec; std::string message; connect(agent, session, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_agent_connect"); } } /** * Error-fetching wrapper around libssh2_agent_get_identity. * * Returns 1 when reached end of keys. Return value has no meaning when * `ec == false` */ inline int get_identity( LIBSSH2_AGENT* agent, LIBSSH2_SESSION* session, libssh2_agent_publickey** out, libssh2_agent_publickey* previous, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_agent_get_identity(agent, out, previous); if (rc < 0) { ec = ssh::detail::last_error_code(session, e_msg); } return rc; } /** * Exception wrapper around libssh2_agent_get_identity. * * Returns 1 when reached end of keys. */ inline int get_identity(LIBSSH2_AGENT* agent, LIBSSH2_SESSION* session, libssh2_agent_publickey** out, libssh2_agent_publickey* previous) { boost::system::error_code ec; std::string message; int rc = get_identity(agent, session, out, previous, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_agent_get_identity"); } return rc; } /** * Error-fetching wrapper around libssh2_agent_list_identities. */ inline void list_identities( LIBSSH2_AGENT* agent, LIBSSH2_SESSION* session, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_agent_list_identities(agent); if (rc < 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_agent_list_identities. */ inline void list_identities(LIBSSH2_AGENT* agent, LIBSSH2_SESSION* session) { boost::system::error_code ec; std::string message; list_identities(agent, session, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_agent_list_identities"); } } /** * Error-fetching wrapper around libssh2_agent_userauth. */ inline void userauth(LIBSSH2_AGENT* agent, LIBSSH2_SESSION* session, const char* user_name, libssh2_agent_publickey* identity, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_agent_userauth(agent, user_name, identity); if (rc < 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_agent_userauth. */ inline void userauth(LIBSSH2_AGENT* agent, LIBSSH2_SESSION* session, const char* user_name, libssh2_agent_publickey* identity) { boost::system::error_code ec; std::string message; userauth(agent, session, user_name, identity, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_agent_userauth"); } } } } } } // namespace ssh::detail::libssh2::agent #endif ================================================ FILE: ssh/detail/libssh2/knownhost.hpp ================================================ /** @file Exception wrapper round raw libssh2 knownhost functions. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_DETAIL_LIBSSH2_KNOWNHOST_HPP #define SSH_DETAIL_LIBSSH2_KNOWNHOST_HPP #include // last_error_code, SSH_DETAIL_THROW_API_ERROR_CODE #include // errinfo_api_function #include // BOOST_THROW_EXCEPTION #include // LIBSSH2_SESSION, LIBSSH2_KNOWNHOSTS, libssh2_knownhost_* // See ssh/detail/libssh2/libssh2.hpp for rules governing functions in this // namespace namespace ssh { namespace detail { namespace libssh2 { namespace knownhost { /** * Error-fetching wrapper around libssh2_knownhost_init. */ inline LIBSSH2_KNOWNHOSTS* init(LIBSSH2_SESSION* session, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { LIBSSH2_KNOWNHOSTS* hosts = ::libssh2_knownhost_init(session); if (!hosts) { ec = ssh::detail::last_error_code(session, e_msg); } return hosts; } /** * Exception wrapper around libssh2_knownhost_init. */ inline LIBSSH2_KNOWNHOSTS* init(LIBSSH2_SESSION* session) { boost::system::error_code ec; std::string message; LIBSSH2_KNOWNHOSTS* hosts = init(session, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_knownhost_init"); } return hosts; } /** * Error-fetching wrapper around libssh2_knownhost_readline. */ inline void readline(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, const char* line, size_t line_length, int type, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_knownhost_readline(hosts, line, line_length, type); if (rc < 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_knownhost_readline. */ inline void readline(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, const char* line, size_t line_length, int type) { boost::system::error_code ec; std::string message; readline(session, hosts, line, line_length, type, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_knownhost_readline"); } } /** * Error-fetching wrapper around libssh2_knownhost_writeline. */ inline void writeline(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, libssh2_knownhost* host, char* buffer, size_t buffer_length, size_t* written_length_out, int type, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_knownhost_writeline(hosts, host, buffer, buffer_length, written_length_out, type); if (rc < 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_knownhost_writeline. */ inline void writeline(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, libssh2_knownhost* host, char* buffer, size_t buffer_length, size_t* written_length_out, int type) { boost::system::error_code ec; std::string message; writeline(session, hosts, host, buffer, buffer_length, written_length_out, type, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_knownhost_writeline"); } } /** * Error-fetching wrapper around libssh2_knownhost_get. * * @returns 1 if finished. The return code has no meaning if `ec == false`. */ inline int get(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, libssh2_knownhost** store, libssh2_knownhost* current_position, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_knownhost_get(hosts, store, current_position); if (rc < 0) { ec = ssh::detail::last_error_code(session, e_msg); } return rc; } /** * Exception wrapper around libssh2_knownhost_get. * * @returns 1 if finished. */ inline int get(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, libssh2_knownhost** store, libssh2_knownhost* current_position) { boost::system::error_code ec; std::string message; int rc = get(session, hosts, store, current_position, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_knownhost_get"); } return rc; } /** * Error-fetching wrapper around libssh2_knownhost_add. */ inline void add(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, const char* host, const char* salt, const char* key, size_t key_length, int typemask, libssh2_knownhost** store, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_knownhost_add(hosts, host, salt, key, key_length, typemask, store); if (rc < 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_knownhost_add. */ inline void add(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, const char* host, const char* salt, const char* key, size_t key_length, int typemask, libssh2_knownhost** store) { boost::system::error_code ec; std::string message; add(session, hosts, host, salt, key, key_length, typemask, store, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_knownhost_add"); } } /** * Error-fetching wrapper around libssh2_knownhost_del. */ inline void del(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, libssh2_knownhost* entry, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_knownhost_del(hosts, entry); if (rc < 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_knownhost_del. */ inline void del(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, libssh2_knownhost* entry) { boost::system::error_code ec; std::string message; del(session, hosts, entry, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_knownhost_del"); } } /** * Error-fetching wrapper around libssh2_knownhost_check. */ inline int check(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, const char* host, const char* key, size_t key_length, int typemask, libssh2_knownhost** knownhost, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_knownhost_check(hosts, host, key, key_length, typemask, knownhost); switch (rc) { case LIBSSH2_KNOWNHOST_CHECK_MATCH: case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND: // Positive values, NOT ERROR CODES, won't appear in last_error return rc; case LIBSSH2_KNOWNHOST_CHECK_FAILURE: // This value, also positive, also not an error code, means there // was an error so check last_error to find the error code default: ec = ssh::detail::last_error_code(session, e_msg); return LIBSSH2_KNOWNHOST_CHECK_FAILURE; } } /** * Exception wrapper around libssh2_knownhost_check. */ inline int check(LIBSSH2_SESSION* session, LIBSSH2_KNOWNHOSTS* hosts, const char* host, const char* key, size_t key_length, int typemask, libssh2_knownhost** knownhost) { boost::system::error_code ec; std::string message; int rc = check(session, hosts, host, key, key_length, typemask, knownhost, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_knownhost_check"); } return rc; } } } } } // namespace ssh::detail::libssh2::knownhost #endif ================================================ FILE: ssh/detail/libssh2/libssh2.hpp ================================================ /** @file ssh::detail::libssh2 namespace documentation. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_DETAIL_LIBSSH2_LIBSSH2_HPP #define SSH_DETAIL_LIBSSH2_LIBSSH2_HPP namespace ssh { namespace detail { /** * Exception-throwing wrappers around libssh2 functions. * * This wrapper functions in this namespace adhere to the following * restrictions: * * - The behaviour is identical to that of the wrapped function except * that the range of possible return values (via return or * out-parameter) may be reduced by substituting them for * exceptions. Additionally, an error code and message out-parameter * may be set to have a value. * * - The signature, including the return type, exactly matches the * signature of the wrapped function, with three exceptions: * * + it may include additional parameters (such as a session pointer) * that are not in the original signature in order to fetch or return * error details. * + if the range of return values is reduced (see above) such that * the remaining values simply indicate success, the return type * may be changed to `void`. * * - As a consequence of the previous restrictions, any resources that * need freeing when returned by the wrapped function, also need * freeing after calling the wrapped version. * * - No references to the arguments are stored once the wrapper * terminates, whether that termination is by return or by * exception. In particular, the exceptions thrown contain * no shared data. * * - It is permitted to call these functions from within code that is * non-recursively locked on the given session. Therefore no * coordination of concurrent threads of execution is performed by * the wrappers and only one thread may call these wrapper functions * (or an libssh2 function) with the same session at any time. * * Any function not able to adhere to these restrictions is not * eligible for inclusion in this namespace. * * Rationale * --------- * * The main reason for keeping these wrappers here is to make sure any * locking we introduce in the future for thread-safety spans both the * function call and the code to retrieve any error. This is necessary * as otherwise the exception thrown may be from an error caused by * another thread's call to a function with the same session (only the * details of one error are stored per session). * * This namespace defines a boundary beyond which all functions behave * in the way defined here. This makes it easier to keep track of * session lifetimes as well as where to (and not to) lock the * session. */ namespace libssh2 { } } } // namespace ssh::detail::libssh2 #endif ================================================ FILE: ssh/detail/libssh2/session.hpp ================================================ /** @file Exception wrapper round raw libssh2 session functions. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_DETAIL_LIBSSH2_SESSION_HPP #define SSH_DETAIL_LIBSSH2_SESSION_HPP #include // last_error_code, SSH_DETAIL_THROW_API_ERROR_CODE #include // BOOST_THROW_EXCEPTION #include // bad_alloc #include // LIBSSH2_SESSION, libssh2_session_* // See ssh/detail/libssh2/libssh2.hpp for rules governing functions in this // namespace namespace ssh { namespace detail { namespace libssh2 { namespace session { /** * Thin exception wrapper around libssh2_session_init. */ inline LIBSSH2_SESSION* init() { LIBSSH2_SESSION* session = ::libssh2_session_init_ex(NULL, NULL, NULL, NULL); if (!session) BOOST_THROW_EXCEPTION( std::bad_alloc("Failed to allocate new ssh session")); return session; } /** * Error-fetching wrapper around libssh2_session_startup. */ inline void startup(LIBSSH2_SESSION* session, int socket, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_session_startup(session, socket); if (rc != 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_session_startup. */ inline void startup(LIBSSH2_SESSION* session, int socket) { boost::system::error_code ec; std::string message; startup(session, socket, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_session_startup"); } } /** * Error-fetching wrapper around libssh2_session_disconnect. */ inline void disconnect( LIBSSH2_SESSION* session, const char* description, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_session_disconnect(session, description); if (rc != 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_session_disconnect. */ inline void disconnect(LIBSSH2_SESSION* session, const char* description) { boost::system::error_code ec; std::string message; disconnect(session, description, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_session_disconnect"); } } } } } } // namespace ssh::detail::libssh2::session #endif ================================================ FILE: ssh/detail/libssh2/sftp.hpp ================================================ /** @file Error-reporting wrapper round raw libssh2 SFTP functions. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_DETAIL_LIBSSH2_SFTP_HPP #define SSH_DETAIL_LIBSSH2_SFTP_HPP #include // last_sftp_error_code #include // last_error_code #include #include #include // BOOST_THROW_EXCEPTION #include // assert #include #include // LIBSSH2_SESSION, LIBSSH2_SFTP #include // libssh2_sftp_* // See ssh/detail/libssh2/libssh2.hpp for rules governing functions in this // namespace namespace ssh { namespace detail { namespace libssh2 { namespace sftp { /** * Error-fetching wrapper around libssh2_sftp_init. */ inline LIBSSH2_SFTP* init(LIBSSH2_SESSION* session, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { LIBSSH2_SFTP* sftp = ::libssh2_sftp_init(session); if (!sftp) { ec = ssh::detail::last_error_code(session, e_msg); } return sftp; } /** * Exception wrapper around libssh2_sftp_init. */ inline LIBSSH2_SFTP* init(LIBSSH2_SESSION* session) { boost::system::error_code ec; std::string message; LIBSSH2_SFTP* sftp = init(session, ec, message); if (ec) SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_sftp_init"); return sftp; } /** * Error-fetching wrapper around libssh2_sftp_open_ex. */ inline LIBSSH2_SFTP_HANDLE* open(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* filename, unsigned int filename_len, unsigned long flags, long mode, int open_type, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { LIBSSH2_SFTP_HANDLE* handle = ::libssh2_sftp_open_ex( sftp, filename, filename_len, flags, mode, open_type); if (!handle) { ec = ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } return handle; } /** * Exception wrapper around libssh2_sftp_open_ex. */ inline LIBSSH2_SFTP_HANDLE* open(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* filename, unsigned int filename_len, unsigned long flags, long mode, int open_type) { boost::system::error_code ec; std::string message; LIBSSH2_SFTP_HANDLE* handle = open(session, sftp, filename, filename_len, flags, mode, open_type, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE_WITH_PATH( ec, message, "libssh2_sftp_open_ex", filename, filename_len); } return handle; } /** * Error-fetching wrapper around libssh2_sftp_symlink_ex. * * The return value has no meaning if `resolve_action` is * `LIBSSH2_SFTP_SYMLINK` or if `ec == true`. If `resolve_action` is * `LIBSSH2_SFTP_READLINK` and `LIBSSH2_SFTP_REALPATH` the return code, * if successful, is the number of bytes written to the buffer. */ inline int symlink_ex( LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len, char* target, unsigned int target_len, int resolve_action, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { // Slightly odd treatment of the return value because the success value // is 0 for `LIBSSH2_SFTP_SYMLINK` but >= 0 for `LIBSSH2_SFTP_READLINK` or // `LIBSSH2_SFTP_REALPATH`. int rc = ::libssh2_sftp_symlink_ex(sftp, path, path_len, target, target_len, resolve_action); switch (resolve_action) { case LIBSSH2_SFTP_READLINK: case LIBSSH2_SFTP_REALPATH: if (rc < 0) { ec = ::ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } break; case LIBSSH2_SFTP_SYMLINK: default: if (rc != 0) { assert(rc < 0); ec = ::ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } break; } return rc; } /** * Exception wrapper around libssh2_sftp_symlink_ex. * * The return value has no meaning if `resolve_action` is * `LIBSSH2_SFTP_SYMLINK`. If `resolve_action` is`LIBSSH2_SFTP_READLINK` and * `LIBSSH2_SFTP_REALPATH` the return code is the number of bytes written to * the buffer. */ inline int symlink_ex(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len, char* target, unsigned int target_len, int resolve_action) { boost::system::error_code ec; std::string message; int rc = symlink_ex(session, sftp, path, path_len, target, target_len, resolve_action, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE_WITH_PATH( ec, message, "libssh2_sftp_symlink_ex", path, path_len); } return rc; } /** * Error-fetching wrapper around libssh2_sftp_symlink. */ inline void symlink(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len, const char* target, unsigned int target_len, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { // Uses the raw libssh2_sftp_symlink_ex function so we aren't forced // to use strlen. symlink_ex(session, sftp, path, path_len, const_cast(target), target_len, LIBSSH2_SFTP_SYMLINK, ec, e_msg); } /** * Exception wrapper around libssh2_sftp_symlink. */ inline void symlink(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len, const char* target, unsigned int target_len) { // Uses the raw libssh2_sftp_symlink_ex function so we aren't forced // to use strlen. symlink_ex(session, sftp, path, path_len, const_cast(target), target_len, LIBSSH2_SFTP_SYMLINK); } /** * Error-fetching wrapper around libssh2_sftp_stat_ex. */ inline void stat(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len, int stat_type, LIBSSH2_SFTP_ATTRIBUTES* attributes, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_sftp_stat_ex(sftp, path, path_len, stat_type, attributes); if (rc < 0) { ec = ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } } /** * Exception wrapper around libssh2_sftp_stat_ex. */ inline void stat(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len, int stat_type, LIBSSH2_SFTP_ATTRIBUTES* attributes) { boost::system::error_code ec; std::string message; stat(session, sftp, path, path_len, stat_type, attributes, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE_WITH_PATH( ec, message, "libssh2_sftp_stat_ex", path, path_len); } } /** * Error-fetching wrapper around libssh2_sftp_fstat_ex. */ inline void fstat(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, LIBSSH2_SFTP_HANDLE* handle, LIBSSH2_SFTP_ATTRIBUTES* attributes, int fstat_type, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_sftp_fstat_ex(handle, attributes, fstat_type); if (rc != 0) { ec = ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } } /** * Exception wrapper around libssh2_sftp_fstat_ex. */ inline void fstat(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, LIBSSH2_SFTP_HANDLE* handle, LIBSSH2_SFTP_ATTRIBUTES* attributes, int fstat_type) { boost::system::error_code ec; std::string message; fstat(session, sftp, handle, attributes, fstat_type, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_sftp_fstat_ex"); } } /** * Error-fetching wrapper around libssh2_sftp_unlink_ex. */ inline void unlink_ex(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_sftp_unlink_ex(sftp, path, path_len); if (rc < 0) { ec = ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } } /** * Exception wrapper around libssh2_sftp_unlink_ex. */ inline void unlink_ex(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len) { boost::system::error_code ec; std::string message; unlink_ex(session, sftp, path, path_len, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE_WITH_PATH( ec, message, "libssh2_sftp_unlink_ex", path, path_len); } } /** * Error-fetching wrapper around libssh2_sftp_mkdir_ex. */ inline void mkdir_ex(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len, long mode, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_sftp_mkdir_ex(sftp, path, path_len, mode); if (rc < 0) { ec = ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } } /** * Exception wrapper around libssh2_sftp_mkdir_ex. */ inline void mkdir_ex(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len, long mode) { boost::system::error_code ec; std::string message; mkdir_ex(session, sftp, path, path_len, mode, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE_WITH_PATH( ec, message, "libssh2_sftp_mkdir_ex", path, path_len); } } /** * Error-fetching wrapper around libssh2_sftp_rmdir_ex. */ inline void rmdir_ex(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_sftp_rmdir_ex(sftp, path, path_len); if (rc < 0) { ec = ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } } /** * Exception wrapper around libssh2_sftp_rmdir_ex. */ inline void rmdir_ex(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* path, unsigned int path_len) { boost::system::error_code ec; std::string message; rmdir_ex(session, sftp, path, path_len, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE_WITH_PATH( ec, message, "libssh2_sftp_rmdir_ex", path, path_len); } } /** * Error-fetching wrapper around libssh2_sftp_rename_ex */ inline void rename(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* source, unsigned int source_len, const char* destination, unsigned int destination_len, long flags, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_sftp_rename_ex(sftp, source, source_len, destination, destination_len, flags); if (rc) { ec = ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } } /** * Exception wrapper around libssh2_sftp_rename_ex */ inline void rename(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, const char* source, unsigned int source_len, const char* destination, unsigned int destination_len, long flags) { boost::system::error_code ec; std::string message; rename(session, sftp, source, source_len, destination, destination_len, flags, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE_WITH_PATH( ec, message, "libssh2_sftp_rename_ex", source, source_len); } } /** * Error-fetching wrapper around libssh2_sftp_read. */ inline ssize_t read(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, LIBSSH2_SFTP_HANDLE* file_handle, char* buffer, size_t buffer_len, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { ssize_t count = ::libssh2_sftp_read(file_handle, buffer, buffer_len); if (count < 0) { ec = ::ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } return count; } /** * Exception wrapper around libssh2_sftp_read. */ inline ssize_t read(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, LIBSSH2_SFTP_HANDLE* file_handle, char* buffer, size_t buffer_len) { boost::system::error_code ec; std::string message; ssize_t count = read(session, sftp, file_handle, buffer, buffer_len, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_sftp_read"); } return count; } /** * Error-fetching wrapper around libssh2_sftp_write. */ inline ssize_t write(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, LIBSSH2_SFTP_HANDLE* file_handle, const char* data, size_t data_len, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { ssize_t count = ::libssh2_sftp_write(file_handle, data, data_len); if (count < 0) { ec = ::ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } return count; } /** * Exception wrapper around libssh2_sftp_write. */ inline ssize_t write(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, LIBSSH2_SFTP_HANDLE* file_handle, const char* data, size_t data_len) { boost::system::error_code ec; std::string message; ssize_t count = write(session, sftp, file_handle, data, data_len, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_sftp_write"); } return count; } /** * Error-fetching wrapper around libssh2_sftp_readdir_ex. */ inline int readdir_ex( LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, LIBSSH2_SFTP_HANDLE* handle, char* buffer, size_t buffer_len, char* longentry, size_t longentry_len, LIBSSH2_SFTP_ATTRIBUTES* attrs, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_sftp_readdir_ex(handle, buffer, buffer_len, longentry, longentry_len, attrs); if (rc < 0) { ec = ::ssh::filesystem::detail::last_sftp_error_code(session, sftp, e_msg); } return rc; } /** * Exception wrapper around libssh2_sftp_readdir_ex. */ inline int readdir_ex(LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, LIBSSH2_SFTP_HANDLE* handle, char* buffer, size_t buffer_len, char* longentry, size_t longentry_len, LIBSSH2_SFTP_ATTRIBUTES* attrs) { boost::system::error_code ec; std::string message; int rc = readdir_ex(session, sftp, handle, buffer, buffer_len, longentry, longentry_len, attrs, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_sftp_readdir_ex"); } return rc; } } } } } // namespace ssh::detail::libssh2::filesystem #endif ================================================ FILE: ssh/detail/libssh2/userauth.hpp ================================================ /** @file Exception wrapper round raw libssh2 userauth functions. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_DETAIL_LIBSSH2_USERAUTH_HPP #define SSH_DETAIL_LIBSSH2_USERAUTH_HPP #include // last_error_code, SSH_DETAIL_THROW_API_ERROR_CODE #include // errinfo_api_function #include #include #include // BOOST_THROW_EXCEPTION #include // LIBSSH2_SESSION, libssh2_userauth_* // See ssh/detail/libssh2/libssh2.hpp for rules governing functions in this // namespace namespace ssh { namespace detail { namespace libssh2 { namespace userauth { /** * Error-fetching wrapper around libssh2_userauth_list. * * May return NULL if authentication succeeded with 'none' method. In this * case 'ec == false'. */ inline const char* list(LIBSSH2_SESSION* session, const char* username, unsigned int username_len, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { const char* method_list = ::libssh2_userauth_list(session, username, username_len); if (!method_list) { ec = ssh::detail::last_error_code(session, e_msg); } return method_list; } /** * Exception wrapper around libssh2_userauth_list. * * Returns NULL if authentication succeeded with 'none' method. */ inline const char* list(LIBSSH2_SESSION* session, const char* username, unsigned int username_len) { boost::system::error_code ec; std::string message; const char* method_list = list(session, username, username_len, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_userauth_list"); } return method_list; } /** * Error-fetching wrapper around libssh2_userauth_password_ex. */ inline void password(LIBSSH2_SESSION* session, const char* username, size_t username_len, const char* password, size_t password_len, LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb)), boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_userauth_password_ex(session, username, username_len, password, password_len, passwd_change_cb); if (rc != 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_userauth_password_ex. */ inline void password(LIBSSH2_SESSION* session, const char* username, size_t username_len, const char* password_string, size_t password_len, LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb))) { boost::system::error_code ec; std::string message; password(session, username, username_len, password_string, password_len, passwd_change_cb, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, "libssh2_userauth_password_ex"); } } /** * Error-fetching wrapper around libssh2_userauth_keyboard_interactive_ex. */ inline void keyboard_interactive_ex( LIBSSH2_SESSION* session, const char* username, unsigned int username_len, LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*response_callback)), boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = ::libssh2_userauth_keyboard_interactive_ex( session, username, username_len, response_callback); if (rc != 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_userauth_keyboard_interactive_ex. */ inline void keyboard_interactive_ex( LIBSSH2_SESSION* session, const char* username, unsigned int username_len, LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*response_callback))) { boost::system::error_code ec; std::string message; keyboard_interactive_ex(session, username, username_len, response_callback, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE( ec, message, "libssh2_userauth_keyboard_interactive_ex"); } } /** * Error-fetching wrapper around libssh2_userauth_publickey_fromfile_ex. */ inline void public_key_from_file( LIBSSH2_SESSION* session, const char* username, size_t username_len, const char* public_key_path, const char* private_key_path, const char* passphrase, boost::system::error_code& ec, boost::optional e_msg = boost::optional()) { int rc = libssh2_userauth_publickey_fromfile_ex( session, username, username_len, public_key_path, private_key_path, passphrase); if (rc != 0) { ec = ssh::detail::last_error_code(session, e_msg); } } /** * Exception wrapper around libssh2_userauth_publickey_fromfile_ex. */ inline void public_key_from_file(LIBSSH2_SESSION* session, const char* username, size_t username_len, const char* public_key_path, const char* private_key_path, const char* passphrase) { boost::system::error_code ec; std::string message; public_key_from_file(session, username, username_len, public_key_path, private_key_path, passphrase, ec, message); if (ec) { SSH_DETAIL_THROW_API_ERROR_CODE( ec, message, "libssh2_userauth_publickey_fromfile_ex"); } } } } } } // namespace ssh::detail::libssh2::userauth #endif ================================================ FILE: ssh/detail/session_state.hpp ================================================ /** @file RAII lifetime management of libssh2 sessions. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_DETAIL_SESSION_STATE_HPP #define SSH_DETAIL_SESSION_STATE_HPP #include // init #include #include #include #include #include #include // BOOST_THROW_EXCEPTION #include #include // LIBSSH2_SESSION namespace ssh { namespace detail { /** * RAII object managing session state that must be maintained together. * * Manages the graceful shutdown/destruction of the session. * * Unlike a lot of simple allocate-deallocate RAII, this class has to manage * an, optional, post-allocation 'starup' stage and ensure that, if started, * it is shutdown before de-allocation. This means that we have to be * careful of the lifetime of the unstarted session in the code below. * The session may fail to start but must still be freed. */ class session_state : private boost::noncopyable { // // Intentionally not movable to prevent the public classes that own // this object moving it when they are themselves moved. This object // is referenced by other classes that don't own it so the owning classes // need to leave it where it is when they move so as not to invalidate // the other references. Making this non-copyable, non-movable enforces // that. // public: typedef boost::mutex::scoped_lock scoped_lock; /** * Creates a session that is not (and never will be) connected to a host. */ session_state() : m_session(::ssh::detail::libssh2::session::init()) { } /** * Creates a session connected to a host over the given socket. */ session_state(int socket, const std::string& disconnection_message) : m_session(libssh2::session::init()) { // Session is 'alive' from this point onwards. All paths must // eventually free it. boost::system::error_code ec; std::string error_message; libssh2::session::startup(m_session, socket, ec, error_message); if (ec) { // Must free session here as destructor won't be called ::libssh2_session_free(m_session); BOOST_THROW_EXCEPTION( boost::system::system_error(ec, error_message)); } else { // Setting the disconnection message signals to the destructor // that disconnection is necessary m_disconnection_message = disconnection_message; } } ~session_state() throw() { // Ignoring any errors because there's nothing we can do about them if (m_disconnection_message) { boost::system::error_code ec; libssh2::session::disconnect(m_session, m_disconnection_message->c_str(), ec); } ::libssh2_session_free(m_session); } scoped_lock aquire_lock() { return scoped_lock(m_mutex); } LIBSSH2_SESSION* session_ptr() { return m_session; } private: mutable boost::mutex m_mutex; ///< Coordinates multiple-threads using of non-thread-safe LIBSSH2_SESSION. LIBSSH2_SESSION* m_session; // Overloading this to hold both the message and flag whether disconnection // is necessary. boost::optional m_disconnection_message; }; } } // namespace ssh::detail #endif ================================================ FILE: ssh/detail/sftp_channel_state.hpp ================================================ /** @file RAII lifetime management of libssh2 SFTP channels. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_DETAIL_SFTP_CHANNEL_STATE_HPP #define SSH_DETAIL_SFTP_CHANNEL_STATE_HPP #include // init #include #include #include // LIBSSH2_SFTP namespace ssh { namespace detail { inline LIBSSH2_SFTP* do_sftp_init(session_state& session) { session_state::scoped_lock lock = session.aquire_lock(); return libssh2::sftp::init(session.session_ptr()); } /** * RAII object managing SFTP channel state that must be maintained together. * * Manages the graceful startup/shutdown the SFTP channel and does so in * a thread-safe manner. */ class sftp_channel_state : private boost::noncopyable { // // Intentionally not movable to prevent the public classes that own // this object moving it when they are themselves moved. This object // is referenced by other classes that don't own it so the owning classes // need to leave it where it is when they move so as not to invalidate // the other references. Making this non-copyable, non-movable enforces // that. // public: typedef session_state::scoped_lock scoped_lock; /** * Creates SFTP channel that closes itself in a thread-safe manner * when it goes out of scope. */ sftp_channel_state(session_state& session) : m_session(session), m_sftp(do_sftp_init(session_ref())) { } ~sftp_channel_state() throw() { session_state::scoped_lock lock = session_ref().aquire_lock(); ::libssh2_sftp_shutdown(m_sftp); } scoped_lock aquire_lock() { return session_ref().aquire_lock(); } LIBSSH2_SESSION* session_ptr() { return session_ref().session_ptr(); } LIBSSH2_SFTP* sftp_ptr() { return m_sftp; } private: session_state& session_ref() { return m_session; } session_state& m_session; LIBSSH2_SFTP* m_sftp; }; } } // namespace ssh::detail #endif ================================================ FILE: ssh/filesystem/path.hpp ================================================ // Copyright 2015, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SSH_FILESYSTEM_PATH_HPP #define SSH_FILESYSTEM_PATH_HPP #include #include #include // to_utf #include #include // get_system_locale #include #include #include #include #include // logic_error #include #include // swap namespace ssh { namespace filesystem { namespace detail { /** * String tokeniser that separates on /, unless it is leading or trailing. * * The filesystem concept treats leading and trailing directory separators (/) * specially. A leading separator is the root dirctory and is kept as a token. * A trailing separator is a directory path indicator and causes a dot token (.) * to be emitted. */ // Because the iterators produce paths, std::lexicograpical_compare will not // terminate. Therefore we have our own that works on the string form of the // path in each segment template inline int lexical_compare(Iterator lhs, const Iterator& lhs_end, Iterator rhs, const Iterator& rhs_end) { while (lhs != lhs_end && rhs != rhs_end) { int comparison = lhs->native().compare(rhs->native()); if (comparison == 0) { ++lhs; ++rhs; continue; } else { return comparison; } } if (lhs != lhs_end) { return 1; } else if (rhs != rhs_end) { return -1; } else { return 0; } } template inline typename StringType::size_type find_next_slash(const StringType& string, typename StringType::size_type starting_position) { return string.find('/', starting_position); } template inline typename StringType::size_type find_previous_slash(const StringType& string, typename StringType::size_type starting_position) { return string.rfind('/', starting_position); } template inline typename StringType::size_type find_next_non_slash(const StringType& string, typename StringType::size_type starting_position) { return string.find_first_not_of('/', starting_position); } template inline typename StringType::size_type find_previous_non_slash(const StringType& string, typename StringType::size_type starting_position) { // NOTE: We are implementing what should be rfind_last_not_of. This // must already exist somewhere, no? // UNOBVIOUS: Because the loop control variable is unsigned and we are // looping downwards, the condition has to check for a number larger than // the range, not smaller for (typename StringType::size_type i = starting_position; i < starting_position + 1; --i) { if (string[i] != '/') { return i; } } return StringType::npos; } inline std::locale utf8_locale() { return boost::locale::generator().generate( boost::locale::util::get_system_locale(true)); } inline std::locale system_locale() { return boost::locale::generator().generate( boost::locale::util::get_system_locale(false)); } inline std::string from_source(const std::string& source) { return source; } inline std::string from_source(const std::wstring& source) { return boost::locale::conv::utf_to_utf(source, boost::locale::conv::stop); } template inline std::string from_source(const InputIterator& begin, const InputIterator& end) { typedef std::basic_string< typename std::iterator_traits::value_type> string_type; string_type source_string(begin, end); return from_source(source_string); } } class path : boost::totally_ordered { public: typedef std::string string_type; typedef string_type::value_type value_type; class iterator; typedef iterator const_iterator; path() { } template path(const Source& source) : m_path(detail::from_source(source)) { } template path(const InputIterator& begin, const InputIterator& end) : m_path(detail::from_source(begin, end)) { } bool is_relative() const { return !is_absolute(); } bool is_absolute() const { return !empty() && m_path[0] == '/'; } bool empty() const { return m_path.empty(); } bool has_parent_path() const { return !parent_path().empty(); } path parent_path() const; bool has_relative_path() const { return !relative_path().empty(); } path relative_path() const; bool has_filename() const { return !filename().empty(); } path filename() const; string_type native() const { return m_path; } operator string_type() const { return native(); } template std::basic_string string() const; template <> std::string string() const { return native(); } template <> std::wstring string() const { return boost::locale::conv::utf_to_utf(m_path); } std::string u8string() const { return native(); } std::string string() const { // Native string is UTF-8 but this method returns string in // platform-native encoding return from_utf(detail::system_locale()); } std::wstring wstring() const { return string(); } int compare(const path& rhs) const; iterator begin() const; iterator end() const; path& operator/=(const path& rhs) { if (!empty()) { string_type lhs_string = boost::algorithm::trim_right_copy_if( m_path, boost::algorithm::is_any_of("/")); string_type rhs_string = boost::algorithm::trim_left_copy_if( rhs.m_path, boost::algorithm::is_any_of("/")); m_path = lhs_string + '/' + rhs_string; } else { m_path = rhs.m_path; } return *this; } template path& operator/=(const Source& rhs) { return (*this) /= path(rhs); } private: template static path path_from_range(const InputIterator& begin, const InputIterator& end) { path p; for (InputIterator position = begin; position != end; ++position) { p /= *position; } return p; } std::string from_utf(const std::locale& locale) const { return boost::locale::conv::from_utf(m_path, locale, boost::locale::conv::stop); } // IMPORTANT: The encoding of this path is UTF8, which is not necessarily // the default encoding for strings of string_type string_type m_path; }; class path::iterator : public boost::iterator_facade { private: friend class path; explicit iterator(const path* source_path, string_type::size_type position) : m_source(source_path), m_current_positions(std::make_pair( string_type::size_type(position), find_element_last_position(m_source->m_path, position))), m_current_segment(path( element_from_positions(m_source->m_path, m_current_positions))) { } // NOT the end iterator - behaviour undefined iterator() { } friend class boost::iterator_core_access; bool equal(const iterator& other) const { return m_source == other.m_source && m_current_positions == other.m_current_positions; } const path& dereference() const { if (m_current_positions.first == m_source->m_path.size()) { BOOST_THROW_EXCEPTION( std::logic_error("dereferencing past the end of the path")); } else { return m_current_segment; } } void increment() { m_current_positions = next_element_positions(m_source->m_path, m_current_positions); if (m_current_positions.first != m_source->m_path.size()) { m_current_segment = path( element_from_positions(m_source->m_path, m_current_positions)); } } void decrement() { m_current_positions = previous_element_positions(m_source->m_path, m_current_positions); m_current_segment = path(element_from_positions(m_source->m_path, m_current_positions)); } static std::pair next_element_positions(const string_type& source, std::pair current_positions) { string_type::size_type new_first = find_next_element_first_position(source, current_positions.first); string_type::size_type new_last = find_element_last_position(source, new_first); return std::make_pair(new_first, new_last); } static std::pair previous_element_positions( const string_type& source, std::pair current_positions) { string_type::size_type new_first = find_previous_element_first_position( source, current_positions.first); string_type::size_type new_last = find_element_last_position(source, new_first); return std::make_pair(new_first, new_last); } static string_type::size_type find_next_element_first_position(const string_type& source, string_type::size_type current_position) { if (current_position == source.size()) { BOOST_THROW_EXCEPTION(std::logic_error("already at end of path")); } else if (current_position == source.size() - 1) { return source.size(); } else if (source[current_position] == '/') { if (current_position == 0) { string_type::size_type next_non_slash_position = detail::find_next_non_slash(source, current_position); if (next_non_slash_position == string_type::npos) { // Path only contains slashes so we have consumed // everything. // Move off the end to indicate this return source.size(); } else { // next segment starts after slash(es) return next_non_slash_position; } } else { assert(!"position is at slash that isn't leading or trailing"); BOOST_THROW_EXCEPTION(std::logic_error("unreachable")); } } else { string_type::size_type next_slash_position = detail::find_next_slash(source, current_position); if (next_slash_position == string_type::npos) { // Path ends - no new position return source.size(); } else { string_type::size_type next_non_slash_position = detail::find_next_non_slash(source, next_slash_position); if (next_non_slash_position == string_type::npos) { // Path ends with trailing slash(es) - slash is new position return next_slash_position; } else { // Normal case - slash found - next segment starts after // slash return next_non_slash_position; } } } } static string_type::size_type find_previous_element_first_position( const string_type& source, string_type::size_type current_position) { if (current_position == 0) { BOOST_THROW_EXCEPTION(std::logic_error("already at start of path")); } else if (current_position == source.size()) { // Because iterators are half-open, we may be at a position past the // last character so we have to treat this as a special case string_type::size_type previous_slash_position = detail::find_previous_slash(source, current_position - 1); if (previous_slash_position == string_type::npos) { // we ran off the beginning of the path so we must be off the // end of a single segment relative path return 0; } else { if (previous_slash_position == source.size() - 1) { return previous_slash_position; } else { return previous_slash_position + 1; } } } else if (source[current_position] == '/') { if (current_position == source.size() - 1) { string_type::size_type previous_slash_position = detail::find_previous_slash(source, current_position - 1); if (previous_slash_position == string_type::npos) { // we ran off the beginning of the path so we must be at the // slash following the first segment of a relative path return 0; } else { return previous_slash_position + 1; } } else { assert(!"position is at slash that isn't leading or trailing"); BOOST_THROW_EXCEPTION(std::logic_error("unreachable")); } } else { assert(source[current_position - 1] == '/'); string_type::size_type previous_non_slash_position = detail::find_previous_non_slash(source, current_position - 1); if (previous_non_slash_position == string_type::npos) { // We're at the first segment of an absolute path. // The leading slash is the next position return 0; } else { string_type::size_type previous_slash_position = detail::find_previous_slash(source, previous_non_slash_position); if (previous_slash_position == string_type::npos) { // we ran off the beginning of the path so we must be at the // start of the second segment of a relative path return 0; } else { return previous_slash_position + 1; } } } } static string_type::size_type find_element_last_position(const string_type& source, string_type::size_type first_position) { if (first_position == source.size()) { return source.size(); } else if (source[first_position] == '/') { if (first_position == source.size() - 1) { return first_position; } else if (first_position == 0) { return first_position; } else { assert(!"position is at slash that isn't leading or trailing"); BOOST_THROW_EXCEPTION(std::logic_error("unreachable")); } } else { string_type::size_type next_slash_position = source.find_first_of('/', first_position); if (next_slash_position == string_type::npos) { // Path ends - segment ends at last char in string return source.size() - 1; } else { // Normal case - slash found - next segment ends just before it return next_slash_position - 1; } } } static string_type element_from_positions( const string_type& source, std::pair positions) { if (positions.first == source.size()) { return ""; } else if (source[positions.first] == '/') { // leading or trailing / if (positions.first == 0) { return "/"; } else if (positions.first == source.size() - 1) { return "."; } else { assert(!"position is at slash that isn't leading or trailing"); BOOST_THROW_EXCEPTION(std::logic_error("unreachable")); } } else { return string_type(source.begin() + positions.first, source.begin() + positions.second + 1); } } const path* m_source; std::pair m_current_positions; path m_current_segment; }; inline path path::parent_path() const { if (empty()) { return path(); } else { return path_from_range(begin(), --end()); } } inline path path::relative_path() const { if (is_relative()) { return *this; } else { return path_from_range(++begin(), end()); } } inline path path::filename() const { if (empty()) { return path(); } else { return path_from_range(--end(), end()); } } inline int path::compare(const path& rhs) const { return detail::lexical_compare(begin(), end(), rhs.begin(), rhs.end()); } inline path::iterator path::begin() const { return iterator(this, 0); } inline path::iterator path::end() const { return iterator(this, m_path.size()); } template inline std::basic_ostream& operator<<(std::basic_ostream& stream, const path& p) { // TODO: quote string so it can roundtrip stream << p.string(); return stream; } inline bool operator==(const path& lhs, const path& rhs) { return lhs.compare(rhs) == 0; } inline bool operator<(const path& lhs, const path& rhs) { return lhs.compare(rhs) < 0; } template inline path operator/(const path& lhs, const Source& rhs) { path concatenation(lhs); concatenation /= rhs; return concatenation; } } } // namespace ssh::filesystem #endif ================================================ FILE: ssh/filesystem.hpp ================================================ // Copyright 2010, 2012, 2013, 2015, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SSH_FILESYSTEM_HPP #define SSH_FILESYSTEM_HPP #include #include #include #include #include // uint64_t, uintmax_t #include // BOOST_BITMASK #include // BOOST_SCOPED_ENUM* #include // errinfo_api_function #include // iterator_facade #include #include #include #include // BOOST_RV_REF, BOOST_MOVABLE_BUT_NOT_COPYABLE #include #include #include #include // errc #include #include // BOOST_THROW_EXCEPTION #include // min #include // assert #include // bad_alloc #include // invalid_argument #include #include #include namespace ssh { // Forward declared so sftp_filesystem can declare session a friend. This // file doesn't depend on session in any other way class session; namespace filesystem { class file_attributes { public: enum file_type { normal_file, symbolic_link, directory, character_device, block_device, named_pipe, socket, unknown }; file_type type() const { if (is_valid_attribute(LIBSSH2_SFTP_ATTR_PERMISSIONS)) { switch (m_attributes.permissions & LIBSSH2_SFTP_S_IFMT) { case LIBSSH2_SFTP_S_IFIFO: return named_pipe; case LIBSSH2_SFTP_S_IFCHR: return character_device; case LIBSSH2_SFTP_S_IFDIR: return directory; case LIBSSH2_SFTP_S_IFBLK: return block_device; case LIBSSH2_SFTP_S_IFREG: return normal_file; case LIBSSH2_SFTP_S_IFLNK: return symbolic_link; case LIBSSH2_SFTP_S_IFSOCK: return socket; default: return unknown; } } else { return unknown; } } boost::optional permissions() const { if (is_valid_attribute(LIBSSH2_SFTP_ATTR_PERMISSIONS)) { return m_attributes.permissions; } else { return boost::optional(); } } boost::optional size() const { if (is_valid_attribute(LIBSSH2_SFTP_ATTR_SIZE)) { return m_attributes.filesize; } else { return boost::optional(); } } boost::optional uid() const { if (is_valid_attribute(LIBSSH2_SFTP_ATTR_UIDGID)) { return m_attributes.uid; } else { return boost::optional(); } } boost::optional gid() const { if (is_valid_attribute(LIBSSH2_SFTP_ATTR_UIDGID)) { return m_attributes.gid; } else { return boost::optional(); } } /** * @todo Use Boost.DateTime or other decent datatype. */ boost::optional last_accessed() const { if (is_valid_attribute(LIBSSH2_SFTP_ATTR_ACMODTIME)) { return m_attributes.atime; } else { return boost::optional(); } } /** * @todo Use Boost.DateTime or other decent datatype. */ boost::optional last_modified() const { if (is_valid_attribute(LIBSSH2_SFTP_ATTR_ACMODTIME)) { return m_attributes.mtime; } else { return boost::optional(); } } private: friend class sftp_file; friend class sftp_filesystem; // to construct in attributes method explicit file_attributes(const LIBSSH2_SFTP_ATTRIBUTES& raw_attributes) : m_attributes(raw_attributes) { } bool is_valid_attribute(unsigned long attribute_type) const { return (m_attributes.flags & attribute_type) != 0; } LIBSSH2_SFTP_ATTRIBUTES m_attributes; }; class sftp_file : boost::totally_ordered { public: sftp_file(const path& file, const std::string& long_entry, const LIBSSH2_SFTP_ATTRIBUTES& attributes) : m_file(file), m_long_entry(long_entry), m_attributes(attributes) { } ssh::filesystem::path path() const { return m_file; } const std::string& long_entry() const { return m_long_entry; } const file_attributes& attributes() const { return m_attributes; } private: ::ssh::filesystem::path m_file; std::string m_long_entry; file_attributes m_attributes; }; inline bool operator<(const sftp_file& lhs, const sftp_file& rhs) { return lhs.path() < rhs.path(); } enum file_type { // prevent clash with perms::unknown, rename once using C++11 enum classes none_, not_found, regular, directory, symlink, block, character, fifo, socket, // prevent clash with perms::unknown, rename once using C++11 enum classes unknown_, }; BOOST_BITMASK(file_type); enum perms { none = 0, owner_read = LIBSSH2_SFTP_S_IRUSR, owner_write = LIBSSH2_SFTP_S_IWUSR, owner_exec = LIBSSH2_SFTP_S_IXUSR, owner_all = LIBSSH2_SFTP_S_IRWXU, group_read = LIBSSH2_SFTP_S_IRGRP, group_write = LIBSSH2_SFTP_S_IWGRP, group_exec = LIBSSH2_SFTP_S_IXGRP, group_all = LIBSSH2_SFTP_S_IRWXG, others_read = LIBSSH2_SFTP_S_IROTH, others_write = LIBSSH2_SFTP_S_IWOTH, others_exec = LIBSSH2_SFTP_S_IXOTH, others_all = LIBSSH2_SFTP_S_IRWXO, mask = 07777, unknown = 0xffff, add_perms = 0x10000, remove_perms = 0x20000, }; BOOST_BITMASK(perms); namespace detail { inline file_type permissions_to_file_type(unsigned long permissions) { // Mask permissions to consider only file-type bits switch (permissions & LIBSSH2_SFTP_S_IFMT) { case 0: return file_type::none_; case LIBSSH2_SFTP_S_IFIFO: return file_type::fifo; case LIBSSH2_SFTP_S_IFCHR: return file_type::character; case LIBSSH2_SFTP_S_IFDIR: return file_type::directory; case LIBSSH2_SFTP_S_IFBLK: return file_type::block; case LIBSSH2_SFTP_S_IFREG: return file_type::regular; case LIBSSH2_SFTP_S_IFLNK: return file_type::symlink; case LIBSSH2_SFTP_S_IFSOCK: return file_type::socket; default: return file_type::unknown_; } } inline unsigned long file_type_to_permissions(file_type type) { // Mask permissions to consider only file-type bits switch (type) { case file_type::none_: return 0; case file_type::fifo: return LIBSSH2_SFTP_S_IFIFO; case file_type::character: return LIBSSH2_SFTP_S_IFCHR; case file_type::directory: return LIBSSH2_SFTP_S_IFDIR; case file_type::block: return LIBSSH2_SFTP_S_IFBLK; case file_type::regular: return LIBSSH2_SFTP_S_IFREG; case file_type::symlink: return LIBSSH2_SFTP_S_IFLNK; case file_type::socket: return LIBSSH2_SFTP_S_IFSOCK; default: BOOST_THROW_EXCEPTION(std::logic_error("Unrecognised file_type")); } } } class file_status { public: // The set of file_type values is a superset of the possible types encoded // by LIBSSH2_SFTP_ATTRIBUTES (e.g. file_type::not_found), so we store the // former, not the latter. explicit file_status(file_type type = file_type::none_, perms permissions = perms::unknown) : m_type(type), m_permissions(permissions) { } explicit file_status(const LIBSSH2_SFTP_ATTRIBUTES& attributes) { if (is_available_attribute(attributes, LIBSSH2_SFTP_ATTR_PERMISSIONS)) { m_type = detail::permissions_to_file_type(attributes.permissions); m_permissions = static_cast(attributes.permissions) & perms::mask; } else { m_type = file_type::unknown_; m_permissions = perms::unknown; } if (is_available_attribute(attributes, LIBSSH2_SFTP_ATTR_SIZE)) { m_file_size = attributes.filesize; } if (is_available_attribute(attributes, LIBSSH2_SFTP_ATTR_ACMODTIME)) { m_last_write_time = attributes.mtime; } } file_type type() const { return m_type; } perms permissions() const { return m_permissions; } // Non-standard - only included as top-level function in standard boost::uintmax_t file_size() const { if (!m_file_size) { BOOST_THROW_EXCEPTION( boost::system::system_error(boost::system::errc::not_supported, boost::system::generic_category())); } return *m_file_size; } // Non-standard - only included as top-level function in standard std::time_t last_write_time() const { if (!m_last_write_time) { BOOST_THROW_EXCEPTION( boost::system::system_error(boost::system::errc::not_supported, boost::system::generic_category())); } return *m_last_write_time; } private: bool is_available_attribute(const LIBSSH2_SFTP_ATTRIBUTES attributes, unsigned long attribute_type) const { return (attributes.flags & attribute_type) != 0; } file_type m_type; perms m_permissions; boost::optional m_file_size; boost::optional m_last_write_time; }; class sftp_filesystem; namespace detail { inline boost::shared_ptr<::ssh::detail::file_handle_state> open_directory(::ssh::detail::sftp_channel_state& channel, const path& path) { std::string path_string = path.native(); return boost::make_shared<::ssh::detail::file_handle_state>( boost::ref(channel), // http://stackoverflow.com/a/1374266/67013 path_string.data(), path_string.size(), 0, 0, LIBSSH2_SFTP_OPENDIR); } } /** * List the files and directories in a directory. * * The iterator is copyable but all copies are linked so that incrementing * one will increment all the copies. */ class directory_iterator : public boost::iterator_facade { public: /** * End-of-directory marker. */ // ForwardIterators are REQUIRED to be default-constructible, yukky as // that is directory_iterator() { } // directory_iterator is not implemented in terms of the sftp_filesystem // public interface. It uses the the channel's internals, so the channel // should control it. Therefore the only way to create it is // via the channel class, which has special access to the private // constructor via the client-attorney idiom. /// @cond INTERNAL /** * Defines the single permitted factory of `directory_iterator` * instances. * This class calls the private constructor on behalf of the factory. * See http://stackoverflow.com/q/3217390/67013. */ class factory_attorney { private: friend class sftp_filesystem; directory_iterator operator()(::ssh::detail::sftp_channel_state& channel, const path& path) { return directory_iterator(channel, path); } directory_iterator operator()() { return directory_iterator(); } }; /// @endcond private: directory_iterator(::ssh::detail::sftp_channel_state& sftp_channel, const path& path) : m_directory(path), m_handle(detail::open_directory(sftp_channel, path)), m_attributes(LIBSSH2_SFTP_ATTRIBUTES()) { next_file(); } friend class boost::iterator_core_access; void increment() { if (m_handle == NULL) BOOST_THROW_EXCEPTION(std::range_error("No more files")); next_file(); } bool equal(directory_iterator const& other) const { return this->m_handle == other.m_handle; } void next_file() { // yuk! hardcoded buffer sizes. unfortunately, libssh2 doesn't // give us a choice so we allocate massive buffers here and then // take measures later to reduce the footprint std::vector filename_buffer(1024, '\0'); std::vector longentry_buffer(1024, '\0'); LIBSSH2_SFTP_ATTRIBUTES attrs = LIBSSH2_SFTP_ATTRIBUTES(); while (true) { int rc; { ::ssh::detail::file_handle_state::scoped_lock lock = m_handle->aquire_lock(); rc = ::ssh::detail::libssh2::sftp::readdir_ex( m_handle->session_ptr(), m_handle->sftp_ptr(), m_handle->file_handle(), &filename_buffer[0], filename_buffer.size(), &longentry_buffer[0], longentry_buffer.size(), &attrs); // IMPORTANT: must unlock before possible handle reset below // which would lock the session again to close the file handle } if (rc == 0) // end of files { m_handle.reset(); return; } else { assert(rc > 0); // copy attributes to member one we know we're overwriting the // last-retrieved file's properties m_attributes = attrs; // we don't assume that the filename is null-terminated but rc // holds the number of bytes written to the buffer so we can // shrink // the filename string to that size std::string file_name = std::string( &filename_buffer[0], (std::min)(static_cast(rc), filename_buffer.size())); if (file_name == "." || file_name == "..") { continue; } else { m_file_name = file_name; } // the long entry must be usable in an ls -l listing according // to // the standard so I'm interpreting this to mean it can't // contain // embedded NULLs so we force NULL-termination and then allocate // the string to be the NULL-terminated size which will likely // be // much smaller than the buffer longentry_buffer[longentry_buffer.size() - 1] = '\0'; m_long_entry = std::string(&longentry_buffer[0]); return; } } } sftp_file dereference() const { if (m_handle == NULL) BOOST_THROW_EXCEPTION( std::logic_error("Can't dereference the end of a collection")); return sftp_file(m_directory / m_file_name, m_long_entry, m_attributes); } // The file handle is shared between all copies of the iterator because // iterators must be copyable boost::shared_ptr<::ssh::detail::file_handle_state> m_handle; path m_directory; /// @name Properties of last successfully listed file. // @{ std::string m_file_name; std::string m_long_entry; LIBSSH2_SFTP_ATTRIBUTES m_attributes; // @} }; namespace detail { BOOST_SCOPED_ENUM_START(path_status){non_existent, non_directory, directory}; BOOST_SCOPED_ENUM_END ; inline BOOST_SCOPED_ENUM(path_status) check_status(sftp_filesystem& filesystem, const path& path); } BOOST_SCOPED_ENUM_START(overwrite_behaviour){ /** * Do not overwrite an existing file at the destination. * * If the file exists function will throw an exception. */ prevent_overwrite, /** * Overwrite any existing file at the destination. * * The SFTP server may not support overwriting files, in which case this * acts like `prevent_overwrite`. */ allow_overwrite, /** * Overwrite any existing file using *only* atomic methods. If atomic * methods * are not available on the server, the overwrite will not be performed by * other methods and the function will throw an exception. * * The SFTP server may not support overwriting files, in which case this * acts like `prevent_overwrite`. */ atomic_overwrite}; BOOST_SCOPED_ENUM_END class sftp_input_device; class sftp_output_device; class sftp_io_device; /** * Connection to the filesystem on a remote server via an SSH/SFTP connection. * * Filesystem connections are non-copyable. The connection is closed when the * object is destroyed. */ class sftp_filesystem : private boost::noncopyable { BOOST_MOVABLE_BUT_NOT_COPYABLE(sftp_filesystem) public: /** * Move constructor. */ sftp_filesystem(BOOST_RV_REF(sftp_filesystem) other) : m_sftp(boost::move(other.m_sftp)) { } /** * Move-assignment. */ sftp_filesystem& operator=(BOOST_RV_REF(sftp_filesystem) other) { m_sftp = boost::move(other.m_sftp); return *this; } /** * Create an iterator over the contents of the given directory. * * The iterator is copyable but all copies are linked so that incrementing * one will increment all the copies. * * The `sftp_filesystem` (and, transitively, the `session`) must outlive * all non-end copies of the iterator. It is the caller's responsibility * to ensure this. */ directory_iterator directory_iterator(const path& path) { return ssh::filesystem::directory_iterator::factory_attorney()( sftp_ref(), path); } /** * Create an iterator marking the end of a directory. */ ssh::filesystem::directory_iterator directory_iterator() { return ssh::filesystem::directory_iterator::factory_attorney()(); } /** * Query a file for its attributes. * * If @a follow_links is @c true, the file that is queried is the target of * any chain of links. Otherwise, it is the link itself. * * @todo Split into `status` and `symlink_status` to mirror Boost.Filesystem * API. */ file_attributes attributes(const path& file, bool follow_links) { std::string file_path = file.native(); LIBSSH2_SFTP_ATTRIBUTES attributes = LIBSSH2_SFTP_ATTRIBUTES(); { ::ssh::detail::sftp_channel_state::scoped_lock lock = sftp_ref().aquire_lock(); ::ssh::detail::libssh2::sftp::stat( sftp_ref().session_ptr(), sftp_ref().sftp_ptr(), file_path.data(), file_path.size(), (follow_links) ? LIBSSH2_SFTP_STAT : LIBSSH2_SFTP_LSTAT, &attributes); } return file_attributes(attributes); } path resolve_link_target(const path& link) { std::string link_string = link.native(); return symlink_resolve(link_string.data(), link_string.size(), LIBSSH2_SFTP_READLINK); } path canonical_path(const path& link) { std::string link_string = link.native(); return symlink_resolve(link_string.data(), link_string.size(), LIBSSH2_SFTP_REALPATH); } /// @cond INTERNAL /** * Defines the single permitted factory of `sftp_filesystem` instances. * This class calls the private constructor on behalf of the factory. * See http://stackoverflow.com/q/3217390/67013. */ class factory_attorney { private: friend class ssh::session; sftp_filesystem operator()(::ssh::detail::session_state& session_state) { return sftp_filesystem(session_state); } }; /// @endcond private: friend class factory_attorney; explicit sftp_filesystem(::ssh::detail::session_state& session_state) : m_sftp(new ::ssh::detail::sftp_channel_state(session_state)) { } friend class sftp_input_device; friend class sftp_output_device; friend class sftp_io_device; friend bool create_directory(sftp_filesystem& fs, const path& new_directory); friend void create_symlink(sftp_filesystem& fs, const path& link, const path& target); friend file_status status(sftp_filesystem& fs, const path& target); friend void permissions(sftp_filesystem& fs, const path& file, perms new_permissions); friend void rename(sftp_filesystem& fs, const path& source, const path& destination, BOOST_SCOPED_ENUM(overwrite_behaviour) overwrite_hint); friend bool remove(sftp_filesystem& fs, const path& target); friend boost::uintmax_t remove_all(sftp_filesystem& fs, const path& target); bool create_directory(const path& new_directory) { std::string new_directory_string = new_directory.native(); try { ::ssh::detail::sftp_channel_state::scoped_lock lock = sftp_ref().aquire_lock(); ::ssh::detail::libssh2::sftp::mkdir_ex( sftp_ref().session_ptr(), sftp_ref().sftp_ptr(), new_directory_string.data(), new_directory_string.size(), LIBSSH2_SFTP_S_IRWXU | LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IXGRP | LIBSSH2_SFTP_S_IROTH | LIBSSH2_SFTP_S_IXOTH); return true; } catch (const boost::system::system_error&) { // Might just be because it already exists. Let's check that and if // ignore if that's the case. // Doing this test after avoids an extra trip to the server in the // common case. // We don't test the exception's error code because OpenSSH just // returns FX_FAILURE which could have many causes. The only way // to be sure the directory is already there is to check explicitly. // There is an inevitable race condition here---the directory may // have been added/removed since the failure---so the result is a // best guess. Because of the race, it also doesn't matter that we // unlock and re-lock in check_status. Transactional integrity // isn't lost because it was never there. switch (detail::check_status(*this, new_directory)) { case detail::path_status::non_directory: case detail::path_status::non_existent: throw; case detail::path_status::directory: return false; default: assert(false); BOOST_THROW_EXCEPTION(std::logic_error("Unknown path status")); return false; } } } void create_symlink(const path& link, const path& target) { std::string link_string = link.native(); std::string target_string = target.native(); ::ssh::detail::sftp_channel_state::scoped_lock lock = sftp_ref().aquire_lock(); ::ssh::detail::libssh2::sftp::symlink( sftp_ref().session_ptr(), sftp_ref().sftp_ptr(), link_string.data(), link_string.size(), target_string.data(), target_string.size()); } file_status status(const path& target) { std::string file_path = target.native(); LIBSSH2_SFTP_ATTRIBUTES attributes = LIBSSH2_SFTP_ATTRIBUTES(); { ::ssh::detail::sftp_channel_state::scoped_lock lock = sftp_ref().aquire_lock(); boost::system::error_code ec; std::string message; ::ssh::detail::libssh2::sftp::stat( sftp_ref().session_ptr(), sftp_ref().sftp_ptr(), file_path.data(), file_path.size(), LIBSSH2_SFTP_STAT, &attributes, ec, message); if (ec) { if (ec == boost::system::errc::no_such_file_or_directory) { return file_status(file_type::not_found, perms::unknown); } else { SSH_DETAIL_THROW_API_ERROR_CODE_WITH_PATH( ec, message, "libssh2_sftp_stat_ex", file_path.c_str(), file_path.size()); } } } return file_status(attributes); } void permissions(const path& file, perms new_permissions) { if (new_permissions & perms::add_perms && new_permissions & perms::remove_perms) { BOOST_THROW_EXCEPTION(std::logic_error( "add_perms and remove_perms are mutually exclusive")); } else if (new_permissions & perms::add_perms) { perms current_permissions = status(file).permissions(); new_permissions = (new_permissions & perms::mask) | current_permissions; } else if (new_permissions & perms::remove_perms) { perms current_permissions = status(file).permissions(); new_permissions = ~(new_permissions & perms::mask) & current_permissions; } std::string file_path = file.native(); LIBSSH2_SFTP_ATTRIBUTES attributes = LIBSSH2_SFTP_ATTRIBUTES(); attributes.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; attributes.permissions = static_cast(new_permissions & perms::mask); ::ssh::detail::sftp_channel_state::scoped_lock lock = sftp_ref().aquire_lock(); ::ssh::detail::libssh2::sftp::stat( sftp_ref().session_ptr(), sftp_ref().sftp_ptr(), file_path.data(), file_path.size(), LIBSSH2_SFTP_SETSTAT, &attributes); } void rename(const path& source, const path& destination, BOOST_SCOPED_ENUM(overwrite_behaviour) overwrite_hint) { std::string source_string = source.native(); std::string destination_string = destination.native(); int flags; switch (overwrite_hint) { case overwrite_behaviour::prevent_overwrite: flags = 0; break; case overwrite_behaviour::allow_overwrite: flags = LIBSSH2_SFTP_RENAME_OVERWRITE; break; case overwrite_behaviour::atomic_overwrite: // The spec says OVERWRITE is implied by ATOMIC but specifying both // to be on the safe side flags = LIBSSH2_SFTP_RENAME_OVERWRITE | LIBSSH2_SFTP_RENAME_ATOMIC; break; default: BOOST_THROW_EXCEPTION( std::invalid_argument("Unrecognised overwrite behaviour")); } ::ssh::detail::sftp_channel_state::scoped_lock lock = sftp_ref().aquire_lock(); ::ssh::detail::libssh2::sftp::rename( sftp_ref().session_ptr(), sftp_ref().sftp_ptr(), source_string.data(), source_string.size(), destination_string.data(), destination_string.size(), flags); } bool remove(const path& target) { // Unlike the POSIX/Boost.Filesystem API we are following, the SFTP // protocol mirrors the C API where directories can only be removed // using the special RMDIR command. // // We tried to avoid an extra round trip to the server (to stat the // file) by blindly trying the common case of non-directories and // ignoring the first SFTP error. The theory was that any real // error should also occur on the second (rmdir) attempt. // But that's not true because the second error might be complaining // that we're trying the wrong kind of delete while the first error // is the actual problem (permissions, for example). Saving the first // error, and overwriting the second error with it, doesn't solve the // problem either as it could be the second error that gives the real // problem with the first error being wrong-kind-of-delete. Basically // we can't know which error is 'real'. If we did, we'd know the // filetype already! switch (detail::check_status(*this, target)) { case detail::path_status::non_existent: return false; case detail::path_status::directory: return remove_empty_directory(target); case detail::path_status::non_directory: // This includes 'unknown' file type. What's the alternative? return remove_one_file(target); default: assert(false); BOOST_THROW_EXCEPTION(std::logic_error("Unknown path status")); return 0U; } } boost::uintmax_t remove_all(const path& target) { switch (detail::check_status(*this, target)) { case detail::path_status::non_existent: return 0U; case detail::path_status::directory: return remove_directory(target); case detail::path_status::non_directory: // This includes 'unknown' file type. What's the alternative? return remove_one_file(target); default: assert(false); BOOST_THROW_EXCEPTION(std::logic_error("Unknown path status")); return 0U; } } bool remove_one_file(const path& file) { return do_remove(file, false); } bool remove_empty_directory(const path& file) { return do_remove(file, true); } boost::uintmax_t remove_directory(const path& root); bool do_remove(const path& target, bool is_directory) { std::string target_string = target.native(); try { ::ssh::detail::sftp_channel_state::scoped_lock lock = sftp_ref().aquire_lock(); if (is_directory) { ::ssh::detail::libssh2::sftp::rmdir_ex( sftp_ref().session_ptr(), sftp_ref().sftp_ptr(), target_string.data(), target_string.size()); } else { ::ssh::detail::libssh2::sftp::unlink_ex( sftp_ref().session_ptr(), sftp_ref().sftp_ptr(), target_string.data(), target_string.size()); } } catch (const boost::system::system_error& e) { if (e.code() == boost::system::errc::no_such_file_or_directory) { // Mirror the Boost.Filesystem API which doesn't treat this // as an error. return false; } else { throw; } } return true; } /** * Common parts of readlink and realpath. */ path symlink_resolve(const char* path, unsigned int path_len, int resolve_action) { // yuk! hardcoded buffer sizes. unfortunately, libssh2 doesn't // give us a choice so we allocate massive buffers here and then // take measures later to reduce the footprint std::vector target_path_buffer(1024, '\0'); ::ssh::detail::sftp_channel_state::scoped_lock lock = sftp_ref().aquire_lock(); int len = ::ssh::detail::libssh2::sftp::symlink_ex( sftp_ref().session_ptr(), sftp_ref().sftp_ptr(), path, path_len, &target_path_buffer[0], target_path_buffer.size(), resolve_action); return ::ssh::filesystem::path(&target_path_buffer[0], &target_path_buffer[0] + len); } ::ssh::detail::sftp_channel_state& sftp_ref() { return *m_sftp; } // Using an auto_ptr (eventually unique_ptr) so that the other objects // that reference this state continue to reference a valid object even if // this sftp_filesystem object is moved. The moved filesystem will only // move the pointer but the state will remain at the same address. // Using a value member meant that moving the filesystem, relocated the // channel state but the other objects don't get made aware of that. // Result: crash. // The other objects using this state include directory iterators and // file streams. // See http://stackoverflow.com/a/20493410/67013. std::auto_ptr<::ssh::detail::sftp_channel_state> m_sftp; }; // Only needed for C++03 support with Boost move-emulation because C++11 // std::swap does this type of swap already inline void swap(sftp_filesystem& lhs, sftp_filesystem& rhs) { sftp_filesystem tmp(boost::move(lhs)); lhs = boost::move(rhs); rhs = boost::move(tmp); } /** * Make a directory accessible from the given path. * * @returns `true` if a new directory was created at `new_directory` * `false` if a directory already existed on that path. * * This function mirrors Boost.Filesystem `create_directory` except that * directories are created with 0755 permissions instead of 0777. 0755 is * more secure and the recommended permissions for directories on web server * so seems more appropriate. It's not clear why Boost.Filesystem chooses * 0777 instead. */ inline bool create_directory(sftp_filesystem& fs, const path& new_directory) { return fs.create_directory(new_directory); } /** * Create a symbolic link. * * @param link Path to the new link on the remote filesystem. Must not * already exist. * @param target Path of file or directory to be linked to. * * @WARNING All versions of OpenSSH and probably many other servers are * implemented incorrectly and swap the order of the @p link and * @p target parameters. To connect to these servers you will * have to pass the parameters to this function in the wrong * order! */ inline void create_symlink(sftp_filesystem& fs, const path& link, const path& target) { return fs.create_symlink(link, target); } /** * Change one path to a file with another. * * After this function completes, `source` is no longer a path to the * file that it referenced before calling the function, and `destination` * is a new path to that file. * * @param source * Path to the file on the remote filesystem. File must already exist. * @param destination * Path to which the file will be moved. File may already exist. If it * does exist and `allow_overwrite` is `false`, the function will throw * an exception. * @param overwrite_hint * Optional hint suggesting preferred overwrite behaviour if * `destination` * is already a path to a file before this function is called. Only * `prevent_overwrite` is guaranteed to be obeyed. All other flags are * suggestions that the server is free to disregard (most SFTP servers * disregard these flags). If it does so and `destination` is already a * path to a file, this function will throw an unspecified * `boost::system::system_error`. * * @throws `boost::system::system_error` if `destination` is already a * path to a file before this function is called and either * `prevent_overwrite` is specified as `overwrite_hint` or the * server did not support the given hint. * * `atomic_overwrite` is the default value of `overwrite_hint` to give the * closest alignment to POSIX/Boost.Filesystem `rename`. However, as * explained above, the server is free to refuse to overwrite in the * presence of an existing `destination`. Therefore the APIs do not align * completely. * * @todo Not currently supporting the NATIVE flag as it's not at all clear * what it does. */ inline void rename(sftp_filesystem& fs, const path& source, const path& destination, BOOST_SCOPED_ENUM(overwrite_behaviour) overwrite_hint = overwrite_behaviour::atomic_overwrite) { fs.rename(source, destination, overwrite_hint); } /** * Remove a file. * * Removes `target` on the filesystem available via this object. If * `target` is a symlink, only removes the link, not what the link * resolves to. If `target` is a directory, removes it only if the * directory is empty. * * @returns `true` if the file was removed and `false` if the file did not * exist in the first place. * @throws `boost::system::system_error` if `target` is a non-empty * directory. * * If the calling code already knows whether `target` is a directory, this * function adds the overhead of a single extra stat call to the server * above what would be possible using plain SFTP unlink/rmdir. This trip is * needed to find out that information and allows us to mirror the * POSIX/Boost.Filesystem remove functions that do not differentiate * directories. */ inline bool remove(sftp_filesystem& fs, const path& target) { return fs.remove(target); } /** * Remove a file and anything below it in the hierarchy. * * Removes `target` on the filesystem available via this object. If * `target` is a symlink, only removes the link, not what the link * resolves to. If `target` is a directory, removes it and all its * contents. * * @returns the number of files removed. * * If the calling code already knows whether `target` is a directory, this * function adds the overhead of a single extra stat call to the server * above what would be possible using plain SFTP unlink/rmdir. This trip is * needed to find out that information and allows us to mirror the * POSIX/Boost.Filesystem remove functions that do not differentiate * directories. * * All files below the target must be statted (indirectly via directory listing) * by any implementation so this function adds no overhead for those. */ inline boost::uintmax_t remove_all(sftp_filesystem& fs, const path& target) { return fs.remove_all(target); } namespace detail { inline BOOST_SCOPED_ENUM(path_status) check_status(sftp_filesystem& filesystem, const path& path) { try { file_attributes attrs = filesystem.attributes(path, false); if (attrs.type() == file_attributes::directory) { return path_status::directory; } else { return path_status::non_directory; } } catch (const boost::system::system_error& e) { // Process errors by catching the exception, rather than intercepting // the error code directly, so as not to duplicate the exception info // processing. if (e.code() == boost::system::errc::no_such_file_or_directory) { // Mirror the Boost.Filesystem API which doesn't treat this as an // error. return path_status::non_existent; } else { throw; } } } } /** * Does a file exist at the given path. */ inline bool exists(sftp_filesystem& filesystem, const path& file) { try { filesystem.attributes(file, false); } catch (const boost::system::system_error& e) { if (e.code() == boost::system::errc::no_such_file_or_directory) { return false; } else { throw; } } return true; } inline path resolve_link_target(sftp_filesystem& filesystem, const sftp_file& link) { return filesystem.resolve_link_target(link.path()); } inline path canonical_path(sftp_filesystem& filesystem, const sftp_file& link) { return filesystem.canonical_path(link.path()); } /** * Get the attributes of a file. * * If the given path does not exist, this function will not throw an * exception. Instead, the returned `file_status` will have type * `file_type::not_found`. */ inline file_status status(sftp_filesystem& filesystem, const path& file) { return filesystem.status(file); } /** * Is the give status from a regular file? */ inline bool is_regular_file(const file_status& status) { return status.type() == file_type::regular; } /** * Is the given path a regular file? */ inline bool is_regular_file(sftp_filesystem& filesystem, const path& file) { return is_regular_file(status(filesystem, file)); } /** * Is the give status from a directory? */ inline bool is_directory(const file_status& status) { return status.type() == file_type::directory; } /** * Is the given path a directory? */ inline bool is_directory(sftp_filesystem& filesystem, const path& file) { return is_directory(status(filesystem, file)); } /** * Change the permissions for a file. * * If `perms::add_perms` or `perms::remove_perms` are included in * `new_permissions`, the permissions are added to or removed from the * existing. If neither `perms::add_perms` nor `perms::remove_perms` is included * in `new_permissions`, the permissions are set exactly as given. */ inline void permissions(sftp_filesystem& filesystem, const path& file, perms new_permissions) { return filesystem.permissions(file, new_permissions); } /** * Returns the size of the given file, in bytes. */ inline boost::uintmax_t file_size(sftp_filesystem& filesystem, const path& file) { return status(filesystem, file).file_size(); } /** * Returns the time of the last write to the given file. * * Whether the return value is accurate depends on the filesystem on which the * file is stored. */ inline std::time_t last_write_time(sftp_filesystem& filesystem, const path& file) { return status(filesystem, file).last_write_time(); } /** * Determine whether the given file or directory is empty. */ inline bool is_empty(sftp_filesystem& filesystem, const path& file) { file_status s = status(filesystem, file); if (is_directory(s)) { return filesystem.directory_iterator(file) == filesystem.directory_iterator(); } else { return s.file_size() == 0; } } // Needs directory_iterator implementation so outside sftp_filesystem class // body inline boost::uintmax_t sftp_filesystem::remove_directory(const path& root) { boost::uintmax_t count = 0U; for (ssh::filesystem::directory_iterator directory = directory_iterator(root); directory != directory_iterator(); ++directory) { const sftp_file& file = *directory; if (file.path().filename() == "." || file.path().filename() == "..") { continue; } if (file.attributes().type() == file_attributes::directory) { count += remove_directory(file.path()); } else { if (remove_one_file(file.path())) { ++count; } else { // Something else deleted the file before we could } } } if (remove_empty_directory(root)) { ++count; } else { // Something else deleted the directory before we could or it // never existed in the first place } return count; } } } // namespace ssh::filesystem #endif ================================================ FILE: ssh/host_key.hpp ================================================ /** @file Host-key wrapper. @if license Copyright (C) 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_HOST_KEY_HPP #define SSH_HOST_KEY_HPP #include #include // BOOST_FOREACH #include // shared_ptr #include // BOOST_THROW_EXCEPTION #include // ostringstream #include // invalid_argument #include #include // pair #include #include namespace ssh { namespace detail { /** * Thin wrapper around libssh2_session_hostkey. */ inline std::pair hostkey(session_state& session) { // Session owns the string. // Lock until we finish copying the key string from the session. I // don't know if other calls to the session are currently able to // change it, but they might one day. // Locking it for the duration makes it thread-safe either way. detail::session_state::scoped_lock lock = session.aquire_lock(); size_t len = 0; int type = LIBSSH2_HOSTKEY_TYPE_UNKNOWN; const char* key = libssh2_session_hostkey(session.session_ptr(), &len, &type); if (key) return std::make_pair(std::string(key, len), type); else return std::make_pair(std::string(), type); } /** * Thin wrapper around libssh2_hostkey_hash. * * @param T Type of collection to return. Sensible examples * include std::string or std::vector. * @param session libssh2 session pointer * @param hash_type Hash method being requested. */ template inline T hostkey_hash(session_state& session, int hash_type) { // Session owns the data. // Lock until we finish copying the key hash bytes from the session. I // don't know if other calls to the session are currently able to // change it, but they might one day. // Locking it for the duration makes it thread-safe either way. detail::session_state::scoped_lock lock = session.aquire_lock(); const T::value_type* hash_bytes = reinterpret_cast( ::libssh2_hostkey_hash(session.session_ptr(), hash_type)); size_t len = 0; if (hash_type == LIBSSH2_HOSTKEY_HASH_MD5) len = 16; else if (hash_type == LIBSSH2_HOSTKEY_HASH_SHA1) len = 20; else BOOST_THROW_EXCEPTION(std::invalid_argument("Unknown hash type")); if (hash_bytes) return T(hash_bytes, hash_bytes + len); else return T(); } /** * Thin wrapper around libssh2_session_methods. */ inline std::string method(session_state& session, int method_type) { // Session owns the string. // Lock until we finish copying the string from the session. I // don't know if other calls to the session are currently able to // change it, but they might one day. // Locking it for the duration makes it thread-safe either way. detail::session_state::scoped_lock lock = session.aquire_lock(); const char* key_type = libssh2_session_methods(session.session_ptr(), method_type); if (key_type) return std::string(key_type); else return std::string(); } } /** * Possible types of host-key algorithm. */ struct hostkey_type { enum enum_t { unknown, rsa1, ssh_rsa, ssh_dss }; }; namespace detail { /** * Convert the returned key-type from libssh2_session_hostkey into a value from * the hostkey_type enum. */ inline hostkey_type::enum_t type_to_hostkey_type(int type) { switch (type) { case LIBSSH2_HOSTKEY_TYPE_RSA: return hostkey_type::ssh_rsa; case LIBSSH2_HOSTKEY_TYPE_DSS: return hostkey_type::ssh_dss; default: return hostkey_type::unknown; } } } /** * Class representing the session's current negotiated host-key. * * As well as the raw key itself, this class provides MD5 and SHA1 hashes and * key metadata. */ class host_key { public: explicit host_key(detail::session_state& session) : // We pull everything out of the session here and store it to avoid // instances of this class depending on the lifetime of the session m_key(detail::hostkey(session)), m_algorithm_name(detail::method(session, LIBSSH2_METHOD_HOSTKEY)), m_md5_hash(detail::hostkey_hash>( session, LIBSSH2_HOSTKEY_HASH_MD5)), m_sha1_hash(detail::hostkey_hash>( session, LIBSSH2_HOSTKEY_HASH_SHA1)) { } /** * Host-key either raw or base-64 encoded. * * @see is_base64() */ std::string key() const { return m_key.first; } /** * Is the key returned by key() base64-encoded (printable)? */ bool is_base64() const { return false; } /** * Type of the key algorithm e.g., ssh-dss. */ hostkey_type::enum_t algorithm() const { return detail::type_to_hostkey_type(m_key.second); } /** * Printable name of the method negotiated for the key algorithm. */ std::string algorithm_name() const { return m_algorithm_name; } /** * Hostkey sent by the server to identify itself, hashed with the MD5 * algorithm. * * @returns Hash as binary data; it is not directly printable * (@see hexify()). */ std::vector md5_hash() const { return m_md5_hash; } /** * Hostkey sent by the server to identify itself, hashed with the SHA1 * algorithm. * * @returns Hash as binary data; it is not directly printable * (@see hexify()). */ std::vector sha1_hash() const { return m_sha1_hash; } private: std::pair m_key; std::string m_algorithm_name; std::vector m_md5_hash; std::vector m_sha1_hash; }; /** * Turn a collection of bytes into a printable hexidecimal string. * * @param bytes Collection of bytes. * @param nibble_sep String to place between each pair of hexidecimal * characters. * @param uppercase Whether to use uppercase or lowercase hexidecimal. */ template std::string hexify(const T& bytes, const std::string& nibble_sep = ":", bool uppercase = false) { std::ostringstream hex_hash; if (uppercase) hex_hash << std::uppercase; else hex_hash << std::nouppercase; hex_hash << std::hex << std::setfill('0'); BOOST_FOREACH (unsigned char b, bytes) { if (!hex_hash.str().empty()) hex_hash << nibble_sep; unsigned int i = b; hex_hash << std::setw(2) << i; } return hex_hash.str(); } } // namespace ssh #endif ================================================ FILE: ssh/knownhost.hpp ================================================ /** @file Interface to known-host mechanism. @if license Copyright (C) 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_KNOWNHOST_HPP #define SSH_KNOWNHOST_HPP #include // ssh::detail::libssh2::knownhost #include #include #include // errinfo_file_name #include // errinfo #include // path #include // path-enabled fstream #include // iterator_facade #include #include // shared_ptr #include // errc #include // BOOST_THROW_EXCEPTION #undef min #include // for_each, transform #include // assert #include // iterator_traits #include // invalid_argument, logic_error #include #include #include namespace ssh { class knownhost; namespace detail { /** * Entry-reading functor. */ template class read_entry : public std::unary_function { public: read_entry(boost::shared_ptr session, boost::shared_ptr hosts) : m_session(session), m_hosts(hosts) { } /** * Read entry into libssh2 knownhost collection. */ void operator()(const T& entry) { detail::session_state::scoped_lock lock = m_session->aquire_lock(); detail::libssh2::knownhost::readline(m_session->session_ptr(), m_hosts.get(), entry.data(), entry.length(), TYPE); } private: boost::shared_ptr m_session; boost::shared_ptr m_hosts; }; /** * Entry-writing functor. */ template class write_entry : public std::unary_function { public: write_entry(boost::shared_ptr /*session*/, boost::shared_ptr /*hosts*/) { } /** * Write entry from collection to string. * * The return type must be able to create a writable char buffer of a * certain size by a call to its constructor like this: * T(size, default_val). This is the case for both string and * vector. */ std::string operator()(const knownhost& host) { return host.to_string(TYPE); } }; /** * Line proxy to return strings line-by-line from istream_iterator. */ class line { public: friend std::istream& operator>>(std::istream& is, line& l) { std::getline(is, l.m_data); return is; } friend std::ostream& operator<<(std::ostream& os, const line& l) { os << l.m_data; return os; } friend bool operator!=(const std::string& other, const line& l) { return other != l.m_data; } friend bool operator==(const line& l, const std::string& other) { return other == l.m_data; } std::string::size_type length() const { return m_data.length(); } std::string::const_pointer data() const { return m_data.data(); } private: std::string m_data; }; /** * Fetch next host. * * @returns NULL if finished. */ inline libssh2_knownhost* next_host(boost::shared_ptr session, boost::shared_ptr hosts, libssh2_knownhost* current_position) { libssh2_knownhost* host = NULL; detail::session_state::scoped_lock lock = session->aquire_lock(); int rc = ::ssh::detail::libssh2::knownhost::get( session->session_ptr(), hosts.get(), &host, current_position); assert(rc == 0 || rc == 1); if (rc == 1) // finished { assert(host == NULL); host = NULL; } return host; } /** * Create new host entry in collection of hosts. */ inline libssh2_knownhost* add(boost::shared_ptr session, boost::shared_ptr hosts, const std::string& host_or_ip, const std::string& salt, const std::string& key, int type, bool base64_key) { if (base64_key) type |= LIBSSH2_KNOWNHOST_KEYENC_BASE64; else type |= LIBSSH2_KNOWNHOST_KEYENC_RAW; libssh2_knownhost* host = NULL; detail::session_state::scoped_lock lock = session->aquire_lock(); detail::libssh2::knownhost::add(session->session_ptr(), hosts.get(), host_or_ip.c_str(), (salt.empty()) ? NULL : salt.c_str(), key.data(), key.length(), type, &host); return host; } /** * Return the libssh2 key string which may include a comment appended to * the end separated by whitespace. */ inline std::string internal_key(const libssh2_knownhost* pos) { return (pos && pos->key) ? pos->key : std::string(); } } class knownhost { private: friend class knownhost_collection; friend class knownhost_iterator; knownhost(boost::shared_ptr session, boost::shared_ptr hosts, libssh2_knownhost* pos) : m_session(session), m_hosts(hosts), m_pos(pos) { } public: std::string name() const { return (m_pos && m_pos->name) ? m_pos->name : std::string(); } std::string key() const { std::string k = detail::internal_key(m_pos); std::string::size_type space = k.find(' '); if (space != std::string::npos) return k.substr(0, space); else return k; } /** * Return the optional comment attached to the host entry. * * @todo Fetch comment properly once libssh2 API allows it. */ std::string comment() const { std::string k = detail::internal_key(m_pos); std::string::size_type space = k.find(' '); if (space != std::string::npos && space + 1 != std::string::npos) return k.substr(space + 1); else return std::string(); } std::string to_string(int type) const { // get minimum required buffer size (doesn't include null-term) size_t required_len = 0; boost::system::error_code ec; { detail::session_state::scoped_lock lock = m_session->aquire_lock(); detail::libssh2::knownhost::writeline(m_session->session_ptr(), m_hosts.get(), m_pos, NULL, 0, &required_len, type, ec); } assert(ec == boost::system::errc::no_buffer_space); required_len++; // returned val doesn't include NULL-terminator // now repeat but with a properly allocated buffer and no ec so // errors cause exception std::vector buf(required_len); { detail::session_state::scoped_lock lock = m_session->aquire_lock(); ::ssh::detail::libssh2::knownhost::writeline( m_session->session_ptr(), m_hosts.get(), m_pos, &buf[0], buf.size(), &required_len, type); } assert(required_len == buf.size() - 1); // Return line excluding '\n' and NULL-terminator return std::string(&buf[0], std::min(buf.size() - 2, required_len - 1)); } /** * The key algorithm as an algorithm name. */ std::string key_algo() const { switch (m_pos->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) { case LIBSSH2_KNOWNHOST_KEY_RSA1: return "rsa1"; case LIBSSH2_KNOWNHOST_KEY_SSHRSA: return "ssh-rsa"; case LIBSSH2_KNOWNHOST_KEY_SSHDSS: return "ssh-dss"; default: return "unknown"; } } /** @name Predicate members. */ // @{ /** * Hostname is not encoded; it is plain-text. * e.g. hostname.example.com */ bool is_name_plain() const { return (m_pos->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) == LIBSSH2_KNOWNHOST_TYPE_PLAIN; } /** * Hostname and salt is hashed using sha1 and base64-encoded. * * When this predicate is true, name() returns an empty string as the * hash can't be converted back to a hostname. */ bool is_name_sha1() const { return (m_pos->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) == LIBSSH2_KNOWNHOST_TYPE_SHA1; } /** * Hostname encoded with some user-defined encoding. */ bool is_name_custom() const { return (m_pos->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) == LIBSSH2_KNOWNHOST_TYPE_CUSTOM; } // @} private: boost::shared_ptr m_session; boost::shared_ptr m_hosts; libssh2_knownhost* m_pos; }; /** * @todo Should libssh2_knownhost_get take a const. */ class knownhost_iterator : public boost::iterator_facade { public: /** * Create an iterator to the end of the collection. */ knownhost_iterator() : m_pos(NULL) { } /** * Remove a host at the given iterator position from the collection. * * After this function returns, any iterators that pointed to the removed * item (including the given one) will be invalidated. Attempting to * call any of their member functions results in undefined behaviour. * * @returns Iterator to the next item in the collection or the end of the * collection if there are no more items. */ friend knownhost_iterator erase(knownhost_iterator it) { knownhost_iterator next = it; next++; detail::session_state::scoped_lock lock = it.m_session->aquire_lock(); // this call invalidates the given iterator detail::libssh2::knownhost::del(it.m_session->session_ptr(), it.m_hosts.get(), it.m_pos); return next; } private: // knownhost_collection is the factories for non-end knownhost_iterators, // so is declared a friend of knownhost_iterator so that it may call the // private constructors friend class knownhost_collection; /** * Create an iterator to the beginning of the collection. */ knownhost_iterator(boost::shared_ptr session, boost::shared_ptr hosts) : m_session(session), m_hosts(hosts), m_pos(detail::next_host(m_session, m_hosts, NULL)) { } /** * Create an iterator to a point in the collection indicated by pos. */ knownhost_iterator(boost::shared_ptr session, boost::shared_ptr hosts, libssh2_knownhost* pos) : m_session(session), m_hosts(hosts), m_pos(pos) { } friend class boost::iterator_core_access; void increment() { if (m_pos == NULL) BOOST_THROW_EXCEPTION(std::logic_error( "Can't increment past the end of a collection")); m_pos = detail::next_host(m_session, m_hosts, m_pos); } bool equal(knownhost_iterator const& other) const { return this->m_pos == other.m_pos; } knownhost dereference() const { if (m_pos == NULL) BOOST_THROW_EXCEPTION( std::logic_error("Can't dereference the end of a collection")); return knownhost(m_session, m_hosts, m_pos); } boost::shared_ptr m_session; boost::shared_ptr m_hosts; libssh2_knownhost* m_pos; }; /** * Result returned by knownhost_collection::find(). */ class knownhost_search_result { public: knownhost_search_result(const knownhost_iterator& it, const knownhost_iterator& end, bool match) : m_host(it), m_end(end), m_match(match) { assert(!match || (m_host != m_end)); } knownhost_iterator host() const { return m_host; } bool mismatch() const { return !m_match && (m_host != m_end); } bool match() const { return m_match && (m_host != m_end); } bool not_found() const { return m_host == m_end; } private: knownhost_iterator m_host; knownhost_iterator m_end; bool m_match; }; namespace detail { /** * Convert a value from the hostkey_type enum into an integer suitable * for use by libssh2_knownhost_add. */ inline int hostkey_type_to_add_type(ssh::hostkey_type::enum_t type) { switch (type) { case ssh::hostkey_type::rsa1: return LIBSSH2_KNOWNHOST_KEY_RSA1; case ssh::hostkey_type::ssh_rsa: return LIBSSH2_KNOWNHOST_KEY_SSHRSA; case ssh::hostkey_type::ssh_dss: return LIBSSH2_KNOWNHOST_KEY_SSHDSS; case ssh::hostkey_type::unknown: default: BOOST_THROW_EXCEPTION( std::invalid_argument("Unrecognised key algorithm")); } } inline boost::shared_ptr init(boost::shared_ptr session) { detail::session_state::scoped_lock lock = session->aquire_lock(); return boost::shared_ptr( libssh2::knownhost::init(session->session_ptr()), libssh2_knownhost_free); } } /** * Collection of known-host entries. */ class knownhost_collection { public: explicit knownhost_collection() : m_session(boost::make_shared()), m_hosts(detail::init(m_session)) { // We construct a new session here, rather than taking one as an // argument, because it is only used for memory allocation. It // doesn't need to be connected to anything so it's an unnecessary // burden on the caller to expect them to provide one. } knownhost_iterator begin() const { return knownhost_iterator(m_session, m_hosts); } knownhost_iterator end() const { return knownhost_iterator(); } knownhost_search_result find(const std::string& host, const std::string& key, bool base64_key) const { int type = LIBSSH2_KNOWNHOST_TYPE_PLAIN; if (base64_key) type |= LIBSSH2_KNOWNHOST_KEYENC_BASE64; else type |= LIBSSH2_KNOWNHOST_KEYENC_RAW; libssh2_knownhost* match = NULL; int rc; { detail::session_state::scoped_lock lock = m_session->aquire_lock(); rc = detail::libssh2::knownhost::check( m_session->session_ptr(), m_hosts.get(), host.c_str(), key.data(), key.length(), type, &match); } switch (rc) { case LIBSSH2_KNOWNHOST_CHECK_MATCH: return knownhost_search_result( knownhost_iterator(m_session, m_hosts, match), end(), true); case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: return knownhost_search_result( knownhost_iterator(m_session, m_hosts, match), end(), false); case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND: return knownhost_search_result(end(), end(), false); default: assert(false); BOOST_THROW_EXCEPTION(std::logic_error("Unexpected return code")); } } knownhost_search_result find(const std::string& host, const ssh::host_key& key) { return find(host, key.key(), key.is_base64()); } knownhost add(const std::string& host_or_ip, const std::string& key, ssh::hostkey_type::enum_t algorithm, bool base64_key) { int type = LIBSSH2_KNOWNHOST_TYPE_PLAIN | detail::hostkey_type_to_add_type(algorithm); libssh2_knownhost* host = detail::add(m_session, m_hosts, host_or_ip, std::string(), key, type, base64_key); return knownhost(m_session, m_hosts, host); } knownhost add(const std::string& host_or_ip, const ssh::host_key& key) { return add(host_or_ip, key.key(), key.algorithm(), key.is_base64()); } knownhost add_hashed(const std::string& host_or_ip, const std::string& salt, const std::string& key, ssh::hostkey_type::enum_t algorithm, bool base64_key) { int type = LIBSSH2_KNOWNHOST_TYPE_SHA1 | detail::hostkey_type_to_add_type(algorithm); libssh2_knownhost* host = detail::add(m_session, m_hosts, host_or_ip, salt, key, type, base64_key); return knownhost(m_session, m_hosts, host); } knownhost add_hashed(const std::string& host_or_ip, const std::string& salt, const ssh::host_key& key) { return add_hashed(host_or_ip, salt, key.key(), key.algorithm(), key.is_base64()); } knownhost add_custom(const std::string& host_or_ip, const std::string& key, ssh::hostkey_type::enum_t algorithm, bool base64_key) { int type = LIBSSH2_KNOWNHOST_TYPE_CUSTOM | detail::hostkey_type_to_add_type(algorithm); libssh2_knownhost* host = detail::add(m_session, m_hosts, host_or_ip, std::string(), key, type, base64_key); return knownhost(m_session, m_hosts, host); } knownhost add_custom(const std::string& host_or_ip, const ssh::host_key& key) { return add_custom(host_or_ip, key.key(), key.algorithm(), key.is_base64()); } protected: /** * Initialise the known-hosts collection from a range of entries. * * @param begin Iterator to the start of the range. * @param end Iterator indicating termination (half-closed). * @param TYPE Type of entry to read. Currently the only supported type * is LIBSSH2_KNOWNHOST_FILE_OPENSSH in which case the * entry must be in OpenSSH known_hosts format (hashed or * unhashed). * @param It InputIterator to range of entries in known_hosts format. */ template void load_entries(const InputIt& begin, const InputIt& end) { typedef std::iterator_traits::value_type value_t; std::for_each(begin, end, detail::read_entry(m_session, m_hosts)); } /** * Initialise the known-hosts collection from a range of entries. * * @param begin Iterator to the start of the range. * @param end Iterator indicating termination (half-closed). * @param type Type of entry to read. Currently the only supported type * is LIBSSH2_KNOWNHOST_FILE_OPENSSH in which case the * entry must be in OpenSSH known_hosts format (hashed or * unhashed). */ template OutputIt save_entries(const knownhost_iterator& begin, const knownhost_iterator& end, OutputIt output) const { return std::transform(begin, end, output, detail::write_entry(m_session, m_hosts)); } private: boost::shared_ptr m_session; boost::shared_ptr m_hosts; }; knownhost add(knownhost_collection& hosts, const std::string& host_or_ip, const ssh::host_key& key) { return hosts.add(host_or_ip, key.key(), key.algorithm(), key.is_base64()); } knownhost add_hashed(knownhost_collection& hosts, const std::string& host_or_ip, const std::string& salt, const ssh::host_key& key) { return hosts.add_hashed(host_or_ip, salt, key.key(), key.algorithm(), key.is_base64()); } knownhost add_custom(knownhost_collection& hosts, const std::string& host_or_ip, const ssh::host_key& key) { return hosts.add_custom(host_or_ip, key.key(), key.algorithm(), key.is_base64()); } knownhost update(knownhost_collection& hosts, const std::string& host_or_ip, const ssh::host_key& key, const knownhost_search_result& entry) { erase(entry.host()); return add(hosts, host_or_ip, key); } /** * Collection of known-host entries stored in OpenSSH known_hosts format. * * In the absence of changes, entries are written back exactly as they * were read, with the following exceptions: * - ip,hostname combinations are split onto two lines, ip first * - tabs in seperators are replaced by a single space */ class openssh_knownhost_collection : public knownhost_collection { public: /** Initialise collection from a range of OpenSSH known_hosts lines. */ template openssh_knownhost_collection(InputIt begin, InputIt end) { load_entries(begin, end); } /** Initialise collection from an OpenSSH known_hosts file. */ openssh_knownhost_collection(const boost::filesystem::path& filename) { boost::filesystem::ifstream file(filename); if (!file) BOOST_THROW_EXCEPTION( boost::enable_error_info(std::runtime_error( "Could not read from known-hosts file '" + complete(filename).string() + "'")) << boost::errinfo_file_name(filename.string())); load_entries( std::istream_iterator(file), std::istream_iterator()); } /** * Save range of entries to an output iterator in OpenSSH known_hosts * format. * * Entries do @b not end in a newline character. */ template OutputIt save(const knownhost_iterator& begin, const knownhost_iterator& end, OutputIt output) const { return save_entries( begin, end, output); } /** Save all entires to an OpenSSH known_hosts file. */ void save(const boost::filesystem::path& filename) const { boost::filesystem::ofstream file(filename); if (!file) BOOST_THROW_EXCEPTION( boost::enable_error_info( std::runtime_error("Could not write to known-hosts file")) << boost::errinfo_file_name(filename.string())); save(begin(), end(), std::ostream_iterator(file, "\n")); } }; } // namespace ssh #endif ================================================ FILE: ssh/session.hpp ================================================ /** @file SSH session object. @if license Copyright (C) 2010, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_SESSION_HPP #define SSH_SESSION_HPP #include #include // ssh::detail::libssh2::session #include // ssh::detail::libssh2::userauth #include #include // sftp_filesystem #include // is_any_of #include #include #include // path, used for key paths #include #include // BOOST_RV_REF, BOOST_MOVABLE_BUT_NOT_COPYABLE #include #include #include #include #include #include #include #include // error_code, errc #include #include // BOOST_THROW_EXCEPTION #include // auto_ptr #include #include // pair, make_pair #include #include namespace ssh { namespace filesystem { class sftp_filesystem; } namespace detail { inline std::pair convert_prompt(const LIBSSH2_USERAUTH_KBDINT_PROMPT& prompt) { return std::make_pair(std::string(prompt.text, prompt.length), prompt.echo); } inline void convert_response(LIBSSH2_USERAUTH_KBDINT_RESPONSE& raw_response, const std::string& response) { // XXX: Should use session MALLOC here raw_response.text = static_cast( malloc(response.length() * sizeof(std::string::value_type))); // XXX: what happens if we encounter an exception after this point? raw_response.length = response.length(); response.copy(raw_response.text, raw_response.length); } /** * Glue between libssh2's ideas of a responder and this c++ wrapper's responder. * * It's not safe to throw exceptions through libssh2 C code, so we have to catch * them in the static callback (dethunker), and somehow communicate them back to * the C++ code which can safely rethrow them. * * The only available channel of communication is the challenge-responder in the * abstract but the user provides that so we don't want them to have to provide * anything special. This class adds the 'something special' by wrapping the * challenge-responder and stashing anything needed to interpret the result. */ template class challenge_response_translator { public: challenge_response_translator(const ChallengeResponder& resp) : m_responder(resp), m_called(false) { } /** * Perform the challenge-response authentication translating between the * interfaces as we go. */ bool do_challenge_response(LIBSSH2_SESSION* session, const std::string username) { boost::system::error_code ec; std::string message; // IMPORTANT: No need to lock here. The session is locked by the caller // to encompass setting the abstract so that the abstract isn't // inadvertently overwritten. libssh2::userauth::keyboard_interactive_ex( session, username.data(), username.size(), &dethunker, ec, message); return translate_status(ec, message); } void operator()(const char* name, int name_len, const char* instruction, int instruction_len, int num_prompts, const LIBSSH2_USERAUTH_KBDINT_PROMPT* raw_prompts, LIBSSH2_USERAUTH_KBDINT_RESPONSE* raw_responses) throw() { m_called = true; try { call_inner_responder(name, name_len, instruction, instruction_len, num_prompts, raw_prompts, raw_responses); } catch (...) { m_exception = boost::current_exception(); } } private: /** * Merge any errors reported by libssh2 with any exception throw in * the responder. * * Merging the two is non-trivial. There are at least 9 scenarios * that can arise: * (1) Authentication was successful: * a) and the responder executed completely * b) despite the responder throwing an exception. Possible because * the exception just causes outstanding responses to be sent to * the server blank, and the server may be satisfied with these * blank responses. There is no way to abort authentication via * the callback. * TODO: maybe modify libssh2 to allow aborting from the callback. * c) without needing to call the responder. Scary. * (2) Authentication positively rejected: * a) even though the responder executed completely. E.g. the user * gave the wrong response. * b) because the responder threw an exception and the server * rejected the (possibly partially-complete) responses. * c) without needing to call the responder, e.g. kb-interactive not * set up properly on the server (yes, this does actually happen, * we tested it - e.g the cygwin server). * (3) Some other failure occurred: * a) even though the responder executed completely. * b) the responder threw an exception but the failure is unrelated * (because it's not possible to abort, it must be unrelated). * c) before needing to call the responder. */ bool translate_status(const boost::system::error_code& ec, const std::string& message) { if (!ec) { // Situation (1) above. Merge all three situations and just // report the successful authentication. Any exception thrown in // the // responder is ignored. // // XXX: There is a tricky use-case here. If a user cancels a // challenge-response prompt and that causes an exception, // the caller has no way to tell that the user cancelled if the // authentication nevertheless succeeded. Arguably, that is // the correct behaviour as it is more important to know the // authentication state of the session than the user's // response. An even better solution would be if we could // abort authentication from the callback but that may not be // possible. RFC 4256 Section 3.4 says that sending the wrong // number of responses back must always result in failure, so // responding with zero replies might work ... unless the // server // sent zero prompts. return true; } else if (ec == boost::system::errc::permission_denied) { // Situation (2) above. // a) is a non-error failure. In other words, the kind of // failure that wouldn't be reported to the user with an error // dialog. The most likely response to this result is to attempt // authentication again. It wouldn't be appropriate to report // these failures as exceptions so, instead, we return false. // // b) and c) are both errors so we need to throw exceptions. // We can only tell c) and a) apart by whether the responder // was called so we have to wrap the given responder to record // that information. For b) the most relevant exception is the // one thrown by the wrapped responder which is also by this // class. if (!m_called) { // c) assert(!m_exception); BOOST_THROW_EXCEPTION(boost::system::system_error(ec, message)); } else if (m_exception) { // b) boost::rethrow_exception(*m_exception); } else { // a) assert(m_called); return false; } } else { // Situation (3) above BOOST_THROW_EXCEPTION(boost::system::system_error(ec, message)); } // If the user cancels the operation, our callback should throw an // E_ABORT exception which we catch here. } /** * Unpacks the stashed responder. */ template static void dethunker(const char* name, int name_len, const char* instruction, int instruction_len, int num_prompts, const LIBSSH2_USERAUTH_KBDINT_PROMPT* raw_prompts, LIBSSH2_USERAUTH_KBDINT_RESPONSE* raw_responses, void** abstract) throw() { challenge_response_translator& responder = *static_cast*>( *abstract); responder(name, name_len, instruction, instruction_len, num_prompts, raw_prompts, raw_responses); } /** * Do the two-way interface translation. */ void call_inner_responder(const char* name, int name_len, const char* instruction, int instruction_len, int num_prompts, const LIBSSH2_USERAUTH_KBDINT_PROMPT* raw_prompts, LIBSSH2_USERAUTH_KBDINT_RESPONSE* raw_responses) { std::vector> prompts; boost::push_back( prompts, boost::iterator_range( raw_prompts, raw_prompts + num_prompts) | boost::adaptors::transformed(convert_prompt)); // Either the name or the instruction may be a NULL pointer as they // are optional fields std::string name_string = name ? std::string(name, name_len) : std::string(); std::string instruction_string = instruction ? std::string(instruction, instruction_len) : std::string(); boost::range::for_each( boost::iterator_range( raw_responses, raw_responses + num_prompts), m_responder(name_string, instruction_string, prompts), convert_response); } ChallengeResponder m_responder; bool m_called; boost::optional m_exception; }; } /** * An SSH session connected to a host. * * Sessions are non-copyable. If copy semantics are required, use * a `shared_ptr`. * * The session is disconnected from the server when the object is destroyed. * * Rationale * --------- * It is important that clients are able to guarantee that a session has been * disconnected at a particular point. Because the underlying SSH session * cannot be meaningfully duplicated, making this class copyable would only * be possible by sharing the underlying SSH session between the copies. * This would mean that the session would only be disconnected when the last * copy is destroyed, which is harder to control. */ class session : private boost::noncopyable { BOOST_MOVABLE_BUT_NOT_COPYABLE(session) public: /** * Start a new SSH session with a host. * * The host is listening on the other end of the given socket. * * The constructor will throw an exception if it cannot connect to the host * or negotiate an SSH session. Therefore any instance of this class * begins life successfully connected to the host. Of course, the * connection may break subsequently and the server is free to terminate * the session at any time. * * @param socket * The socket through which to communicate with the listening server. * @param disconnection_message * An optional message sent to the server when the session is * destroyed. */ session(int socket, const std::string& disconnection_message = "libssh2 C++ bindings session destructor") : m_session(new detail::session_state(socket, disconnection_message)) { } /** * Move constructor. */ session(BOOST_RV_REF(session) other) : m_session(boost::move(other.m_session)) { } /** * Move-assignment. */ session& operator=(BOOST_RV_REF(session) other) { m_session = boost::move(other.m_session); return *this; } /** * Hostkey sent by the server to identify itself. */ ssh::host_key hostkey() { return ssh::host_key(session_ref()); } /** * Names of the methods the server claims are available for * authentication. * * The server is allowed to lie. * * An empty list does not necessarily mean no methods are available. It * might mean that authentication has already succeeded or that no * authentication was needed. Calling this method has the side effect * of authenticating the session in the latter case. */ std::vector authentication_methods(const std::string& username) { boost::system::error_code ec; std::string message; // Locking until we copy out the method string owned by the session. // We don't want another thread inadvertently causing it to be // overwritten While we're reading it. detail::session_state::scoped_lock lock = session_ref().aquire_lock(); const char* method_list = detail::libssh2::userauth::list( session_ref().session_ptr(), username.data(), username.size(), ec, message); if (!method_list) { // Because the userauth list is fetched by trying to authenticate // with method "none", a NULL return might mean that no // authentication was needed. The error code disambiguates this // from a true error. if (!ec) { assert(authenticated()); return std::vector(); } else { BOOST_THROW_EXCEPTION(boost::system::system_error(ec, message)); } } else { std::vector methods; boost::split(methods, method_list, boost::is_any_of(",")); return methods; } } bool authenticated() { detail::session_state::scoped_lock lock = session_ref().aquire_lock(); return ::libssh2_userauth_authenticated(session_ref().session_ptr()) != 0; } /** * Simple password authentication. * * @param username * UTF-8 string identifying the user to authenticate as. * @param password * Password as a UTF-8 string. * * @returns * `true` if authentication successful, `false` if not. * * @throws `boost::system::system_error` * if unexpected failure while trying to authenticate. * * @todo Handle password change callback. */ bool authenticate_by_password(const std::string& username, const std::string& password) { boost::system::error_code ec; std::string message; { detail::session_state::scoped_lock lock = session_ref().aquire_lock(); detail::libssh2::userauth::password( session_ref().session_ptr(), username.data(), username.size(), password.data(), password.size(), NULL, ec, message); } if (!ec) { return true; } else if (ec == boost::system::errc::permission_denied) { // The incorrect password failure is not reported as an exception // because it is not exceptional. return false; } else { BOOST_THROW_EXCEPTION(boost::system::system_error(ec, message)); } } /** * Challenge-response authentication. * * This is also known as keyboard-interactive authentication. The server * challenges the user by requesting one or more pieces of information. * Once the user has responded, the server may request more information * any number of time until it is either satisfied and authenticates the * user or refuses to do so. * * @param username * UTF-8 string identifying the user to authenticate as. * @param responder * Callback to receive the challenges from the server and provide the * corresponding responses. * The callback must be a model of the `ChallengeResponder` concept. * That means it must be callable with a three arguments: * - a string giving the challenge title (may be empty), * - a string giving the challenge instructions (may be empty), and * - a range of zero or more prompts, each a pair whose first member * is the prompt text and whose second member is a boolean indicating * whether the response should be obscured like a password or made * visible. * The call must return a range of responses as strings, one for every * prompt in the same order as the prompts. * * @returns * `true` if authentication successful, `false` if the server positively * rejected the responses produced by the `responder` callback. * * @throws `boost::system::system_error` * if unexpected failure while trying to authenticate or if the server * positively rejects authentication without even calling the * `responder`. * @throws user-defined-exception * if authentication fails because the `responder` threw an exception, * the exception is throw out of this method. * * @warning The responder __must not__ call any code that uses the same * SSH session currently being authenticated. Doing so results * in undefined behaviour (likely deadlock). */ // // We tried to use Boost.Concept here to verify the ChallengeResponder but // gave up. We struggled to do anything useful without BOOST_TYPEOF (which // crashed MSVC) and it's not clear what the benefit of the concept would // have been in any case. It didn't make the requirements of the // responder any more clear than reading the implementation of this // function and I doubt the error messages were any better. // Nevertheless, this might be worth having another go at in the future. // template bool authenticate_interactively(const std::string& username, ChallengeResponder responder) { // The libssh2 C API, of course, takes the callback as a plain-old // static function. The caller, however, may have passed us a callable // object and we need to be able to call that instead. // // As is typical of good C APIs, libssh2 gives us a way to sneak a // pointer to the callback object (or whatever it might be) through // the static callback function via an 'abstract' parameter. // // We set the abstract via the session. The static callback function // receives that and converts it back to the callable object, which // can then be called in the C++ way. // // As an extra twist in the story, we don't pass the responder directly // in the abstract. Instead we pass a version wrapped so that it can // store exceptions encountered, which we rethrow afterwards. detail::challenge_response_translator wrapped_responder(responder); // IMPORTANT: Locked from this point onwards until returning to the // caller so that abstract is not overwritten by another thread // before we pull the responder out of it later detail::session_state::scoped_lock lock = session_ref().aquire_lock(); *::libssh2_session_abstract(session_ref().session_ptr()) = &wrapped_responder; return wrapped_responder.do_challenge_response( session_ref().session_ptr(), username); } /** * Public-key authentication. * * This method requires a path to both the public and private keys because * libssh2 does. It should be possible to derive one from the other so * when libssh2 supports this the method will take one fewer argument. */ void authenticate_by_key_files(const std::string& username, const boost::filesystem::path& public_key, const boost::filesystem::path& private_key, const std::string& passphrase) { detail::session_state::scoped_lock lock = session_ref().aquire_lock(); detail::libssh2::userauth::public_key_from_file( session_ref().session_ptr(), username.data(), username.size(), public_key.string().c_str(), private_key.string().c_str(), passphrase.c_str()); } /** * Connect to any agent running on the system and return object to * authenticate using its identities. */ ::ssh::agent_identities agent_identities() { return ::ssh::agent_identities(session_ref()); } /** * Create a new connection to the remote filesystem over this SSH session. * * @warning It is the caller's responsibility to ensure the filesystem is * connection is shut down before the session is disconnected. * In other words, that the last moved-to destination of the * session outlives the last moved-to destination of the * filesystem. If neither is moved, this is naturally the case. */ filesystem::sftp_filesystem connect_to_filesystem() { return filesystem::sftp_filesystem::factory_attorney()(session_ref()); } private: detail::session_state& session_ref() { return *m_session; } // Using an auto_ptr (eventually unique_ptr) so that the other objects // that reference this state continue to reference a valid object even if // this session object is moved. The moved session will only move the // pointer but the state will remain at the same address. // Using a value member meant that moving the session, relocated the // session state but the other objects don't get made aware of that. // Result: crash. // The other objects using this state include filesystem connections // (and transitively directory iterators and file streams) and // agent identity collections. // See http://stackoverflow.com/a/20493410/67013. std::auto_ptr m_session; }; // C++11 swap has this implementation but we also support C++03 inline void swap(session& lhs, session& rhs) { session tmp(boost::move(lhs)); lhs = boost::move(rhs); rhs = boost::move(tmp); } } // namespace ssh #endif ================================================ FILE: ssh/sftp_error.hpp ================================================ /** @file SFTP error reporting. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_SFTP_ERROR_HPP #define SSH_SFTP_ERROR_HPP #include // last_error_code #include // errinfo_file_name #include // errinfo_api_function #include // throw_exception #include #include // LIBSSH2_FX_*, LIBSSH2_ERROR_SFTP_PROTOCOL, // libssh2_sftp_last_error namespace ssh { namespace filesystem { inline boost::system::error_category& sftp_error_category(); namespace detail { // Cutting LIBSSH2_ prefix off because the FX codes correspond to codes in // the spec, not just in the library #define SSH_CASE_SFTP_RETURN_STRINGISED(x) \ case LIBSSH2_##x: \ return #x; inline std::string sftp_error_code_to_string(unsigned long code) { switch (code) { SSH_CASE_SFTP_RETURN_STRINGISED(FX_OK); SSH_CASE_SFTP_RETURN_STRINGISED(FX_EOF); SSH_CASE_SFTP_RETURN_STRINGISED(FX_NO_SUCH_FILE); SSH_CASE_SFTP_RETURN_STRINGISED(FX_PERMISSION_DENIED); SSH_CASE_SFTP_RETURN_STRINGISED(FX_FAILURE); SSH_CASE_SFTP_RETURN_STRINGISED(FX_BAD_MESSAGE); SSH_CASE_SFTP_RETURN_STRINGISED(FX_NO_CONNECTION); SSH_CASE_SFTP_RETURN_STRINGISED(FX_CONNECTION_LOST); SSH_CASE_SFTP_RETURN_STRINGISED(FX_OP_UNSUPPORTED); SSH_CASE_SFTP_RETURN_STRINGISED(FX_INVALID_HANDLE); SSH_CASE_SFTP_RETURN_STRINGISED(FX_NO_SUCH_PATH); SSH_CASE_SFTP_RETURN_STRINGISED(FX_FILE_ALREADY_EXISTS); SSH_CASE_SFTP_RETURN_STRINGISED(FX_WRITE_PROTECT); SSH_CASE_SFTP_RETURN_STRINGISED(FX_NO_MEDIA); SSH_CASE_SFTP_RETURN_STRINGISED(FX_NO_SPACE_ON_FILESYSTEM); SSH_CASE_SFTP_RETURN_STRINGISED(FX_QUOTA_EXCEEDED); SSH_CASE_SFTP_RETURN_STRINGISED(FX_UNKNOWN_PRINCIPAL); SSH_CASE_SFTP_RETURN_STRINGISED(FX_LOCK_CONFLICT); SSH_CASE_SFTP_RETURN_STRINGISED(FX_DIR_NOT_EMPTY); SSH_CASE_SFTP_RETURN_STRINGISED(FX_NOT_A_DIRECTORY); SSH_CASE_SFTP_RETURN_STRINGISED(FX_INVALID_FILENAME); SSH_CASE_SFTP_RETURN_STRINGISED(FX_LINK_LOOP); default: assert(!"Unknown code"); return boost::lexical_cast(code); } } #undef SSH_CASE_SFTP_RETURN_STRINGISED class _sftp_error_category : public boost::system::error_category { typedef boost::system::error_category super; public: virtual const char* name() const { return "sftp"; } virtual std::string message(int code) const { return sftp_error_code_to_string(code); } virtual boost::system::error_condition default_error_condition(int code) const { switch (code) { case LIBSSH2_FX_NO_SUCH_FILE: return boost::system::errc::no_such_file_or_directory; case LIBSSH2_FX_FILE_ALREADY_EXISTS: return boost::system::errc::file_exists; case LIBSSH2_FX_OP_UNSUPPORTED: return boost::system::errc::operation_not_supported; default: return this->super::default_error_condition(code); } } virtual bool equivalent(int code, const boost::system::error_condition& condition) const { // Any match with the code's default condition is equivalent. The // switch below only needs to match _extra_ conditions that are // also equivalent if (condition == default_error_condition(code)) { return true; } switch (code) { case LIBSSH2_FX_OP_UNSUPPORTED: return condition == boost::system::errc::not_supported; default: return condition == default_error_condition(code); } } private: _sftp_error_category() { } friend boost::system::error_category& ssh::filesystem::sftp_error_category(); }; } inline boost::system::error_category& sftp_error_category() { // C++ standard says this instance is shared across all translation units // http://stackoverflow.com/a/1389403/67013 static detail::_sftp_error_category instance; return instance; } namespace detail { /** * Last error encountered by the SFTP channel as an `error_code` and * optional error description message. */ inline boost::system::error_code last_sftp_error_code( LIBSSH2_SESSION* session, LIBSSH2_SFTP* sftp, boost::optional e_msg = boost::optional()) { // Failing libssh2_sftp_* functions can set an SSH error defined // by the library or an SFTP error defined in the SFTP standard, // in which case the SSH error will be LIBSSH2_ERROR_SFTP_PROTOCOL. // This function checks which case it is and packages the error // with the corresponding category. boost::system::error_code error = ::ssh::detail::last_error_code(session, e_msg); if (error.value() == LIBSSH2_ERROR_SFTP_PROTOCOL) { error = boost::system::error_code(::libssh2_sftp_last_error(sftp), sftp_error_category()); } return error; } } } } // namespace ssh::filesystem #endif ================================================ FILE: ssh/ssh_error.hpp ================================================ /** @file SSH error reporting. @if license Copyright (C) 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_SSH_ERROR_HPP #define SSH_SSH_ERROR_HPP #include // enable_error_info #include #include #include #include // errinfo_api_function #include #include #include #include #include #include // assert #include #include // libssh2_session_last_error, libssh2_session_last_errno namespace ssh { inline boost::system::error_category& ssh_error_category(); namespace detail { #define SSH_CASE_RETURN_STRINGISED(x) \ case x: \ return #x; inline std::string ssh_error_code_to_string(int code) { switch (code) { SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_SOCKET_NONE); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_BANNER_RECV); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_BANNER_SEND); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_INVALID_MAC); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_KEX_FAILURE); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_ALLOC); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_SOCKET_SEND); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_TIMEOUT); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_HOSTKEY_INIT); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_HOSTKEY_SIGN); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_DECRYPT); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_SOCKET_DISCONNECT); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_PROTO); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_PASSWORD_EXPIRED); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_FILE); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_METHOD_NONE); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_AUTHENTICATION_FAILED); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_CHANNEL_OUTOFORDER); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_CHANNEL_FAILURE); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_CHANNEL_UNKNOWN); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_CHANNEL_CLOSED); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_CHANNEL_EOF_SENT); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_SCP_PROTOCOL); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_ZLIB); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_SOCKET_TIMEOUT); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_SFTP_PROTOCOL); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_REQUEST_DENIED); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_METHOD_NOT_SUPPORTED); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_INVAL); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_INVALID_POLL_TYPE); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_PUBLICKEY_PROTOCOL); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_EAGAIN); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_BUFFER_TOO_SMALL); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_BAD_USE); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_COMPRESS); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_OUT_OF_BOUNDARY); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_AGENT_PROTOCOL); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_SOCKET_RECV); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_ENCRYPT); SSH_CASE_RETURN_STRINGISED(LIBSSH2_ERROR_BAD_SOCKET); default: assert(!"Unknown code"); return boost::lexical_cast(code); } } #undef SSH_CASE_RETURN_STRINGISED class _ssh_error_category : public boost::system::error_category { typedef boost::system::error_category super; public: const char* name() const { return "ssh"; } std::string message(int code) const { return ssh_error_code_to_string(code); } virtual boost::system::error_condition default_error_condition(int code) const { switch (code) { case LIBSSH2_ERROR_AUTHENTICATION_FAILED: return boost::system::errc::permission_denied; case LIBSSH2_ERROR_BUFFER_TOO_SMALL: return boost::system::errc::no_buffer_space; default: return this->super::default_error_condition(code); } } private: _ssh_error_category() { } friend boost::system::error_category& ssh::ssh_error_category(); }; } inline boost::system::error_category& ssh_error_category() { // C++ standard says this instance is shared across all translation units // http://stackoverflow.com/a/1389403/67013 static detail::_ssh_error_category instance; return instance; } namespace detail { /** * Last error encountered by the session as an `error_code` and optional error * description message. */ inline boost::system::error_code last_error_code( LIBSSH2_SESSION* session, boost::optional e_msg = boost::optional()) { int val = 0; if (e_msg) { char* message_buf = NULL; // read-only reference int message_len = 0; // len not including NULL-term val = ::libssh2_session_last_error(session, &message_buf, &message_len, false); *e_msg = std::string(message_buf, message_len); } else { val = ::libssh2_session_last_errno(session); } assert(val && "throwing success!"); return boost::system::error_code(val, ssh_error_category()); } // Assumes given exception supports error info already. We don't know the type // of the error-info-enabled exception. This function means we don't need to as // it takes it as a template arg template BOOST_ATTRIBUTE_NORETURN inline void throw_api_exception(Exception e, const char* current_function, const char* source_file, int source_line, const char* api_function, const char* path, size_t path_len) { e << boost::errinfo_api_function(api_function) << boost::throw_function(current_function) << boost::throw_file(source_file) << boost::throw_line(source_line); if (path && path_len > 0) { e << boost::errinfo_file_name(std::string(path, path_len)); } boost::throw_exception(e); } BOOST_ATTRIBUTE_NORETURN inline void throw_api_error_code(boost::system::error_code ec, const std::string& message, const char* current_function, const char* source_file, int source_line, const char* api_function, const char* path, size_t path_len) { boost::system::system_error e = boost::system::system_error(ec, message); throw_api_exception(boost::enable_error_info(e), current_function, source_file, source_line, api_function, path, path_len); } } // namespace detail } // namespace ssh /// @cond INTERNAL #define SSH_DETAIL_THROW_API_ERROR_CODE(ec, message, api_function) \ ::ssh::detail::throw_api_error_code(ec, message, BOOST_CURRENT_FUNCTION, \ __FILE__, __LINE__, api_function, \ NULL, 0) #define SSH_DETAIL_THROW_API_ERROR_CODE_WITH_PATH(ec, message, api_function, \ path, path_len) \ ::ssh::detail::throw_api_error_code(ec, message, BOOST_CURRENT_FUNCTION, \ __FILE__, __LINE__, api_function, \ path, path_len) /// @endcond #endif ================================================ FILE: ssh/stream.hpp ================================================ /** @file SSH SFTP file streams. @if license Copyright (C) 2013, 2015 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SSH_STREAM_HPP #define SSH_STREAM_HPP #include #include #include #include #include #include #include // seekable, input_seekable, // output_seekable #include #include #include // BOOST_THROW_EXCEPTION #include // assert #include // invalid_argument, logic_error #include #include namespace ssh { namespace filesystem { /** * Flags defining how to open a file. * * Using this rather than `std::ios_base::openmode` to allow us to support * non-standard `nocreate` and `noreplace`, which correspond to SFTP file modes, * as well as eliminating `ate` and `binary` flags which we don't support. * * The meaning of the standard flags is the same as in * `std::ios_base::openmode`. */ struct openmode { enum value { /** * Open the file so that it is readable. * * The file must already exist unless `trunc` is also given in which * case a new empty file is created with 0644 permissions. */ in = std::ios_base::in, /** * Open the file so that it is writable. * * The file will be created if it does not already exist, unless `in` is * also given without `trunc`. If a new file is created it will empty * and have 0644 permissions. * * If neither `in` not `app` are given, will truncate any existing file * (i.e. will have same behaviour as if `trunc` had been given). */ out = std::ios_base::out, /** * All writes to the file will append to the existing contents. * * This is more than just opening the file at the end as writes _cannot_ * modify earlier data even if the file is seeked to an earlier point. * * @warning This flag is not supported by common SFTP servers including * the ubiquitous OpenSSH making is pretty useless in practice. */ app = std::ios_base::app, /** * Empties the file when opening it. * * `out` must also be specified for `trunc` to have any effect. `out` * without `app` or `in` behaves as if `trunc` had been given, whether * or not it is. * * @todo How does STL `fstream behave if `out` is not specified? Error? * Or just ignore it like we do? */ trunc = std::ios_base::trunc, /** * Fail if the file does not already exist. * * `in` without `trunc` has this behaviour whether or not `nocreate` is * given. */ nocreate = 0x40, /** * Fail if the file already exists. */ noreplace = 0x80 }; }; inline openmode::value operator|(openmode::value l, openmode::value r) { return static_cast(static_cast(l) | static_cast(r)); } inline openmode::value operator&(openmode::value l, openmode::value r) { return static_cast(static_cast(l) & static_cast(r)); } inline openmode::value operator^(openmode::value l, openmode::value r) { return static_cast(static_cast(l) ^ static_cast(r)); } inline openmode::value& operator|=(openmode::value& l, openmode::value r) { return l = l | r; } inline openmode::value& operator&=(openmode::value& l, openmode::value r) { return l = l & r; } inline openmode::value& operator^=(openmode::value& l, openmode::value r) { return l = l ^ r; } // No operator~ as that might not be safe in C++03 where cannot specify enum // underlying type // (see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3110.html) namespace detail { inline openmode::value translate_flags(std::ios_base::openmode std_mode) { openmode::value our_mode = openmode::value(); if (std_mode & std::ios_base::in) { our_mode |= openmode::in; } if (std_mode & std::ios_base::out) { our_mode |= openmode::out; } if (std_mode & std::ios_base::ate) { // TODO: support it. Should be simple, all we have to do it seek! BOOST_THROW_EXCEPTION( std::invalid_argument("ate flag not yet supported")); } if (std_mode & std::ios_base::app) { our_mode |= openmode::app; } if (std_mode & std::ios_base::trunc) { our_mode |= openmode::trunc; } if (std_mode & std::ios_base::binary) { ; // do nothing. our streams are always binary } return our_mode; } inline long openmode_to_libssh2_flags(openmode::value opening_mode) { long flags = 0; if (opening_mode & openmode::in) { flags |= LIBSSH2_FXF_READ; } if (opening_mode & openmode::out) { flags |= LIBSSH2_FXF_WRITE; if (opening_mode & openmode::in) { // in flag suppresses creation if (opening_mode & openmode::trunc) { // but trunk flag unsuppresses it again if (!(opening_mode & openmode::nocreate)) { // unless nocreate given in which case just truncate // existing flags |= LIBSSH2_FXF_CREAT; if (opening_mode & openmode::noreplace) { flags |= LIBSSH2_FXF_EXCL; } } else if (opening_mode & openmode::noreplace) { BOOST_THROW_EXCEPTION(std::invalid_argument( "Cannot combine nocreate and noreplace")); } // XXX: According to SFTP spec, shouldn't be able to have TRUNC // without CREAT but if it works, it works flags |= LIBSSH2_FXF_TRUNC; } } else { // Unlike the C and C++ file APIs, SFTP files opened only for // writing are not created if they do not already exist and are not // truncated if they do exists. Therefore we explicitly add the // CREAT and TRUNC flags to mirror the C++ fstream behaviour if (!(opening_mode & openmode::nocreate)) { flags |= LIBSSH2_FXF_CREAT; if (opening_mode & openmode::noreplace) { flags |= LIBSSH2_FXF_EXCL; } } else if (opening_mode & openmode::noreplace) { BOOST_THROW_EXCEPTION(std::invalid_argument( "Cannot combine nocreate and noreplace")); } if (opening_mode & openmode::app) { flags |= LIBSSH2_FXF_APPEND; } else { // XXX: According to SFTP spec, shouldn't be able to have TRUNC // without CREAT but if it works, it works flags |= LIBSSH2_FXF_TRUNC; } } } return flags; } inline boost::shared_ptr<::ssh::detail::file_handle_state> open_file(::ssh::detail::sftp_channel_state& sftp, const path& open_path, openmode::value opening_mode) { std::string path_string = open_path.native(); // Open with 644 permissions - good for non-directory files return boost::make_shared<::ssh::detail::file_handle_state>( boost::ref(sftp), // http://stackoverflow.com/a/1374266/67013 path_string.data(), path_string.size(), openmode_to_libssh2_flags(opening_mode), LIBSSH2_SFTP_S_IRUSR | LIBSSH2_SFTP_S_IWUSR | LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IROTH, LIBSSH2_SFTP_OPENFILE); } inline boost::shared_ptr<::ssh::detail::file_handle_state> open_input_file(::ssh::detail::sftp_channel_state& sftp, const path& open_path, openmode::value opening_mode) { // For input streams open files for input even if not given in open // flags. Matches standard library ifstream. return open_file(sftp, open_path, opening_mode | openmode::in); } inline boost::shared_ptr<::ssh::detail::file_handle_state> open_output_file(::ssh::detail::sftp_channel_state& sftp, const path& open_path, openmode::value opening_mode) { // For output streams open files for output even if not given in open // flags. Matches standard library ofstream. return open_file(sftp, open_path, static_cast( opening_mode | openmode::out)); } inline boost::iostreams::stream_offset seek(::ssh::detail::file_handle_state& handle, const path& open_path, boost::iostreams::stream_offset off, std::ios_base::seekdir way) { boost::iostreams::stream_offset new_position = 0; switch (way) { case std::ios_base::beg: new_position = off; break; case std::ios_base::cur: { // FIXME: possible to get integer overflow on addition? new_position = libssh2_sftp_tell64(handle.file_handle()) + off; break; } case std::ios_base::end: // MUST ACCESS SERVER { LIBSSH2_SFTP_ATTRIBUTES attributes = LIBSSH2_SFTP_ATTRIBUTES(); try { ::ssh::detail::file_handle_state::scoped_lock lock = handle.aquire_lock(); ::ssh::detail::libssh2::sftp::fstat( handle.session_ptr(), handle.sftp_ptr(), handle.file_handle(), &attributes, LIBSSH2_SFTP_STAT); } catch (boost::exception& e) { e << boost::errinfo_file_name(open_path.string()); throw; } new_position = attributes.filesize + off; break; } default: BOOST_THROW_EXCEPTION(std::invalid_argument("Unknown seek direction")); } if (new_position < 0) { BOOST_THROW_EXCEPTION( std::logic_error("Cannot seek before start of file")); } libssh2_sftp_seek64(handle.file_handle(), new_position); return new_position; } inline std::streamsize read(::ssh::detail::file_handle_state& handle, const path& open_path, char* buffer, std::streamsize buffer_size) { try { // This method is only allowed to return a read count less than the // requested read amount if the end-of-file has been reached. In other // words, non-blocking short reads are not allowed (see // http://bit.ly/1ixEagu and http://bit.ly/1ejYm2T). Therefore we loop // until all the given buffer has been filled or we reach EOF. ssize_t count = 0; do { ::ssh::detail::file_handle_state::scoped_lock lock = handle.aquire_lock(); ssize_t rc = ::ssh::detail::libssh2::sftp::read( handle.session_ptr(), handle.sftp_ptr(), handle.file_handle(), buffer + count, buffer_size - count); if (rc == 0) break; // EOF count += rc; } while (count < buffer_size); return count; } catch (boost::exception& e) { e << boost::errinfo_file_name(open_path.string()); throw; } } inline std::streamsize write(::ssh::detail::file_handle_state& handle, const path& open_path, const char* data, std::streamsize data_size) { try { // Despite it's signature, this method is not allowed to return a // written count less than the given write amount. The signature is the // way it is so that Boost.IOStreams devices can support non-blocking // behaviour in the future, but we use our devices to implement STL // streams which don't support non-blocking devices (see // http://bit.ly/1ixEagu and http://bit.ly/1ejYm2T). Therefore we loop // until all data is written. ssize_t count = 0; do { ::ssh::detail::file_handle_state::scoped_lock lock = handle.aquire_lock(); count += ::ssh::detail::libssh2::sftp::write( handle.session_ptr(), handle.sftp_ptr(), handle.file_handle(), data + count, data_size - count); } while (count < data_size); assert(count == data_size); return count; } catch (boost::exception& e) { e << boost::errinfo_file_name(open_path.string()); throw; } } const std::streamsize DEFAULT_BUFFER_SIZE = 1024 * 32; struct input_device_category : boost::iostreams::input_seekable, boost::iostreams::optimally_buffered_tag { }; struct output_device_category : boost::iostreams::output_seekable, boost::iostreams::optimally_buffered_tag { }; struct io_device_category : boost::iostreams::seekable, boost::iostreams::optimally_buffered_tag { }; /** * Allows setting buffer size on boost::iostreams::stream based streams. * * `boost::iostreams::stream` only forwards three constructor arguments so this * class is necessary to pass up the buffer size argument to the device. */ template class sftp_stream : public boost::iostreams::stream { public: // Using separate constructors rather than default arguments so they pick up // the defaults from the devices sftp_stream(sftp_filesystem& channel, const path& open_path) { open(Device(channel, open_path)); } sftp_stream(sftp_filesystem& channel, const path& open_path, openmode::value opening_mode) { open(Device(channel, open_path, opening_mode)); } sftp_stream(sftp_filesystem& channel, const path& open_path, openmode::value opening_mode, std::streamsize buffer_size) { open(Device(channel, open_path, opening_mode), buffer_size); } sftp_stream(sftp_filesystem& channel, const path& open_path, std::ios_base::openmode opening_mode) { open(Device(channel, open_path, opening_mode)); } sftp_stream(sftp_filesystem& channel, const path& open_path, std::ios_base::openmode opening_mode, std::streamsize buffer_size) { open(Device(channel, open_path, opening_mode), buffer_size); } // We pass the device to `open` rather than creating and passing it to the // stream it in the initialiser list because of a subtle consequence of // ios_base being a virtual base class (via virtual basic_ios) and // ios_base::init having to be called before ios_base destructor. // // If we initialise boost::iostreams::stream in the list but sftp_io_device // constructor throws an exception, we get an access violation because // ios_base is already constructed (virtual bases constructed first // irrespective of hierarchy) but the stream class constructor, which calls // ios_base::init, is not yet called. The exception prevents the stream // class constructor being called but causes ios_base to be destroyed. }; } class sftp_input_device : public boost::iostreams::device { public: sftp_input_device(sftp_filesystem& channel, const path& open_path, openmode::value opening_mode = openmode::in) : m_open_path(open_path), m_handle(detail::open_input_file(channel.sftp_ref(), m_open_path, opening_mode)) { } sftp_input_device(sftp_filesystem& channel, const path& open_path, std::ios_base::openmode opening_mode) : m_open_path(open_path), m_handle( detail::open_input_file(channel.sftp_ref(), m_open_path, detail::translate_flags(opening_mode))) { } std::streamsize optimal_buffer_size() const { return detail::DEFAULT_BUFFER_SIZE; } std::streamsize read(char* buffer, std::streamsize buffer_size) { return detail::read(*m_handle, m_open_path, buffer, buffer_size); } boost::iostreams::stream_offset seek(boost::iostreams::stream_offset off, std::ios_base::seekdir way) { return detail::seek(*m_handle, m_open_path, off, way); } private: path m_open_path; boost::shared_ptr m_handle; }; /** * Input file stream. * * File is opened according to `openmode` flags but always opened as if * `openmode::in` has been specified, regardless of whether it is. * * By default opened as if `openmode::in` is the only flag specified. File * always opened in binary mode. SFTP does not have a text mode. */ typedef detail::sftp_stream ifstream; class sftp_output_device : public boost::iostreams::device { public: sftp_output_device(sftp_filesystem& channel, const path& open_path, openmode::value opening_mode = openmode::out) : m_open_path(open_path), m_handle(detail::open_output_file(channel.sftp_ref(), m_open_path, opening_mode)) { } sftp_output_device(sftp_filesystem& channel, const path& open_path, std::ios_base::openmode opening_mode) : m_open_path(open_path), m_handle( detail::open_output_file(channel.sftp_ref(), m_open_path, detail::translate_flags(opening_mode))) { } std::streamsize optimal_buffer_size() const { return detail::DEFAULT_BUFFER_SIZE; } std::streamsize write(const char* data, std::streamsize data_size) { return detail::write(*m_handle, m_open_path, data, data_size); } boost::iostreams::stream_offset seek(boost::iostreams::stream_offset off, std::ios_base::seekdir way) { return detail::seek(*m_handle, m_open_path, off, way); } private: path m_open_path; boost::shared_ptr<::ssh::detail::file_handle_state> m_handle; }; /** * Output file stream. * * File is opened according to `openmode` flags but always opened as if * `openmode::out` has been specified, regardless of whether it is. * * By default opened as if `openmode::out` is the only flag specified. File * always opened in binary mode. SFTP does not have a text mode. */ typedef detail::sftp_stream ofstream; class sftp_io_device : public boost::iostreams::device { public: sftp_io_device(sftp_filesystem& channel, const path& open_path, openmode::value opening_mode = openmode::in | openmode::out) : m_open_path(open_path), m_handle( detail::open_file(channel.sftp_ref(), m_open_path, opening_mode)) { } sftp_io_device(sftp_filesystem& channel, const path& open_path, std::ios_base::openmode opening_mode) : m_open_path(open_path), m_handle(detail::open_file(channel.sftp_ref(), m_open_path, detail::translate_flags(opening_mode))) { } std::streamsize optimal_buffer_size() const { return detail::DEFAULT_BUFFER_SIZE; } std::streamsize read(char* buffer, std::streamsize buffer_size) { return detail::read(*m_handle, m_open_path, buffer, buffer_size); } std::streamsize write(const char* data, std::streamsize data_size) { return detail::write(*m_handle, m_open_path, data, data_size); } boost::iostreams::stream_offset seek(boost::iostreams::stream_offset off, std::ios_base::seekdir way) { return detail::seek(*m_handle, m_open_path, off, way); } private: path m_open_path; boost::shared_ptr<::ssh::detail::file_handle_state> m_handle; }; /** * Input/output file stream. * * By default opened as if `openmode::in` and `openmode::out` are both * specified. * * File always opened in binary mode. SFTP does not have a text mode. */ typedef detail::sftp_stream fstream; } } // namespace ssh::filesystem #endif ================================================ FILE: swish/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin # Street, Fifth Floor, Boston, MA 02110-1301 USA. add_subdirectory(connection) add_subdirectory(drop_target) add_subdirectory(forms) add_subdirectory(frontend) add_subdirectory(host_folder) add_subdirectory(nse) add_subdirectory(provider) add_subdirectory(remote_folder) add_subdirectory(shell) add_subdirectory(shell_folder) add_subdirectory(shell_folder/com_dll) add_subdirectory(versions) ================================================ FILE: swish/CoFactory.hpp ================================================ /** @file Mixin class which gives CComObjects a creator of AddReffed instances. @if license Copyright (C) 2008, 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #pragma once #include "atl.hpp" // Common ATL setup namespace swish { template class CCoFactory { public: /** * Static factory method. * * This creator method provides a CComObject-based class with a way to * create instances with exception-safe lifetimes. The created * instance is AddReffed, unlike those create by CreateInstance which * have a reference count of 0. * * @returns Smart pointer to the CComObject-based COM object. * @throws com_error if creation fails. */ static ATL::CComPtr CreateCoObject() throw(...) { ATL::CComObject *pObject = NULL; HRESULT hr = ATL::CComObject::CreateInstance(&pObject); ATLENSURE_SUCCEEDED(hr); return pObject; } }; }; // namespace swish ================================================ FILE: swish/atl.hpp ================================================ /** @file Set up ATL support. @if license Copyright (C) 2009, 2010, 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ /** * @file * * Any files that need ATL support should include this header to ensure * that ATL is set up consistently across different files and projects. * * This file must be included before any other ATL headers as they depend * on and already being included. Also, any macros * in this file must be allowed to affect the behaviour of other parts of * ATL. This is contrary to the usual top-down include order. */ #pragma once #define _ATL_FREE_THREADED #ifdef _ATL_SINGLE_THREADED #error "_ATL_SINGLE_THREADED conflicts with _ATL_FREE_THREADED" #endif #ifdef _ATL_APARTMENT_THREADED #error "_ATL_APARTMENT_THREADED conflicts with _ATL_FREE_THREADED" #endif #define _ATL_NO_AUTOMATIC_NAMESPACE // Make some CString constructors explicit #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // (U)LONGLONG CComVariant support #define _ATL_SUPPORT_VT_I8 #ifdef _DEBUG //#define _ATL_DEBUG_QI //#define _ATL_DEBUG_INTERFACES #endif #define _ATL_CUSTOM_THROW #include // HRESULT __declspec(noreturn) inline void AtlThrow(HRESULT hr); #include // base ATL classes #include #include // com_error /** * Custom ATL thrower which throws std::exception derived exceptions. */ __declspec(noreturn) inline void AtlThrow(HRESULT hr) { throw comet::com_error(hr); }; ================================================ FILE: swish/connection/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES authenticated_session.cpp connection_spec.cpp running_session.cpp session_manager.cpp session_pool.cpp authenticated_session.hpp connection_spec.hpp running_session.hpp session_manager.hpp session_pool.hpp) add_library(connection ${SOURCES}) hunter_add_package(Comet) hunter_add_package(Washer) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) target_link_libraries(connection PUBLIC ssh provider Washer::washer Comet::comet ${Boost_LIBRARIES}) ================================================ FILE: swish/connection/authenticated_session.cpp ================================================ /** @file SSH session authentication. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #include "authenticated_session.hpp" #include "swish/utils.hpp" // WideStringToUtf8String #include // openssh_knownhost_collection #include #include // sftp_filesystem #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // bstr_t #include // com_error #include // path #include // ofstream #include // BOOST_FOREACH #include #include #include #include // errc #include #include // BOOST_THROW_EXCEPTION #include #include #include // logic_error #include #include using swish::connection::authenticated_session; using swish::connection::running_session; using swish::utils::WideStringToUtf8String; using swish::utils::home_directory; using ssh::hexify; using ssh::host_key; using ssh::knownhost_search_result; using ssh::openssh_knownhost_collection; using ssh::session; using ssh::filesystem::sftp_filesystem; using comet::bstr_t; using comet::com_error; using comet::com_ptr; using boost::filesystem::path; using boost::filesystem::ofstream; using boost::function; using boost::move; using boost::mutex; using boost::optional; namespace errc = boost::system::errc; using boost::system::system_error; using std::exception; using std::logic_error; using std::pair; using std::string; using std::vector; using std::wstring; namespace swish { namespace connection { namespace { const path known_hosts_path = home_directory() / L".ssh" / L"known_hosts"; void verify_host_key( const wstring& host, running_session& session, com_ptr consumer) { assert(consumer); ssh::session& sess(session.get_session()); string utf8_host = WideStringToUtf8String(host); host_key key = sess.hostkey(); bstr_t hostkey_algorithm = key.algorithm_name(); bstr_t hostkey_hash = hexify(key.md5_hash()); assert(!hostkey_hash.empty()); assert(!hostkey_algorithm.empty()); // YUK YUK YUK: Accessing and modifying host key files should not be // here. It should be done by the callback. // make sure known_hosts file exists create_directories(known_hosts_path.parent_path()); ofstream(known_hosts_path, std::ios::app); openssh_knownhost_collection hosts(known_hosts_path); knownhost_search_result result = hosts.find(utf8_host, key); if (result.mismatch()) { HRESULT hr = consumer->OnHostkeyMismatch( bstr_t(host).in(), hostkey_hash.in(), hostkey_algorithm.in()); if (hr == S_OK) { update(hosts, utf8_host, key, result); // update known_hosts hosts.save(known_hosts_path); } else if (hr == S_FALSE) return; // continue but don't add else BOOST_THROW_EXCEPTION( com_error("User aborted on host key mismatch", E_ABORT)); } else if (result.not_found()) { HRESULT hr = consumer->OnHostkeyUnknown( bstr_t(host).in(), hostkey_hash.in(), hostkey_algorithm.in()); if (hr == S_OK) { add(hosts, utf8_host, key); // add to known_hosts hosts.save(known_hosts_path); } else if (hr == S_FALSE) return; // continue but don't add else BOOST_THROW_EXCEPTION( com_error("User aborted on unknown host key", E_ABORT)); } } BOOST_SCOPED_ENUM_START(authentication_result) { authenticated, aborted, try_remaining_methods }; BOOST_SCOPED_ENUM_END /** * Authenticates with remote host by asking the user to supply a password. * * This uses the callback to the SftpConsumer to obtain the password from * the user. If the password is wrong or other error occurs, the user is * asked for the password again. This repeats until the user supplies a * correct password or cancels the request. * * @throws `std::exception` * if authentication fails for an unexpected reason, in other words, * a reason other than the user cancelling the authentication. * * @returns * `authenticated` if authentication was successful, * `aborted` if the user aborted early. * @note that unsuccessful is not a return value as the function keeps * re-prompting until successful or cancelled. */ BOOST_SCOPED_ENUM(authentication_result) password_authentication( const string& utf8_username, running_session& session, com_ptr consumer) { // Loop until successfully authenticated or user cancels for(;;) { optional password = consumer->prompt_for_password(); if (!password) { return authentication_result::aborted; } string utf8_password = WideStringToUtf8String(*password); if (session.get_session().authenticate_by_password( utf8_username, utf8_password)) { return authentication_result::authenticated; } // TODO: handle password change callback here } } namespace { class user_aborted_authentication : public virtual boost::exception, public std::runtime_error { public: user_aborted_authentication() : boost::exception(), std::runtime_error("User aborted authentication") {} }; /** * Delegates challenge-response to a consumer. */ class consumer_responder { public: consumer_responder(com_ptr consumer) : m_consumer(consumer) {} template vector operator()( const string& title, const string& instructions, const PromptRange& prompts) { optional> responses = m_consumer->challenge_response(title, instructions, prompts); if (responses) { return *responses; } else { BOOST_THROW_EXCEPTION(user_aborted_authentication()); } } private: com_ptr m_consumer; }; } /** * Authenticates with remote host by challenge-response interaction. * * This uses the ISftpConsumer callback to challenge the user for various * pieces of information (usually just their password). * * @returns * `authenticated` if authentication successful, * `aborted` if the `consumer` reports that the user aborted authentication, * `try_remaining_methods` is authentication failed in a way that makes * sense to not give up completely: i.e. if the server positively rejects * authentication without even calling the responder. * * @throws `boost::system::system_error` * if unexpected SSH-related failure while trying to authenticate or * @throws `std::exception` * if authentication fails for an unexpected reason, in other words, * a reason other than the user cancelling the authentication. * If authentication fails because the `consumer` threw an exception, * that exception will be the one throw out of this method. * @note that unsuccessful authentication is not a return value as the function * keeps re-prompting until successful or cancelled. */ BOOST_SCOPED_ENUM(authentication_result) keyboard_interactive_authentication( const string& utf8_username, running_session& session, com_ptr consumer) { // Loop until successfully authenticated or user cancels. try { while (!session.get_session().authenticate_interactively( utf8_username, consumer_responder(consumer))) {} } catch (const system_error& e) { if (e.code() == errc::permission_denied) { // Authentication was positively rejected by the server but not // because of anything our responder did (which would have simply // caused the while loop to end above). This is most likely the // server lying about supporting kb-int authentication. // Cygwin OpenSSH does this. // // Although an error, we choose to silently ignore this one and // move on to try other authentication methods. return authentication_result::try_remaining_methods; } else { throw; } } catch (const user_aborted_authentication&) { // Unlike simple password authentication, the user cancelling an // interactive authentication isn't signalled by the return code // because interactive authentications can't actually be aborted. // Instead we find out about an abortion when authentication fails and // the responder threw an exception. Therefore we catch our custom // "user aborted" exception here and translate that into the boolean // result. return authentication_result::aborted; } assert(session.get_session().authenticated()); // Double-check return authentication_result::authenticated; } BOOST_SCOPED_ENUM(authentication_result) public_key_file_based_authentication( const string& utf8_username, running_session& session, com_ptr consumer) { assert(consumer); optional> key_files = consumer->key_files(); if (key_files) { // TODO: unlock public key using passphrase session.get_session().authenticate_by_key_files( utf8_username, key_files->second, key_files->first, ""); assert(session.get_session().authenticated()); return authentication_result::authenticated; } else { return authentication_result::try_remaining_methods; } } BOOST_SCOPED_ENUM(authentication_result) public_key_agent_authentication( const string& utf8_username, running_session& session, com_ptr consumer) { try { BOOST_FOREACH( ssh::identity key, session.get_session().agent_identities()) { try { key.authenticate(utf8_username); return authentication_result::authenticated; } catch (const exception&) { /* Ignore and try the next */ } } } catch(const exception&) { /* No agent running probably. Either way, give up. */ } // None of the agent identities worked. Sob. Back to passwords then. return authentication_result::try_remaining_methods; } /** * Tries to authenticate the user with the remote server. * * The remote server is queried for which authentication methods it supports * and these are tried one at time until one succeeds in the order: * public-key, keyboard-interactive, plain password. * * @throws com_error if authentication fails: * - E_ABORT if user cancelled the operation (via ISftpConsumer) * - E_FAIL otherwise */ void authenticate_user( const wstring& user, running_session& session, com_ptr consumer) { assert(!user.empty()); assert(user[0] != '\0'); string utf8_username = WideStringToUtf8String(user); vector method_names = session.get_session().authentication_methods(utf8_username); // This test must come _after_ fetching the methods as that is what may // prompt the premature authentication if (session.get_session().authenticated()) { // Golly. What a silly server. return; } else if (method_names.empty()) { BOOST_THROW_EXCEPTION( std::exception("No supported authentication methods found")); } typedef function< BOOST_SCOPED_ENUM(authentication_result)( const string&, running_session&, com_ptr)> method; vector authentication_methods; // The order of adding the methods is important; some are preferred over // others. Added in descending order of preference. if (find(method_names.begin(), method_names.end(), "publickey") != method_names.end()) { // This old way is only kept around to support the tests. Its almost // useless for anything else as we don't pass the 'consumer' enough // information to identify which key to use. authentication_methods.push_back(public_key_file_based_authentication); // And now the nice new way using agents. authentication_methods.push_back(public_key_agent_authentication); } if (find(method_names.begin(), method_names.end(), "keyboard-interactive") != method_names.end()) { authentication_methods.push_back(keyboard_interactive_authentication); } if (find(method_names.begin(), method_names.end(), "password") != method_names.end()) { authentication_methods.push_back(password_authentication); } BOOST_FOREACH(method& auth_attempt, authentication_methods) { switch (auth_attempt(utf8_username, session, consumer)) { case authentication_result::authenticated: return; case authentication_result::aborted: BOOST_THROW_EXCEPTION( com_error("User aborted authentication", E_ABORT)); case authentication_result::try_remaining_methods: continue; default: BOOST_THROW_EXCEPTION( logic_error("Unrecognised authentication result")); } } BOOST_THROW_EXCEPTION( com_error("No authentication method succeeded", E_FAIL)); } running_session create_and_authenticate( const wstring& host, unsigned int port, const wstring& user, com_ptr consumer) { running_session session(host, port); verify_host_key(host, session, consumer); // Legal to fail here, e.g. user refused to accept host key authenticate_user(user, session, consumer); // Legal to fail here, e.g. wrong password/key assert(session.get_session().authenticated()); return move(session); } } authenticated_session::authenticated_session( const wstring& host, unsigned int port, const wstring& user, com_ptr consumer) : m_session(create_and_authenticate(host, port, user, consumer)), m_filesystem(m_session.get_session().connect_to_filesystem()) {} authenticated_session::authenticated_session( BOOST_RV_REF(authenticated_session) other) : m_session(move(other.m_session)), m_filesystem(move(other.m_filesystem)) {} authenticated_session& authenticated_session::operator=( BOOST_RV_REF(authenticated_session) other) { swap(authenticated_session(move(other)), *this); return *this; } session& authenticated_session::get_session() { return m_session.get_session(); } sftp_filesystem& authenticated_session::get_sftp_filesystem() { return m_filesystem; } bool authenticated_session::is_dead() { return m_session.is_dead(); } void swap(authenticated_session& lhs, authenticated_session& rhs) { boost::swap(lhs.m_session, rhs.m_session); boost::swap(lhs.m_filesystem, rhs.m_filesystem); } }} // namespace swish::connection ================================================ FILE: swish/connection/authenticated_session.hpp ================================================ /** @file SSH session authentication. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SWISH_CONNECTION_AUTHENTICATED_SESSION_HPP #define SWISH_CONNECTION_AUTHENTICATED_SESSION_HPP #include "swish/connection/running_session.hpp" #include "swish/provider/sftp_provider.hpp" // ISftpConsumer #include #include #include // com_ptr #include // BOOST_RV_REF, BOOST_MOVABLE_BUT_NOT_COPYABLE #include #include #include namespace swish { namespace connection { /** * SSH session authenticated with the server. * * The point of this class is remove uncertainty as to whether the session * is usable. Every instance is successfully authenticated with the server * and has a running SFTP channel. * * XXX: Maybe the SFTP channel part should be separated. It's unclear if Swish * ever needs the two concepts separately. */ class authenticated_session : private boost::noncopyable { BOOST_MOVABLE_BUT_NOT_COPYABLE(authenticated_session) public: /* template authenticated_session( const std::wstring& host, const std::wstring& user, unsigned int port, Authentication& authentication) : m_session(host, port) { boost::mutex::scoped_lock(m_session.aquire_lock()); ssh::session session = m_session.get_session(); authentication.approve_host_key(session.host_key()); } */ /** * Creates and authenticates an SSH session and start SFTP channel. * * @param host * Name of the remote host to connect the session to. * @param port * Port on the remote host to connect to. * @param user * User to authenticate as. * @param consumer * Callback used for user-interaction needed to authenticate, such as * requesting a password. * * @throws com_error if any part of this process fails: * - E_ABORT if user cancelled the operation (via ISftpConsumer) * - E_FAIL otherwise */ authenticated_session( const std::wstring& host, unsigned int port, const std::wstring& user, comet::com_ptr consumer); /** * Move constructor. */ authenticated_session(BOOST_RV_REF(authenticated_session) other); /** * Move assignment. */ authenticated_session& operator=(BOOST_RV_REF(authenticated_session) other); bool is_dead(); // This class really represents an SFTP channel rather than an // authenticated session. Clients only use the session accessors // below to report errors and this will be replaced by the wrapper // sftp code which handles this internally. Therefore we will be able // to remove these accessors from the public interface. ssh::session& get_session(); ssh::filesystem::sftp_filesystem& get_sftp_filesystem(); friend void swap(authenticated_session& lhs, authenticated_session& rhs); private: running_session m_session; ssh::filesystem::sftp_filesystem m_filesystem; }; }} // namespace swish::connection #endif ================================================ FILE: swish/connection/connection.vcproj ================================================ ================================================ FILE: swish/connection/connection_spec.cpp ================================================ /** @file Specify a connection. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "connection_spec.hpp" #include "swish/connection/authenticated_session.hpp" #include // BOOST_THROW_EXCEPTION #include // tie #include // < #include // invalid_argument using comet::com_ptr; using boost::tie; using std::invalid_argument; using std::wstring; namespace swish { namespace connection { connection_spec::connection_spec( const wstring& host, const wstring& user, const int port) : m_host(host), m_user(user), m_port(port) { if (host.empty()) BOOST_THROW_EXCEPTION(invalid_argument("Host name required")); if (user.empty()) BOOST_THROW_EXCEPTION(invalid_argument("User name required")); } authenticated_session connection_spec::create_session( com_ptr consumer) const { return authenticated_session(m_host, m_port, m_user, consumer); } bool connection_spec::operator<(const connection_spec& other) const { // Reusing comparison from tuples - no point reinventing the wheel // See: http://stackoverflow.com/q/6218812/67013 return tie(m_host, m_user, m_port) < tie(other.m_host, other.m_user, other.m_port); } }} // namespace swish::connection ================================================ FILE: swish/connection/connection_spec.hpp ================================================ /** @file Specify a connection. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_CONNECTION_CONNECTION_SPEC_HPP #define SWISH_CONNECTION_CONNECTION_SPEC_HPP #pragma once #include "swish/provider/sftp_provider.hpp" // ISftpConsumer #include // com_ptr #include namespace swish { namespace connection { // Forward-declaring this here, rather than including the header, prevents // test/remote_folder/remote_commands_test.cpp from crashing the compiler by // letting that file not include the ssh_error.hpp file that uses lexical_cast // and through there. I don't know why. Just go with it. class authenticated_session; /** * Represents specification for a connection to an SFTP server. * * Instances of this class are just recipes for connecting, they are *not* * the running connections themselves. Running connections are called * sessions and can be created and queried via this class. */ class connection_spec { public: connection_spec( const std::wstring& host, const std::wstring& user, int port); /** * Returns a new SFTP session based on this specification. * * The returned session is authenticated ready for use. Any * interaction needed to authenticate is performed via the `consumer` * callback. */ authenticated_session create_session( comet::com_ptr consumer) const; bool operator<(const connection_spec& other) const; private: std::wstring m_host; std::wstring m_user; int m_port; }; /** * Interface for connection making logic. * * Connection strategy is not uniform. Sometime we want to establish a running * connection and pass that into an object so that it can use it at will. * Other times we want to the connection to be established---an activity that * may disturb the user with dialogues---as late as possible just before it * will be used. * * This interface abstracts such decisions behind a uniform way to request a * connection. */ /*class connection_maker { public: virtual ~connection_maker() = 0; virtual boost::shared_ptr provider() = 0; virtual comet::com_ptr consumer() = 0; };*/ }} // namespace swish::remote_folder #endif ================================================ FILE: swish/connection/interruptable_session.hpp ================================================ /** @file A session that can die mid-way through an operation. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_CONNECTION_INTERRUPTABLE_SESSION_HPP #define SWISH_CONNECTION_INTERRUPTABLE_SESSION_HPP #include "swish/connection/connection_spec.hpp" #include #include // auto_ptr namespace swish { namespace connection { class interruptable_session { public: ssh::host_key hostkey() const; std::vector authentication_methods( const std::string& username); bool authenticated() const; bool authenticate_by_password( const std::string& username, const std::string& password); template bool authenticate_interactively( const std::string& username, ChallengeResponder responder) { return m_session->authenticate_interactively(username, responder); } void authenticate_by_key_files( const std::string& username, const boost::filesystem::path& public_key, const boost::filesystem::path& private_key, const std::string& passphrase); ssh::agent_identities agent_identities(); ssh::filesystem::sftp_filesystem connect_to_filesystem(); /** * Forcibly disconnect the session. * * Causes all future uses of the object to throw exceptions. */ void terminate(); private: std::auto_ptr m_session; }; }} #endif ================================================ FILE: swish/connection/running_session.cpp ================================================ // Copyright 2008, 2009, 2010, 2011, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "running_session.hpp" #include "swish/remotelimits.h" #include "swish/debug.hpp" // Debug macros #include "swish/port_conversion.hpp" // port_to_string #include "swish/utils.hpp" // WideStringToUtf8String #include #include // sftp_filesystem #include // Boost sockets: only used for name resolving #include #include // BOOST_THROW_EXCEPTION #include #include using swish::port_to_string; using swish::utils::WideStringToUtf8String; using ssh::session; using ssh::filesystem::sftp_filesystem; using boost::asio::error::host_not_found; using boost::asio::io_service; using boost::asio::ip::tcp; using boost::move; using boost::shared_ptr; using boost::system::get_system_category; using boost::system::system_error; using boost::system::error_code; using std::string; using std::wstring; namespace swish { namespace connection { namespace { /** * Connect a socket to the given port on the given host. * * @throws A boost::system::system_error if there is a failure. */ void connect_socket_to_host(tcp::socket& socket, const wstring& host, unsigned int port, io_service& io) { assert(!host.empty()); assert(host[0] != L'\0'); // Convert host address to a UTF-8 string string host_name = WideStringToUtf8String(host); tcp::resolver resolver(io); typedef tcp::resolver::query Lookup; Lookup query(host_name, port_to_string(port)); tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); tcp::resolver::iterator end; error_code error = host_not_found; while (error && endpoint_iterator != end) { socket.close(); socket.connect(*endpoint_iterator++, error); } if (error) BOOST_THROW_EXCEPTION(system_error(error)); } // We have to have this weird function because Boost.ASIO doesn't // support Boost.Move rvalue-emulation. // // Ideally, connect_socket_to_host would return the connected sockets // so could be used in the running_session initialiser list below. And // the the initialiser for m_session would have a valid socket to use. // // But as we can't return the valid socket, we have to connect it *during* // the m_session initialisation, *after* the m_socket initialisation. Yuk! ssh::session session_on_socket(tcp::socket& socket, const wstring& host, unsigned int port, io_service& io, const string& disconnection_message) { connect_socket_to_host(socket, host, port, io); return ssh::session(socket.native(), disconnection_message); } } running_session::running_session(const wstring& host, unsigned int port) : m_io(new io_service(0)), m_socket(new tcp::socket(*m_io)), m_session(session_on_socket(*m_socket, host, port, *m_io, "Swish says goodbye.")) { } running_session::running_session(BOOST_RV_REF(running_session) other) : m_io(move(other.m_io)), m_socket(move(other.m_socket)), m_session(move(other.m_session)) { } running_session& running_session::operator=(BOOST_RV_REF(running_session) other) { swap(running_session(move(other)), *this); return *this; } session& running_session::get_session() { return m_session; } bool running_session::is_dead() { fd_set socket_set; FD_ZERO(&socket_set); FD_SET(m_socket->native(), &socket_set); TIMEVAL tv = TIMEVAL(); int rc = ::select(1, &socket_set, NULL, NULL, &tv); if (rc < 0) BOOST_THROW_EXCEPTION( system_error(::WSAGetLastError(), get_system_category())); return rc != 0; } void swap(running_session& lhs, running_session& rhs) { boost::swap(lhs.m_io, rhs.m_io); boost::swap(lhs.m_socket, rhs.m_socket); boost::swap(lhs.m_session, rhs.m_session); } } } // namespace swish::connection ================================================ FILE: swish/connection/running_session.hpp ================================================ /** @file C++ wrapper round Libssh2 SSH and SFTP session creation. @if license Copyright (C) 2008, 2009, 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SWISH_CONNECTION_RUNNING_SESSION_HPP #define SWISH_CONNECTION_RUNNING_SESSION_HPP #include #include // Boost sockets #include // BOOST_RV_REF, BOOST_MOVABLE_BUT_NOT_COPYABLE #include #include // auto_ptr #include namespace swish { namespace connection { /** * An SSH session connected to a port on a server. * * The session may or may not be authenticated. * * The point of this class is to add host resolution and death detection * to the existing libssh2 C++ binding session object. */ class running_session : private boost::noncopyable { BOOST_MOVABLE_BUT_NOT_COPYABLE(running_session) public: /** * Connect to host server and start new SSH connection on given port. */ running_session(const std::wstring& host, unsigned int port); /** * Move constructor. */ running_session(BOOST_RV_REF(running_session) other); /** * Move assignment. */ running_session& operator=(BOOST_RV_REF(running_session) other); /** * Has the connection broken since we connected? * * This only gives the correct answer as long as we're not expecting data * to arrive on the socket. select()ing a silent socket should return 0. * If it doesn't, it indicates that the connection is broken. * * XXX: we could double-check this by reading from the socket. It would return * 0 if the socket is closed. * * @see http://www.libssh2.org/mail/libssh2-devel-archive-2010-07/0050.shtml */ bool is_dead(); ssh::session& get_session(); friend void swap(running_session& lhs, running_session& rhs); private: // Must use auto_ptr for these members to make our class movable because // Boost.ASIO doesn't support move emulation std::auto_ptr m_io; ///< Boost IO system std::auto_ptr m_socket; ///< TCP/IP socket to remote host ssh::session m_session; ///< libssh2 session }; }} // namespace swish::connection #endif ================================================ FILE: swish/connection/session_manager.cpp ================================================ /** @file Reservation system for sessions. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "session_manager.hpp" #include "swish/connection/session_pool.hpp" #include #include // seconds #include #include #include #include #include // call_once #include #include #include // auto_ptr #include #include using comet::com_ptr; using boost::adaptors::transformed; using boost::bind; using boost::call_once; using boost::condition_variable; using boost::function; using boost::mutex; using boost::noncopyable; using boost::once_flag; using boost::posix_time::seconds; using boost::uuids::random_generator; using boost::uuids::uuid; using std::auto_ptr; using std::list; using std::map; using std::string; using std::vector; namespace swish { namespace connection { namespace { class task_registration { public: // We tag the registration with a UUID because it, and its copies, must // be uniquely identifiable. The task name is not enough as many tasks // make share a name. We can't just use the object address, though, // because copies must be equal. task_registration( const string& task_name, const connection_spec& specification) : m_tag(random_generator()()), m_task_name(task_name), m_specification(specification) {} // Copies take same tag bool operator==(const task_registration& other) const { return m_tag == other.m_tag; } string name() const { return m_task_name; } // So that unregistering doesn't have to search all unrelated connection's // tasks to find the matching task ID connection_spec specification() const { return m_specification; } private: uuid m_tag; string m_task_name; connection_spec m_specification; }; } // Hides the implementation details from the session_manager.hpp file. class session_reservation_impl : private boost::noncopyable { public: session_reservation_impl( authenticated_session& session, const function& unreserve) : m_session(session), m_unreserve(unreserve) {} ~session_reservation_impl() { m_unreserve(); } authenticated_session& session() { return m_session; } private: authenticated_session& m_session; function m_unreserve; }; namespace { // Purpose: to maintain the book of reservations in an orderly // fashion. This means cleaning out entries for old connection_specs that // don't have any more tasks class reservations_ledger { typedef map> reservations_mapping; public: void new_reservation( const connection_spec& specification, const task_registration& task) { m_reservations[specification].push_back(task); } vector reservations_for_connection( const connection_spec& specification) const { reservations_mapping::const_iterator pos = m_reservations.find(specification); if (pos == m_reservations.end()) { return vector(); } else { return vector( pos->second.begin(), pos->second.end()); } } void unreserve(const task_registration& task) { list& connection_registrations = m_reservations[task.specification()]; connection_registrations.remove(task); // To stop us building up a map full of empty lists for connections // no longer in use, we remove the connection entry once it has // no more tasks if (connection_registrations.empty()) { m_reservations.erase(m_reservations.find(task.specification())); } } private: reservations_mapping m_reservations; }; string extract_task_name(const task_registration& task) { return task.name(); } // Hides the implementation details from the session_manager.hpp file. class session_manager_impl { public: bool has_session(const connection_spec& specification) const { return session_pool().has_session(specification); } session_reservation reserve_session( connection_spec specification, com_ptr consumer, const std::string& task_name) { task_registration task_id(task_name, specification); // Locking just before getting the session from the pool to make sure // another thread can't disconnect it just as we are about to become // first and only reservation (if there were other reservations // already, it couldn't get disconnected regardless) mutex::scoped_lock lock(m_reservations_guard); authenticated_session& session = session_pool().pooled_session(specification, consumer); m_reservations.new_reservation(specification, task_id); m_reservations_changed.notify_all(); return session_reservation( new session_reservation_impl( session, bind(&session_manager_impl::unreserve_session, this, task_id))); } void disconnect_session( const connection_spec& specification, session_manager::progress_callback notification_sink) { // Lock here so that no new reservations can be made once we've decided // to disconnect this one, until we disconnect it // Although we lock reservations of ALL sessions, not just this one, // it's not a big problem because we quickly unlock them if waiting // for tasks to unreserve this one. If not waiting for tasks, // disconnecting the session is quick so also not a problem in practice. mutex::scoped_lock lock(m_reservations_guard); bool proceed_with_disconnection = wait_for_remaining_uses( specification, notification_sink, lock); if (proceed_with_disconnection) { session_pool().remove_session(specification); } } private: bool wait_for_remaining_uses( const connection_spec& specification, session_manager::progress_callback notification_sink, mutex::scoped_lock& lock) { while (true) { vector reservations = m_reservations.reservations_for_connection(specification); if (reservations.empty()) { // We notify the callback that tasks have completed so it can // shut down any progress UI. // Ideally, we would use a separate no-argument overload for // this, but that requires some way to overload // boost::functions. Basically, we need full type erasure notification_sink(vector()); return true; } // The callback controls whether we continue waiting or whether // we abort so that the user's UI isn't blocked else if (notification_sink( reservations | transformed(extract_task_name))) { // It is important to use a timed wait because we need to // respond to cancellation promptly. // If we used a regular wait we would only consult the user // callback, and notice that the user had cancelled, when the // number of tasks waiting changed. This may be infrequent. // For a single long-running task, that would be the same as // preventing the user cancelling at all. // It is important that we wait using a lock on the same mutex // as the thread changing the reservations. If there is only // one reservation and it goes away because its task completes // on another thread, that thread must not be able to try // and notify us of the change between where we check for // empty reservations (above) and where we wait for empty // reservations (below). If that could happen, the wait would // have missed the final notification of end-of-reservations. // (see http://stackoverflow.com/a/6924160/67013). // // It's not a fatal problem, because the wait uses a // timeout, but we should still avoid it. m_reservations_changed.timed_wait( lock, boost::posix_time::seconds(3)); } else { return false; } } } // Used by session_registration to unregister the session when that // ticket object goes out of scope void unreserve_session(const task_registration& task_id) { mutex::scoped_lock lock(m_reservations_guard); m_reservations.unreserve(task_id); } session_manager_impl() {}; mutex m_reservations_guard; reservations_ledger m_reservations; condition_variable m_reservations_changed; public: static session_manager_impl& get() { call_once(m_initialise_once, do_init); return *m_instance; } static void do_init() { m_instance.reset(new session_manager_impl()); } static once_flag m_initialise_once; static auto_ptr m_instance; }; once_flag session_manager_impl::m_initialise_once; auto_ptr session_manager_impl::m_instance; } session_reservation::session_reservation(session_reservation_impl* pimpl) : m_pimpl(pimpl) {} session_reservation::session_reservation( BOOST_RV_REF(session_reservation) other) : m_pimpl(other.m_pimpl) { other.m_pimpl = NULL; } session_reservation& session_reservation::operator=( BOOST_RV_REF(session_reservation) other) { if (&other != this) { delete m_pimpl; m_pimpl = other.m_pimpl; other.m_pimpl = NULL; } return *this; } session_reservation::~session_reservation() { delete m_pimpl; } authenticated_session& session_reservation::session() { return m_pimpl->session(); } session_reservation session_manager::reserve_session( const connection_spec& specification, com_ptr consumer, const string& task_name) { return session_manager_impl::get().reserve_session( specification, consumer, task_name); } bool session_manager::has_session(const connection_spec& specification) { return session_manager_impl::get().has_session(specification); } void session_manager::disconnect_session( const connection_spec& specification, progress_callback notification_sink) { return session_manager_impl::get().disconnect_session( specification, notification_sink); } }} ================================================ FILE: swish/connection/session_manager.hpp ================================================ /** @file Reservation system for sessions. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_CONNECTION_SESSION_FACTORY_HPP #define SWISH_CONNECTION_SESSION_FACTORY_HPP #include "swish/connection/authenticated_session.hpp" #include "swish/connection/connection_spec.hpp" #include "swish/provider/sftp_provider.hpp" // ISftpConsumer #include #include #include // BOOST_RV_REF, BOOST_MOVABLE_BUT_NOT_COPYABLE #include #include #include namespace swish { namespace connection { class session_reservation_impl; /** * Ticket that prevents a session being disconnected. * * A caller may use a session if-and-only-if they a ticket for it. * Using a session without a ticket may lead to the session being * destroyed at an unexpected moment and is undefined behaviour. */ class session_reservation : private boost::noncopyable { BOOST_MOVABLE_BUT_NOT_COPYABLE(session_reservation) public: /** * Move constructor. */ session_reservation(BOOST_RV_REF(session_reservation) other); /** * Move-assignment. */ session_reservation& operator=(BOOST_RV_REF(session_reservation) other); /** * Releases session reservation. */ ~session_reservation(); /** * Returns reference to reserved session. * * Only guaranteed valid for the lifetime of this reservation (or any * moved-to destinations). The reference must not be stored for use * after this reservation is destroyed. */ authenticated_session& session(); session_reservation(session_reservation_impl* pimpl); private: session_reservation_impl* m_pimpl; }; // ALL Swish sessions (except in unit tests) must be created through this // factory to register their interest so that the disconnection code knows // which, if any, tasks are preventing disconnection. class session_manager { public: typedef boost::any_range< std::string, boost::forward_traversal_tag, std::string, std::ptrdiff_t> task_name_range; typedef boost::function progress_callback; /** * Register interest in a session. * * Caller receives a ticket containing a reference to the session. The * session cannot be disconnected until the ticket is destroyed so callers * should hold tickets for the minimum amount of time. * * The session and any objects it creates are only valid for the lifetime * of the ticket. The caller must not hold a reference to the session or * its createes after the reservation is destroyed as call to * `disconnect_session` will disconnect and destroy the session. Any * subsequent uses of those references would cause a crash. */ session_reservation reserve_session( const connection_spec& specification, comet::com_ptr consumer, const std::string& task_name); /** * Is a connection with the given specification already connected? * * Indicates whether the session matches one already running or whether * the session would need to to be created anew, should the caller decide to * call `reserve_session`. */ bool has_session(const connection_spec& specification); /** * Disconnect and destroy the session matching the specification. * * If tasks have reserved the session, the call will block until they * all give up their tickets. The `notification_sink` will be called: * - initially, with the names of the pending tasks * - again, each time a pending task gives up its reservation * - with an empty range when there are no more (or never were any) pending * tasks */ void disconnect_session( const connection_spec& specification, progress_callback notification_sink); }; }} #endif ================================================ FILE: swish/connection/session_pool.cpp ================================================ /** @file Pool of reusuable SFTP connections. @if license Copyright (C) 2007, 2008, 2009, 2010, 2011, 2013, 2014 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "session_pool.hpp" // Using ptr_map because move-aware map isn't usable with C++03 #include //#include // move-aware map #include #include // call_once #include // auto_ptr using swish::provider::sftp_provider; using comet::com_ptr; using boost::call_once; using boost::container::map; using boost::mutex; using boost::once_flag; using std::auto_ptr; namespace swish { namespace connection { namespace { /** * Hides the implementation details from the session_pool.hpp file. */ class session_pool_impl { // Using ptr_map because move-aware map isn't usable with C++03 // (http://bit.ly/1jP9BDL, https://svn.boost.org/trac/boost/ticket/6618) typedef boost::ptr_map pool_mapping; //typedef boost::container::map // pool_mapping; public: static session_pool_impl& get() { call_once(m_initialise_once, do_init); return *m_instance; } static void destroy() { m_instance.reset(); } authenticated_session& pooled_session( connection_spec specification, com_ptr consumer) { mutex::scoped_lock lock(m_session_pool_guard); pool_mapping::iterator session = m_sessions.find(specification); if (session != m_sessions.end()) { // Dead sessions are replaced in the pool so that we always serve // something usable if (session->second->is_dead()) { m_sessions.replace( session, new authenticated_session( specification.create_session(consumer))); } } else { session = m_sessions.insert( specification, new authenticated_session( specification.create_session(consumer))).first; } return *(session->second); } bool has_session(const connection_spec& specification) const { mutex::scoped_lock lock(m_session_pool_guard); return m_sessions.find(specification) != m_sessions.end(); } void remove_session(const connection_spec& specification) { mutex::scoped_lock lock(m_session_pool_guard); m_sessions.erase(specification); } private: session_pool_impl() {}; static void do_init() { m_instance.reset(new session_pool_impl); } static once_flag m_initialise_once; static auto_ptr m_instance; mutable mutex m_session_pool_guard; pool_mapping m_sessions; }; once_flag session_pool_impl::m_initialise_once; auto_ptr session_pool_impl::m_instance; } authenticated_session& session_pool::pooled_session( const connection_spec& specification, com_ptr consumer) { return session_pool_impl::get().pooled_session(specification, consumer); } void session_pool::destroy() { return session_pool_impl::destroy(); } bool session_pool::has_session(const connection_spec& specification) const { return session_pool_impl::get().has_session(specification); } void session_pool::remove_session(const connection_spec& specification) { return session_pool_impl::get().remove_session(specification); } }} // namespace swish::connection ================================================ FILE: swish/connection/session_pool.hpp ================================================ /** @file Pool of reusable SFTP connections. @if license Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_CONNECTION_SESSION_POOL_HPP #define SWISH_CONNECTION_SESSION_POOL_HPP #pragma once #include "swish/connection/authenticated_session.hpp" #include "swish/connection/connection_spec.hpp" #include "swish/provider/sftp_provider.hpp" // ISftpConsumer #include // com_ptr #include namespace swish { namespace connection { /** * Per-process pool of sessions. * * All instances of this class share the same pool of sessions. */ class session_pool { public: /** * Returns a running SFTP session based on the given specification. * * If an appropriate SFTP session already exists in the pool, * that connection is reused. Otherwise a new one is created, and added * to the pool. * * The returned session is authenticated ready for use. Any * interaction needed to authenticate is performed via the `consumer` * callback. */ authenticated_session& pooled_session( const connection_spec& specification, comet::com_ptr consumer); /** * Is a connection with the given specification in the pool? * * Indicates whether the session matches one already running or whether * the session would need to to be created anew, should the caller decide to * call pooled_session(). */ bool has_session(const connection_spec& specification) const; /** * Remove the specified session from the pool. */ void remove_session(const connection_spec& specification); /** * Destroy the singleton pool. */ void destroy(); }; }} // namespace swish::connection #endif ================================================ FILE: swish/debug.hpp ================================================ /** @file Debug macros. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include // For _com_error #define TRACE(msg, ...) ATLTRACE(msg ## "\n", __VA_ARGS__) #define FUNCTION_TRACE TRACE(__FUNCTION__" called"); #define METHOD_TRACE TRACE(__FUNCTION__" called (this=%p)", this); #ifdef _DEBUG #define REPORT(expr) \ do { \ LPVOID lpMsgBuf; \ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, \ NULL, ::GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), \ (LPTSTR) &lpMsgBuf, 0, NULL ); \ _ASSERT_EXPR((expr), (LPTSTR)lpMsgBuf); LocalFree(lpMsgBuf); \ } while(0) #else #define REPORT(expr) (expr) #endif #ifdef UNREACHABLE #undef UNREACHABLE #endif #ifdef _DEBUG #define UNREACHABLE ATLASSERT(0); #else #define UNREACHABLE __assume(0); #endif #define ATLENSURE_REPORT_HR(expr, error, hr) \ do { \ int __atl_condVal=!!(expr); \ _ASSERT_EXPR(__atl_condVal, _com_error((error)).ErrorMessage()); \ if(!(__atl_condVal)) return (hr); \ } while (0) #define ATLENSURE_REPORT_THROW(expr, error, hr) \ do { \ int __atl_condVal=!!(expr); \ _ASSERT_EXPR(__atl_condVal, _com_error((error)).ErrorMessage()); \ if(!(__atl_condVal)) AtlThrow(hr); \ } while (0) #ifdef _DEBUG #define ATLASSERT_REPORT(expr, error) \ do { \ int __atl_condVal=!!(expr); \ _ASSERT_EXPR(__atl_condVal, _com_error((error)).ErrorMessage()); \ } while (0) #else #define ATLASSERT_REPORT(expr, error) ((void)0) #endif // _DEBUG #ifdef _DEBUG #define ATLVERIFY_REPORT(expr, error) \ do { \ int __atl_condVal=!!(expr); \ _ASSERT_EXPR(__atl_condVal, _com_error((error)).ErrorMessage()); \ } while (0) #else #define ATLVERIFY_REPORT(expr, error) (expr) #endif // DEBUG ================================================ FILE: swish/drop_target/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES CopyFileOperation.cpp CreateDirectoryOperation.cpp DropTarget.cpp DropUI.cpp PidlCopyPlan.cpp SequentialPlan.cpp CopyFileOperation.hpp CreateDirectoryOperation.hpp DropActionCallback.hpp DropTarget.hpp DropUI.hpp Operation.hpp PidlCopyPlan.hpp Plan.hpp Progress.hpp RootedSource.hpp SequentialPlan.hpp SftpDestination.hpp) add_library(drop_target ${SOURCES}) hunter_add_package(Comet) hunter_add_package(Washer) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) target_link_libraries(drop_target PUBLIC Washer::washer Comet::comet PRIVATE ${Boost_LIBRARIES}) ================================================ FILE: swish/drop_target/CopyFileOperation.cpp ================================================ /** @file File copy operation. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "CopyFileOperation.hpp" #include "swish/remote_folder/remote_pidl.hpp" // create_remote_itemid #include "swish/shell_folder/SftpDirectory.h" // CSftpDirectory #include // stream_from_pidl #include // trace #include // datetime_t #include // com_error #include // int64_t #include // translate #include // wformat #include // shared_ptr #include // BOOST_THROW_EXCEPTION #include // assert #include #include // wstringstream using swish::provider::sftp_provider; using swish::remote_folder::create_remote_itemid; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::pidl_t; using washer::shell::stream_from_pidl; using washer::trace; using boost::int64_t; using boost::function; using boost::locale::translate; using boost::locale::wformat; using boost::shared_ptr; using comet::com_error; using comet::com_ptr; using comet::datetime_t; using std::exception; using std::wstringstream; namespace swish { namespace drop_target { namespace { const size_t COPY_CHUNK_SIZE = 1024 * 32; /** * Return size of the streamed object in bytes. */ int64_t size_of_stream(const com_ptr& stream) { STATSTG statstg; HRESULT hr = stream->Stat(&statstg, STATFLAG_NONAME); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(stream, hr)); return statstg.cbSize.QuadPart; } /** * Write a stream to the provider at the given path. * * If it already exists, we want to ask the user for confirmation. * The poor-mans way of checking if the file is already there is to * try to get the file read-only first. If this fails, assume the * file noes not already exist. * * @bug The get may have failed for a different reason or this * may not work reliably on all SFTP servers. A safer * solution would be an explicit stat on the file. * * @bug Of course, there is a race condition here. After we check if the * file exists, someone else may have created it. Unfortunately, * there is nothing we can do about this as SFTP doesn't give us * a way to do this atomically such as locking a file. */ void copy_stream_to_remote_destination( com_ptr local_stream, shared_ptr provider, const resolved_destination& target, OperationCallback& callback) { CSftpDirectory sftp_directory(target.directory(), provider); cpidl_t file = create_remote_itemid( target.filename(), false, false, L"", L"", 0, 0, 0, 0, datetime_t::now(), datetime_t::now()); if (sftp_directory.exists(file)) { bool can_overwrite = callback.request_overwrite_permission( target.as_absolute_path()); if (!can_overwrite) return; } com_ptr remote_stream; try { remote_stream = sftp_directory.GetFile(file, true); } catch (const com_error& provider_error) { // TODO: once we decomtaminate the provider, move this to the // snitching drop target so it can use the info in the task dialog wstringstream new_message; new_message << translate(L"Unable to create file on the server:") << L"\n"; new_message << provider_error.description(); new_message << L"\n" << target.as_absolute_path(); BOOST_THROW_EXCEPTION( com_error( new_message.str(), provider_error.hr(), provider_error.source(), provider_error.guid(), provider_error.help_file(), provider_error.help_context())); } ::SHChangeNotify( SHCNE_CREATE, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT, (target.directory() + file).get(), NULL); // Set both streams back to the start LARGE_INTEGER move = {0}; HRESULT hr = local_stream->Seek(move, SEEK_SET, NULL); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(local_stream, hr)); hr = remote_stream->Seek(move, SEEK_SET, NULL); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(remote_stream, hr)); // Do the copy in chunks allowing us to cancel the operation // and display progress ULARGE_INTEGER cb; cb.QuadPart = COPY_CHUNK_SIZE; int64_t done = 0; int64_t total = size_of_stream(local_stream); while (true) { callback.check_if_user_cancelled(); ULARGE_INTEGER cbRead = {0}; ULARGE_INTEGER cbWritten = {0}; // TODO: make our own CopyTo that propagates errors hr = local_stream->CopyTo( remote_stream.get(), cb, &cbRead, &cbWritten); assert(FAILED(hr) || cbRead.QuadPart == cbWritten.QuadPart); if (FAILED(hr)) BOOST_THROW_EXCEPTION( com_error_from_interface(local_stream, hr)); try { // We create a different version of the PIDL here whose filesize // is the amount copied so far. Otherwise Explorer shows a // 0-byte file when the copying is done. file = create_remote_itemid( target.filename(), false, false, L"", L"", 0, 0, 0, done, datetime_t::now(), datetime_t::now()); ::SHChangeNotify( SHCNE_UPDATEITEM, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT, (target.directory() + file).get(), NULL); } catch(const exception& e) { // Ignoring error; failing to update the shell doesn't // warrant aborting the transfer trace("Failed to notify shell of file update %s") % e.what(); } // A failure to update the progress isn't a good enough reason // to abort the copy so we swallow the exception. try { done += cbWritten.QuadPart; callback.update_progress(done, total); } catch (const exception& e) { trace("Progress update threw exception: %s") % e.what(); assert(false); } if (cbRead.QuadPart == 0) break; // finished } } } CopyFileOperation::CopyFileOperation( const RootedSource& source, const SftpDestination& destination) : m_source(source), m_destination(destination) {} std::wstring CopyFileOperation::title() const { return (wformat( translate( L"Top line of a transfer progress window saying which " L"file is being copied. {1} is replaced with the file path " L"and must be included in your translation.", L"Copying '{1}'")) % m_source.relative_name()).str(); } std::wstring CopyFileOperation::description() const { return (wformat( translate( L"Second line of a transfer progress window giving the destination " L"directory. {1} is replaced with the directory path and must be " L"included in your translation.", L"To '{1}'")) % m_destination.root_name()).str(); } void CopyFileOperation::operator()( OperationCallback& callback, shared_ptr provider) const { com_ptr stream = stream_from_pidl(m_source.pidl()); resolved_destination resolved_target(m_destination.resolve_destination()); copy_stream_to_remote_destination( stream, provider, resolved_target, callback); } Operation* CopyFileOperation::do_clone() const { return new CopyFileOperation(*this); } }} ================================================ FILE: swish/drop_target/CopyFileOperation.hpp ================================================ /** @file File copy operation. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_COPYFILEOPERATION_HPP #define SWISH_DROP_TARGET_COPYFILEOPERATION_HPP #pragma once #include "swish/drop_target/Operation.hpp" #include "swish/drop_target/RootedSource.hpp" #include "swish/drop_target/SftpDestination.hpp" #include "swish/provider/sftp_provider.hpp" #include #include // apidl_t namespace swish { namespace drop_target { class CopyFileOperation : public Operation { public: CopyFileOperation( const RootedSource& source, const SftpDestination& destination); public: // Operation virtual std::wstring title() const; virtual std::wstring description() const; virtual void operator()( OperationCallback& callback, boost::shared_ptr provider) const; private: virtual Operation* do_clone() const; RootedSource m_source; SftpDestination m_destination; }; }} #endif ================================================ FILE: swish/drop_target/CreateDirectoryOperation.cpp ================================================ /** @file Directory creation operation. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "CreateDirectoryOperation.hpp" #include "swish/shell_folder/SftpDirectory.h" // CSftpDirectory #include // translate #include // wformat using swish::provider::sftp_provider; using washer::shell::pidl::pidl_t; using washer::shell::pidl::apidl_t; using boost::function; using boost::locale::translate; using boost::locale::wformat; using boost::shared_ptr; using comet::com_ptr; using std::wstring; namespace swish { namespace drop_target { CreateDirectoryOperation::CreateDirectoryOperation( const RootedSource& source, const SftpDestination& destination) : m_source(source), m_destination(destination) {} wstring CreateDirectoryOperation::title() const { return (wformat( translate( L"Top line of a transfer progress window saying which " L"file is being copied. {1} is replaced with the file path " L"and must be included in your translation.", L"Copying '{1}'")) % m_source.relative_name()).str(); } wstring CreateDirectoryOperation::description() const { return (wformat( translate( L"Second line of a transfer progress window giving the destination " L"directory. {1} is replaced with the directory path and must be " L"included in your translation.", L"To '{1}'")) % m_destination.root_name()).str(); } void CreateDirectoryOperation::operator()( OperationCallback& callback, shared_ptr provider) const { callback.update_progress(0, 1); resolved_destination resolved_target(m_destination.resolve_destination()); CSftpDirectory sftp_directory( resolved_target.directory(), provider); sftp_directory.CreateDirectory(resolved_target.filename()); callback.update_progress(1, 1); } Operation* CreateDirectoryOperation::do_clone() const { return new CreateDirectoryOperation(*this); } }} ================================================ FILE: swish/drop_target/CreateDirectoryOperation.hpp ================================================ /** @file Directory creation operation. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_CREATEDIRECTORYOPERATION_HPP #define SWISH_DROP_TARGET_CREATEDIRECTORYOPERATION_HPP #pragma once #include "swish/drop_target/Operation.hpp" #include "swish/drop_target/RootedSource.hpp" #include "swish/drop_target/SftpDestination.hpp" #include "swish/provider/sftp_provider.hpp" #include namespace swish { namespace drop_target { class CreateDirectoryOperation : public Operation { public: CreateDirectoryOperation( const RootedSource& source, const SftpDestination& target); virtual std::wstring title() const; virtual std::wstring description() const; virtual void operator()( OperationCallback& callback, boost::shared_ptr provider) const; private: virtual Operation* do_clone() const; RootedSource m_source; SftpDestination m_destination; }; }} #endif ================================================ FILE: swish/drop_target/DropActionCallback.hpp ================================================ /** @file User interaction during a drop. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_DROPACTIONCALLBACK_HPP #define SWISH_DROP_TARGET_DROPACTIONCALLBACK_HPP #pragma once #include #include // auto_ptr namespace swish { namespace drop_target { class Progress; /** * Interface for drop target to communicate with the user during a drop. */ class DropActionCallback { public: virtual ~DropActionCallback() {} virtual bool can_overwrite(const ssh::filesystem::path& target) = 0; virtual std::auto_ptr progress() = 0; virtual void handle_last_exception() = 0; }; }} #endif ================================================ FILE: swish/drop_target/DropTarget.cpp ================================================ /** @file Expose the remote filesystem as an IDropTarget. @if license Copyright (C) 2009, 2010, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "DropTarget.hpp" #include "swish/drop_target/PidlCopyPlan.hpp" #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include "swish/shell_folder/data_object/ShellDataObject.hpp" // PidlFormat, ShellDataObject #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // shared_ptr #include #include // BOOST_THROW_EXCEPTION #include // com_error #include #include // com_ptr #include // auto_coinit using swish::shell_folder::data_object::ShellDataObject; using swish::shell_folder::data_object::PidlFormat; using swish::provider::sftp_provider; using washer::shell::pidl::pidl_t; using washer::shell::pidl::apidl_t; using boost::shared_ptr; using boost::thread; using comet::auto_coinit; using comet::com_error; using comet::com_ptr; using comet::GIT; using comet::GIT_cookie; template<> struct comet::comtype { static const IID& uuid() throw() { return IID_IAsyncOperation; } typedef IUnknown base; }; template<> struct comet::comtype { static const IID& uuid() throw() { return IID_IDataObject; } typedef IUnknown base; }; namespace swish { namespace drop_target { namespace { // private /** * Given a DataObject and bitfield of allowed DROPEFFECTs, determine * which drop effect, if any, should be chosen. If none are * appropriate, return DROPEFFECT_NONE. */ DWORD determine_drop_effect( const com_ptr& pdo, DWORD allowed_effects) { if (pdo) { PidlFormat format(pdo); if (format.pidl_count() > 0) { if (allowed_effects & DROPEFFECT_COPY) return DROPEFFECT_COPY; } } return DROPEFFECT_NONE; } } /** * Copy the items in the DataObject to the remote target. * * @param source_format Clipboard PIDL format holding the items to be copied. * @param provider SFTP connection to copy data over. * @param destination_root PIDL to target directory in the remote filesystem * to copy items into. * @param progress Progress dialogue. */ void copy_format_to_provider( PidlFormat source_format, shared_ptr provider, const apidl_t& destination_root, shared_ptr callback) { PidlCopyPlan copy_list(source_format, destination_root); copy_list.execute_plan(*callback, provider); } namespace { void async_copy_format_to_provider( GIT_cookie marshalling_cookie, shared_ptr provider, apidl_t destination_root, shared_ptr callback) { auto_coinit com; GIT git; // These interface from the GIT will be properly marshalled across thread // apartments com_ptr data_object = git.get_interface(marshalling_cookie); com_ptr async = try_cast(data_object); try { try { copy_format_to_provider( PidlFormat(data_object), provider, destination_root, callback); } catch (...) { callback->handle_last_exception(); throw; } } catch (const com_error& e) { async->EndOperation(e.hr(), NULL, DROPEFFECT_COPY); } catch (...) { async->EndOperation(E_FAIL, NULL, DROPEFFECT_COPY); } git.revoke_interface(marshalling_cookie); } } /** * Copy the items in the DataObject to the remote target. * * @param data_object IDataObject holding the items to be copied. * @param provider SFTP connection to copy data over. * @param remote_directory PIDL to target directory in the remote filesystem * to copy items into. */ void copy_data_to_provider( com_ptr data_object, shared_ptr provider, const apidl_t& remote_directory, shared_ptr callback) { ShellDataObject data(data_object); if (data.has_pidl_format()) { if (data.supports_async()) { com_ptr async = data.async(); HRESULT hr = async->StartOperation(NULL); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(async, hr)); // We place the interfaces in the Global Interface Table because // the other thread needs marshalled versions of the interfaces. // The GIT promises to provide that. GIT git; GIT_cookie marshalling_cookie = git.register_interface(data_object); thread( &async_copy_format_to_provider, marshalling_cookie, provider, remote_directory, callback).detach(); } else { copy_format_to_provider( PidlFormat(data_object), provider, remote_directory, callback); } } else { BOOST_THROW_EXCEPTION( com_error("DataObject doesn't contain a supported format")); } } /** * Create an instance of the DropTarget initialised with a data provider. */ CDropTarget::CDropTarget( shared_ptr provider, const apidl_t& remote_directory, shared_ptr callback) : m_provider(provider), m_remote_directory(remote_directory), m_callback(callback) {} /** * Indicate whether the contents of the DataObject can be dropped on * this DropTarget. * * @todo Take account of the key state. */ STDMETHODIMP CDropTarget::DragEnter( IDataObject* pdo, DWORD /*grfKeyState*/, POINTL /*pt*/, DWORD* pdwEffect) { try { if (!pdwEffect) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); m_data_object = pdo; *pdwEffect = determine_drop_effect(pdo, *pdwEffect); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Refresh the choice drop effect for the last DataObject passed to DragEnter. * Although the DataObject will not have changed, the key state and allowed * effects bitfield may have. * * @todo Take account of the key state. */ STDMETHODIMP CDropTarget::DragOver( DWORD /*grfKeyState*/, POINTL /*pt*/, DWORD* pdwEffect) { try { if (!pdwEffect) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); *pdwEffect = determine_drop_effect(m_data_object, *pdwEffect); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * End the drag-and-drop loop for the current DataObject. */ STDMETHODIMP CDropTarget::DragLeave() { try { m_data_object = NULL; } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Perform the drop operation by either copying or moving the data * in the DataObject to the remote target. * * @todo Take account of the key state. */ STDMETHODIMP CDropTarget::Drop( IDataObject* pdo, DWORD /*grfKeyState*/, POINTL /*pt*/, DWORD* pdwEffect) { try { try { if (!pdwEffect) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); // Drop doesn't need to maintain any state and is handed a fresh // copy of the IDataObject so we can can immediately cancel the // one we were using for the other parts of the drag-drop loop m_data_object = NULL; *pdwEffect = determine_drop_effect(pdo, *pdwEffect); if (pdo && *pdwEffect == DROPEFFECT_COPY) { copy_data_to_provider( pdo, m_provider, m_remote_directory, m_callback); } } catch (...) { m_callback->handle_last_exception(); throw; } } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } }} // namespace swish::drop_target ================================================ FILE: swish/drop_target/DropTarget.hpp ================================================ /** @file Expose the remote filesystem as an IDropTarget. @if license Copyright (C) 2009, 2010, 2011, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_DROPTARGET_HPP #define SWISH_DROP_TARGET_DROPTARGET_HPP #pragma once #include "swish/provider/sftp_provider.hpp" // sftp_provider #include "swish/drop_target/DropActionCallback.hpp" // DropActionCallback #include "swish/drop_target/Progress.hpp" // Progress #include // object_with_site #include // apidl_t #include #include // com_ptr #include // simple_object #include // IDropTarget #include // IObjectWithSite template<> struct comet::comtype { static const IID& uuid() throw() { return IID_IDropTarget; } typedef IUnknown base; }; namespace swish { namespace drop_target { class CDropTarget : public comet::simple_object { public: typedef IDropTarget interface_is; /** * Create SFTP drop target. */ CDropTarget( boost::shared_ptr provider, const washer::shell::pidl::apidl_t& remote_directory, boost::shared_ptr callback); /** @name IDropTarget methods */ // @{ IFACEMETHODIMP DragEnter( __in_opt IDataObject* pDataObj, __in DWORD grfKeyState, __in POINTL pt, __inout DWORD* pdwEffect); IFACEMETHODIMP DragOver( __in DWORD grfKeyState, __in POINTL pt, __inout DWORD* pdwEffect); IFACEMETHODIMP DragLeave(); IFACEMETHODIMP Drop( __in_opt IDataObject* pDataObj, __in DWORD grfKeyState, __in POINTL pt, __inout DWORD* pdwEffect); // @} private: boost::shared_ptr m_provider; washer::shell::pidl::apidl_t m_remote_directory; comet::com_ptr m_data_object; boost::shared_ptr m_callback; }; void copy_data_to_provider( comet::com_ptr data_object, boost::shared_ptr provider, const washer::shell::pidl::apidl_t& remote_directory, boost::shared_ptr callback); }} // namespace swish::drop_target #endif ================================================ FILE: swish/drop_target/DropUI.cpp ================================================ /** @file User-interaction for DropTarget. @if license Copyright (C) 2010, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "DropUI.hpp" #include "swish/frontend/announce_error.hpp" // announce_last_exception #include "swish/trace.hpp" // trace #include // window_from_ole_window #include // message_box #include #include #include #include // com_error #include // com_ptr #include // translate, wformat #include #include // BOOST_THROW_EXCEPTION #include // assert #include // wstringstream #include using swish::frontend::announce_last_exception; using swish::tracing::trace; using washer::com::window_from_ole_window; using namespace washer::gui::message_box; using washer::gui::progress; using washer::window::window; using washer::window::window_handle; using comet::com_error; using comet::com_ptr; using ssh::filesystem::path; using boost::locale::translate; using boost::locale::wformat; using boost::noncopyable; using boost::optional; using std::auto_ptr; using std::wstringstream; using std::wstring; namespace swish { namespace drop_target { namespace { /** * Drain any messages in the queue. */ void do_events() { MSG msg; BOOL result; while (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { result = ::GetMessage(&msg, NULL, 0, 0); if (result == 0) // WM_QUIT { ::PostQuitMessage(msg.wParam); break; } else if (result == -1) { return; } else { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } } } /** * Exception-safe lifetime manager for an IProgressDialog object. * * Calls StartProgressDialog when created and StopProgressDialog when * destroyed. */ class DropProgress : public noncopyable, public Progress { public: DropProgress( const optional< window >& owner, const wstring& title) : m_inner(create_dialog(owner, title)) {} /** * Has the user cancelled the operation via the progress dialogue? */ bool user_cancelled() { return m_inner.user_cancelled(); } // Because we are no longer doing the transfer in a different COM // apartment, which would pump messages during the call, the UI blocks // on the drop. That includes not showing the progress dialog. // // Therefore, we pump outstanding messages every time there is // an update. I don't think this it the right solution, but we can't // run the progress dialog in a different thread as that breaks // the windows rules. // // The UI is still not wonderfully responsive because it can only // update a little each time the progress is updated. We may be able // to do better once we use libssh2's non-blocking API as then we // can pump messages more frequently. /** * Set the indexth line of the display to the given text. */ void line(DWORD index, const wstring& text) { m_inner.line(index, text); do_events(); } /** * Set the indexth line of the display to the given path. * * Uses the inbuilt path compression. */ void line_path(DWORD index, const wstring& text) { m_inner.line_compress_paths_if_needed(index, text); do_events(); } /** * Update the indicator to show current progress level. */ void update(ULONGLONG so_far, ULONGLONG out_of) { m_inner.update(so_far, out_of); do_events(); } /** * Force the dialogue window to disappear. * * Useful, for instance, to temporarily hide the progress display while * displaying other dialogues in the middle of the process whose * progress is being monitored. */ void hide() { optional< window > window = m_inner.window(); if (window) window->enable(false); do_events(); } /** * Force the dialogue window to appear. * * Useful to force the window to appear quicker than it normally would, * and to redisplay the window after hiding it. * * @see hide */ void show() { optional< window > window = m_inner.window(); if (window) window->enable(true); do_events(); } private: static progress create_dialog( const optional< window >& owner, const wstring& title) { return progress( owner, title, progress::modality::non_modal, progress::time_estimation::automatic_time_estimate, progress::bar_type::finite, progress::minimisable::yes, progress::cancellability::cancellable); } progress m_inner; }; /** * Disables a progress window for duration of its scope and reenables * after. */ class ScopedDisabler { public: ScopedDisabler(Progress& progress) : m_progress(progress) { m_progress.hide(); } ~ScopedDisabler() { m_progress.show(); } private: Progress& m_progress; }; } DropUI::DropUI(const optional< window >& owner) : m_owner(owner) {} /** * Does user give permission to overwrite remote target file? */ bool DropUI::can_overwrite(const path& target) { if (!m_owner) return false; wstringstream message; message << wformat(translate( L"This folder already contains a file named '{1}'.")) % target.filename(); message << "\n\n"; message << translate(L"Would you like to replace it?"); // If the caller has already displayed the progress dialog, we must // force-hide it as it gets in the way of other UI ScopedDisabler disable_progress(*m_progress); button_type::type button = message_box( (m_owner) ? m_owner->hwnd() : NULL, message.str(), translate(L"Confirm File Replace"), box_type::yes_no_cancel, icon_type::question); switch (button) { case button_type::yes: return true; case button_type::no: return false; case button_type::cancel: default: BOOST_THROW_EXCEPTION(com_error(E_ABORT)); } } void DropUI::handle_last_exception() { // Only report errors with a dialog if we are given a window we // can use as a dialogue owner. We can assume if the caller // didn't give us one, they don't want UI. if (m_owner) { announce_last_exception( m_owner->hwnd(), translate(L"Unable to transfer files"), translate( L"You might not have permission to write to this " L"directory.")); } throw; } namespace { class DummyProgress : public Progress { public: virtual bool user_cancelled() { return false; }; virtual void line(DWORD, const std::wstring&) {} virtual void line_path(DWORD, const std::wstring&) {} virtual void update(ULONGLONG, ULONGLONG) {} virtual void hide() {} virtual void show() {} }; } /** * Pass ownership of a progress display scope to caller. * * We hang on to the progress dialog so that we can hide it if and when we * show other dialogs (something the built-in Explorer FTP extension doesn't * do and really should). * * The caller gets a Progress object whose lifetime determines when the dialog * is started and ended. When it goes out of scope the dialog is stopped and * disappears. In other words, the progress dialog is safely stopped even * if an exception is thrown. */ auto_ptr DropUI::progress() { auto_ptr p; if (m_owner) { p = auto_ptr( new DropProgress(m_owner, translate(L"Progress", L"Copying..."))); } else { p = auto_ptr(new DummyProgress()); } // HACK: we keep a raw copy of the pointer so we can hide the progress // if needed later when displaying the confirm-overwrite box. There // has got to be a safer way to do this. m_progress = p.get(); return p; } }} // namespace swish::drop_target ================================================ FILE: swish/drop_target/DropUI.hpp ================================================ /** @file User-interaction for DropTarget. @if license Copyright (C) 2010, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_DROPUI_HPP #define SWISH_DROP_TARGET_DROPUI_HPP #pragma once #include "swish/drop_target/DropActionCallback.hpp" #include "swish/drop_target/Progress.hpp" #include #include #include // auto_ptr namespace swish { namespace drop_target { /** * DropTarget callback turning requests into GUI windows so user can * handle them. */ class DropUI : public DropActionCallback { public: DropUI( const boost::optional< washer::window::window >& owner); virtual bool can_overwrite(const ssh::filesystem::path& target); virtual std::auto_ptr progress(); virtual void handle_last_exception(); private: boost::optional< washer::window::window > m_owner; Progress* m_progress; }; }} // namespace swish::drop_target #endif ================================================ FILE: swish/drop_target/Operation.hpp ================================================ /** @file Interface to drop target operations. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_OPERATION_HPP #define SWISH_DROP_TARGET_OPERATION_HPP #pragma once #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include // uintmax_t #include // function #include #include #include // com_ptr #include // assert #include namespace swish { namespace drop_target { class DropActionCallback; /** * Interface through which individual drop operations interact with user. * * Purpose: to abstract the interaction so that an operation can pretend it * is the only operation happening. The operation doesn't need to think * about the lifetime of the progress display and just updates it as it * wishes till so_far == out_of. */ class OperationCallback { public: /** * Throw com_error(E_ABORT) if user cancelled. * * It throws rather than returning a boolean in order to force the operation * to abort with an exception. This behaviour is expected by drag-and-drop. */ virtual void check_if_user_cancelled() const = 0; virtual bool request_overwrite_permission( const ssh::filesystem::path& target) const = 0; virtual void update_progress( boost::uintmax_t so_far, boost::uintmax_t out_of) = 0; virtual ~OperationCallback() {} }; /** * Interface of operation functors making up a drop. */ class Operation { public: virtual std::wstring title() const = 0; virtual std::wstring description() const = 0; virtual void operator()( OperationCallback& callback, boost::shared_ptr provider) const = 0; Operation* clone() const { Operation* item = do_clone(); assert(typeid(*this) == typeid(*item) && "do_clone() sliced object!"); return item; } private: virtual Operation* do_clone() const = 0; }; inline Operation* new_clone(const Operation& item) { return item.clone(); } }} // namespace swish::drop_target #endif ================================================ FILE: swish/drop_target/PidlCopyPlan.cpp ================================================ /** @file Plan copying items in PIDL clipboard format to remote server. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "PidlCopyPlan.hpp" #include "swish/drop_target/CopyFileOperation.hpp" #include "swish/drop_target/CreateDirectoryOperation.hpp" #include "swish/drop_target/RootedSource.hpp" #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include // path #include // bind #include #include #include // BOOST_THROW_EXCEPTION #include // stream_from_pidl, bind_to_handler_object #include // pidl_shell_item #include // com_error #include using swish::provider::sftp_provider; using swish::shell_folder::data_object::PidlFormat; using washer::shell::bind_to_handler_object; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::pidl_t; using washer::shell::pidl_shell_item; using washer::shell::stream_from_pidl; using ssh::filesystem::path; using comet::com_error; using comet::com_ptr; using boost::bind; using boost::make_function_output_iterator; using boost::shared_ptr; using boost::ref; using std::wstring; namespace swish { namespace drop_target { namespace { /** * Return the name the copy should have at the target location. */ path target_name_from_source(const RootedSource& source) { return pidl_shell_item(source.pidl()).friendly_name( pidl_shell_item::friendly_name_type::relative); } template void output_operations_for_stream_pidl( const RootedSource& source, const SftpDestination& destination, OutIt output_iterator) { path new_name = target_name_from_source(source); SftpDestination new_destination = destination / new_name; CopyFileOperation operation(source, new_destination); *output_iterator++ = operation; } template void output_operations_for_folder_pidl( com_ptr folder, const RootedSource& source, const SftpDestination& destination, OutIt output_iterator) { path new_name = target_name_from_source(source); SftpDestination new_destination = destination / new_name; *output_iterator++ = CreateDirectoryOperation(source, new_destination); com_ptr e; HRESULT hr = folder->EnumObjects( NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, e.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(folder, hr)); cpidl_t item; while (hr == S_OK && e->Next(1, item.out(), NULL) == S_OK) { output_operations_for_pidl( source / item, new_destination, output_iterator); } } template void output_operations_for_pidl( const RootedSource& source, const SftpDestination& destination, OutIt output_iterator) { try { /* Test if streamable. We don't use this stream to perform the operation as that would mean large transfers keeping open a large number of file handles while building the copy plan - a bad idea, especially if the files are on another remote server */ stream_from_pidl(source.pidl()); output_operations_for_stream_pidl( source, destination, output_iterator); } catch (const com_error&) { // Treating the item as something with an IStream has failed // Now we try to treat it as an IShellFolder and hope we // have more success com_ptr folder = bind_to_handler_object(source.pidl()); output_operations_for_folder_pidl( folder, source, destination, output_iterator); } } } /** * Create plan to copy items represented by clipboard PIDL format. * * Expands the top-level PIDLs into a list of all items in the hierarchy. */ PidlCopyPlan::PidlCopyPlan( const PidlFormat& source_format, const apidl_t& destination_root) { for (unsigned int i = 0; i < source_format.pidl_count(); ++i) { apidl_t pidl = source_format.file(i); output_operations_for_pidl( RootedSource( source_format.parent_folder(), source_format.relative_file(i)), SftpDestination(destination_root, path()), make_function_output_iterator( bind(&SequentialPlan::add_stage, ref(m_plan), _1))); } } void PidlCopyPlan::execute_plan( DropActionCallback& callback, shared_ptr provider) const { m_plan.execute_plan(callback, provider); } void PidlCopyPlan::add_stage(const Operation& entry) { m_plan.add_stage(entry); } }} ================================================ FILE: swish/drop_target/PidlCopyPlan.hpp ================================================ /** @file Plan copying items in PIDL clipboard format to remote server. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_PIDLCOPYPLAN_HPP #define SWISH_DROP_TARGET_PIDLCOPYPLAN_HPP #pragma once #include "swish/drop_target/Operation.hpp" #include "swish/drop_target/Plan.hpp" #include "swish/drop_target/SequentialPlan.hpp" #include "swish/provider/sftp_provider.hpp" #include "swish/shell_folder/data_object/ShellDataObject.hpp" // PidlFormat #include namespace swish { namespace drop_target { /** * Plan copying items in PIDL clipboard format to remote server. */ class PidlCopyPlan /* final */ : public Plan { public: PidlCopyPlan( const swish::shell_folder::data_object::PidlFormat& source, const washer::shell::pidl::apidl_t& destination); public: // Plan virtual void execute_plan( DropActionCallback& callback, boost::shared_ptr provider) const; public: void add_stage(const Operation& stage); private: SequentialPlan m_plan; }; }} #endif ================================================ FILE: swish/drop_target/Plan.hpp ================================================ /** @file Executable schedule of operations. @if license Copyright (C) 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_PLAN_HPP #define SWISH_DROP_TARGET_PLAN_HPP #pragma once #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include "swish/drop_target/Progress.hpp" #include namespace swish { namespace drop_target { class DropActionCallback; /** * Interface for executable schedule of operations. */ class Plan { public: virtual ~Plan() {} virtual void execute_plan( DropActionCallback& callback, boost::shared_ptr provider) const = 0; }; }} #endif ================================================ FILE: swish/drop_target/Progress.hpp ================================================ /** @file Progress callback. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_PROGRESS_HPP #define SWISH_DROP_TARGET_PROGRESS_HPP #pragma once #include #include // DWORD, ULONGLONG namespace swish { namespace drop_target { class Progress { public: virtual ~Progress() {} virtual bool user_cancelled() = 0; virtual void line(DWORD index, const std::wstring& text) = 0; virtual void line_path(DWORD index, const std::wstring& text) = 0; virtual void update(ULONGLONG so_far, ULONGLONG out_of) = 0; virtual void hide() = 0; virtual void show() = 0; }; }} #endif ================================================ FILE: swish/drop_target/RootedSource.hpp ================================================ /** @file Source PIDL with common root. @if license Copyright (C) 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_ROOTEDSOURCE_HPP #define SWISH_DROP_TARGET_ROOTEDSOURCE_HPP #pragma once #include // apidl_t, pidl_t #include #include // pidl_shell_item #include namespace swish { namespace drop_target { namespace detail { inline std::wstring display_name_of_item( const washer::shell::pidl::apidl_t& pidl) { using washer::shell::pidl_shell_item; return pidl_shell_item(pidl).friendly_name( pidl_shell_item::friendly_name_type::relative); } /** * Return the parsing path name for a PIDL relative the the given parent. */ inline std::wstring relative_name_for_pidl( const washer::shell::pidl::apidl_t& parent, const washer::shell::pidl::pidl_t& pidl) { std::wstring name; washer::shell::pidl::apidl_t abs = parent; washer::shell::pidl::pidl_iterator it(pidl); while (it != washer::shell::pidl::pidl_iterator()) { if (!name.empty()) name += L"\\"; abs += *it++; name += display_name_of_item(abs); } return name; } } /** * Shell-based source relative to a root. * * Purpose: to maintain the connection between a particular source item in a * multi-item transfer and the common root of all the items. * * To the user, a given source item in a file transfer does not exist in * isolation. All the items in the transfer are with respect to a particular * root. Paths shown as progress information, for example, are typically given * with respect rather than as absolute paths. This class exists to maintain * that relationship. */ class RootedSource { public: RootedSource( const washer::shell::pidl::apidl_t& common_root, const washer::shell::pidl::pidl_t& relative_branch) : m_root(common_root), m_branch(relative_branch) {} const washer::shell::pidl::apidl_t& common_root() const { return m_root; } washer::shell::pidl::apidl_t pidl() const { return m_root + m_branch; } std::wstring relative_name() const { return detail::relative_name_for_pidl(m_root, m_branch); } RootedSource operator/(const washer::shell::pidl::pidl_t& pidl) const { return RootedSource(m_root, m_branch + pidl); } private: const washer::shell::pidl::apidl_t m_root; const washer::shell::pidl::pidl_t m_branch; }; }} #endif ================================================ FILE: swish/drop_target/SequentialPlan.cpp ================================================ /** @file Standard drop operation plan implementation. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "SequentialPlan.hpp" #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include "swish/drop_target/DropActionCallback.hpp" #include "swish/drop_target/Operation.hpp" #include // uintmax_t #include // numeric_cast #include #include // BOOST_THROW_EXCEPTION #include // com_error #include #include // assert #include // auto_ptr using swish::provider::sftp_provider; using comet::com_error; using comet::com_ptr; using ssh::filesystem::path; using boost::numeric_cast; using boost::shared_ptr; using boost::uintmax_t; using std::auto_ptr; using std::size_t; namespace swish { namespace drop_target { namespace { /** * Calculate percentage. * * @bug Throws if using ludicrously large sizes. */ uintmax_t percentage(uintmax_t done, uintmax_t total) { if (total == 0) return 100; else return numeric_cast((done * 100) / total); } /** * Calculator of 'intra-file' progress. * * Purpose: to translate between the progress increments reported by * a single operation and the overall progress of the sequence of * operations. * * In other words, it handles the small increments that happen during the * upload of one file amongst many. We need this to give meaningful * progress when only a small number of files are being dropped where the * time spent on a single file makes up a significant portion of the * overall transfer. */ class IntraSequenceCallback : public OperationCallback { public: IntraSequenceCallback( OperationCallback& sequence_callback, size_t current_file_index, size_t total_files) : m_callback(sequence_callback), m_current_file_index(current_file_index), m_total_files(total_files) {} virtual void check_if_user_cancelled() const { return m_callback.check_if_user_cancelled(); } virtual bool request_overwrite_permission(const path& target) const { return m_callback.request_overwrite_permission(target); } /** * Update the overall sequence progress with the intra-operation * progress. * * This uses of resolution of 100 update intervals per file in the * sequence. In other words, the intra-operation is converted to a * percentage. */ virtual void update_progress(uintmax_t so_far, uintmax_t out_of) { uintmax_t percent_done = percentage(so_far, out_of); uintmax_t current = (m_current_file_index * 100) + percent_done; m_callback.update_progress(current, m_total_files * 100); } private: OperationCallback& m_callback; size_t m_current_file_index; const size_t m_total_files; }; /** * Executes one of a sequence of operations. * * Purpose: to liaise between the Operation and the DropActionCallback * interface used to communicator with the user. * * The DropActionCallback creates and starts the progress dialogue when * it is requested so part of that liaison is making sure this only * happens once for the entire sequence of operations. */ class OperationExecutor : private OperationCallback { public: OperationExecutor(DropActionCallback& callback) : m_callback(callback) {} void operator()( const Operation& operation, size_t operation_index, size_t total_operations, shared_ptr provider) { progress().line_path(1, operation.title()); progress().line_path(2, operation.description()); IntraSequenceCallback micro_updater( *this, operation_index, total_operations); check_if_user_cancelled(); operation(micro_updater, provider); // We update here as well, fixing the progress to a file // boundary, as we don't completely trust the intra-file // progress. A stream could have lied about its size messing // up the count. This will override any such errors. assert(operation_index + 1 <= total_operations); progress().update(operation_index + 1, total_operations); } virtual void check_if_user_cancelled() const { if (m_progress.get()) { if (m_progress->user_cancelled()) BOOST_THROW_EXCEPTION(com_error(E_ABORT)); } } virtual bool request_overwrite_permission(const path& target) const { return m_callback.can_overwrite(target); } virtual void update_progress(uintmax_t so_far, uintmax_t out_of) { progress().update(so_far, out_of); } private: Progress& progress() { if (!m_progress.get()) m_progress = m_callback.progress(); return *m_progress; } DropActionCallback& m_callback; auto_ptr m_progress; }; } void SequentialPlan::execute_plan( DropActionCallback& callback, shared_ptr provider) const { OperationExecutor executor(callback); for (unsigned int i = 0; i < m_copy_list.size(); ++i) { const Operation& operation = m_copy_list.at(i); executor(operation, i, m_copy_list.size(), provider); } } void SequentialPlan::add_stage(const Operation& entry) { m_copy_list.push_back(entry.clone()); } }} ================================================ FILE: swish/drop_target/SequentialPlan.hpp ================================================ /** @file Standard drop operation plan implementation. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_SEQUENTIALPLAN_HPP #define SWISH_DROP_TARGET_SEQUENTIALPLAN_HPP #pragma once #include "swish/drop_target/Plan.hpp" #include "swish/provider/sftp_provider.hpp" #include #include namespace swish { namespace drop_target { class Operation; /** * Standard plan implementation made from list of Operation objects. * * Each object is executed in the order they are added and progress * displayed accordingly. */ class SequentialPlan /* final */ : public Plan { public: // Plan virtual void execute_plan( DropActionCallback& callback, boost::shared_ptr provider) const; public: void add_stage(const Operation& entry); private: boost::ptr_vector m_copy_list; }; }} #endif ================================================ FILE: swish/drop_target/SftpDestination.hpp ================================================ /** @file Abstraction of SFTP drop destination. @if license Copyright (C) 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_DROP_TARGET_SFTPDESTINATION_HPP #define SWISH_DROP_TARGET_SFTPDESTINATION_HPP #pragma once #include "swish/remote_folder/swish_pidl.hpp" // absolute_path_from_swish_pidl #include "swish/remote_folder/remote_pidl.hpp" // create_remote_itemid #include // apidl_t #include // pidl_shell_item #include #include // BOOST_FOREACH #include // BOOST_THROW_EXCEPTION #include // logic_error #include namespace swish { namespace drop_target { /** * A destination (directory or file) on the remote server given as a * directory PIDL and a filename. */ class resolved_destination { public: resolved_destination( const washer::shell::pidl::apidl_t& remote_directory, const std::wstring& filename) : m_remote_directory(remote_directory), m_filename(filename) { if (ssh::filesystem::path(m_filename).has_parent_path()) BOOST_THROW_EXCEPTION( std::logic_error( "Path not properly resolved; filename expected")); } const washer::shell::pidl::apidl_t& directory() const { return m_remote_directory; } const std::wstring filename() const { return m_filename; } ssh::filesystem::path as_absolute_path() const { return swish::remote_folder::absolute_path_from_swish_pidl( m_remote_directory) / m_filename; } private: washer::shell::pidl::apidl_t m_remote_directory; std::wstring m_filename; }; /** * A destination (directory or file) on the remote server given as a * path relative to a PIDL. * * As in an FGD, the path may be multi-level. The directories named by the * intermediate sections may not exist so care must be taken that the, * destinations are used in the order listed in the FGD which is designed * to make sure they exist. */ class SftpDestination { public: SftpDestination( const washer::shell::pidl::apidl_t& remote_root, const ssh::filesystem::path& relative_path) : m_remote_root(remote_root), m_relative_path(relative_path) { if (relative_path.is_absolute()) BOOST_THROW_EXCEPTION( std::logic_error("Path must be relative to root")); } resolved_destination resolve_destination() const { washer::shell::pidl::apidl_t directory = m_remote_root; BOOST_FOREACH( ssh::filesystem::path intermediate_directory_name, m_relative_path.parent_path()) { directory += swish::remote_folder::create_remote_itemid( intermediate_directory_name.wstring(), true, false, L"", L"", 0, 0, 0, 0, comet::datetime_t::now(), comet::datetime_t::now()); } return resolved_destination( directory, m_relative_path.filename().wstring()); } SftpDestination operator/(const ssh::filesystem::path& path) const { return SftpDestination(m_remote_root, m_relative_path / path); } std::wstring root_name() const { using washer::shell::pidl_shell_item; return pidl_shell_item(m_remote_root).friendly_name( pidl_shell_item::friendly_name_type::absolute); } private: washer::shell::pidl::apidl_t m_remote_root; ssh::filesystem::path m_relative_path; }; }} #endif ================================================ FILE: swish/forms/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES add_host.cpp password.cpp add_host.hpp password.hpp) add_library(forms ${SOURCES}) hunter_add_package(Washer) find_package(Washer REQUIRED CONFIG) target_link_libraries(forms PRIVATE host_folder Washer::washer ${Boost_LIBRARIES}) ================================================ FILE: swish/forms/add_host.cpp ================================================ /** @file New host dialogue. @if license Copyright (C) 2010, 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "add_host.hpp" #include "swish/remotelimits.h" #include "swish/host_folder/host_management.hpp" // ConnectionExists #include // button #include // edit #include // icon #include // label #include // line #include // spinner #include // form #include // module_handle #include // load_icon #include // bind #include // lexical_cast #include // translate #include // BOOST_THROW_EXCEPTION #include // exception #include using swish::host_folder::host_management::ConnectionExists; using washer::module_handle; using ezel::form; using washer::gui::hicon; using washer::gui::load_icon; using namespace ezel::controls; using boost::bad_lexical_cast; using boost::bind; using boost::lexical_cast; using boost::locale::translate; using std::wstring; namespace swish { namespace forms { namespace { const int DEFAULT_PORT = 22; const wchar_t* FORBIDDEN_USER_CHARS = L":\t\n\r\b"; const wchar_t* FORBIDDEN_CHARS = L"@: \t\n\r\b\"'\\"; const wchar_t* FORBIDDEN_PATH_CHARS = L"\"\t\n\r\b\\"; const wchar_t* ICON_MODULE = L"user32.dll"; const int ICON_ERROR = 103; const int ICON_INFO = 104; const int ICON_SIZE = 16; /** * Host information entry dialog box. * * The dialog obtains SSH connection information from the user. * * Text fields: * - "Name:" Friendly name for connection * - "User:" SSH acount user name * - "Host:" Remote host address/name * - "Path:" Path for initial listing * Numeric field: * - "Port:" TCP/IP port to connect over * * @image html "add_host.png" "Dialogue box appearance" */ class AddHostForm { public: AddHostForm(HWND owner) : m_form(translate(L"New SFTP Connection"), 0, 0, 275, 176), m_cancelled(true), m_name_box(edit(L"", 42, 9, 222, 13)), m_host_box( edit(L"", 42, 58, 156, 13, edit::style::force_lowercase)), m_port_box( edit(L"", 228, 58, 26, 13, edit::style::only_allow_numbers)), m_port_spinner( spinner( 254, 58, 10, 13, MIN_PORT, MAX_PORT, DEFAULT_PORT, spinner::style::no_thousand_separator)), m_user_box(edit(L"", 42, 76, 156, 13)), m_path_box(edit(L"", 42, 115, 222, 13)), m_status( label( L"", 23, 158, 135, 20, label::style::ampersand_not_special)), m_icon(icon(2,153,21,20)), m_ok( button( translate(L"Create"), 162, 155, 50, 14, true)) { // every time a field is changed we revalidate all the fields, // enable or disable the OK button and a display a status message // if needed m_name_box.on_change().connect( bind(&AddHostForm::update_validity, this)); m_host_box.on_change().connect( bind(&AddHostForm::update_validity, this)); m_user_box.on_change().connect( bind(&AddHostForm::update_validity, this)); m_path_box.on_change().connect( bind(&AddHostForm::update_validity, this)); m_port_box.on_change().connect( bind(&AddHostForm::update_validity, this)); // TODO: clarify difference between on_change and on_text_changed m_port_box.on_text_changed().connect( bind(&AddHostForm::update_validity, this)); m_form.add_control( label(translate(L"New Host", L"&Label:"), 12, 11, 28, 8)); m_form.add_control(m_name_box); m_form.add_control( label(translate( L"For example: \"Home Computer\"."), 42, 25, 228, 18)); m_form.add_control( label(translate( L"Specify the details of the computer and account you " L"would like to connect to:"), 12, 45, 258, 18)); m_form.add_control( label(translate(L"New Host", L"&Host:"), 12, 60, 30, 8)); m_form.add_control(m_host_box); m_form.add_control( label(translate(L"New Host", L"&Port:"), 204, 60, 18, 8)); m_form.add_control(m_port_box); m_form.add_control(m_port_spinner); m_form.add_control( label(translate(L"New Host", L"&User:"), 12, 78, 56, 8)); m_form.add_control(m_user_box); m_form.add_control( label(translate( L"Specify the directory on the server that you would like " L"Swish to start the connection in:"), 12, 96, 258, 18)); m_form.add_control( label(translate(L"New Host", L"P&ath:"), 12, 117, 35, 8)); m_form.add_control(m_path_box); m_form.add_control( label( translate(L"Example: /home/yourusername"), 42, 131, 144, 8)); m_form.add_control(line(0, 147, 277)); m_ok.on_click().connect(bind(&AddHostForm::on_ok, this)); m_form.add_control(m_ok); button cancel(translate(L"Cancel"), 216, 155, 50, 14); cancel.on_click().connect(m_form.killer()); m_form.add_control(cancel); m_form.add_control(m_status); m_form.add_control(m_icon); m_error = load_icon( module_handle(ICON_MODULE), ICON_ERROR, ICON_SIZE, ICON_SIZE); m_information = load_icon( module_handle(ICON_MODULE), ICON_INFO, ICON_SIZE, ICON_SIZE); update_validity(); m_form.show(owner); } /** @name Accessors */ // @{ bool was_cancelled() const { return m_cancelled; } const std::wstring name() const { return m_name_box.text(); } const std::wstring host() const { return m_host_box.text(); } const std::wstring user() const { return m_user_box.text(); } int port() const { return lexical_cast(m_port_box.text()); } const std::wstring path() const { return m_path_box.text(); } // @} private: /** @name Field validity */ // @{ /** * Check if the value in the dialog box Name field is valid. * * Criteria: * - The field must not contain more than @ref MAX_LABEL_LEN * characters. */ bool is_valid_name() const { return name().length() <= MAX_LABEL_LEN; } /** * Check if the value in the dialog box Host field is valid. * * Criteria: * - The field must not contain more than @ref MAX_HOSTNAME_LEN * characters and must not contain any characters from * @ref FORBIDDEN_CHARS * * @todo Use a regexp to do this properly. */ bool is_valid_host() const { wstring text = host(); return text.length() <= MAX_HOSTNAME_LEN && text.find_first_of(FORBIDDEN_CHARS) == wstring::npos; } /** * Check if the value in the dialog box User field is valid. * * Criteria: * - The field must not contain more than @ref MAX_USERNAME_LEN * characters and must not contain any characters from * @ref FORBIDDEN_USER_CHARS. * * @todo The validity criteria are woefully inadequate: * - There are many characters that are not allowed in usernames. */ bool is_valid_user() const { wstring text = user(); return text.length() <= MAX_USERNAME_LEN && text.find_first_of(FORBIDDEN_USER_CHARS) == wstring::npos; } /** * Checks if the value in the dialog box Port field is valid. * * Criteria: * - The field must contain a number between 0 and 65535 * (@ref MAX_PORT). */ bool is_valid_port() const { try { return port() >= MIN_PORT && port() <= MAX_PORT; } catch (const bad_lexical_cast&) { return false; } } /** * Checks if the value in the dialog box Path field is valid. * * Criteria: * - The path field must not contain more than @ref MAX_PATH_LEN * characters and must not contain any characters from * @ref FORBIDDEN_PATH_CHARS. * * @todo The validity criteria are woefully inadequate: * - Paths can contain almost any character. Some will have to be * escaped. */ bool is_valid_path() const { wstring text = path(); return text.length() <= MAX_PATH_LEN && text.find_first_of(FORBIDDEN_PATH_CHARS) == wstring::npos; } bool all_fields_complete() const { return !(name().empty() || host().empty() || user().empty() || path().empty()); } // @} /** @name Event handlers */ // @{ void on_ok() { m_form.end(); m_cancelled = false; } /** * Disable the OK button if a field in the dialog is invalid. * * Also set the status icon and message. */ void update_validity() { bool enable_ok = false; bool info_icon_if_error = false; if (!is_valid_name()) { m_status.text( translate( L"The label cannot be longer than 30 characters.")); } else if (!is_valid_host()) { m_status.text(translate(L"The host name is invalid.")); } else if (!is_valid_port()) { m_status.text( translate( L"The port is not valid (between 0 and 65535).")); } else if (!is_valid_user()) { m_status.text(translate(L"The username is invalid.")); } else if (!is_valid_path()) { m_status.text(translate(L"The path is invalid.")); } else if (ConnectionExists(name())) { m_status.text( translate( L"A connection with the same label already exists. " L"Please try another.")); } else if (!all_fields_complete()) { info_icon_if_error = true; m_status.text(translate(L"Complete all fields.")); } else { enable_ok = true; } if (!enable_ok) { m_icon.change_icon( (info_icon_if_error) ? m_information.get() : m_error.get()); } m_icon.visible(!enable_ok); m_status.visible(!enable_ok); m_ok.enable(enable_ok); } // @} form m_form; bool m_cancelled; /** @name GUI controls */ // @{ edit m_name_box; edit m_host_box; edit m_port_box; spinner m_port_spinner; edit m_user_box; edit m_path_box; label m_status; ///< Status message window icon m_icon; ///< Status icon display area button m_ok; // @} /** @name Preloaded icons */ // @{ hicon m_error; ///< Small icon displaying a red error cross hicon m_information; ///< Small icon displaying a blue 'i' symbol // @} }; } /** * Display add host dialogue box and return the details entered by the user. * * @throws If the user cancels the dialogue. */ host_info add_host(HWND owner) { AddHostForm host_form(owner); if (host_form.was_cancelled()) BOOST_THROW_EXCEPTION(std::exception("user cancelled form")); host_info info = { host_form.name(), host_form.host(), host_form.user(), host_form.port(), host_form.path() }; return info; } }} // namespace swish::forms ================================================ FILE: swish/forms/add_host.hpp ================================================ /** @file New host dialogue. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_FORMS_ADD_HOST_HPP #define SWISH_FORMS_ADD_HOST_HPP #pragma once #include #include // HWND namespace swish { namespace forms { struct host_info { std::wstring name; std::wstring host; std::wstring user; int port; std::wstring path; }; host_info add_host(HWND owner); }} // namespace swish::forms #endif ================================================ FILE: swish/forms/password.cpp ================================================ /** @file Form class for login password prompt. @if license Copyright (C) 2010, 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "password.hpp" #include // button #include // edit #include // label #include // form #include // bind #include // translate #include using ezel::form; using namespace ezel::controls; using boost::bind; using boost::locale::translate; using std::wstring; namespace swish { namespace forms { namespace { class PasswordForm { public: PasswordForm(HWND hwnd_owner, const wstring& prompt) : m_form(L"Swish", 0, 0, 219, 49), m_cancelled(true), m_password_box(edit(L"", 7, 18, 148, 14, edit::style::password)) { m_form.add_control(m_password_box); m_form.add_control(label(prompt, 7, 7, 149, 8)); button ok(translate(L"OK"), 162, 7, 50, 16, true); ok.on_click().connect(bind(&PasswordForm::on_ok, this)); m_form.add_control(ok); button cancel(translate(L"Cancel"), 162, 26, 50, 16); cancel.on_click().connect(m_form.killer()); m_form.add_control(cancel); m_form.show(hwnd_owner); } void on_ok() { m_form.end(); m_cancelled = false; } bool was_cancelled() const { return m_cancelled; } wstring password() const { return m_password_box.text(); } private: form m_form; bool m_cancelled; edit m_password_box; }; } bool password_prompt( HWND hwnd_owner, const wstring& prompt, wstring& password_out) { PasswordForm pass_form(hwnd_owner, prompt); if (pass_form.was_cancelled()) { password_out.clear(); return false; } password_out = pass_form.password(); return true; } }} // namespace swish::forms ================================================ FILE: swish/forms/password.hpp ================================================ /** @file Login password prompt. @if license Copyright (C) 2010, 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_FORMS_PASSWORD_HPP #define SWISH_FORMS_PASSWORD_HPP #pragma once #include #include // HWND namespace swish { namespace forms { bool password_prompt( HWND hwnd_owner, const std::wstring& prompt, std::wstring& password_out); }} // namespace swish::forms #endif ================================================ FILE: swish/frontend/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(COMMAND_SOURCES commands/About.cpp commands/About.hpp) set(SOURCES announce_error.hpp bind_best_taskdialog.hpp UserInteraction.hpp winsparkle_shower.hpp announce_error.cpp bind_best_taskdialog.cpp UserInteraction.cpp winsparkle_shower.cpp ${COMMAND_SOURCES}) add_library(frontend ${SOURCES}) hunter_add_package(Comet) hunter_add_package(Washer) hunter_add_package(WinSparkle) hunter_add_package(WTL) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) find_package(WinSparkle REQUIRED CONFIG) target_link_libraries(frontend PRIVATE nse versions WinSparkle::WinSparkle PUBLIC WTL::wtl Washer::washer Comet::comet) ================================================ FILE: swish/frontend/UserInteraction.cpp ================================================ /** @file Component to handle user-interaction between the user and an SftpProvider. @if license Copyright (C) 2008, 2009, 2010, 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "UserInteraction.hpp" #include "swish/debug.hpp" #include "swish/forms/password.hpp" // password_prompt #include "swish/frontend/bind_best_taskdialog.hpp" // best_taskdialog #include "swish/shell_folder/KbdInteractiveDialog.h" // Keyboard-interactive auth dialog box #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // task_dialog_builder #include // message_box #include // com_error #include // bind #include // format #include // translate #include // assert #include // wstringstream #include using swish::forms::password_prompt; using swish::frontend::best_taskdialog; using namespace washer::gui; using comet::com_error; using boost::filesystem::path; using boost::locale::translate; using boost::optional; using boost::wformat; using std::pair; using std::string; using std::vector; using std::wstring; using std::wstringstream; namespace swish { namespace frontend { CUserInteraction::CUserInteraction(HWND hwnd) : m_hwnd(hwnd) {} /** * Displays UI dialog to get password from user and returns it. * * If no window given, abort authentication. */ optional CUserInteraction::prompt_for_password() { if (m_hwnd) { wstring password; if (password_prompt( m_hwnd, translate(L"Prompt on password dialog", L"Password:"), password)) { return password; } } return optional(); } optional> CUserInteraction::key_files() { // Swish doesn't use this way of pub-key auth - it uses Pageant via // the agent interface. This method is only implemented by unit // test helpers. return optional>(); } optional> CUserInteraction::challenge_response( const string& title, const string& instructions, const vector>& prompts) { if (!m_hwnd) BOOST_THROW_EXCEPTION(com_error("User interation forbidden", E_FAIL)); // We don't show the dialog if there is nothing to tell the user. // Kb-int authentication usually seems to end with such an empty // interaction for some reason. if (title.empty() && instructions.empty() && prompts.empty()) { // Not optional> because that means abort. return vector(); } // Show dialogue and fetch responses when user clicks OK CKbdInteractiveDialog dlg(title, instructions, prompts); if (dlg.DoModal(m_hwnd) == IDCANCEL) return optional>(); return dlg.GetResponses(); } namespace { HRESULT on_confirm_overwrite( const wstring& old_file, const wstring& new_file, HWND hwnd) { assert(hwnd); if (!hwnd) BOOST_THROW_EXCEPTION(com_error("User interation forbidden", E_FAIL)); wstringstream message; message << wformat( translate(L"The folder already contains a file named '%1%'")) % old_file; message << L"\n\n"; message << wformat( translate( L"Would you like to replace the existing file\n\n\t%1%\n\n" L"with this one?\n\n\t%2%")) % old_file % new_file; message_box::button_type::type button_clicked = message_box::message_box( hwnd, message.str(), translate(L"File already exists"), message_box::box_type::yes_no, message_box::icon_type::question, 2); switch (button_clicked) { case message_box::button_type::yes: return S_OK; case message_box::button_type::no: default: return E_ABORT; } } } HRESULT CUserInteraction::OnConfirmOverwrite( BSTR bstrOldFile, BSTR bstrNewFile ) { try { return on_confirm_overwrite(bstrOldFile, bstrNewFile, m_hwnd); } WASHER_COM_CATCH_AUTO_INTERFACE(); } namespace { /** Click handler callback. */ HRESULT return_hr(HRESULT hr) { return hr; } HRESULT on_hostkey_mismatch( const wstring& host, const wstring& key, const wstring& key_type, HWND hwnd) { if (!hwnd) BOOST_THROW_EXCEPTION(com_error("User interation forbidden", E_FAIL)); wstring title = translate(L"Mismatched host-key"); wstring instruction = translate(L"WARNING: the SSH host-key has changed!"); wstringstream message; message << wformat( translate( L"The SSH host-key sent by '%1%' to identify itself doesn't match " L"the known key for this server. This could mean a third-party " L"is pretending to be the computer you're trying to connect to " L"or the system administrator may have just changed the key.")) % host; message << L"\n\n"; message << translate( L"It is important to check this is the right key fingerprint:"); message << wformat(L"\n\n %1% %2%") % key_type % key; task_dialog::task_dialog_builder td( hwnd, instruction, message.str(), title, washer::gui::task_dialog::icon_type::warning, true, boost::bind(return_hr, E_ABORT)); td.add_button( translate( L"I trust this key: &update and connect\n" L"You won't have to verify this key again unless it changes"), boost::bind(return_hr, S_OK)); td.add_button( translate( L"I trust this key: &just connect\n" L"You will be warned about this key again next time you " L"connect"), boost::bind(return_hr, S_FALSE)); td.add_button( translate( L"&Cancel\n" L"Choose this option unless you are sure the key is correct"), boost::bind(return_hr, E_ABORT), true); return td.show(); } HRESULT on_hostkey_unknown( const wstring& host, const wstring& key, const wstring& key_type, HWND hwnd) { if (!hwnd) BOOST_THROW_EXCEPTION(com_error("User interation forbidden", E_FAIL)); wstring title = translate(L"Unknown host-key"); wstringstream message; message << wformat( translate( L"The server '%1%' has identified itself with an SSH host-key " L"whose fingerprint is:")) % host; message << wformat(L"\n\n %1% %2%\n\n") % key_type % key; message << translate( L"If you are not expecting this key, a third-party may be pretending " L"to be the computer you're trying to connect to."); wstring instruction = translate(L"Verify unknown SSH host-key"); task_dialog::task_dialog_builder td( hwnd, instruction, message.str(), title, washer::gui::task_dialog::icon_type::information, true, boost::bind(return_hr, E_ABORT)); td.add_button( translate( L"I trust this key: &store and connect\n" L"You won't have to verify this key again unless it changes"), boost::bind(return_hr, S_OK)); td.add_button( translate( L"I trust this key: &just connect\n" L"You will be asked to verify the key again next time you " L"connect"), boost::bind(return_hr, S_FALSE)); td.add_button( translate( L"&Cancel\n" L"Choose this option unless you are sure the key is correct"), boost::bind(return_hr, E_ABORT), true); return td.show(); } } HRESULT CUserInteraction::OnHostkeyMismatch( BSTR bstrHostName, BSTR bstrHostKey, BSTR bstrHostKeyType) { try { return on_hostkey_mismatch( bstrHostName, bstrHostKey, bstrHostKeyType, m_hwnd); } WASHER_COM_CATCH_AUTO_INTERFACE(); } HRESULT CUserInteraction::OnHostkeyUnknown( BSTR bstrHostName, BSTR bstrHostKey, BSTR bstrHostKeyType) { try { return on_hostkey_unknown( bstrHostName, bstrHostKey, bstrHostKeyType, m_hwnd); } WASHER_COM_CATCH_AUTO_INTERFACE(); } }} // namespace swish::frontend ================================================ FILE: swish/frontend/UserInteraction.hpp ================================================ /** @file Component to handle user-interaction between the user and an SftpProvider. @if license Copyright (C) 2008, 2009, 2010, 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_FRONTEND_USERINTERACTION_HPP #define SWISH_FRONTEND_USERINTERACTION_HPP #pragma once #include "swish/provider/sftp_provider.hpp" // ISftpConsumer #include // simple_object namespace swish { namespace frontend { class CUserInteraction : public comet::simple_object { public: typedef ISftpConsumer interface_is; CUserInteraction(HWND hwnd); /** * User interaction callbacks. * @{ */ // ISftpConsumer Methods virtual boost::optional prompt_for_password(); virtual boost::optional< std::pair> key_files(); virtual boost::optional> challenge_response( const std::string& title, const std::string& instructions, const std::vector>& prompts); HRESULT OnConfirmOverwrite( __in BSTR bstrOldFile, __in BSTR bstrNewFile ); HRESULT OnHostkeyMismatch( __in BSTR bstrHostName, __in BSTR bstrHostKey, __in BSTR bstrHostKeyType ); HRESULT OnHostkeyUnknown( __in BSTR bstrHostName, __in BSTR bstrHostKey, __in BSTR bstrHostKeyType ); /* @} */ private: HWND m_hwnd; ///< Window to use as parent for user interaction. }; }} // namespace swish::frontend #endif ================================================ FILE: swish/frontend/announce_error.cpp ================================================ /** @file Reporting exceptions to the user. @if license Copyright (C) 2010, 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "announce_error.hpp" #include "swish/frontend/bind_best_taskdialog.hpp" // best_taskdialog #include // task_dialog_builder #include // com_error #include #include // translate #include // assert #include #include // wstringstream using swish::frontend::best_taskdialog; using namespace washer::gui::task_dialog; using comet::com_error; using boost::locale::translate; using boost::diagnostic_information; using std::exception; using std::wstring; using std::wstringstream; namespace { wstring hexify_hr(HRESULT hr) { wstringstream stream; stream << std::hex << std::showbase << hr; return stream.str(); } #define SWISH_ANNOUNCE_ERROR_HRESULT_CASE(hr) case (hr): return L#hr; wstring hresult_code(HRESULT hr) { switch (hr) { SWISH_ANNOUNCE_ERROR_HRESULT_CASE(S_OK); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(S_FALSE); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_UNEXPECTED); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_NOTIMPL); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_OUTOFMEMORY); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_INVALIDARG); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_NOINTERFACE); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_POINTER); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_HANDLE); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_ABORT); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_FAIL); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_ACCESSDENIED); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(E_PENDING); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_CANTSAVE); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_INCOMPLETE); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_FILENOTFOUND); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_ACCESSDENIED); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_UNIMPLEMENTEDFUNCTION); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_INVALIDHANDLE); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_FILEALREADYEXISTS); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_DISKISWRITEPROTECTED); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_MEDIUMFULL); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_LOCKVIOLATION); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_INVALIDPARAMETER); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_INVALIDFUNCTION); SWISH_ANNOUNCE_ERROR_HRESULT_CASE(STG_E_INSUFFICIENTMEMORY); default: return hexify_hr(hr); } } #undef SWISH_ANNOUNCE_ERROR_HRESULT_CASE /** * @todo Convert narrow to wide strings properly. */ wstring format_exception(const exception& error) { wstringstream details; details << error.what(); details << "\n\nBug report information: " << diagnostic_information(error).c_str(); return details.str(); } wstring format_exception(const com_error& error) { wstringstream details; details << error.w_str(); details << L"\n\nHRESULT: " << hresult_code(error.hr()); details << "\n\nBug report information: " << diagnostic_information(error).c_str(); return details.str(); } } namespace swish { namespace frontend { void announce_error( HWND hwnd, const wstring& problem, const wstring& suggested_resolution, const wstring& details) { task_dialog_builder td( hwnd, problem, suggested_resolution, L"Swish", icon_type::error, true); td.extended_text( details, expansion_position::below, initial_expansion_state::default, translate(L"Show &details (which may not be in your language)"), translate(L"Hide &details")); td.show(); } void announce_last_exception( HWND hwnd, const wstring& title, const wstring& suggested_resolution, bool force_ui) { // Only try and announce if we have an owner window if (!force_ui && hwnd == NULL) return; // Each call to announce_error below is guarded with a try/catch. // I've tested these catch handler and they works the way I // expected: they swallows the newly thrown exception allowing the // 'throw' statement at the bottom to rethrow the original exception. // XXX: I can't find whether this is guaranteed by the C++ // standard. Implementing this behaviour must mean maintaining // some form of thrown exception stack. try { throw; } catch (const com_error& error) { try { if (error.hr() != E_ABORT) announce_error( hwnd, title, suggested_resolution, format_exception(error)); } catch (...) { assert(!"Exception announcer threw new exception"); } } catch (const exception& error) { try { announce_error( hwnd, title, suggested_resolution, format_exception(error)); } catch (...) { assert(!"Exception announcer threw new exception"); } } #ifdef DEBUG catch (...) { try { announce_error( hwnd, title, suggested_resolution, L"Woooooo there soldier! Completely unrecognised type of " L"exception."); } catch (...) { assert(!"Exception announcer threw new exception"); } } #endif } }} // namespace swish::frontend ================================================ FILE: swish/frontend/announce_error.hpp ================================================ /** @file Reporting exceptions to the user. @if license Copyright (C) 2010, 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_FRONTEND_ANNOUNCE_ERROR_HPP #define SWISH_FRONTEND_ANNOUNCE_ERROR_HPP #pragma once #include #include // HWND namespace swish { namespace frontend { void announce_error( HWND hwnd, const std::wstring& problem, const std::wstring& suggested_resolution, const std::wstring& details); void announce_last_exception( HWND hwnd, const std::wstring& title, const std::wstring& suggested_resolution, bool force_ui=false); }} // namespace swish::frontend #endif ================================================ FILE: swish/frontend/bind_best_taskdialog.cpp ================================================ /** @file TaskDialogIndirect implementation selector. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "bind_best_taskdialog.hpp" #include "swish/atl.hpp" // required by TaskDialog.h #include // load_function #include // Task98DialogIndirect #include using washer::gui::task_dialog::tdi_function; using washer::load_function; using std::exception; namespace swish { namespace frontend { tdi_function bind_best_taskdialog() { try { return load_function< HRESULT WINAPI (const TASKDIALOGCONFIG*, int*, int*, BOOL*)>( "comctl32.dll", "TaskDialogIndirect"); } catch (const exception&) { return ::Task98DialogIndirect; } } }} // namespace swish::frontend ================================================ FILE: swish/frontend/bind_best_taskdialog.hpp ================================================ /** @file TaskDialogIndirect implementation selector. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_FRONTEND_BIND_BEST_TASKDIALOG #define SWISH_FRONTEND_BIND_BEST_TASKDIALOG #pragma once #include // tdi_function, tdi_implementation namespace swish { namespace frontend { washer::gui::task_dialog::tdi_function bind_best_taskdialog(); class best_taskdialog : public washer::gui::task_dialog::tdi_implementation { public: best_taskdialog() : washer::gui::task_dialog::tdi_implementation(bind_best_taskdialog()) {} }; }} // namespace swish::frontend #endif ================================================ FILE: swish/frontend/commands/About.cpp ================================================ /* Copyright (C) 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "About.hpp" #include "swish/versions/version.hpp" // release_version #include #include // module_path #include // uuid_t #include // translate #include #include // assert #include // ostringstream #include using swish::nse::Command; using swish::nse::command_site; using namespace washer::gui::message_box; using washer::module_path; using washer::shell::pidl::apidl_t; using washer::window::window; using comet::com_ptr; using comet::uuid_t; using boost::locale::translate; using boost::filesystem::path; using boost::optional; using std::ostringstream; using std::string; // http://stackoverflow.com/a/557859/67013 EXTERN_C IMAGE_DOS_HEADER __ImageBase; namespace swish { namespace frontend { namespace commands { namespace { const uuid_t ABOUT_COMMAND_ID(L"b816a885-5022-11dc-9153-0090f5284f85"); path installation_path() { return module_path(((HINSTANCE)&__ImageBase)); } } About::About() : Command( translate( L"Title of command used to show the Swish 'About' box in the " L"Explorer Help menu", L"About &Swish"), ABOUT_COMMAND_ID, translate( L"Displays version, licence and copyright information for Swish.")) {} BOOST_SCOPED_ENUM(Command::state) About::state( com_ptr, bool /*ok_to_be_slow*/) const { return state::enabled; } void About::operator()( com_ptr, const command_site& site, com_ptr) const { optional> view_window = site.ui_owner(); if (!view_window) return; string snapshot = snapshot_version(); if (snapshot.empty()) { snapshot = translate( "Placeholder version if actual version is not known", "unknown"); } ostringstream message; message << "Swish " << release_version().as_string() << "\n" << translate( "A short description of Swish", "Easy SFTP for Windows Explorer") << "\n\n" << "Copyright (C) 2006-2013 Alexander Lamaison and contributors.\n\n" << "This program comes with ABSOLUTELY NO WARRANTY. This is free " "software, and you are welcome to redistribute it under the terms " "of the GNU General Public License as published by the Free " "Software Foundation, either version 3 of the License, or " "(at your option) any later version.\n\n" << translate("Title of a version description", "Snapshot:") << " " << snapshot << "\n" << translate("Title for a date and time", "Build time:") << " " << build_date() << " " << build_time() << "\n" << translate("Title of a filesystem path", "Installation path:") << " " << installation_path().string(); message_box( view_window->hwnd(), message.str(), translate("Title of About dialog box", "About Swish"), box_type::ok, icon_type::information); } }}} // namespace swish::frontend::commands ================================================ FILE: swish/frontend/commands/About.hpp ================================================ /* Copyright (C) 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_FRONTEND_COMMANDS_ABOUT_HPP #define SWISH_FRONTEND_COMMANDS_ABOUT_HPP #include "swish/nse/Command.hpp" // Command namespace swish { namespace frontend { namespace commands { class About : public swish::nse::Command { public: About(); virtual BOOST_SCOPED_ENUM(state) state( comet::com_ptr selection, bool ok_to_be_slow) const; void operator()( comet::com_ptr selection, const swish::nse::command_site& site, comet::com_ptr bind_ctx) const; }; }}} // namespace swish::frontend::commands #endif ================================================ FILE: swish/frontend/winsparkle_shower.cpp ================================================ /** @file Manage WinSparkle initialisation and cleanup. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "winsparkle_shower.hpp" #include using std::string; using std::wstring; namespace swish { namespace frontend { winsparkle_shower::winsparkle_shower( const string& appcast_url, const wstring& app_name, const wstring& app_version, const wstring& company_name, const string& relative_registry_path) : m_needs_cleanup(false) { win_sparkle_set_appcast_url(appcast_url.c_str()); win_sparkle_set_registry_path(relative_registry_path.c_str()); win_sparkle_set_app_details( company_name.c_str(), app_name.c_str(), app_version.c_str()); } void winsparkle_shower::show() { // the dialog may be requested more than once so we need to clean up // before showing it again if (m_needs_cleanup) win_sparkle_cleanup(); m_needs_cleanup = true; win_sparkle_init(); } winsparkle_shower::~winsparkle_shower() { win_sparkle_cleanup(); } }} // namespace swish::frontend ================================================ FILE: swish/frontend/winsparkle_shower.hpp ================================================ /** @file Manage WinSparkle initialisation and cleanup. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_FRONTEND_WINSPARKLE_SHOWER_HPP #define SWISH_FRONTEND_WINSPARKLE_SHOWER_HPP #pragma once #include namespace swish { namespace frontend { class winsparkle_shower { public: winsparkle_shower( const std::string& appcast_url, const std::wstring& app_name, const std::wstring& app_version, const std::wstring& company_name, const std::string& relative_registry_path); ~winsparkle_shower(); void show(); private: bool m_needs_cleanup; }; }} // namespace swish::frontend #endif ================================================ FILE: swish/host_folder/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(COMMAND_SOURCES commands/Add.cpp commands/Add.hpp commands/CloseSession.cpp commands/CloseSession.hpp commands/commands.cpp commands/commands.hpp commands/LaunchAgent.cpp commands/LaunchAgent.hpp commands/Remove.cpp commands/Remove.hpp commands/Rename.cpp commands/Rename.hpp) set(SOURCES columns.cpp context_menu_callback.hpp context_menu_callback.cpp host_itemid_connection.cpp host_management.cpp menu_command_manager.cpp properties.cpp ViewCallback.cpp columns.hpp extract_icon.hpp host_itemid_connection.hpp host_management.hpp host_pidl.hpp menu_command_manager.hpp overlay_icon.hpp properties.hpp ViewCallback.hpp ${COMMAND_SOURCES}) add_library(host_folder ${SOURCES}) hunter_add_package(Comet) hunter_add_package(Washer) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) target_link_libraries(host_folder PRIVATE connection nse version forms frontend shell_folder shell PUBLIC Washer::washer Comet::comet ${Boost_LIBRARIES}) ================================================ FILE: swish/host_folder/ViewCallback.cpp ================================================ /* Handler for host folder's interaction with Explorer Shell Folder View. Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ViewCallback.hpp" #include "swish/host_folder/commands/commands.hpp" // host commands #include "swish/shell/shell_item_array.hpp" #include "swish/utils.hpp" // Utf8StringToWideString #include "swish/versions/version.hpp" // release_version #include // last_error #include // shell_browser, shell_view #include #include #include // BOOST_THROW_EXCEPTION #include // assert #include #include // pair using swish::host_folder::commands::host_folder_task_pane_tasks; using swish::host_folder::commands::host_folder_task_pane_titles; using swish::nse::IEnumUICommand; using swish::nse::IUIElement; using swish::release_version; using swish::utils::Utf8StringToWideString; using comet::com_ptr; using washer::last_error; using washer::shell::pidl::apidl_t; using washer::shell::shell_browser; using washer::shell::shell_view;; using washer::window::window; using washer::window::window_handle; using boost::enable_error_info; using boost::errinfo_api_function; using std::auto_ptr; using std::pair; using std::wstring; template<> struct comet::comtype { static const IID& uuid() throw() { return IID_IDataObject; } typedef IUnknown base; }; namespace swish { namespace host_folder { namespace { /** * Return a ShellItemArray representing the items currently selected. * * @return NULL if nothing is selected. */ com_ptr selection_shell_item_array( com_ptr browser) { com_ptr view = shell_view(browser); com_ptr item_array; view->GetItemObject( SVGIO_SELECTION, item_array.iid(), reinterpret_cast(item_array.out())); // We don't care if getting the array succeeded - if it did, great; // return it. If not we will return a NULL pointer indicating that no // items were selected return item_array; } bool is_vista_or_greater() { OSVERSIONINFO version = OSVERSIONINFO(); version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (::GetVersionEx(&version) == FALSE) BOOST_THROW_EXCEPTION( enable_error_info(last_error()) << errinfo_api_function("GetVersionEx")); return version.dwMajorVersion > 5; } } /** * Create customisation callback object for Explorer default shell view. * * @param folder The folder for whom we are creating this callback object. */ CViewCallback::CViewCallback(const apidl_t& folder) : m_folder(folder), m_winsparkle( "http://www.swish-sftp.org/autoupdate/appcast.xml", L"Swish", Utf8StringToWideString(release_version().as_string()), L"", "Software\\Swish\\Updates") {} /** * The folder window is being created. * * The shell is notifying us of the folder view's window handle. */ bool CViewCallback::on_window_created(HWND hwnd_view) { if (hwnd_view) { m_view = window(window_handle::foster_handle(hwnd_view)); } if (m_view) { m_winsparkle.show(); } return true; } /** * Tell the shell that we might notify it of update events that apply to this * folder (specified using our absolute PIDL). * * We are notified via SFVM_FSNOTIFY if any events indicated here occurr. * * @TODO: It's possible that @a events already has events set in its bitmask * so maybe we should 'or' our extra events with it. */ bool CViewCallback::on_get_notify( PCIDLIST_ABSOLUTE& pidl_monitor, LONG& events) { events = SHCNE_UPDATEDIR | SHCNE_UPDATEITEM | SHCNE_RENAMEITEM | SHCNE_RENAMEFOLDER | SHCNE_DELETE | SHCNE_MKDIR | SHCNE_RMDIR; pidl_monitor = m_folder.get(); // Owned by us return true; } /** * The shell is telling us that an event (probably a SHChangeNotify of some * sort) has affected one of our item. Just nod. If we don't it doesn't work. */ bool CViewCallback::on_fs_notify( PCIDLIST_ABSOLUTE /*pidl*/, LONG /*event*/) { return true; } bool CViewCallback::on_merge_menu(QCMINFO& menu_info) { m_menu_manager = auto_ptr( new menu_command_manager(menu_info, m_view, m_folder)); return true; // I would have expected to have to remove these menu items // in SFVM_UNMERGEMENU but this seems to happen automatically } bool CViewCallback::on_selection_changed( SFV_SELECTINFO& /*selection_info*/) { update_menus(); return true; } bool CViewCallback::on_init_menu_popup( UINT /*first_command_id*/, int /*menu_index*/, HMENU /*menu*/) { update_menus(); return true; } bool CViewCallback::on_invoke_command(UINT command_id) { return m_menu_manager->invoke(command_id, selection(), ole_site()); } #pragma warning(push) #pragma warning(disable:4996) // std::copy ... may be unsafe bool CViewCallback::on_get_help_text( UINT command_id, UINT buffer_size, LPTSTR buffer) { wstring help_text; bool handled = m_menu_manager->help_text( command_id, help_text, selection()); if (handled) { size_t copied = help_text.copy(buffer, buffer_size - 1); buffer[copied] = _T('\0'); return true; } else { return false; } } #pragma warning(pop) bool CViewCallback::on_get_webview_content( SFV_WEBVIEW_CONTENT_DATA& content_out) { assert(content_out.pFolderTasksExpando == NULL); assert(content_out.pExtraTasksExpando == NULL); assert(content_out.pEnumRelatedPlaces == NULL); // HACK: webview conflicts with ExplorerCommands so we disable it if // ExplorerCommands are likely to be used. if (is_vista_or_greater()) return false; pair< com_ptr, com_ptr > tasks = host_folder_task_pane_titles(m_folder); content_out.pExtraTasksExpando = tasks.first.detach(); content_out.pFolderTasksExpando = tasks.second.detach(); return true; } bool CViewCallback::on_get_webview_tasks( SFV_WEBVIEW_TASKSECTION_DATA& tasks_out) { //for some reason this fails on 64-bit //assert(tasks_out.pEnumExtraTasks == NULL); assert(tasks_out.pEnumFolderTasks == NULL); // HACK: webview conflicts with ExplorerCommands so we disable it if // ExplorerCommands are likely to be used. if (is_vista_or_greater()) return false; pair< com_ptr, com_ptr > commands = host_folder_task_pane_tasks(m_folder); tasks_out.pEnumExtraTasks = commands.first.detach(); tasks_out.pEnumFolderTasks = commands.second.detach(); return true; } /** * Items currently selected in the folder view. */ com_ptr CViewCallback::selection() { com_ptr browser = shell_browser(ole_site()); return selection_shell_item_array(browser); } /** * Update the menus to match the current selection. */ void CViewCallback::update_menus() { m_menu_manager->update_state(selection()); } }} // namespace swish::host_folder ================================================ FILE: swish/host_folder/ViewCallback.hpp ================================================ /* Handler for host folder's interaction with Explorer Shell Folder View. Copyright (C) 2008, 2009, 2010, 2011, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_HOST_FOLDER_VIEW_CALLBACK_HPP #define SWISH_HOST_FOLDER_VIEW_CALLBACK_HPP #pragma once #include "swish/frontend/winsparkle_shower.hpp" // winsparkle_shower #include "swish/host_folder/menu_command_manager.hpp" #include "swish/nse/view_callback.hpp" // CViewCallback #include // object_with_site #include #include // apidl_t #include #include // com_ptr #include // simple_object #include // optional #include namespace swish { namespace host_folder { class CViewCallback : public comet::simple_object { public: CViewCallback(const washer::shell::pidl::apidl_t& folder_pidl); private: /// @name SFVM_* message handlers // @{ virtual bool on_window_created(HWND hwnd_view); virtual bool on_get_notify(PCIDLIST_ABSOLUTE& pidl_monitor, LONG& events); virtual bool on_fs_notify(PCIDLIST_ABSOLUTE pidl, LONG event); virtual bool on_merge_menu(QCMINFO& menu_info); virtual bool on_selection_changed(SFV_SELECTINFO& selection_info); virtual bool on_init_menu_popup( UINT first_command_id, int menu_index, HMENU menu); virtual bool on_invoke_command(UINT command_id); virtual bool on_get_help_text( UINT command_id, UINT buffer_size, LPTSTR buffer); virtual bool on_get_webview_content(SFV_WEBVIEW_CONTENT_DATA& content_out); virtual bool on_get_webview_tasks(SFV_WEBVIEW_TASKSECTION_DATA& tasks_out); // @} comet::com_ptr selection(); void update_menus(); boost::optional> m_view; ///< Folder view window washer::shell::pidl::apidl_t m_folder; ///< Owning folder swish::frontend::winsparkle_shower m_winsparkle; ///< Autoupdate checker std::auto_ptr m_menu_manager; }; }} // namespace swish::host_folder #endif ================================================ FILE: swish/host_folder/columns.cpp ================================================ /** @file Host folder detail columns. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "columns.hpp" #pragma warning(push) #pragma warning(disable: 4510 4610) // Cannot generate default constructor #include // array #pragma warning(pop) #include // translate #include #include // LVCFMT_* #include // PKEY_ * using washer::shell::property_key; using boost::array; using boost::locale::translate; using std::wstring; namespace swish { namespace host_folder { namespace { /** * Static column information. * Order of entries must correspond to the indices in columnIndices. */ const boost::array column_key_index = { { { PKEY_ItemNameDisplay, translate("Property (filename/label)", "Name"), SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30 }, { PKEY_ComputerName, translate("Property", "Host"), SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30 }, { PKEY_SwishHostUser, translate("Property", "Username"), SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30 }, { PKEY_SwishHostPort, translate("Property", "Port"), SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 20 }, { PKEY_ItemPathDisplay, translate("Property", "Remote path"), SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30 }, { PKEY_ItemType, translate("Property", "Type"), SHCOLSTATE_TYPE_STR | SHCOLSTATE_SECONDARYUI, LVCFMT_LEFT, 30 } } }; } /** * Return a column entry. */ const column_entry& HostColumnEntries::entry(size_t index) const { return column_key_index.at(index); } /** * Convert index to a corresponding PROPERTYKEY. */ const property_key& property_key_from_column_index(size_t index) { return column_key_index.at(index).m_key; } }} // namespace swish::host_folder ================================================ FILE: swish/host_folder/columns.hpp ================================================ /** @file Host folder detail columns. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_HOST_FOLDER_COLUMNS_HPP #define SWISH_HOST_FOLDER_COLUMNS_HPP #pragma once #include "properties.hpp" // property_from_pidl, compare_pidls_by_property #include "swish/nse/StaticColumn.hpp" // StaticColumn #include // cpidl_t #include // property_key #include // utf_to_utf #include // message #include // SHCOLSTATEF #include namespace swish { namespace host_folder { #pragma warning(push) #pragma warning(disable: 4510 4610) // Cannot generate default constructor struct column_entry { /** * @name Fields. * * These can be set using an aggregate initialiser: { val1, val2, ... } */ // @{ washer::shell::property_key m_key; boost::locale::message m_title; SHCOLSTATEF m_flags; int m_format; int m_avg_char_width; // @} std::wstring title() const { return boost::locale::conv::utf_to_utf(m_title.str()); } SHCOLSTATEF flags() const { return m_flags; } int format() const { return m_format; } int avg_char_width() const { return m_avg_char_width; } /** * Convert the column's property variant to a string. * * Transforms the output using m_stringifier, if any, otherwise performs * simple wstring conversion. */ std::wstring detail(const washer::shell::pidl::cpidl_t& pidl) const { return property_from_pidl(pidl, m_key); } int compare( const washer::shell::pidl::cpidl_t& lhs, const washer::shell::pidl::cpidl_t& rhs) const { return compare_pidls_by_property(lhs, rhs, m_key); } }; #pragma warning(pop) /** * StaticColumn-compatible interface to the static column data. */ class HostColumnEntries { protected: const column_entry& entry(size_t index) const; }; typedef swish::nse::StaticColumn Column; const washer::shell::property_key& property_key_from_column_index( size_t index); }} // namespace swish::host_folder #endif ================================================ FILE: swish/host_folder/commands/Add.cpp ================================================ /* Copyright (C) 2010, 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Add.hpp" #include "swish/forms/add_host.hpp" // add_host #include "swish/host_folder/host_management.hpp" // AddConnectionToRegistry #include // com_error #include // uuid_t #include // translate #include // BOOST_THROW_EXCEPTION #include // assert #include #include // make_pair #include // SHChangeNotify using swish::forms::add_host; using swish::forms::host_info; using swish::nse::Command; using swish::nse::command_site; using swish::host_folder::host_management::AddConnectionToRegistry; using swish::host_folder::host_management::ConnectionExists; using washer::shell::pidl::apidl_t; using washer::window::window; using comet::com_error; using comet::com_ptr; using comet::uuid_t; using boost::locale::translate; using boost::optional; using std::wstring; namespace swish { namespace host_folder { namespace commands { namespace { const uuid_t ADD_COMMAND_ID(L"b816a880-5022-11dc-9153-0090f5284f85"); /** * Cause Explorer to refresh any windows displaying the owning folder. * * Inform shell that something in our folder changed (we don't know * exactly what the new PIDL is until we reload from the registry, hence * UPDATEDIR). */ void notify_shell(const apidl_t folder_pidl) { assert(folder_pidl); ::SHChangeNotify( SHCNE_UPDATEDIR, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT, folder_pidl.get(), NULL); } } Add::Add(const apidl_t& folder_pidl) : Command( translate(L"&Add SFTP Connection"), ADD_COMMAND_ID, translate(L"Create a new SFTP connection with Swish."), L"shell32.dll,-258", translate(L"&Add SFTP Connection..."), translate(L"Add Connection")), m_folder_pidl(folder_pidl) {} BOOST_SCOPED_ENUM(Command::state) Add::state( com_ptr, bool /*ok_to_be_slow*/) const { return state::enabled; } /** Display dialog to get connection info from user. */ void Add::operator()( com_ptr, const command_site& site, com_ptr) const { optional> view_window = site.ui_owner(); if (view_window) { host_info info = add_host(view_window->hwnd()); if (ConnectionExists(info.name)) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); AddConnectionToRegistry( info.name, info.host, info.port, info.user, info.path); notify_shell(m_folder_pidl); } } }}} // namespace swish::host_folder::commands ================================================ FILE: swish/host_folder/commands/Add.hpp ================================================ /* Copyright (C) 2010, 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_HOST_FOLDER_COMMANDS_ADD_HPP #define SWISH_HOST_FOLDER_COMMANDS_ADD_HPP #include "swish/nse/Command.hpp" // Command #include // apidl_t namespace swish { namespace host_folder { namespace commands { class Add : public swish::nse::Command { public: Add(const washer::shell::pidl::apidl_t& folder_pidl); virtual BOOST_SCOPED_ENUM(state) state( comet::com_ptr selection, bool ok_to_be_slow) const; void operator()( comet::com_ptr selection, const swish::nse::command_site& site, comet::com_ptr bind_ctx) const; private: washer::shell::pidl::apidl_t m_folder_pidl; }; }}} // namespace swish::host_folder::commands #endif ================================================ FILE: swish/host_folder/commands/CloseSession.cpp ================================================ /* Copyright (C) 2013, 2014, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "CloseSession.hpp" #include "swish/connection/session_manager.hpp" #include "swish/shell/shell_item_array.hpp" #include "swish/shell/parent_and_item.hpp" #include "swish/frontend/bind_best_taskdialog.hpp" // best_taskdialog #include "swish/remote_folder/pidl_connection.hpp" // connection_from_pidl #include #include // com_ptr #include // com_error, com_error_from_interface #include // uuid_t #include #include // BOOST_FOREACH #include // translate #include // utf_to_utf #include #include #include #include // promise, packaged_task #include #include // BOOST_THROW_EXCEPTION #include // in_place #include // assert #include // auto_ptr #include // wostringstream; #include #include // pair #include // SHChangeNotify using swish::connection::session_manager; using swish::frontend::best_taskdialog; using swish::nse::Command; using swish::nse::command_site; using swish::remote_folder::connection_from_pidl; using namespace washer::gui::task_dialog; using washer::shell::pidl::apidl_t; using washer::window::window; using comet::com_error; using comet::com_error_from_interface; using comet::com_ptr; using comet::uuid_t; using boost::bind; using boost::unique_future; using boost::locale::conv::utf_to_utf; using boost::locale::translate; using boost::noncopyable; using boost::optional; using boost::packaged_task; using boost::promise; using boost::shared_ptr; using boost::thread; using std::auto_ptr; using std::endl; using std::make_pair; using std::pair; using std::string; using std::wostringstream; using std::wstring; namespace swish { namespace host_folder { namespace commands { namespace { const uuid_t CLOSE_SESSION_COMMAND_ID("b816a886-5022-11dc-9153-0090f5284f85"); /** * Cause Explorer to refresh the UI view of the given item. */ void notify_shell(const apidl_t item) { ::SHChangeNotify( SHCNE_UPDATEITEM, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT, item.get(), NULL); } } CloseSession::CloseSession() : Command( translate(L"&Close SFTP connection"), CLOSE_SESSION_COMMAND_ID, translate(L"Close the authenticated connection to the server."), L"shell32.dll,-11", translate(L"&Close SFTP Connection..."), translate(L"Close Connection")) {} BOOST_SCOPED_ENUM(Command::state) CloseSession::state( com_ptr selection, bool /*ok_to_be_slow*/) const { if (!selection) { // Selection unknown. return state::hidden; } switch (selection->size()) { case 1: { com_ptr item = selection->at(0); com_ptr folder_and_pidls = try_cast(item); apidl_t item_pidl = folder_and_pidls->absolute_item_pidl(); if (session_manager().has_session(connection_from_pidl(item_pidl))) return state::enabled; else return state::hidden; } case 0: return state::hidden; default: // This means multiple items are selected. We disable rather than // hide the buttons to let the user know the option exists but that // we don't support multi-host session closure. return state::disabled; } } namespace { void start_marquee(progress_bar bar) { bar(marquee_progress()); } template wstring ui_content_text(const PendingTaskRange& pending_tasks) { wostringstream content; content << translate( L"Explanation in progress dialog", L"The following tasks are using the session:"); content << endl << endl; BOOST_FOREACH(const std::string& task_name, pending_tasks) { content << L"\x2022 "; content << utf_to_utf(task_name); content << endl; } content << endl; content << translate( L"Explanation of why we are displaying progress dialog. " L"'them' refers to the tasks we are waiting for.", L"Waiting for them to finish."); return content.str(); } void do_nothing_command() {} template pair>, shared_ptr> start_async(Callable operation) { packaged_task task(operation); shared_ptr> result( new unique_future(boost::move(task.get_future()))); shared_ptr running_task(new thread(boost::move(task))); return make_pair(result, running_task); } template class async_task_dialog_runner : private noncopyable { public: explicit async_task_dialog_runner(task_dialog_builder builder) : m_builder(builder), m_dialog(m_promised_dialog.get_future()), m_result( start_async( bind(&async_task_dialog_runner::dialog_loop, this))) {} ~async_task_dialog_runner() { // Ideally, we would use boost::async to run the dialog, which returns // a future whose destructor blocks until the dialog finishes. Making // that future a member of this class then ensures the member variables // remain valid for entire lifetime of the async operation. // // However, Boost 1.49 doesn't have async() so we need to keep // the thread around and join in the destructor m_result.second->join(); } task_dialog dialog() { // Dialog creation might have failed so we don't want to block here // on an event that may never happen. // FIXME: Horrible mess with a race condition: creation may fail // with an exception after we check for it. The solution is to // rewrite the task dialog class to use futures. if (m_result.first->has_exception()) { m_result.first->get(); } return m_dialog.get(); } unique_future& result() { return *(m_result.first); } private: void on_create(const task_dialog& dialog) { m_promised_dialog.set_value(dialog); } template Result dialog_loop() { return m_builder.show( bind(&async_task_dialog_runner::on_create, this, _1)); } task_dialog_builder m_builder; // Class is not movable because thread holds reference to this instance // when bound to `dialog_loop`. Moving it would leave moved thread // using `dialog_loop` with old `this` promise m_promised_dialog; unique_future m_dialog; pair>, shared_ptr> m_result; }; class running_dialog { public: running_dialog( auto_ptr> runner, command_id id) : m_dialog_runner(runner), m_id(id) {} task_dialog dialog() { return m_dialog_runner->dialog(); } command_id dismissal_command_id() { return m_id; } bool dialog_has_been_dismissed() { return m_dialog_runner->result().has_value(); } private: auto_ptr> m_dialog_runner; command_id m_id; }; template running_dialog run_task_dialog(const PendingTaskRange& pending_tasks) { task_dialog_builder builder( NULL, //m_parent_window, translate( L"Title of a progress dialog", L"Disconnecting session"), ui_content_text(pending_tasks), L"Swish", icon_type::information); builder.include_progress_bar(start_marquee); command_id id = builder.add_button( button_type::cancel, do_nothing_command); auto_ptr> runner( new async_task_dialog_runner(builder)); return running_dialog(runner, id); } class waiting_ui { public: template explicit waiting_ui(const PendingTaskRange& pending_tasks) : m_dialog(run_task_dialog(pending_tasks)) {} template bool update(const PendingTaskRange& pending_tasks) { if (boost::empty(pending_tasks)) { m_dialog.dialog().invoke_command( m_dialog.dismissal_command_id()); return true; } else { m_dialog.dialog().content(ui_content_text(pending_tasks)); return !m_dialog.dialog_has_been_dismissed(); } } private: running_dialog m_dialog; }; class disconnection_progress : private noncopyable { public: template bool operator()(const PendingTaskRange& pending_tasks) { if (!m_dialog) { // No need to start dialog if there are no tasks if (!boost::empty(pending_tasks)) { // Using in-place-factory because waiting_ui's copy // constructor requires NON-const ref which optional // assignment doesn't allow m_dialog = in_place(pending_tasks); } return true; } else { return m_dialog->update(pending_tasks); } } private: optional m_dialog; }; }; void CloseSession::operator()( com_ptr selection, const command_site&, com_ptr) const { // TODO: use the view to decide whether to show a progress dialog if (selection->size() != 1) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); com_ptr item = selection->at(0); com_ptr folder_and_pidls = try_cast(item); apidl_t selected_item = folder_and_pidls->absolute_item_pidl(); disconnection_progress progress; session_manager().disconnect_session( connection_from_pidl(selected_item), boost::ref(progress)); notify_shell(selected_item); } }}} // namespace swish::host_folder::commands ================================================ FILE: swish/host_folder/commands/CloseSession.hpp ================================================ /* Copyright (C) 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_HOST_FOLDER_COMMANDS_CLOSE_SESSION_HPP #define SWISH_HOST_FOLDER_COMMANDS_CLOSE_SESSION_HPP #pragma once #include "swish/nse/Command.hpp" // Command namespace swish { namespace host_folder { namespace commands { class CloseSession : public swish::nse::Command { public: CloseSession(); virtual BOOST_SCOPED_ENUM(state) state( comet::com_ptr selection, bool ok_to_be_slow) const; void operator()( comet::com_ptr selection, const swish::nse::command_site& site, comet::com_ptr bind_ctx) const; }; }}} // namespace swish::host_folder::commands #endif ================================================ FILE: swish/host_folder/commands/LaunchAgent.cpp ================================================ /* Copyright (C) 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "LaunchAgent.hpp" #include // last_error #include // module_path, module_handle #include // com_error #include // uuid_t #include // errinfo #include // errinfo_file_name #include // errinfo_api_function #include // translate #include // path #include // BOOST_THROW_EXCEPTION #include // assert #include #include // SHChangeNotify using swish::nse::Command; using swish::nse::command_site; using washer::module_handle; using washer::module_path; using washer::shell::pidl::apidl_t; using comet::com_error; using comet::com_ptr; using comet::uuid_t; using boost::locale::translate; using boost::filesystem::path; using std::wstring; // http://stackoverflow.com/a/557859/67013 EXTERN_C IMAGE_DOS_HEADER __ImageBase; namespace swish { namespace host_folder { namespace commands { namespace { const uuid_t ADD_COMMAND_ID(L"b816a884-5022-11dc-9153-0090f5284f85"); const path PAGEANT_FILE_NAME = L"pageant.exe"; path pageant_path() { return module_path(((HINSTANCE)&__ImageBase)).parent_path() / PAGEANT_FILE_NAME; } /** * Cause Explorer to refresh any windows displaying the owning folder. * * Inform shell that something in our folder changed (we don't know * exactly what the new PIDL is until we reload from the registry, hence * UPDATEDIR). */ void notify_shell(const apidl_t folder_pidl) { assert(folder_pidl); ::SHChangeNotify( SHCNE_UPDATEDIR, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT, folder_pidl.get(), NULL); } } LaunchAgent::LaunchAgent(const apidl_t& folder_pidl) : Command( translate( L"Title of command used to launch the SSH agent program", L"&Launch key agent"), ADD_COMMAND_ID, translate(L"Launch Putty SSH key agent, Pageant."), L"", translate( L"Title of command used to launch the SSH agent program", L"&Launch key agent"), translate( L"Title of command used to launch the SSH agent program", L"Launch key agent")), m_folder_pidl(folder_pidl) {} BOOST_SCOPED_ENUM(Command::state) LaunchAgent::state( com_ptr, bool /*ok_to_be_slow*/) const { HWND hwnd = ::FindWindowW(L"Pageant", L"Pageant"); return (hwnd) ? state::hidden : state::enabled; } void LaunchAgent::operator()( com_ptr, const command_site& site, com_ptr) const { static wstring pageant = pageant_path().wstring(); STARTUPINFOW si = STARTUPINFOW(); PROCESS_INFORMATION pi = PROCESS_INFORMATION(); if (!::CreateProcessW( pageant.c_str(), NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) BOOST_THROW_EXCEPTION( boost::enable_error_info(washer::last_error()) << boost::errinfo_file_name("pageant") << boost::errinfo_api_function("CreateProcess")); // Notify the shell because it needs to prod the commands to recalculate // their visibility so that we can tell it not to show our button now // that Pageant is running. notify_shell(m_folder_pidl); } }}} // namespace swish::host_folder::commands ================================================ FILE: swish/host_folder/commands/LaunchAgent.hpp ================================================ /* Copyright (C) 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_HOST_FOLDER_COMMANDS_LAUNCH_AGENT_HPP #define SWISH_HOST_FOLDER_COMMANDS_LAUNCH_AGENT_HPP #pragma once #include "swish/nse/Command.hpp" // Command #include // apidl_t namespace swish { namespace host_folder { namespace commands { class LaunchAgent : public swish::nse::Command { public: LaunchAgent(const washer::shell::pidl::apidl_t& folder_pidl); virtual BOOST_SCOPED_ENUM(state) state( comet::com_ptr selection, bool ok_to_be_slow) const; void operator()( comet::com_ptr selection, const swish::nse::command_site& site, comet::com_ptr bind_ctx) const; private: washer::shell::pidl::apidl_t m_folder_pidl; }; }}} // namespace swish::host_folder::commands #endif ================================================ FILE: swish/host_folder/commands/Remove.cpp ================================================ /* Copyright (C) 2010, 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Remove.hpp" #include "swish/host_folder/host_management.hpp" // RemoveConnectionFromRegistry #include "swish/host_folder/host_pidl.hpp" // find_host_itemid, host_item_view #include "swish/shell/parent_and_item.hpp" #include "swish/shell/shell_item_array.hpp" #include // com_error #include // uuid_t #include // translate #include // BOOST_THROW_EXCEPTION #include // assert #include using swish::nse::Command; using swish::nse::command_site; using swish::host_folder::find_host_itemid; using swish::host_folder::host_itemid_view; using swish::host_folder::host_management::RemoveConnectionFromRegistry; using washer::shell::pidl::apidl_t; using comet::com_error; using comet::com_ptr; using comet::uuid_t; using boost::locale::translate; using boost::optional; using std::wstring; namespace swish { namespace host_folder { namespace commands { namespace { const uuid_t REMOVE_COMMAND_ID("b816a881-5022-11dc-9153-0090f5284f85"); /** * Cause Explorer to refresh any windows displaying the owning folder. * * Inform shell that something in our folder changed (we don't know * exactly what the new PIDL is until we reload from the registry, hence * UPDATEDIR). */ void notify_shell(const apidl_t& folder_pidl) { ::SHChangeNotify( SHCNE_UPDATEDIR, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT, folder_pidl.get(), NULL); } } Remove::Remove(const apidl_t& folder_pidl) : Command( translate(L"&Remove SFTP Connection"), REMOVE_COMMAND_ID, translate(L"Remove a SFTP connection created with Swish."), L"shell32.dll,-240", translate(L"&Remove SFTP Connection..."), translate(L"Remove Connection")), m_folder_pidl(folder_pidl) {} BOOST_SCOPED_ENUM(Command::state) Remove::state( com_ptr selection, bool /*ok_to_be_slow*/) const { if (!selection) { // Selection unknown. return state::hidden; } switch (selection->size()) { case 1: return state::enabled; case 0: return state::hidden; default: // This means multiple items are selected. We disable rather than // hide the buttons to let the user know the option exists but that // we don't support multi-host removal yet. // TODO: support multi-host removal return state::disabled; } } void Remove::operator()( com_ptr selection, const command_site& site, com_ptr) const { // XXX: for the moment we only allow removing one item. // is this what we want? if (selection->size() != 1) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); com_ptr item = selection->at(0); com_ptr folder_and_pidls = try_cast(item); apidl_t selected_item = folder_and_pidls->absolute_item_pidl(); wstring label = host_itemid_view(*find_host_itemid(selected_item)).label(); assert(!label.empty()); if (label.empty()) BOOST_THROW_EXCEPTION(com_error(E_UNEXPECTED)); RemoveConnectionFromRegistry(label); notify_shell(m_folder_pidl); } }}} // namespace swish::host_folder::commands ================================================ FILE: swish/host_folder/commands/Remove.hpp ================================================ /* Copyright (C) 2010, 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_HOST_FOLDER_COMMANDS_REMOVE_HPP #define SWISH_HOST_FOLDER_COMMANDS_REMOVE_HPP #pragma once #include "swish/nse/Command.hpp" // Command #include // apidl_t namespace swish { namespace host_folder { namespace commands { class Remove : public swish::nse::Command { public: Remove(const washer::shell::pidl::apidl_t& folder_pidl); virtual BOOST_SCOPED_ENUM(state) state( comet::com_ptr selection, bool ok_to_be_slow) const; void operator()( comet::com_ptr selection, const swish::nse::command_site& site, comet::com_ptr bind_ctx) const; private: washer::shell::pidl::apidl_t m_folder_pidl; }; }}} // namespace swish::host_folder::commands #endif ================================================ FILE: swish/host_folder/commands/Rename.cpp ================================================ /* Copyright (C) 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Rename.hpp" #include "swish/shell/shell.hpp" // put_view_item_into_rename_mode #include "swish/shell/parent_and_item.hpp" #include "swish/shell/shell_item_array.hpp" #include // shell_browser, shell_view #include // com_error #include // com_ptr #include // uuid_t #include // translate #include // BOOST_THROW_EXCEPTION #include // assert #include using swish::nse::Command; using swish::nse::command_site; using swish::shell::put_view_item_into_rename_mode; using washer::shell::pidl::cpidl_t; using washer::shell::shell_browser; using washer::shell::shell_view; using comet::com_error; using comet::com_ptr; using comet::uuid_t; using boost::locale::translate; using std::wstring; namespace swish { namespace host_folder { namespace commands { namespace { const uuid_t RENAME_COMMAND_ID("b816a883-5022-11dc-9153-0090f5284f85"); } Rename::Rename() : Command( translate(L"&Rename SFTP Connection"), RENAME_COMMAND_ID, translate(L"Rename an SFTP connection created with Swish."), L"shell32.dll,133", translate(L"&Rename SFTP Connection..."), translate(L"Rename Connection")) {} BOOST_SCOPED_ENUM(Command::state) Rename::state( com_ptr selection, bool /*ok_to_be_slow*/) const { if (!selection) { // Selection unknown. return state::hidden; } switch (selection->size()) { case 1: return state::enabled; case 0: return state::hidden; default: // This means multiple items are selected. We disable rather than // hide the buttons to let the user know the option exists but that // we don't support multi-host renaming. return state::disabled; } } // This command just puts the item into rename (edit) mode. When the user // finishes typing the new name, the shell takes care of performing the rest of // the renaming process by calling SetNameOf() on the HostFolder void Rename::operator()( com_ptr selection, const command_site& site, com_ptr) const { if (selection->size() != 1) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); com_ptr view = shell_view(shell_browser(site.ole_site())); com_ptr item = selection->at(0); com_ptr folder_and_pidls = try_cast(item); cpidl_t selected_item = folder_and_pidls->item_pidl(); put_view_item_into_rename_mode(view, selected_item); } }}} // namespace swish::host_folder::commands ================================================ FILE: swish/host_folder/commands/Rename.hpp ================================================ /* Copyright (C) 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_HOST_FOLDER_COMMANDS_RENAME_HPP #define SWISH_HOST_FOLDER_COMMANDS_RENAME_HPP #include "swish/nse/Command.hpp" // Command namespace swish { namespace host_folder { namespace commands { class Rename : public swish::nse::Command { public: Rename(); virtual BOOST_SCOPED_ENUM(state) state( comet::com_ptr selection, bool ok_to_be_slow) const; void operator()( comet::com_ptr selection, const swish::nse::command_site& site, comet::com_ptr bind_ctx) const; }; }}} // namespace swish::host_folder::commands #endif ================================================ FILE: swish/host_folder/commands/commands.cpp ================================================ /* Copyright (C) 2010, 2011, 2012, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "commands.hpp" #include "swish/host_folder/commands/Add.hpp" #include "swish/host_folder/commands/CloseSession.hpp" #include "swish/host_folder/commands/LaunchAgent.hpp" #include "swish/host_folder/commands/Remove.hpp" #include "swish/host_folder/commands/Rename.hpp" #include "swish/nse/explorer_command.hpp" // CExplorerCommand* #include "swish/nse/task_pane.hpp" // CUIElementErrorAdapter #include // simple_object #include // make_smart_enumeration #include // translate #include // make_shared #include // assert #include #include // make_pair #include using swish::nse::CExplorerCommand; using swish::nse::CExplorerCommandProvider; using swish::nse::CUICommand; using swish::nse::CUIElementErrorAdapter; using swish::nse::Command; using swish::nse::IEnumUICommand; using swish::nse::IUICommand; using swish::nse::IUIElement; using swish::nse::WebtaskCommandTitleAdapter; using washer::shell::pidl::apidl_t; using comet::com_ptr; using comet::make_smart_enumeration; using comet::simple_object; using boost::locale::translate; using boost::make_shared; using boost::shared_ptr; using std::make_pair; using std::vector; using std::wstring; namespace swish { namespace host_folder { namespace commands { com_ptr host_folder_command_provider( const apidl_t& folder_pidl) { CExplorerCommandProvider::ordered_commands commands; commands.push_back(new CExplorerCommand(folder_pidl)); commands.push_back(new CExplorerCommand(folder_pidl)); commands.push_back(new CExplorerCommand()); commands.push_back(new CExplorerCommand()); commands.push_back(new CExplorerCommand(folder_pidl)); return new CExplorerCommandProvider(commands); } class CSftpTasksTitle : public simple_object { public: virtual std::wstring title( const comet::com_ptr& /*items*/) const { return translate(L"SFTP Tasks"); } virtual std::wstring icon( const comet::com_ptr& /*items*/) const { return L"shell32.dll,-9"; } virtual std::wstring tool_tip( const comet::com_ptr& /*items*/) const { return translate( L"These tasks help you manage Swish SFTP connections."); } }; std::pair, com_ptr > host_folder_task_pane_titles(const apidl_t& /*folder_pidl*/) { return make_pair(new CSftpTasksTitle(), com_ptr()); } std::pair, com_ptr > host_folder_task_pane_tasks(const apidl_t& folder_pidl) { typedef shared_ptr< vector< com_ptr > > shared_command_vector; shared_command_vector commands = make_shared< vector< com_ptr > >(); commands->push_back( new CUICommand>(folder_pidl)); commands->push_back( new CUICommand>(folder_pidl)); commands->push_back( new CUICommand>()); commands->push_back( new CUICommand>()); commands->push_back( new CUICommand>(folder_pidl)); com_ptr e = make_smart_enumeration(commands); return make_pair(e, com_ptr()); } }}} // namespace swish::host_folder::commands ================================================ FILE: swish/host_folder/commands/commands.hpp ================================================ /* Copyright (C) 2010, 2011, 2012, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_HOST_FOLDER_COMMANDS_HPP #define SWISH_HOST_FOLDER_COMMANDS_HPP #pragma once #include "swish/nse/UICommand.hpp" // IUIElement #include // apidl_t #include // com_ptr #include // IExplorerCommandProvider namespace swish { namespace host_folder { namespace commands { comet::com_ptr host_folder_command_provider( const washer::shell::pidl::apidl_t& folder_pidl); std::pair, comet::com_ptr > host_folder_task_pane_titles(const washer::shell::pidl::apidl_t& folder_pidl); std::pair< comet::com_ptr, comet::com_ptr > host_folder_task_pane_tasks(const washer::shell::pidl::apidl_t& folder_pidl); }}} // namespace swish::host_folder::commands #endif ================================================ FILE: swish/host_folder/context_menu_callback.cpp ================================================ /* Copyright (C) 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "swish/host_folder/commands/Remove.hpp" #include "swish/host_folder/context_menu_callback.hpp" // context_menu_callback #include "swish/nse/command_site.hpp" #include "swish/shell/shell_item_array.hpp" // shell_item_array_from_data_object #include #include #include // DFM_CMD_DELETE using swish::nse::command_site; using swish::shell::shell_item_array_from_data_object; using washer::shell::pidl::apidl_t; using washer::window::window; using washer::window::window_handle; using comet::com_ptr; using boost::optional; using std::wstring; namespace swish { namespace host_folder { context_menu_callback::context_menu_callback(const apidl_t& folder_pidl) : m_folder_pidl(folder_pidl) {} namespace { bool do_invoke_command( const apidl_t& folder_pidl, HWND hwnd_view, com_ptr selection_data_object, UINT item_offset, const wstring& /*arguments*/, int /*window_mode*/, com_ptr context_menu_site) { if (item_offset == DFM_CMD_DELETE) { com_ptr selection = shell_item_array_from_data_object(selection_data_object); // Use given window as a UI owner fallback because, if we compile // with pre-Vista support, the OLE site will always be NULL optional> fallback_ui_owner; if (hwnd_view) { fallback_ui_owner = window( window_handle::foster_handle(hwnd_view)); } commands::Remove deletion_command(folder_pidl); deletion_command( selection, command_site(context_menu_site, fallback_ui_owner), NULL); return true; } else { return false; } } } bool context_menu_callback::invoke_command( HWND hwnd_view, com_ptr selection, UINT item_offset, const wstring& arguments) { return do_invoke_command( m_folder_pidl, hwnd_view, selection, item_offset, arguments, SW_NORMAL, NULL); } /** * @todo Take account of the behaviour flags. */ bool context_menu_callback::invoke_command( HWND hwnd_view, com_ptr selection, UINT item_offset, const wstring& arguments, DWORD /*behaviour_flags*/, UINT /*minimum_id*/, UINT /*maximum_id*/, const CMINVOKECOMMANDINFO& invocation_details, com_ptr context_menu_site) { return do_invoke_command( m_folder_pidl, hwnd_view, selection, item_offset, arguments, invocation_details.nShow, context_menu_site); } }} // namespace swish::remote_folder ================================================ FILE: swish/host_folder/context_menu_callback.hpp ================================================ /* HostFolder context menu implementation. Copyright (C) 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_HOST_FOLDER_CONTEXT_MENU_CALLBACK_HPP #define SWISH_HOST_FOLDER_CONTEXT_MENU_CALLBACK_HPP #include "swish/nse/default_context_menu_callback.hpp" // default_context_menu_callback #include // apidl_t #include // com_ptr #include #include // IDataObject #include // CMINVOKECOMMANDINFO #include // HWND namespace swish { namespace host_folder { class context_menu_callback : public swish::nse::default_context_menu_callback { public: context_menu_callback(const washer::shell::pidl::apidl_t& folder_pidl); private: bool invoke_command( HWND hwnd_view, comet::com_ptr selection, UINT item_offset, const std::wstring& arguments); bool invoke_command( HWND hwnd_view, comet::com_ptr selection, UINT item_offset, const std::wstring& arguments, DWORD behaviour_flags, UINT minimum_id, UINT maximum_id, const CMINVOKECOMMANDINFO& invocation_details, comet::com_ptr context_menu_site); washer::shell::pidl::apidl_t m_folder_pidl; }; }} // namespace swish::host_folder #endif ================================================ FILE: swish/host_folder/extract_icon.hpp ================================================ /** @file Host folder icons. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_HOST_FOLDER_EXTRACT_ICON_HPP #define SWISH_HOST_FOLDER_EXTRACT_ICON_HPP #include "swish/host_folder/host_pidl.hpp" // host_itemid_view, #include // cpidl_t #include #include #include // StringCchCopy namespace swish { namespace host_folder { class extract_icon_co : public comet::simple_object { public: extract_icon_co( const boost::optional>& owning_view, const washer::shell::pidl::cpidl_t& item) : m_owning_view(owning_view), m_item(item) {} /** * Extract an icon bitmap given the information passed. * * We return S_FALSE to tell the shell to extract the icons itself. */ STDMETHODIMP extract_icon_co::Extract( LPCTSTR /*location*/, UINT /*index*/, HICON* /*large_icon_out*/, HICON* /*small_icon_out*/, UINT /*desired_sizes*/) { return S_FALSE; } /** * Retrieve the location of the appropriate icon. * * We set all SFTP hosts to have the icon from shell32.dll. */ STDMETHODIMP extract_icon_co::GetIconLocation( UINT /*flags*/, wchar_t* location_buffer_out, UINT buffer_size, int* index_out, UINT* flags_out) { // type of use (flags) is ignored for host folder // Set host to have the ICS host icon StringCchCopy(location_buffer_out, buffer_size, L"shell32.dll"); *index_out = 17; // Force call to Extract *flags_out = GIL_DONTCACHE; return S_OK; } private: boost::optional> m_owning_view; washer::shell::pidl::cpidl_t m_item; }; }} #endif ================================================ FILE: swish/host_folder/host_itemid_connection.cpp ================================================ /** @file Relates host item IDs to SFTP connections. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "host_itemid_connection.hpp" #include "swish/connection/connection_spec.hpp" #include "swish/host_folder/host_pidl.hpp" // find_host_itemid, host_itemid_view using swish::connection::connection_spec; using swish::host_folder::host_itemid_view; namespace swish { namespace host_folder { connection_spec connection_from_host_itemid(const host_itemid_view& host_itemid) { return connection_spec( host_itemid.host(), host_itemid.user(), host_itemid.port()); } }} // namespace swish::host_folder ================================================ FILE: swish/host_folder/host_itemid_connection.hpp ================================================ /** @file Relates host item IDs to SFTP connections. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_HOST_FOLDER_HOST_ITEM_CONNECTION_HPP #define SWISH_HOST_FOLDER_HOST_ITEM_CONNECTION_HPP #pragma once #include "swish/connection/connection_spec.hpp" #include "swish/provider/sftp_provider.hpp" #include "swish/host_folder/host_pidl.hpp" // host_itemid_view namespace swish { namespace host_folder { /** * Converts a host item ID into a connection specification. */ swish::connection::connection_spec connection_from_host_itemid( const host_itemid_view& host_itemid); }} // namespace swish::host_folder #endif ================================================ FILE: swish/host_folder/host_management.cpp ================================================ /** @file Management functions for host entries saved in the registry. @if license Copyright (C) 2009, 2015 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "host_management.hpp" #include "swish/debug.hpp" #include "swish/host_folder/host_pidl.hpp" // create_host_itemid, // host_itemid_view #include #include #include using swish::host_folder::create_host_itemid; using swish::host_folder::host_itemid_view; using washer::shell::pidl::cpidl_t; using comet::regkey; using boost::optional; using std::runtime_error; using std::transform; using std::wstring; using std::vector; namespace { // private static const wstring CONNECTIONS_REGISTRY_KEY_NAME = L"Software\\Swish\\Connections"; static const wchar_t* HOST_VALUE_NAME = L"Host"; static const wchar_t* PORT_VALUE_NAME = L"Port"; static const wchar_t* USER_VALUE_NAME = L"User"; static const wchar_t* PATH_VALUE_NAME = L"Path"; regkey get_connection_from_registry(const wstring& label) { regkey swish_connections = regkey(HKEY_CURRENT_USER).open( CONNECTIONS_REGISTRY_KEY_NAME); return swish_connections.open(label); } } namespace swish { namespace host_folder { namespace host_management { /** * Get a single connection from the registry as a PIDL. * * @pre The connection, if present, is a subkey of the * @c Software\\Swish\\Connections registry key whose name is given * by @p label. * * @param label Friendly name of the connection to load. * * @returns A host PIDL holding the connection details. */ optional FindConnectionInRegistry(const wstring& label) { regkey swish_connections = regkey(HKEY_CURRENT_USER).open_nothrow( CONNECTIONS_REGISTRY_KEY_NAME); // Legal to fail here - may be first ever connection if(!swish_connections) return optional(); regkey connection = swish_connections.open(label); if(!connection) return optional(); wstring host = connection[HOST_VALUE_NAME]; int port = connection[PORT_VALUE_NAME]; wstring user = connection[USER_VALUE_NAME]; wstring path = connection[PATH_VALUE_NAME]; return create_host_itemid(host, user, path, port, label); } namespace { cpidl_t GetConnectionDetailsFromRegistry(const wstring& label) { return *FindConnectionInRegistry(label); } } /** * Load all the connections stored in the registry into PIDLs. * * It's possible that there aren't any connections in * the @c Software\\Swish\\Connections key of the registry, in which case * the vector is left empty. * * @returns Vector of PIDLs containing the details of all the SFTP * stored in the registry. * @throws com_error if something unexpected happens such as corrupt * registry structure. */ vector LoadConnectionsFromRegistry() { vector connection_pidls; regkey connections = regkey(HKEY_CURRENT_USER).open( CONNECTIONS_REGISTRY_KEY_NAME); if (connections) // Legal to fail here - may be first ever connection { regkey::subkeys_type connection_collection = connections.enumerate().subkeys(); transform( connection_collection.begin(), connection_collection.end(), back_inserter(connection_pidls), GetConnectionDetailsFromRegistry); } return connection_pidls; } /** * Add a host entry to the Swish connection key with the given details. * * If the connections key does not already exits (because no hosts have * been added yet) the key is created and the host added to it. */ void AddConnectionToRegistry( wstring label, wstring host, int port, wstring username, wstring path) { DWORD key_disposition; regkey connection = regkey(HKEY_CURRENT_USER).create( CONNECTIONS_REGISTRY_KEY_NAME + L"\\" + label, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, 0, &key_disposition); if (key_disposition == REG_OPENED_EXISTING_KEY) { BOOST_THROW_EXCEPTION( runtime_error("connection already exists in registry")); } else { connection[HOST_VALUE_NAME] = host; connection[PORT_VALUE_NAME] = port; connection[USER_VALUE_NAME] = username; connection[PATH_VALUE_NAME] = path; } } namespace { // TODO: move into Comet void delete_subkey_recursively( const regkey& key, const wstring& subkey_name); void delete_all_subkeys_recursively(const regkey& key) { regkey::subkeys_type subkeys = key.enumerate().subkeys(); for(regkey::subkeys_type::iterator it=subkeys.begin(); it != subkeys.end(); ++it) { delete_subkey_recursively(key, *it); } } void delete_subkey_recursively( const regkey& key, const wstring& subkey_name) { delete_all_subkeys_recursively(key.open(subkey_name)); key.delete_subkey(subkey_name); } } /** * Remove a host entry from the Swish connections registry key by label. */ void RemoveConnectionFromRegistry(wstring label) { regkey connections = regkey(HKEY_CURRENT_USER).open( CONNECTIONS_REGISTRY_KEY_NAME); delete_subkey_recursively(connections, label); } void RenameConnectionInRegistry(const wstring& from_label, const wstring& to_label) { regkey connection = get_connection_from_registry(from_label); wstring host = connection[HOST_VALUE_NAME]; int port = connection[PORT_VALUE_NAME]; wstring user = connection[USER_VALUE_NAME]; wstring path = connection[PATH_VALUE_NAME]; AddConnectionToRegistry(to_label, host, port, user, path); RemoveConnectionFromRegistry(from_label); } namespace { class label_matches { public: label_matches(const wstring& label) : m_label(label) {} bool operator()(const cpidl_t& connection) { return host_itemid_view(connection).label() == m_label; } private: wstring m_label; }; } /** * Returns whether a host entry with the given label exists in the registry. */ bool ConnectionExists(wstring label) { if (label.size() < 1) return false; vector connections = LoadConnectionsFromRegistry(); return find_if( connections.begin(), connections.end(), label_matches(label)) != connections.end(); } }}} // namespace swish::host_folder::host_management ================================================ FILE: swish/host_folder/host_management.hpp ================================================ /** @file Management functions for host entries saved in the registry. @if license Copyright (C) 2009, 2011, 2015 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_HOST_FOLDER_HOST_MANAGEMENT_HPP #define SWISH_HOST_FOLDER_HOST_MANAGEMENT_HPP #pragma once #include // cpidl_t #include #include #include namespace swish { namespace host_folder { namespace host_management { std::vector LoadConnectionsFromRegistry(); void AddConnectionToRegistry( std::wstring label, std::wstring host, int port, std::wstring username, std::wstring path); boost::optional FindConnectionInRegistry( const std::wstring& label); void RemoveConnectionFromRegistry(std::wstring label); void RenameConnectionInRegistry( const std::wstring& from_label, const std::wstring& to_label); bool ConnectionExists(std::wstring label); }}} // namespace swish::host_folder::host_management #endif ================================================ FILE: swish/host_folder/host_pidl.hpp ================================================ /** @file PIDL access particular to host folder PIDLs. @if license Copyright (C) 2011, 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_HOST_FOLDER_HOST_PIDL_HPP #define SWISH_HOST_FOLDER_HOST_PIDL_HPP #pragma once #include "swish/remotelimits.h" // Text field limits #include // pidl_t, apidl_t, cpidl_t #include // raw_pidl_iterator #include // wformat #include // numeric_cast #include // BOOST_STATIC_ASSERT #include // BOOST_THROW_EXCEPTION #include #ifndef STRICT_TYPED_ITEMIDS #error Currently, swish requires strict PIDL types: define STRICT_TYPED_ITEMIDS #endif #include // Raw PIDL types #include // ua_wcslen, ua_wcscpy_s #include // find_if #include // memset #include #include // runtime_error #include #include namespace swish { namespace host_folder { namespace detail { #include struct host_item_id { USHORT cb; DWORD dwFingerprint; WCHAR wszLabel[MAX_LABEL_LENZ]; WCHAR wszUser[MAX_USERNAME_LENZ]; WCHAR wszHost[MAX_HOSTNAME_LENZ]; WCHAR wszPath[MAX_PATH_LENZ]; USHORT uPort; static const DWORD FINGERPRINT = 0x496c1066; }; #include BOOST_STATIC_ASSERT((sizeof(host_item_id) % sizeof(DWORD)) == 0); inline std::wstring copy_unaligned_string(const wchar_t __unaligned* source) { std::vector buffer(::ua_wcslen(source) + 1); ::ua_wcscpy_s(&buffer[0], buffer.size(), source); return std::wstring(&buffer[0]); } } /** * View internal fields of host folder PIDLs. * * The viewer doesn't take ownership of the PIDL it's passed so it must remain * valid for the duration of the viewer's use. */ class host_itemid_view { public: // We have to take the PIDL as a template, rather than that as a pidl_t // as the PIDL passed might be a cpidl_t or an apidl_t. In this case // the pidl would be converted to a pidl_t using a temporary which is // destroyed immediately after the constructor returns, thereby // invalidating the PIDL we've stored a reference to. template explicit host_itemid_view( const washer::shell::pidl::basic_pidl& pidl) : m_itemid(reinterpret_cast(pidl.get())) {} explicit host_itemid_view(PCUIDLIST_RELATIVE pidl) : m_itemid(reinterpret_cast(pidl)) {} bool valid() const { if (m_itemid == NULL) return false; return ((m_itemid->cb == sizeof(detail::host_item_id)) && (m_itemid->dwFingerprint == detail::host_item_id::FINGERPRINT)); } std::wstring host() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a host item")); return detail::copy_unaligned_string(m_itemid->wszHost); } std::wstring user() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a host item")); return detail::copy_unaligned_string(m_itemid->wszUser); } std::wstring label() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a host item")); return detail::copy_unaligned_string(m_itemid->wszLabel); } ssh::filesystem::path path() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a host item")); return ssh::filesystem::path( detail::copy_unaligned_string(m_itemid->wszPath)); } int port() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a host item")); return m_itemid->uPort; } private: const detail::host_item_id __unaligned* m_itemid; }; namespace detail { struct is_valid_host_item { bool operator()(const washer::shell::pidl::pidl_t& pidl) { return host_itemid_view(pidl).valid(); } }; } /** * Search a (multi-level) PIDL to find the host folder ITEMID. * * In any Swish PIDL there should be at most one as it doesn't make sense for * a file to be under more than one host. * * @returns an iterator pointing to the position of the host ITEMID in the * original PIDL. * @throws if no host ITEMID is found in the PIDL. */ inline washer::shell::pidl::raw_pidl_iterator find_host_itemid( PCIDLIST_ABSOLUTE pidl) { washer::shell::pidl::raw_pidl_iterator begin(pidl); washer::shell::pidl::raw_pidl_iterator end; // Search along pidl until we find one that matches our fingerprint or // we run off the end washer::shell::pidl::raw_pidl_iterator pos = std::find_if( begin, end, detail::is_valid_host_item()); if (pos != end) return pos; else BOOST_THROW_EXCEPTION( std::runtime_error("PIDL doesn't contain host ITEMID")); } inline washer::shell::pidl::raw_pidl_iterator find_host_itemid( const washer::shell::pidl::apidl_t& pidl) { return swish::host_folder::find_host_itemid(pidl.get()); } namespace detail { #include struct host_item_template { host_item_id id; SHITEMID terminator; }; #include } /** * Construct a new host folder PIDL with the fields initialised. */ inline washer::shell::pidl::cpidl_t create_host_itemid( const std::wstring& host, const std::wstring& user, const ssh::filesystem::path& path, int port, const std::wstring& label=std::wstring()) { // We create the item on the stack and then clone it into // a CoTaskMemAllocated pidl when we return it as a cpidl_t detail::host_item_template item; std::memset(&item, 0, sizeof(item)); item.id.cb = sizeof(item.id); item.id.dwFingerprint = detail::host_item_id::FINGERPRINT; #pragma warning(push) #pragma warning(disable:4996) host.copy(item.id.wszHost, MAX_HOSTNAME_LENZ); item.id.wszHost[MAX_HOSTNAME_LENZ - 1] = wchar_t(); user.copy(item.id.wszUser, MAX_USERNAME_LENZ); item.id.wszUser[MAX_USERNAME_LENZ - 1] = wchar_t(); path.wstring().copy(item.id.wszPath, MAX_PATH_LENZ); item.id.wszPath[MAX_PATH_LENZ - 1] = wchar_t(); label.copy(item.id.wszLabel, MAX_LABEL_LENZ); item.id.wszLabel[MAX_LABEL_LENZ - 1] = wchar_t(); #pragma warning(pop) item.id.uPort = boost::numeric_cast(port); assert(item.terminator.cb == 0); return washer::shell::pidl::cpidl_t( reinterpret_cast(&item)); } /** * Retrieve the long name of the host connection from the PIDL. * * The long name is either the canonical form if @a canonical is set: * sftp://username\@hostname:port/path * or, if not set and if the port is the default port, the reduced form: * sftp://username\@hostname/path */ inline std::wstring url_from_host_itemid( const washer::shell::pidl::cpidl_t itemid, bool canonical) { host_itemid_view host_pidl(itemid); if (canonical || host_pidl.port() != SFTP_DEFAULT_PORT) { return str( boost::wformat(L"sftp://%s@%s:%u/%s") % host_pidl.user() % host_pidl.host() % host_pidl.port() % host_pidl.path().wstring()); } else { return str( boost::wformat(L"sftp://%s@%s/%s") % host_pidl.user() % host_pidl.host() % host_pidl.path().wstring()); } } }} // namespace swish::host_folder #endif ================================================ FILE: swish/host_folder/menu_command_manager.cpp ================================================ /* Manage complexities of adding and removing menu items in host window. Copyright (C) 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "menu_command_manager.hpp" #include "swish/frontend/commands/About.hpp" #include "swish/host_folder/commands/Add.hpp" #include "swish/host_folder/commands/CloseSession.hpp" #include "swish/host_folder/commands/LaunchAgent.hpp" #include "swish/host_folder/commands/Remove.hpp" #include "swish/host_folder/commands/Rename.hpp" #include "swish/nse/command_site.hpp" #include // find_first_item_with_id #include #include #include #include // selectability #include #include #include // trace #include // diagnostic_information #include // BOOST_FOREACH #include #include // BOOST_THROW_EXCEPTION #include // assert #include // logic_error, runtime_error using swish::frontend::commands::About; using swish::host_folder::commands::Add; using swish::host_folder::commands::CloseSession; using swish::host_folder::commands::LaunchAgent; using swish::host_folder::commands::Remove; using swish::host_folder::commands::Rename; using swish::nse::Command; using swish::nse::command_site; using namespace washer::gui::menu; using washer::shell::pidl::apidl_t; using washer::trace; using washer::window::window; using comet::com_ptr; using boost::diagnostic_information; using boost::make_shared; using boost::optional; using boost::shared_ptr; using std::logic_error; using std::map; using std::runtime_error; using std::wstring; namespace swish { namespace host_folder { namespace { typedef std::map> menu_id_command_map; template item item_from_menu( const basic_menu& parent_menu, UINT menu_id) { basic_menu::iterator position = find_first_item_with_id( parent_menu.begin(), parent_menu.end(), menu_id); if (position != parent_menu.end()) { return *position; } else { BOOST_THROW_EXCEPTION( runtime_error("Unable to find menu with given ID")); } } item fallback_menu(const menu_bar& parent_menu) { return item_from_menu(parent_menu, FCIDM_MENU_FILE); } /** * Get handle to explorer 'Tools' menu. * * The menu we want to insert into is actually the @e submenu of the * Tools menu @e item. Confusing! */ item tools_menu_with_fallback(const menu_bar& parent_menu) { try { return item_from_menu(parent_menu, FCIDM_MENU_TOOLS); } catch (const std::exception& e) { trace("Failed getting Tools menu: %s") % diagnostic_information(e); return fallback_menu(parent_menu); } } /** * Get handle to explorer 'Help' menu. * * The menu we want to insert into is actually the @e submenu of the * Help menu @e item. Confusing! */ item help_menu_with_fallback(const menu_bar& parent_menu) { try { return item_from_menu(parent_menu, FCIDM_MENU_HELP); } catch (const std::exception& e) { trace("Failed getting help menu: %s") % diagnostic_information(e); return fallback_menu(parent_menu); } } void merge_command_items( UINT first_command_id, const UINT max_command_id, menu destination, menu::iterator insert_position, const menu_id_command_map& commands) { typedef menu_id_command_map::iterator::value_type mapped_command; BOOST_FOREACH(const mapped_command& menu_command, commands) { UINT new_command_id = first_command_id + menu_command.first; if (new_command_id > max_command_id) BOOST_THROW_EXCEPTION( runtime_error("Exeeded permitted merge space")); command_item_description item( string_button_description( menu_command.second->menu_title(NULL)), new_command_id); // TODO: work out how to hide hidden() items. For the moment we // treat them the same as disabled(). // I don't know how to insert a hidden menu item, but Windows Forms // seems to allow it. Maybe they maintain a list of menu items // separate from the menu itself and insert/remove the item to // show/hide it. BOOST_SCOPED_ENUM(selectability) item_state = menu_command.second->state(NULL, false) == Command::state::enabled ? selectability::enabled : selectability::disabled; item.selectability(item_state); // We have to be careful to increment the iterator *after* calling // insert in case we are inserting at the end. Doing // insert_position++ in the call to insert would step off the end. destination.insert(item, insert_position); ++insert_position; } } class merge_tools_command_items { public: typedef void result_type; merge_tools_command_items( UINT first_command_id, const UINT max_command_id, const menu_id_command_map& commands) : m_first_command_id(first_command_id), m_max_command_id(max_command_id), m_commands(commands) {} void operator()(sub_menu_item& sub_menu) { menu::iterator insert_position = sub_menu.menu().begin(); // We hope the 1st and 2nd items are map and unmap network drive, so we // just skip them. So that we don't fail completely if the Tools // menu is bizarre, we make sure there's actually room to skip first. if (sub_menu.menu().size() >= 2) { insert_position += 2; } merge_command_items( m_first_command_id, m_max_command_id, sub_menu.menu(), insert_position, m_commands); } void operator()(command_item&) { BOOST_THROW_EXCEPTION( logic_error("Cannot insert into command item")); } void operator()(separator_item&) { BOOST_THROW_EXCEPTION( logic_error("Cannot insert into separator")); } private: UINT m_first_command_id; const UINT m_max_command_id; menu_id_command_map m_commands; }; class merge_help_command_items { public: typedef void result_type; merge_help_command_items( UINT first_command_id, const UINT max_command_id, const menu_id_command_map& commands) : m_first_command_id(first_command_id), m_max_command_id(max_command_id), m_commands(commands) {} void operator()(sub_menu_item& sub_menu) { // Inserting into the bottom of the menu menu::iterator insert_position = sub_menu.menu().end(); merge_command_items( m_first_command_id, m_max_command_id, sub_menu.menu(), insert_position, m_commands); } void operator()(command_item&) { BOOST_THROW_EXCEPTION( logic_error("Cannot insert into command item")); } void operator()(separator_item&) { BOOST_THROW_EXCEPTION( logic_error("Cannot insert into separator")); } private: UINT m_first_command_id; const UINT m_max_command_id; menu_id_command_map m_commands; }; } menu_command_manager::menu_command_manager( QCMINFO& menu_info, const optional>& view, const apidl_t& folder) : m_view(view), m_folder(folder), m_first_command_id(menu_info.idCmdFirst) { assert(menu_info.idCmdFirst >= FCIDM_SHVIEWFIRST); assert(menu_info.idCmdLast <= FCIDM_SHVIEWLAST); //assert(::IsMenu(menu_info.hmenu)); menu_id_command_map tools_menu_commands; UINT offset = 0; tools_menu_commands[offset++] = make_shared(m_folder); tools_menu_commands[offset++] = make_shared(m_folder); tools_menu_commands[offset++] = make_shared(); tools_menu_commands[offset++] = make_shared(); tools_menu_commands[offset++] = make_shared(m_folder); // Try to get a handle to the Explorer Tools menu and insert // add and remove connection menu items into it if we find it m_tools_menu = tools_menu_with_fallback( menu_handle::foster_handle(menu_info.hmenu)); m_tools_menu->accept( merge_tools_command_items( m_first_command_id, menu_info.idCmdLast, tools_menu_commands)); menu_id_command_map help_menu_commands; help_menu_commands[offset++] = make_shared(); // Try to get a handle to the Explorer Help menu and insert About box m_help_menu = help_menu_with_fallback( menu_handle::foster_handle(menu_info.hmenu)); m_help_menu->accept( merge_help_command_items( m_first_command_id, menu_info.idCmdLast, help_menu_commands)); m_commands.insert(tools_menu_commands.begin(), tools_menu_commands.end()); m_commands.insert(help_menu_commands.begin(), help_menu_commands.end()); // Return value of last menu ID plus 1 // The following works because maps are sorted so rbegin points to the // last and highest item if (m_commands.rbegin() != m_commands.rend()) { menu_info.idCmdFirst += m_commands.rbegin()->first + 1; } // if no commands were added, leave idCmdFirst alone } bool menu_command_manager::invoke( UINT command_id, com_ptr selection, com_ptr ole_site) { menu_id_command_map::iterator pos = m_commands.find(command_id); if (pos != m_commands.end()) { // Use given window as a UI owner fallback in case the SFV callback // object was get an OLE site set (*(pos->second))(selection, command_site(ole_site, m_view), NULL); return true; } else { return false; } } bool menu_command_manager::help_text( UINT command_id, wstring& text_out, com_ptr selection) { menu_id_command_map::iterator pos = m_commands.find(command_id); if (pos != m_commands.end()) { text_out = pos->second->tool_tip(selection); return true; } else { return false; } } namespace { class update_command_items { public: typedef void result_type; update_command_items( com_ptr selection, UINT first_command_id, const menu_id_command_map& commands) : m_selection(selection), m_first_command_id(first_command_id), m_commands(commands) {} class selectability_setter { public: typedef void result_type; selectability_setter(BOOST_SCOPED_ENUM(selectability) selectability) : m_selectability(selectability) {} void operator()(command_item& item) { item.selectability(m_selectability); } template void operator()(T&) { BOOST_THROW_EXCEPTION( logic_error("Unexpected menu item type")); } private: BOOST_SCOPED_ENUM(selectability) m_selectability; }; void operator()(sub_menu_item& sub_menu) { typedef menu_id_command_map::iterator::value_type mapped_command; BOOST_FOREACH(const mapped_command& menu_command, m_commands) { BOOST_SCOPED_ENUM(selectability) command_state = menu_command.second->state(m_selection, false) == Command::state::enabled ? selectability::enabled : selectability::disabled; item menu_item = item_from_menu( sub_menu.menu(), m_first_command_id + menu_command.first); menu_item.accept(selectability_setter(command_state)); } } void operator()(command_item&) { BOOST_THROW_EXCEPTION(logic_error("Cannot insert into command item")); } void operator()(separator_item&) { BOOST_THROW_EXCEPTION(logic_error("Cannot insert into separator")); } private: com_ptr m_selection; UINT m_first_command_id; menu_id_command_map m_commands; }; } void menu_command_manager::update_state(com_ptr selection) { if (!m_tools_menu) BOOST_THROW_EXCEPTION(logic_error("Missing menu")); m_tools_menu->accept( update_command_items(selection, m_first_command_id, m_commands)); } }} ================================================ FILE: swish/host_folder/menu_command_manager.hpp ================================================ /* Manage complexities of adding and removing menu items in host window. Copyright (C) 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_HOST_FOLDER_MENU_COMMAND_MANAGER #define SWISH_HOST_FOLDER_MENU_COMMAND_MANAGER #include "swish/nse/Command.hpp" #include #include // apidl_t #include #include // com_ptr #include #include #include #include #include // QCMINFO namespace swish { namespace host_folder { /** * Unlike for webview tasks and command items, the shell doesn't recognise an * object to manage collections of menu items. This class fill that gap in * order to keep the logic out of the view callback. */ class menu_command_manager { public: /** * Merge items into Explorer menus. */ menu_command_manager( QCMINFO& menu_info, const boost::optional>& view, const washer::shell::pidl::apidl_t& folder); /** * Invoke a command by merge offset. */ bool invoke( UINT command_id, comet::com_ptr selection, comet::com_ptr ole_site); /** * Request tool tip for command by merge offset. */ bool help_text( UINT command_id, std::wstring& text_out, comet::com_ptr selection); /** * Refresh command states to match current selection. */ void update_state(comet::com_ptr selection); private: boost::optional> m_view; ///< Folder view window washer::shell::pidl::apidl_t m_folder; ///< Owning folder UINT m_first_command_id; ///< Start of our tools menu ID range std::map> m_commands; ///< Commands in menu with their menu item ID boost::optional m_tools_menu; ///< Handle to the Explorer 'Tools' menu boost::optional m_help_menu; ///< Handle to the Explorer 'Help' menu }; }} #endif ================================================ FILE: swish/host_folder/overlay_icon.hpp ================================================ /** @file Host folder overlay icons. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_HOST_FOLDER_OVERLAY_ICON_HPP #define SWISH_HOST_FOLDER_OVERLAY_ICON_HPP #include "swish/connection/connection_spec.hpp" #include "swish/connection/session_manager.hpp" #include "swish/host_folder/host_itemid_connection.hpp" // connection_from_host_itemid #include // cpidl_t #include // SHGetIconOverlayIndex namespace swish { namespace host_folder { class overlay_icon { public: overlay_icon(const washer::shell::pidl::cpidl_t& item) : m_connection(connection_from_host_itemid(host_itemid_view(item))) {} bool has_overlay() const { return swish::connection::session_manager().has_session(m_connection); } int index() const { return ::SHGetIconOverlayIndexW(NULL, IDO_SHGIOI_DEFAULT); } int icon_index() const { return INDEXTOOVERLAYMASK(index()); } private: swish::connection::connection_spec m_connection; }; }} #endif ================================================ FILE: swish/host_folder/properties.cpp ================================================ /** @file Host folder property columns. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "properties.hpp" #include "swish/host_folder/host_pidl.hpp" // host_itemid_view #include // map_list_of #include // function #include // translate #include // BOOST_THROW_EXCEPTION #include // assert #include #include // Make DEFINE_PROPERTYKEY() actually define a key #include // PKEY_ * using washer::shell::pidl::cpidl_t; using washer::shell::property_key; using comet::variant_t; using boost::assign::map_list_of; using boost::locale::translate; using std::map; namespace swish { namespace host_folder { // PKEYs for custom swish details/properties // Swish Host FMTID GUID {b816a850-5022-11dc-9153-0090f5284f85} DEFINE_PROPERTYKEY(PKEY_SwishHostUser, 0xb816a850, 0x5022, 0x11dc, \ 0x91, 0x53, 0x00, 0x90, 0xf5, 0x28, 0x4f, 0x85, \ PID_FIRST_USABLE); DEFINE_PROPERTYKEY(PKEY_SwishHostPort, 0xb816a850, 0x5022, 0x11dc, \ 0x91, 0x53, 0x00, 0x90, 0xf5, 0x28, 0x4f, 0x85, \ PID_FIRST_USABLE + 1); namespace { class unknown_property_error : public std::runtime_error { public: unknown_property_error() : std::runtime_error("Unknown property") {} }; typedef map< property_key, boost::function > host_property_map; variant_t net_drive_returner(const cpidl_t& /*pidl*/) { return translate(L"FileType", L"Network Drive").str(); } variant_t label_getter(const cpidl_t& pidl) { return host_itemid_view(pidl).label(); } variant_t host_getter(const cpidl_t& pidl) { return host_itemid_view(pidl).host(); } variant_t user_getter(const cpidl_t& pidl) { return host_itemid_view(pidl).user(); } variant_t port_getter(const cpidl_t& pidl) { return host_itemid_view(pidl).port(); } variant_t path_getter(const cpidl_t& pidl) { return host_itemid_view(pidl).path().wstring(); } const host_property_map host_property_getters = map_list_of (PKEY_ItemNameDisplay, label_getter) // Display name (Label) (PKEY_ComputerName, host_getter) // Hostname (PKEY_SwishHostUser, user_getter) // Username (PKEY_SwishHostPort, port_getter) // SFTP port (PKEY_ItemPathDisplay, path_getter) // Remote filesystem path (PKEY_ItemType, net_drive_returner); // Type: always 'Network Drive' } /** * Get the requested property for a file based on its PIDL. * * Many of these will be standard system properties but some are custom * to Swish if an appropriate one did not already exist. */ variant_t property_from_pidl(const cpidl_t& pidl, const property_key& key) { host_property_map::const_iterator pos = host_property_getters.find(key); if (pos == host_property_getters.end()) BOOST_THROW_EXCEPTION(unknown_property_error()); return (pos->second)(pidl.get()); } /** * Compare two PIDLs by one of their properties. * * @param left First PIDL in the comparison. * @param right Second PIDL in the comparison. * @param key Property on which to compare the two PIDLs. * * @retval -1 if left < right for chosen property. * @retval 0 if left == right for chosen property. * @retval 1 if left > right for chosen property. */ int compare_pidls_by_property( const cpidl_t& left, const cpidl_t& right, const property_key& key) { if (property_from_pidl(left, key) == property_from_pidl(right, key)) return 0; else if (property_from_pidl(left, key) < property_from_pidl(right, key)) return -1; assert(property_from_pidl(left, key) > property_from_pidl(right, key)); return 1; } }} // namespace swish::host_folder ================================================ FILE: swish/host_folder/properties.hpp ================================================ /** @file Properties available for items in a host folder. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_HOST_FOLDER_PROPERTIES_HPP #define SWISH_HOST_FOLDER_PROPERTIES_HPP #pragma once #include // cpidl_t #include // property_key #include // variant_t #include // PROPERTYKEY namespace swish { namespace host_folder { comet::variant_t property_from_pidl( const washer::shell::pidl::cpidl_t& pidl, const washer::shell::property_key& key); int compare_pidls_by_property( const washer::shell::pidl::cpidl_t& pidl_left, const washer::shell::pidl::cpidl_t& pidl_right, const washer::shell::property_key& key); /** * @name Custom properties (PKEYs) for Swish remote folder. * * Ideally, we want as few of these as possible. If an appropriate * one already exists in propkey.h, that should be used instead. * * The Swish remote folder FMTID GUID which collects all the custom * properties together is @c {b816a851-5022-11dc-9153-0090f5284f85}. */ // @{ extern "C" const PROPERTYKEY PKEY_SwishHostUser; extern "C" const PROPERTYKEY PKEY_SwishHostPort; // @} }} // namespace swish::host_folder #endif ================================================ FILE: swish/nse/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES detail/command_state_conversion.hpp Command.cpp command_site.hpp command_site.cpp data_object_util.cpp default_context_menu_callback.cpp explorer_command.cpp UICommand.cpp view_callback.cpp Command.hpp data_object_util.hpp default_context_menu_callback.hpp explorer_command.hpp StaticColumn.hpp task_pane.hpp UICommand.hpp view_callback.hpp) add_library(nse ${SOURCES}) hunter_add_package(Comet) hunter_add_package(Washer) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) target_link_libraries(nse PRIVATE shell shlwapi ${Boost_LIBRARIES} PUBLIC Washer::washer Comet::comet) ================================================ FILE: swish/nse/Command.cpp ================================================ /** @file Swish host folder commands. @if license Copyright (C) 2010, 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "Command.hpp" #include // BOOST_THROW_EXCEPTION using washer::shell::pidl::apidl_t; using comet::com_ptr; using comet::uuid_t; using std::wstring; namespace swish { namespace nse { Command::Command( const wstring& title, const uuid_t& guid, const wstring& tool_tip, const wstring& icon_descriptor, const wstring& menu_title, const wstring& webtask_title) : m_title(title), m_guid(guid), m_tool_tip(tool_tip), m_icon_descriptor(icon_descriptor), m_menu_title(menu_title), m_webtask_title(webtask_title) {} wstring Command::title(comet::com_ptr) const { return m_title; } const uuid_t& Command::guid() const { return m_guid; } wstring Command::tool_tip(comet::com_ptr) const { return m_tool_tip; } wstring Command::icon_descriptor(comet::com_ptr) const { return m_icon_descriptor; } wstring Command::menu_title( comet::com_ptr selection) const { return (m_menu_title.empty()) ? title(selection) : m_menu_title; } wstring Command::webtask_title( comet::com_ptr selection) const { return (m_webtask_title.empty()) ? title(selection) : m_webtask_title; } }} // namespace swish::nse ================================================ FILE: swish/nse/Command.hpp ================================================ /* Copyright (C) 2010, 2011, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_NSE_COMMAND_HPP #define SWISH_NSE_COMMAND_HPP #pragma once #include "swish/nse/command_site.hpp" #include // apidl_t #include // com_ptr #include // uuid_t #include // BOOST_SCOPED_ENUM #include // function #include // creating variadic pass-through contructors #include // IDataObject, IBindCtx #include // IExplorerCommandProvider, IShellItemArray #include namespace swish { namespace nse { class Command { public: Command( const std::wstring& title, const comet::uuid_t& guid, const std::wstring& tool_tip=std::wstring(), const std::wstring& icon_descriptor=std::wstring(), const std::wstring& menu_title=std::wstring(), const std::wstring& webtask_title=std::wstring()); virtual ~Command() {} /** * Invoke to perform the command. * * Concrete commands will provide their implementation by overriding * this method. * * NOTE: If commands need access to the view's window, to use as a UI owner, * they need to get this from the command site parameter. If the owner * window is not available from the site, the command must not show UI. * * @param selection IShellItemArray holding items on which to perform the * command. This may be NULL in which case the * command should only execute if it makes sense to * do so regardless of selected items. */ virtual void operator()( comet::com_ptr selection, const command_site& site, comet::com_ptr bind_ctx) const = 0; // For any of them methods that take a data_object, the implementation // does what is appropriate for a situation where selection information // is not available. // This differs from the situation where it is known that no objects // are selected. In that case, a data_object is provided, but it renders // no items. /** @name Attributes. */ // @{ const comet::uuid_t& guid() const; virtual std::wstring title( comet::com_ptr selection) const; virtual std::wstring tool_tip( comet::com_ptr selection) const; virtual std::wstring icon_descriptor( comet::com_ptr selection) const; /** @name Optional title variants. */ // @{ virtual std::wstring menu_title( comet::com_ptr selection) const; virtual std::wstring webtask_title( comet::com_ptr selection) const; // @} // @} /** @name State. */ // @{ BOOST_SCOPED_ENUM_START(state) { enabled, disabled, hidden }; BOOST_SCOPED_ENUM_END virtual BOOST_SCOPED_ENUM(state) state( comet::com_ptr selection, bool ok_to_be_slow) const = 0; // @} private: std::wstring m_title; comet::uuid_t m_guid; std::wstring m_tool_tip; std::wstring m_icon_descriptor; std::wstring m_menu_title; std::wstring m_webtask_title; }; #ifndef COMMAND_ADAPTER_CONSTRUCTOR_MAX_ARGUMENTS #define COMMAND_ADAPTER_CONSTRUCTOR_MAX_ARGUMENTS 10 #endif #define COMMAND_ADAPTER_VARIADIC_CONSTRUCTOR(N, classname, initialiser) \ BOOST_PP_EXPR_IF(N, template) \ explicit classname(BOOST_PP_ENUM_BINARY_PARAMS(N, A, a)) \ : initialiser(BOOST_PP_ENUM_PARAMS(N, a)) {} // TODO: This class should be changed to use aggregation rather than // inheritance because the latter can lead to a stack overflow if the // superclass calls title() in its implementation of webtask_title() // At the moment, other parts of the code rely on being able to pass // commands with extra methods and have them available template class WebtaskCommandTitleAdapter : public CommandImpl { public: // Define pass-through contructors with variable numbers of arguments #define BOOST_PP_LOCAL_MACRO(N) \ COMMAND_ADAPTER_VARIADIC_CONSTRUCTOR( \ N, WebtaskCommandTitleAdapter, CommandImpl) #define BOOST_PP_LOCAL_LIMITS (0, COMMAND_ADAPTER_CONSTRUCTOR_MAX_ARGUMENTS) #include BOOST_PP_LOCAL_ITERATE() virtual std::wstring title( comet::com_ptr selection) const { return webtask_title(selection); } }; #undef COMMAND_ADAPTER_CONSTRUCTOR_MAX_ARGUMENTS #undef COMMAND_ADAPTER_VARIADIC_CONSTRUCTOR }} // namespace swish::nse #endif ================================================ FILE: swish/nse/StaticColumn.hpp ================================================ /** @file NSE folder columns. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_NSE_STATIC_COLUMN_HPP #define SWISH_NSE_STATIC_COLUMN_HPP #pragma once #include // cpidl_t #include #include // SHCOLSTATEF namespace swish { namespace nse { template class StaticColumn : Base { public: /** * Create a column manager for the indexth column. * * @throws std::range_error if the column index is out of range. */ StaticColumn(size_t index) : m_index(index) {} virtual ~StaticColumn() {} /** * Localised heading of the column. */ std::wstring header() const { return entry(m_index).title(); } /** * Get the contents corresponding to this column for the given PIDL. * * Regardless of the type of the underlying data, this function must always * returns the data as a string. If any formatting is required, it must * be done in this function. */ std::wstring detail(const washer::shell::pidl::cpidl_t& pidl) const { return entry(m_index).detail(pidl); } /** * The number of 'x' characters an average item in the column will occupy. */ int average_width_in_chars() const { return entry(m_index).avg_char_width(); } /** * Returns the state (data type and whether to display by default) for the * column. */ SHCOLSTATEF state() const { return entry(m_index).flags(); } /** * How to display the data in the column (e.g. alignment). */ int format() const { return entry(m_index).format(); } /** * Compare two PIDLs by this column's detail. * * @retval -1 if lhs < rhs * @retval 0 if lhs == rhs * @retval 1 if lhs > rhs */ int compare( const washer::shell::pidl::cpidl_t& lhs, const washer::shell::pidl::cpidl_t& rhs) const { return entry(m_index).compare(lhs, rhs); } private: const size_t m_index; }; }} // swish::nse #endif ================================================ FILE: swish/nse/UICommand.cpp ================================================ /** @file Undocumented Windows XP task pane interfaces. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // IID #include // GUID_NULL #include #include "UICommand.hpp" ================================================ FILE: swish/nse/UICommand.hpp ================================================ /** @file Undocumented Windows XP task pane interfaces. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_NSE_UICOMMAND_HPP #define SWISH_NSE_UICOMMAND_HPP #pragma once #include // enumerated_type_of #include // comtype #include // DEFINE_GUID #include // IShellItemArray namespace swish { namespace nse { DEFINE_GUID(IID_IUIElement, 0xEC6FE84F,0xDC14,0x4FBB,0x88,0x9F,0xEA,0x50,0xFE,0x27,0xFE,0x0F); DEFINE_GUID(IID_IUICommand, 0x4026DFB9,0x7691,0x4142,0xB7,0x1C,0xDC,0xF0,0x8E,0xA4,0xDD,0x9C); DEFINE_GUID(IID_IEnumUICommand, 0x869447DA,0x9F84,0x4E2A,0xB9,0x2D,0x00,0x64,0x2D,0xC8,0xA9,0x11); /** * XP folder web view item. * * Undocumented by Microsoft. Based on public domain code at * http://www.whirlingdervishes.com/nselib/mfc/samples/source.php. * * Copyright (C) 1998-2003 Whirling Dervishes Software. */ struct IUIElement : public IUnknown { virtual HRESULT STDMETHODCALLTYPE get_Name( IShellItemArray* pItemArray, wchar_t** ppszName) = 0; virtual HRESULT STDMETHODCALLTYPE get_Icon( IShellItemArray* pItemArray, wchar_t** ppszIcon) = 0; virtual HRESULT STDMETHODCALLTYPE get_Tooltip( IShellItemArray* pItemArray, wchar_t** ppszInfotip) = 0; }; /** * XP folder web view command. * * Undocumented by Microsoft. Based on public domain code at * http://www.whirlingdervishes.com/nselib/mfc/samples/source.php. * * Copyright (C) 1998-2003 Whirling Dervishes Software. */ struct IUICommand : public IUIElement { virtual HRESULT STDMETHODCALLTYPE get_CanonicalName(GUID* pGuid) = 0; virtual HRESULT STDMETHODCALLTYPE get_State( IShellItemArray* pItemArray, int nRequested, EXPCMDSTATE* pState) = 0; virtual HRESULT STDMETHODCALLTYPE Invoke( IShellItemArray* pItemArray, IBindCtx* pCtx) = 0; }; /** * XP folder web view command enumerator. * * Undocumented by Microsoft. Based on public domain code at * http://www.whirlingdervishes.com/nselib/mfc/samples/source.php. * * Copyright (C) 1998-2003 Whirling Dervishes Software. */ struct IEnumUICommand : public IUnknown { virtual HRESULT STDMETHODCALLTYPE Next( ULONG celt, IUICommand** rgelt, ULONG* pceltFetched) = 0; virtual HRESULT STDMETHODCALLTYPE Skip(ULONG celt) = 0; virtual HRESULT STDMETHODCALLTYPE Reset() = 0; virtual HRESULT STDMETHODCALLTYPE Clone(IEnumUICommand** ppenum) = 0; }; }} // namespace swish::nse template<> struct comet::comtype { static const IID& uuid() throw() { return swish::nse::IID_IUIElement; } typedef IUnknown base; }; template<> struct comet::comtype { static const IID& uuid() throw() { return swish::nse::IID_IUICommand; } typedef swish::nse::IUIElement base; }; template<> struct comet::comtype { static const IID& uuid() throw() { return swish::nse::IID_IEnumUICommand; } typedef IUnknown base; }; template<> struct comet::enumerated_type_of { typedef swish::nse::IUICommand* is; }; template<> struct comet::impl::type_policy { template static void init(swish::nse::IUICommand*& p, const S& s) { p = s.get(); p->AddRef(); } static void clear(swish::nse::IUICommand*& p) { p->Release(); } }; #endif ================================================ FILE: swish/nse/command_site.cpp ================================================ /* Copyright (C) 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "command_site.hpp" #include "swish/shell/shell.hpp" // window_for_ole_site #include #include using swish::shell::window_for_ole_site; using washer::window::window; using comet::com_ptr; using boost::optional; namespace swish { namespace nse { command_site::command_site() {} command_site::command_site(com_ptr ole_site) : m_ole_site(ole_site) {} command_site::command_site( com_ptr ole_site, const optional>& ui_owner_fallback) : m_ole_site(ole_site), m_ui_owner_fallback(ui_owner_fallback) { assert("NULL HWND in initialised optional" && (!m_ui_owner_fallback || m_ui_owner_fallback->hwnd())); } optional> command_site::ui_owner() const { if (m_ole_site) { try { optional> view_window = window_for_ole_site(m_ole_site); if (view_window) { return view_window; } else { return m_ui_owner_fallback; } } catch (const std::exception&) { return m_ui_owner_fallback; } } else { return m_ui_owner_fallback; } } com_ptr command_site::ole_site() const { return m_ole_site; } }} // namespace swish::nse ================================================ FILE: swish/nse/command_site.hpp ================================================ /* Copyright (C) 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_NSE_COMMAND_SITE_HPP #define SWISH_NSE_COMMAND_SITE_HPP #include #include // com_ptr #include #include // IDataObject, IBindCtx namespace swish { namespace nse { /** * OLE site with window fallback. * * The Windows shell shituation for when you can show UI is unclear: should you * use the HWND passed in by the shell when it calls your NSE methods, or should * you use the OLE site. Neither method is available everywhere. * IExplorerCommands, created via a call to CreateViewObject, never get an HWND, * but are treated as an OLE site. Commands invoked via the context menu * integration have an HWND, but no OLE Site. Since Vista they can receive an * OLE site via INVOKECOMMANDEX, but there is no guarantee that code will be * invoked that way and, if compiled with support for Windows XP, that argument * will not be available on any platform. * * One thing is clear: our UI must always have an owner window, otherwise bad * things may happen (see The Old New Thing book). * * The strategy we adopt here is to use this class to abstract over precisely * where the owner window information may arrive from. The commands can just * ask this class for the window and, if any window is obtainable from any * source, the window is returned. Creation sites must initialise this class * with whichever window sources they have: OLE site, window handle, or both. * * If ui_owner returns an uninitialised value, the calling code must not try to * show any UI. * * This class also makes the OLE site available, if present, for commands that * need more specific UI control, such as the ability to set a file icon into * rename mode. This may not be avaible, and the calling code must handle that * possibility. */ class command_site { public: /** * A site where no UI interaction is permitted. */ command_site(); /** * A site where UI interaction is permitted via the OLE site. */ explicit command_site(comet::com_ptr ole_site); /** * A site where UI interaction is permitted via an OLE site or via a window. * * The window, if initialised, is a fallback for the UI owner if the OLE * site was NULL or was not able to provide a window. */ command_site( comet::com_ptr ole_site, const boost::optional< washer::window::window>& ui_owner_fallback); boost::optional> ui_owner() const throw(); comet::com_ptr ole_site() const throw(); private: comet::com_ptr m_ole_site; boost::optional> m_ui_owner_fallback; }; }} // namespace swish::nse #endif ================================================ FILE: swish/nse/data_object_util.cpp ================================================ /** @file Utility functions to work with DataObjects. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "data_object_util.hpp" #include // BHID_DataObject using comet::com_ptr; namespace swish { namespace nse { com_ptr data_object_from_item_array( com_ptr items, com_ptr bind_ctx) { com_ptr data_object; if (items) { items->BindToHandler( bind_ctx.get(), BHID_DataObject, data_object.iid(), reinterpret_cast(data_object.out())); } // We don't care if binding succeeded - if it did, great; we pass // the DataObject. If not, the data_object pointer will be NULL // and we can assume that no items were selected return data_object; } }} // namespace swish::nse ================================================ FILE: swish/nse/data_object_util.hpp ================================================ /** @file Utility functions to work with DataObjects. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_NSE_DATA_OJECT_UTIL_HPP #define SWISH_NSE_DATA_OJECT_UTIL_HPP #include // uuidof, comtype #include // com_ptr #include // IDataObject, IBindCtx #include // IShellItemArray template<> struct comet::comtype { static const IID& uuid() throw() { return IID_IDataObject; } typedef IUnknown base; }; namespace swish { namespace nse { /** * Convert a ShellItemArray to a DataObject. * * This DataObject hold the items in the array in the usual form * expected of a shell DataObject. * * @return NULL if there is a failure. This indicates that the array * was empty. */ comet::com_ptr data_object_from_item_array( comet::com_ptr items, comet::com_ptr bind_ctx=NULL); }} // namespace swish::nse #endif ================================================ FILE: swish/nse/default_context_menu_callback.cpp ================================================ /** @file Handler for Explorer Default Context Menu messages. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/nse/default_context_menu_callback.hpp" #include // WASHER_COM_CATCH #include // com_error #include // com_ptr #include // BOOST_THROW_EXCEPTION #include #include // DFM_* #include // HRESULT, HWND, lparam, wparam, HMENU, IDataObject using comet::com_error; using comet::com_ptr; using std::string; using std::wstring; namespace swish { namespace nse { default_context_menu_callback::~default_context_menu_callback() {} HRESULT default_context_menu_callback::operator()( HWND hwnd, com_ptr selection, UINT menu_message_id, WPARAM wparam, LPARAM lparam) { try { switch (menu_message_id) { case DFM_MERGECONTEXTMENU: { QCMINFO* info = reinterpret_cast(lparam); if (info == NULL) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); bool also_add_default_verbs = merge_context_menu( hwnd, selection, info->hmenu, info->indexMenu, info->idCmdFirst, info->idCmdLast, static_cast(wparam)); return (also_add_default_verbs) ? S_OK : S_FALSE; } case DFM_INVOKECOMMAND: { const wchar_t* arguments = reinterpret_cast(lparam); bool handled = invoke_command( hwnd, selection, static_cast(wparam), (arguments == NULL) ? wstring() : arguments); return (handled) ? S_OK : S_FALSE; } case DFM_INVOKECOMMANDEX: { DFMICS* dfmics = reinterpret_cast(lparam); if (dfmics == NULL) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); const wchar_t* arguments = reinterpret_cast(dfmics->lParam); if (dfmics->pici == NULL) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); bool handled = invoke_command( hwnd, selection, static_cast(wparam), (arguments == NULL) ? wstring() : arguments, dfmics->fMask, dfmics->idCmdFirst, dfmics->idDefMax, *(dfmics->pici), #if (NTDDI_VERSION >= NTDDI_VISTA) dfmics->punkSite); #else NULL); #endif return (handled) ? S_OK : S_FALSE; } case DFM_GETVERBA: { string result; verb( hwnd, selection, static_cast(LOWORD(wparam)), result); UINT buffer_len = static_cast(HIWORD(wparam)); if ((result.size() + 1) > buffer_len) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); char* buffer = reinterpret_cast(lparam); if (buffer == NULL) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); #pragma warning(push) #pragma warning(disable: 4996) result.copy(buffer, buffer_len); #pragma warning(pop) buffer[buffer_len - 1] = char(); return S_OK; } case DFM_GETVERBW: { wstring result; verb( hwnd, selection, static_cast(LOWORD(wparam)), result); UINT buffer_len = static_cast(HIWORD(wparam)); if ((result.size() + 1) > buffer_len) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); wchar_t* buffer = reinterpret_cast(lparam); if (buffer == NULL) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); #pragma warning(push) #pragma warning(disable: 4996) result.copy(buffer, buffer_len); #pragma warning(pop) buffer[buffer_len - 1] = wchar_t(); return S_OK; } case DFM_GETDEFSTATICID: { UINT* command_id_out = reinterpret_cast(lparam); if (command_id_out == NULL) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); bool use_default = !default_menu_item( hwnd, selection, *command_id_out); return (use_default) ? S_FALSE : S_OK; } default: return on_unknown_dfm( hwnd, selection, menu_message_id, wparam, lparam); } } WASHER_COM_CATCH(); } HRESULT default_context_menu_callback::on_unknown_dfm( HWND /*hwnd_view*/, com_ptr /*selection*/, UINT /*menu_message_id*/, WPARAM /*wparam*/, LPARAM /*lparam*/) { return E_NOTIMPL; // Required for Windows 7 to show any menu at all } bool default_context_menu_callback::merge_context_menu( HWND /*hwnd_view*/, com_ptr /*selection*/, HMENU /*hmenu*/, UINT /*first_item_index*/, UINT& /*minimum_id*/, UINT /*maximum_id*/, UINT /*allowed_changes_flags*/) { return true; } bool default_context_menu_callback::invoke_command( HWND /*hwnd_view*/, com_ptr /*selection*/, UINT /*item_offset*/, const wstring& /*arguments*/) { return false; } bool default_context_menu_callback::invoke_command( HWND /*hwnd_view*/, com_ptr /*selection*/, UINT /*item_offset*/, const wstring& /*arguments*/, DWORD /*behaviour_flags*/, UINT /*minimum_id*/, UINT /*maximum_id*/, const CMINVOKECOMMANDINFO& /*invocation_details*/, comet::com_ptr /*context_menu_site*/) { return false; } void default_context_menu_callback::verb( HWND /*hwnd_view*/, com_ptr /*selection*/, UINT /*command_id_offset*/, string& /*verb_out*/) { } void default_context_menu_callback::verb( HWND /*hwnd_view*/, com_ptr /*selection*/, UINT /*command_id_offset*/, wstring& /*verb_out*/) { } bool default_context_menu_callback::default_menu_item( HWND /*hwnd_view*/, com_ptr /*selection*/, UINT& /*default_command_id*/) { return false; } }} // namespace swish::nse ================================================ FILE: swish/nse/default_context_menu_callback.hpp ================================================ /** @file Handler for Explorer Default Context Menu messages. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_NSE_DEFAULT_CONTEXT_MENU_CALLBACK_HPP #define SWISH_NSE_DEFAULT_CONTEXT_MENU_CALLBACK_HPP #pragma once #include // com_ptr #include #include // CMINVOKECOMMANDINFO #include // HRESULT, HWND, LPARAM, WPARAM, HMENU, IDataObject namespace swish { namespace nse { class default_context_menu_callback { public: virtual ~default_context_menu_callback(); /** * Cracks the @c DFM_* callback messages and dispatches them to handlers. */ HRESULT operator()( HWND hwnd, comet::com_ptr selection, UINT menu_message_id, WPARAM wparam, LPARAM lparam); private: /// @name DFM_* message handlers // @{ /** * A message was sent to the callback that we don't know how to crack. * * This method gives subclasses an opportunity to handle messages that we * don't understand or new messages that Microsoft add in the future. * * The default implementation returns E_NOTIMPL. Override this method in * a subclass to capture unhandled messages. * * @warning Any implementation must return E_NOTIMPL for messages it * doesn't recognise. Failure to do so can cause the default * context menu to fail entirely. */ virtual HRESULT on_unknown_dfm( HWND hwnd_view, comet::com_ptr selection, UINT menu_message_id, WPARAM wparam, LPARAM lparam); /** * The default context menu is giving us a chance to add custom items. * * Before returning you must set @a minimum_id to be higher than the * highest command ID you added to the menu. The best way to do this is * to increment @minimum_id for each menu item you add. * * Any changes we make should respect the rules specified via the flags. * * Return true to tell the shell to add default verbs such as Open, * Explorer and Print to the menu. Return false to prevent this. * * The default implementation adds no items to the menu and returns true. * Override this method in a subclass to change the behaviour. */ virtual bool merge_context_menu( HWND hwnd_view, comet::com_ptr selection, HMENU hmenu, UINT first_item_index, UINT& minimum_id, UINT maximum_id, UINT allowed_changes_flags); /** * One of the context menu commands was invoked. * * This could be any of the commands we added via merge_context_menu or * even one of the DFM_CMD_* values which the shell adds for us. * * Return false to tell the shell to handle the command for us. It may * have an inbuilt action or it may just do nothing. Return true means * that we completely handled the action. * * The default implementation just returns true to get default shell * behaviour. Override this method in a subclass to change the behaviour. */ virtual bool invoke_command( HWND hwnd_view, comet::com_ptr selection, UINT item_offset, const std::wstring& arguments); /** * One of the context menu commands was invoked. * * This could be any of the commands we added via merge_context_menu or * even one of the DFM_CMD_* values which the shell adds for us. * * Return false to tell the shell to handle the command for us. It may * have an inbuilt action or it may just do nothing. Return true means * that we completely handled the action. * * The default implementation just returns false to get default shell * behaviour. Override this method in a subclass to change the behaviour. * * @note The context menu site will not be set if compiled with * NTDDI_VERSION < NTDDI_VISTA. */ virtual bool invoke_command( HWND hwnd_view, comet::com_ptr selection, UINT item_offset, const std::wstring& arguments, DWORD behaviour_flags, UINT minimum_id, UINT maximum_id, const CMINVOKECOMMANDINFO& invocation_details, comet::com_ptr context_menu_site); /** * Convert menu command ID offset to verb string. */ virtual void verb( HWND hwnd_view, comet::com_ptr selection, UINT command_id_offset, std::string& verb_out); virtual void verb( HWND hwnd_view, comet::com_ptr selection, UINT command_id_offset, std::wstring& verb_out); /** * The shell is asking which item in the menu it should make default. * * Set the parameter to the desired ID and return true to set the default. * Return false to ask the shell to choose the default itself. * * The default implementation returns true to make the shell choose the * default. Override this method in a subclass to change the behaviour. */ virtual bool default_menu_item( HWND hwnd_view, comet::com_ptr selection, UINT& default_command_id); // @} }; }} // namespace swish::nse #endif ================================================ FILE: swish/nse/detail/command_state_conversion.hpp ================================================ /** @file Conversion between command state representations. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_NSE_DETAIL_COMMAND_STATE_CONVERSION_HPP #define SWISH_NSE_DETAIL_COMMAND_STATE_CONVERSION_HPP #include "swish/nse/Command.hpp" #include // BOOST_THROW_EXCEPTION #include // logic_error #include // EXPCMDSTATE namespace swish { namespace nse { namespace detail { inline EXPCMDSTATE command_state_to_expcmdstate( BOOST_SCOPED_ENUM(Command::state) state_in) { switch (state_in) { case Command::state::enabled: return ECS_ENABLED; case Command::state::disabled: return ECS_DISABLED; case Command::state::hidden: // Add disabled flag as well just to be on the safe side. // As the command button is hidden it shouldn't matter whether it // is disabled return ECS_HIDDEN | ECS_DISABLED; default: BOOST_THROW_EXCEPTION( std::logic_error("Unrecognised command state")); } } }}} #endif ================================================ FILE: swish/nse/explorer_command.cpp ================================================ /** @file Explorer tool-bar command button implementation classes. @if license Copyright (C) 2010, 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "explorer_command.hpp" #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // stl_enumeration #include // com_ptr #include // uuid_t #include // BOOST_THROW_EXCEPTION #include // BOOST_FOREACH #include // SHStrDup using comet::com_error; using comet::com_ptr; using comet::stl_enumeration; using comet::uuid_t; template<> struct comet::enumerated_type_of { typedef IExplorerCommand* is; }; template<> struct comet::impl::type_policy { template static void init(IExplorerCommand*& p, const S& s) { p = s.get(); p->AddRef(); } static void clear(IExplorerCommand*& p) { p->Release(); } }; namespace swish { namespace nse { #pragma region CExplorerCommandProvider implementation /** * Create an ExplorerCommandProvider from exisiting ExplorerCommands. * * Store the ordered vector of commands and build a mapping from GUIDs * to IExplorerCommands for use when looking up via GetCommand. */ CExplorerCommandProvider::CExplorerCommandProvider( const ordered_commands& commands) : m_commands(commands) { BOOST_FOREACH(comet::com_ptr& c, m_commands) { uuid_t guid; HRESULT hr = c->GetCanonicalName(guid.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); m_guid_mapping[guid] = c; } } STDMETHODIMP CExplorerCommandProvider::GetCommands( IUnknown* /*punkSite*/, const IID& riid, void** ppv) { if (ppv) *ppv = NULL; else return E_POINTER; try { com_ptr commands = stl_enumeration::create( m_commands, get_unknown()); return commands->QueryInterface(riid, ppv); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } STDMETHODIMP CExplorerCommandProvider::GetCommand( const GUID& rguidCommandId, const IID& riid, void** ppv) { if (ppv) *ppv = NULL; else return E_POINTER; try { command_map::const_iterator item = m_guid_mapping.find(rguidCommandId); if (item == m_guid_mapping.end()) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); return item->second->QueryInterface(riid, ppv); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } #pragma endregion #pragma region CExplorerCommandErrorAdapter implementation /** * Return command's title string. * * @param[in] psiItemArray Optional array of PIDLs that command would be * executed upon. * @param[out] ppszName Location in which to return character buffer * allocated with CoTaskMemAlloc. */ STDMETHODIMP CExplorerCommandErrorAdapter::GetTitle( IShellItemArray* psiItemArray, wchar_t** ppszName) { if (ppszName) *ppszName = NULL; else return E_POINTER; try { HRESULT hr = ::SHStrDup(title(psiItemArray).c_str(), ppszName); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Return command's icon descriptor. * * This takes the form "shell32.dll,-249" where 249 is the icon's resource ID. * * @param[in] psiItemArray Optional array of PIDLs that command would be * executed upon. * @param[out] ppszIcon Location in which to return character buffer * allocated with CoTaskMemAlloc. */ STDMETHODIMP CExplorerCommandErrorAdapter::GetIcon( IShellItemArray* psiItemArray, wchar_t** ppszIcon) { if (ppszIcon) *ppszIcon = NULL; else return E_POINTER; try { HRESULT hr = ::SHStrDup(icon(psiItemArray).c_str(), ppszIcon); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Return command's tool tip. * * @param[in] psiItemArray Optional array of PIDLs that command would be * executed upon. * @param[out] ppszInfotip Location in which to return character buffer * allocated with CoTaskMemAlloc. */ STDMETHODIMP CExplorerCommandErrorAdapter::GetToolTip( IShellItemArray* psiItemArray, wchar_t** ppszInfotip) { if (ppszInfotip) *ppszInfotip = NULL; else return E_POINTER; try { HRESULT hr = ::SHStrDup(tool_tip(psiItemArray).c_str(), ppszInfotip); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Return command's unique GUID. * * @param[out] pguidCommandName Location in which to return GUID. */ STDMETHODIMP CExplorerCommandErrorAdapter::GetCanonicalName( GUID* pguidCommandName) { if (pguidCommandName) *pguidCommandName = GUID_NULL; else return E_POINTER; try { *pguidCommandName = canonical_name(); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Return the command's state given array of PIDLs. * * @param[in] psiItemArray Optional array of PIDLs that command would be * executed upon. * @param[in] fOkToBeSlow Indicated whether slow operations can be used * when calculating the state. * @param[out] pCmdState Location in which to return the state flags. */ STDMETHODIMP CExplorerCommandErrorAdapter::GetState( IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState) { if (pCmdState) *pCmdState = 0; else return E_POINTER; try { *pCmdState = state(psiItemArray, (fOkToBeSlow) ? true : false); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Execute the code associated with this command instance. * * @param[in] psiItemArray Optional array of PIDLs that command is * executed upon. * @param[in] pbc Optional bind context. */ STDMETHODIMP CExplorerCommandErrorAdapter::Invoke( IShellItemArray* psiItemArray, IBindCtx* pbc) { try { invoke(psiItemArray, pbc); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } STDMETHODIMP CExplorerCommandErrorAdapter::GetFlags(EXPCMDFLAGS* pFlags) { if (pFlags) *pFlags = 0; else return E_POINTER; try { *pFlags = flags(); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } STDMETHODIMP CExplorerCommandErrorAdapter::EnumSubCommands( IEnumExplorerCommand** ppEnum) { if (ppEnum) *ppEnum = NULL; else return E_POINTER; try { *ppEnum = subcommands().detach(); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } #pragma endregion }} // namespace swish::nse ================================================ FILE: swish/nse/explorer_command.hpp ================================================ /* Explorer tool-bar command button implementation classes. Copyright (C) 2010, 2011, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_NSE_EXPLORER_COMMAND_HPP #define SWISH_NSE_EXPLORER_COMMAND_HPP #pragma once #include "swish/nse/command_site.hpp" #include "swish/nse/detail/command_state_conversion.hpp" // command_state_to_expcmdstate #include // object_with_site #include // com_error #include // com_ptr #include // simple_object #include // uuid #include // creating variadic pass-through contructors #include // BOOST_THROW_EXCEPTION #include // IExplorerCommandProvider/IExplorerCommand #include #include #include template<> struct comet::comtype { static const IID& uuid() throw() { return IID_IExplorerCommand; } typedef IUnknown base; }; template<> struct comet::comtype { static const IID& uuid() throw() { return IID_IExplorerCommandProvider; } typedef IUnknown base; }; template<> struct comet::comtype { static const IID& uuid() throw() { return IID_IEnumExplorerCommand; } typedef IUnknown base; }; namespace swish { namespace nse { class CExplorerCommandProvider : public comet::simple_object { public: typedef std::vector > ordered_commands; typedef std::map > command_map; CExplorerCommandProvider(const ordered_commands& commands); /** * Return an Explorer command instance. * * @param[in] punkSite Optional, pointer through which to set a site. * @param[in] riid IID of the requested interface (typically * IEnumExplorerCommand). * @param[out] ppv Location in which to return the requested * interface. */ IFACEMETHODIMP GetCommands( IUnknown* punkSite, const IID& riid, void** ppv); /** * Return an enumerator of IExplorerCommand instances. * * @param[in] rguidCommandId GUID of the requested command. * @param[in] riid IID of the requested interface (typically * IExplorerCommand). * @param[out] ppv Location in which to return the requested * interface. */ IFACEMETHODIMP GetCommand( const GUID& rguidCommandId, const IID& riid, void** ppv); private: ordered_commands m_commands; command_map m_guid_mapping; }; /** * Abstract IExplorerCommand implementation wrapper. * * Wraps a C++ implementation of IExplorerCommand with code to convert it * to the external COM interface. This is an NVI style approach. */ class CExplorerCommandErrorAdapter : public IExplorerCommand { public: typedef IExplorerCommand interface_is; /** @name IExplorerCommand external COM methods. */ // @{ IFACEMETHODIMP GetTitle( IShellItemArray* psiItemArray, wchar_t** ppszName); IFACEMETHODIMP GetIcon( IShellItemArray* psiItemArray, wchar_t** ppszIcon); IFACEMETHODIMP GetToolTip( IShellItemArray* psiItemArray, wchar_t** ppszInfotip); IFACEMETHODIMP GetCanonicalName(GUID* pguidCommandName); IFACEMETHODIMP GetState( IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState); IFACEMETHODIMP Invoke( IShellItemArray* psiItemArray, IBindCtx* pbc); IFACEMETHODIMP GetFlags(EXPCMDFLAGS* pFlags); IFACEMETHODIMP EnumSubCommands(IEnumExplorerCommand** ppEnum); // @} private: /** @name NVI internal interface. * * Implement this to create IExplorerCommand instances. */ // @{ virtual const comet::uuid_t& canonical_name() const = 0; virtual std::wstring title( const comet::com_ptr& items) const = 0; virtual std::wstring tool_tip( const comet::com_ptr& items) const = 0; virtual std::wstring icon( const comet::com_ptr& items) const = 0; virtual EXPCMDSTATE state( const comet::com_ptr& items, bool ok_to_be_slow) const = 0; virtual EXPCMDFLAGS flags() const = 0; virtual comet::com_ptr subcommands() const = 0; virtual void invoke( const comet::com_ptr& items, const comet::com_ptr& bind_ctx) const = 0; // @} }; #ifndef SWISH_COMMAND_CONSTRUCTOR_MAX_ARGUMENTS #define SWISH_COMMAND_CONSTRUCTOR_MAX_ARGUMENTS 10 #endif #define EXPLORER_COMMAND_VARIADIC_CONSTRUCTOR(N, classname, initialiser) \ BOOST_PP_EXPR_IF(N, template) \ explicit classname(BOOST_PP_ENUM_BINARY_PARAMS(N, A, a)) \ : initialiser(BOOST_PP_ENUM_PARAMS(N, a)) {} /** * Implements IExplorerCommands by wrapping command functors. * * @param T Functor which provides the same interface as Command. * * This also implements IObjectWithSite to give the command access to the window * it is in. */ template class CExplorerCommand : public comet::simple_object< CExplorerCommandErrorAdapter, washer::object_with_site> { public: typedef T command_type; // Define pass-through constructors with variable numbers of arguments #define BOOST_PP_LOCAL_MACRO(N) \ EXPLORER_COMMAND_VARIADIC_CONSTRUCTOR(N, CExplorerCommand, m_command) #define BOOST_PP_LOCAL_LIMITS (0, SWISH_COMMAND_CONSTRUCTOR_MAX_ARGUMENTS) #include BOOST_PP_LOCAL_ITERATE() private: /** * Return command's unique GUID. */ const comet::uuid_t& canonical_name() const { return m_command.guid(); } /** * Return command's title string. * * @param items Optional array of PIDLs that command would be executed * upon. */ std::wstring title(const comet::com_ptr& items) const { return m_command.title(items); } /** * Return command's tool tip. * * @param items Optional array of PIDLs that command would be executed * upon. */ std::wstring tool_tip(const comet::com_ptr& items) const { return m_command.tool_tip(items); } /** * Return command's icon descriptor. * * This takes the form "shell32.dll,-249" where 249 is the icon's * resource ID. * * @param items Optional array of PIDLs that command would be executed * upon. */ std::wstring icon(const comet::com_ptr& items) const { return m_command.icon_descriptor(items); } /** * Return the command's state given array of PIDLs. * * @param items Optional array of PIDLs that command would be * executed upon. * @param ok_to_be_slow Indicates whether slow operations can be used * when calculating the state. If false and slow * operations are required, throw E_PENDING. */ EXPCMDSTATE state( const comet::com_ptr& items, bool ok_to_be_slow) const { return detail::command_state_to_expcmdstate( m_command.state(items, ok_to_be_slow)); } EXPCMDFLAGS flags() const { return 0; } comet::com_ptr subcommands() const { BOOST_THROW_EXCEPTION(comet::com_error(E_NOTIMPL)); return NULL; } /** * Execute the code associated with this command. * * @param items Optional array of PIDLs that command is executed upon. * @param bind_ctx Optional bind context. */ void invoke( const comet::com_ptr& items, const comet::com_ptr& bind_ctx) const { m_command(items, command_site(m_ole_site), bind_ctx); } /** * Let the site we have been embedded in pass us a reference to itself. * * Allows the commmand to use UI and other feature of the view. */ void on_set_site(comet::com_ptr ole_site) { m_ole_site = ole_site; } command_type m_command; comet::com_ptr m_ole_site; }; #undef EXPLORER_COMMAND_VARIADIC_CONSTRUCTOR #undef SWISH_COMMAND_CONSTRUCTOR_MAX_ARGUMENTS }} // namespace swish::nse #endif ================================================ FILE: swish/nse/task_pane.hpp ================================================ /* Windows XP web view task pane expandos. Copyright (C) 2010, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /** * @file * @todo This file should probably move to the Washer project although * CUICommand should stay here. */ #ifndef SWISH_NSE_TASK_PANE_EXPANDOS_HPP #define SWISH_NSE_TASK_PANE_EXPANDOS_HPP #pragma once #include "swish/nse/command_site.hpp" #include "swish/nse/detail/command_state_conversion.hpp" // command_state_to_expcmdstate #include "swish/nse/UICommand.hpp" // IUIElement, IUICommand #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // object_with_site #include // com_error #include // com_ptr #include // simple_object #include // creating variadic pass-through contructors #include // BOOST_STATIC_ASSERT #include // BOOST_THROW_EXCEPTION #include // is_base_of #include #include // SHStrDupW namespace swish { namespace nse { /** * Base class for implementations of interfaces the derive from IUIElement. * * The likely candidates are implementations of IUIElement itself and * IUICommand. * * This code has been factored into this templated base class as the * implementations must inherit from the most derived interface only. * Inheriting from both IUIElement and IUICommand will lead to an ambiguous * conversion error. * * Wraps a C++ implementation of IUIElement with code to convert it * to the external COM interface. This is an NVI style approach. */ template class CUIElementErrorAdapterBase : public Interface { BOOST_STATIC_ASSERT((boost::is_base_of::value)); public: typedef Interface interface_is; /** @name IUIElement external COM methods. */ // @{ /** * Return command's title string. * * @param[in] psiItemArray Optional array of PIDLs that command would be * executed upon. * @param[out] ppszName Location in which to return character buffer * allocated with CoTaskMemAlloc. */ virtual IFACEMETHODIMP get_Name( IShellItemArray* psiItemArray, wchar_t** ppszName) { if (ppszName) *ppszName = NULL; else return E_POINTER; try { HRESULT hr = ::SHStrDupW(title(psiItemArray).c_str(), ppszName); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Return command's icon descriptor. * * This takes the form "shell32.dll,-249" where 249 is the icon's resource ID. * * @param[in] psiItemArray Optional array of PIDLs that command would be * executed upon. * @param[out] ppszIcon Location in which to return character buffer * allocated with CoTaskMemAlloc. */ virtual IFACEMETHODIMP get_Icon( IShellItemArray* psiItemArray, wchar_t** ppszIcon) { if (ppszIcon) *ppszIcon = NULL; else return E_POINTER; try { HRESULT hr = ::SHStrDupW(icon(psiItemArray).c_str(), ppszIcon); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Return command's tool tip. * * @param[in] psiItemArray Optional array of PIDLs that command would be * executed upon. * @param[out] ppszInfotip Location in which to return character buffer * allocated with CoTaskMemAlloc. */ virtual IFACEMETHODIMP get_Tooltip( IShellItemArray* psiItemArray, wchar_t** ppszInfotip) { if (ppszInfotip) *ppszInfotip = NULL; else return E_POINTER; try { HRESULT hr = ::SHStrDup(tool_tip(psiItemArray).c_str(), ppszInfotip); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } // @} /** @name NVI internal interface. * * Implement this to create IUIElement instances. */ // @{ virtual std::wstring title( const comet::com_ptr& items) const = 0; virtual std::wstring icon( const comet::com_ptr& items) const = 0; virtual std::wstring tool_tip( const comet::com_ptr& items) const = 0; // @} }; /** * Abstract IUIElement implementation wrapper. * * Wraps a C++ implementation of IUIElement with code to convert it * to the external COM interface. This is an NVI style approach. */ class CUIElementErrorAdapter : public CUIElementErrorAdapterBase {}; /** * Abstract IUICommand implementation wrapper. * * Wraps a C++ implementation of IUICommand with code to convert it * to the external COM interface. This is an NVI style approach. */ class CUICommandErrorAdapter : public CUIElementErrorAdapterBase { public: /** @name IUICommand external COM methods. */ // @{ /** * Return command's unique GUID. * * @param[out] pguidCommandName Location in which to return GUID. */ IFACEMETHODIMP get_CanonicalName(GUID* pguidCommandName) { if (pguidCommandName) *pguidCommandName = GUID_NULL; else return E_POINTER; try { *pguidCommandName = canonical_name(); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Return the command's state given array of PIDLs. * * @param[in] psiItemArray Optional array of PIDLs that command would be * executed upon. * @param[in] fOkToBeSlow Indicated whether slow operations can be used * when calculating the state. * @param[out] pCmdState Location in which to return the state flags. */ IFACEMETHODIMP get_State( IShellItemArray* psiItemArray, int nRequested, //BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState) { if (pCmdState) *pCmdState = 0; else return E_POINTER; try { *pCmdState = state(psiItemArray, (nRequested) ? true : false); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } /** * Execute the code associated with this command instance. * * @param[in] psiItemArray Optional array of PIDLs that command is * executed upon. * @param[in] pbc Optional bind context. */ IFACEMETHODIMP Invoke( IShellItemArray* psiItemArray, IBindCtx* pbc) { try { invoke(psiItemArray, pbc); } WASHER_COM_CATCH_AUTO_INTERFACE(); return S_OK; } // @} private: /** @name NVI internal interface. * * Implement this to create IUICommand instances. */ // @{ virtual const comet::uuid_t& canonical_name() const = 0; virtual EXPCMDSTATE state( const comet::com_ptr& items, bool ok_to_be_slow) const = 0; virtual void invoke( const comet::com_ptr& items, const comet::com_ptr& bind_ctx) const = 0; // @} }; #ifndef SWISH_COMMAND_CONSTRUCTOR_MAX_ARGUMENTS #define SWISH_COMMAND_CONSTRUCTOR_MAX_ARGUMENTS 10 #endif #define COMMAND_ADAPTER_VARIADIC_CONSTRUCTOR(N, classname, initialiser) \ BOOST_PP_EXPR_IF(N, template) \ explicit classname(BOOST_PP_ENUM_BINARY_PARAMS(N, A, a)) \ : initialiser(BOOST_PP_ENUM_PARAMS(N, a)) {} /** * Implements IUICommand by wrapping command functors. * * @param T Functor which provides the same interface as Command. * * This also implements IObjectWithSite to give the command access to the window * it is in. */ template class CUICommand : public comet::simple_object< CUICommandErrorAdapter, washer::object_with_site> { public: typedef T command_type; // Define pass-through contructors with variable numbers of arguments #define BOOST_PP_LOCAL_MACRO(N) \ COMMAND_ADAPTER_VARIADIC_CONSTRUCTOR(N, CUICommand, m_command) #define BOOST_PP_LOCAL_LIMITS (0, SWISH_COMMAND_CONSTRUCTOR_MAX_ARGUMENTS) #include BOOST_PP_LOCAL_ITERATE() private: /** * Return command's unique GUID. */ const comet::uuid_t& canonical_name() const { return m_command.guid(); } /** * Return command's title string. * * @param items Optional array of PIDLs that command would be executed * upon. */ std::wstring title(const comet::com_ptr& items) const { return m_command.title(items); } /** * Return command's tool tip. * * @param items Optional array of PIDLs that command would be executed * upon. */ std::wstring tool_tip(const comet::com_ptr& items) const { return m_command.tool_tip(items); } /** * Return command's icon descriptor. * * This takes the form "shell32.dll,-249" where 249 is the icon's * resource ID. * * @param items Optional array of PIDLs that command would be executed * upon. */ std::wstring icon(const comet::com_ptr& items) const { return m_command.icon_descriptor(items); } /** * Return the command's state given array of PIDLs. * * @param items Optional array of PIDLs that command would be * executed upon. * @param ok_to_be_slow Indicates whether slow operations can be used * when calculating the state. If false and slow * operations are required, throw E_PENDING. */ EXPCMDSTATE state( const comet::com_ptr& items, bool ok_to_be_slow) const { return detail::command_state_to_expcmdstate( m_command.state(items, ok_to_be_slow)); } /** * Execute the code associated with this command. * * @param items Optional array of PIDLs that command is executed upon. * @param bind_ctx Optional bind context. */ void invoke( const comet::com_ptr& items, const comet::com_ptr& bind_ctx) const { m_command(items, command_site(m_ole_site), bind_ctx); } /** * Let the site we have been embedded in pass us a reference to itself. * * Allows the commmand to use UI and other feature of the view. */ virtual void on_set_site(comet::com_ptr ole_site) { m_ole_site = ole_site; } command_type m_command; comet::com_ptr m_ole_site; }; #undef COMMAND_ADAPTER_VARIADIC_CONSTRUCTOR #undef SWISH_COMMAND_CONSTRUCTOR_MAX_ARGUMENTS }} // namespace swish::nse #endif ================================================ FILE: swish/nse/view_callback.cpp ================================================ /** @file Explorer shell view windows callback handler. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "view_callback.hpp" #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // com_error #include // BOOST_THROW_EXCEPTION #include // assert using comet::com_error; namespace { /// @name Undocumented messages // @{ const UINT SFVM_SELECTIONCHANGED = 8; const UINT SFVM_GET_WEBVIEW_CONTENT = 83; const UINT SFVM_GET_WEBVIEW_TASKS = 84; // @} } namespace swish { namespace nse { CViewCallback::~CViewCallback() {} STDMETHODIMP CViewCallback::MessageSFVCB( UINT message, WPARAM wparam, LPARAM lparam) { try { bool handled = false; switch (message) { case SFVM_WINDOWCREATED: handled = on_window_created(reinterpret_cast(wparam)); break; case SFVM_GETNOTIFY: if (wparam == 0 || lparam == 0) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); handled = on_get_notify( *reinterpret_cast(wparam), *reinterpret_cast(lparam)); break; case SFVM_FSNOTIFY: handled = on_fs_notify( reinterpret_cast(wparam), lparam); break; case SFVM_MERGEMENU: if (lparam == 0) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); handled = on_merge_menu(*reinterpret_cast(lparam)); break; case SFVM_SELECTIONCHANGED: // wparam's meaning is unknown if (lparam == 0) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); handled = on_selection_changed( *reinterpret_cast(lparam)); break; case SFVM_INITMENUPOPUP: handled = on_init_menu_popup( LOWORD(wparam), HIWORD(wparam), reinterpret_cast(lparam)); break; case SFVM_INVOKECOMMAND: handled = on_invoke_command(wparam); break; case SFVM_GETHELPTEXT: handled = on_get_help_text( LOWORD(wparam), HIWORD(wparam), reinterpret_cast(lparam)); break; case SFVM_GET_WEBVIEW_CONTENT: if (lparam == 0) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); handled = on_get_webview_content( *reinterpret_cast(lparam)); break; case SFVM_GET_WEBVIEW_TASKS: if (lparam == 0) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); handled = on_get_webview_tasks( *reinterpret_cast(lparam)); break; default: handled = on_unknown_sfvm(message, wparam, lparam); break; } if (handled) { return S_OK; } else { // special treatment for FSNOTIFY because it uses S_FALSE to // suppress default processing. if (message == SFVM_FSNOTIFY) BOOST_THROW_EXCEPTION(com_error(S_FALSE)); else BOOST_THROW_EXCEPTION(com_error(E_NOTIMPL)); } } WASHER_COM_CATCH_AUTO_INTERFACE(); assert(false && "Unreachable"); return E_UNEXPECTED; } bool CViewCallback::on_unknown_sfvm( UINT /*message*/, WPARAM /*wparam*/, LPARAM /*lparam*/) { return false; } bool CViewCallback::on_window_created(HWND /*hwnd_view*/) { return false; } bool CViewCallback::on_get_notify( PCIDLIST_ABSOLUTE& /*pidl_monitor*/, LONG& /*events*/) { return false; } bool CViewCallback::on_fs_notify( PCIDLIST_ABSOLUTE /*pidl*/, LONG /*event*/) { return false; } bool CViewCallback::on_merge_menu(QCMINFO& /*menu_info*/) { return false; } bool CViewCallback::on_selection_changed( SFV_SELECTINFO& /*selection_info*/) { return false; } bool CViewCallback::on_init_menu_popup( UINT /*first_command_id*/, int /*menu_index*/, HMENU /*menu*/) { return false; } bool CViewCallback::on_invoke_command(UINT /*command_id*/) { return false; } bool CViewCallback::on_get_help_text( UINT /*command_id*/, UINT /*buffer_size*/, LPTSTR /*buffer*/) { return false; } bool CViewCallback::on_get_webview_content( SFV_WEBVIEW_CONTENT_DATA& /*content_out*/) { return false; } bool CViewCallback::on_get_webview_tasks( SFV_WEBVIEW_TASKSECTION_DATA& /*tasks_out*/) { return false; } }} // namespace swish::nse ================================================ FILE: swish/nse/view_callback.hpp ================================================ /** @file Explorer shell view windows callback handler. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_NSE_VIEW_CALLBACK_HPP #define SWISH_NSE_VIEW_CALLBACK_HPP #pragma once #include "swish/nse/UICommand.hpp" // IUIElement, IEnumUICommand #include // comtype #include // IShellFolderViewCB template<> struct comet::comtype { static const IID& uuid() throw() { return IID_IShellFolderViewCB; } typedef IUnknown base; }; namespace swish { namespace nse { class CViewCallback : public IShellFolderViewCB { public: typedef IShellFolderViewCB interface_is; virtual ~CViewCallback(); public: // IShellFolderViewCB /** * Callback method for shell view to inform us as things happen. * * This is the way in which the default @c IShellView object that we * created using @c SHCreateShellFolderView allows us to still have a say * in what goes on. As things happen in the view, messages are sent to * this callback allowing us to react to them. * * @param message The @c SFVM_* message type that the view is sending us. * @param wparam One of the possible parameters (varies with message type). * @param lparam Another possible parameter (varies with message type). * * @returns @c S_OK if we handled the message or @c E_NOTIMPL if we did not. */ virtual IFACEMETHODIMP MessageSFVCB( UINT message, WPARAM wparam, LPARAM lparam); protected: /** * SFVM_SELECTIONCHANGED parameter. * * Undocumented by Microsoft. Based on public domain code at * http://www.whirlingdervishes.com/nselib/mfc/samples/source.php. * * Copyright (C) 1998-2003 Whirling Dervishes Software. */ struct SFV_SELECTINFO { UINT uOldState; // 0 UINT uNewState; // LVIS_SELECTED, LVIS_FOCUSED,... LPITEMIDLIST pidl; }; /** * SFVM_GET_WEBVIEW_CONTENT parameter. * * Undocumented by Microsoft. Based on public domain code at * http://www.whirlingdervishes.com/nselib/mfc/samples/source.php. * * Copyright (C) 1998-2003 Whirling Dervishes Software. */ struct SFV_WEBVIEW_CONTENT_DATA { long l1; long l2; nse::IUIElement* pExtraTasksExpando; ///< Expando with dark title nse::IUIElement* pFolderTasksExpando; IEnumIDList* pEnumRelatedPlaces; }; /** * SFVM_GET_WEBVIEW_TASKS parameter. * * Undocumented by Microsoft. Based on public domain code at * http://www.whirlingdervishes.com/nselib/mfc/samples/source.php. * * Copyright (C) 1998-2003 Whirling Dervishes Software. */ struct SFV_WEBVIEW_TASKSECTION_DATA { nse::IEnumUICommand *pEnumExtraTasks; nse::IEnumUICommand *pEnumFolderTasks; }; private: /// @name SFVM_* message handlers // @{ /** * A message was sent to the callback that we don't know how to crack. * * The message is ignored by default but can be captured by the subclasses * if they override on_unknown_sfvm. */ virtual bool on_unknown_sfvm(UINT message, WPARAM wparam, LPARAM lparam); /** * The folder window is being created. * * The shell is notifying us of the folder view's window handle. */ virtual bool on_window_created(HWND hwnd_view); /** * Which events should the shell monitor for changes? * * We are notified via SFVM_FSNOTIFY if any events indicated here occurr. * * @warning The pidl we return in @a pidl_monitor remains owned by this * object and must remain valid until this object is destroyed. */ virtual bool on_get_notify(PCIDLIST_ABSOLUTE& pidl_monitor, LONG& events); /** * An event has occurred affecting one of our items. * * The event is probably the result of a SHChangeNotify of some sort. * Returning false prevents the default view from refreshing to reflect the * change. */ virtual bool on_fs_notify(PCIDLIST_ABSOLUTE pidl, LONG event); /** * The view is asking us if we want to merge any items into * the menu it has created before it adds it to the Explorer window. */ virtual bool on_merge_menu(QCMINFO& menu_info); /** * The view is telling us that something has changed about its selection * state. */ virtual bool on_selection_changed(SFV_SELECTINFO& selection_info); /** * The view is about to display a popup menu. * * This gives us the chance to modify the menu before it is displayed. * * @param first_command_id First ID reserved for client commands. * @param menu_index Menu's index. * @param menu Menu's handle. */ virtual bool on_init_menu_popup( UINT first_command_id, int menu_index, HMENU menu); /** * The view is telling us that a menu or toolbar item has been invoked * in the Explorer window and is giving us a chance to react to it. */ virtual bool on_invoke_command(UINT command_id); /** * Specify help text for menu or toolbar items. */ virtual bool on_get_help_text( UINT command_id, UINT buffer_size, LPTSTR buffer); /** * The shell view is requesting our expando title info. * Undocumented by Microsoft. * * @see http://www.codeproject.com/KB/shell/foldertasks.aspx * @see http://www.eggheadcafe.com/forumarchives/platformsdkshell/Feb2006/post25949644.asp */ virtual bool on_get_webview_content(SFV_WEBVIEW_CONTENT_DATA& content_out); /** * The shell view is requesting our expando members. * Undocumented by Microsoft. * * @see http://www.codeproject.com/KB/shell/foldertasks.aspx * @see http://www.eggheadcafe.com/forumarchives/platformsdkshell/Feb2006/post25949644.asp */ virtual bool on_get_webview_tasks(SFV_WEBVIEW_TASKSECTION_DATA& tasks_out); // @} }; }} // namespace swish::nse #endif ================================================ FILE: swish/port_conversion.hpp ================================================ /** @file Convert between port numbers and canonical strings. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ /** * @file * Use these functions instead of lexical_cast if the port number must * be canonical, e.g., 65535 rather than 65,535 or 65.535. Locales affect * the result of lexical_casts but canonical port numbers must not depend * on the locale. * * Based on a fix provided by David J. */ #ifndef SWISH_PORT_CONVERSION_HPP #define SWISH_PORT_CONVERSION_HPP #pragma once #include // BOOST_THROW_EXCEPTION #include // locale::classic #include // basic_ostringstream #include // logic_error #include namespace swish { /** * Locale-independent port number to port string conversion. */ template inline T basic_port_to_string(long port) { std::basic_ostringstream stream; stream.imbue(std::locale::classic()); // force locale-independence stream << port; if (!stream) BOOST_THROW_EXCEPTION( std::logic_error("Unable to convert port number to string")); return stream.str(); } /** * Locale-independent port number to narrow port string conversion. */ inline std::string port_to_string(long port) { return basic_port_to_string(port); } /** * Locale-independent port number to wide port string conversion. */ inline std::wstring port_to_wstring(long port) { return basic_port_to_string(port); } } // namespace swish #endif ================================================ FILE: swish/provider/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES libssh2_sftp_filesystem_item.cpp Provider.cpp libssh2_sftp_filesystem_item.hpp Provider.hpp sftp_filesystem_item.hpp sftp_provider.hpp ticketed_stream.hpp) add_library(provider ${SOURCES}) hunter_add_package(Comet) hunter_add_package(Washer) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) target_link_libraries(provider PUBLIC Comet::comet connection ${Boost_LIBRARIES} PRIVATE ssh Washer::washer) ================================================ FILE: swish/provider/Provider.cpp ================================================ // Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "Provider.hpp" #include "swish/connection/authenticated_session.hpp" #include "swish/connection/session_manager.hpp" // session_reservation #include "swish/provider/libssh2_sftp_filesystem_item.hpp" #include "swish/provider/sftp_filesystem_item.hpp" #include "swish/remotelimits.h" #include "swish/trace.hpp" // trace #include // bstr_t #include // datetime_t #include // stl_enumeration #include // com_error #include // com_ptr #include // simple_object for STL holder with AddRef lifetime #include // adapt_stream_pointer #include // directory_iterator #include // ofstream, ifstream #include // path #include // make_filter_iterator #include // make_shared #include // BOOST_RV_REF #include // BOOST_THROW_EXCEPTION #include // system_error, system_category #include // assert #include #include // invalid_argument #include #include // to hold listing using swish::connection::authenticated_session; using swish::connection::session_reservation; using swish::tracing::trace; using comet::adapt_stream_pointer; using comet::bstr_t; using comet::com_error; using comet::com_ptr; using comet::datetime_t; using comet::stl_enumeration; using boost::make_filter_iterator; using boost::make_shared; namespace errc = boost::system::errc; using boost::system::system_category; using boost::system::system_error; using ssh::filesystem::directory_iterator; using ssh::filesystem::file_attributes; using ssh::filesystem::fstream; using ssh::filesystem::ifstream; using ssh::filesystem::ofstream; using ssh::filesystem::overwrite_behaviour; using ssh::filesystem::path; using ssh::filesystem::sftp_filesystem; using ssh::filesystem::sftp_file; using std::exception; using std::invalid_argument; using std::string; using std::wstring; using std::vector; namespace swish { namespace provider { class provider { public: explicit provider(BOOST_RV_REF(session_reservation) session_ticket); directory_listing listing(const path& directory); comet::com_ptr get_file(const path& file_path, std::ios_base::openmode open_mode); VARIANT_BOOL rename(com_ptr consumer, const path& from_path, const path& to_path); void remove_all(const path& path); void create_new_directory(const path& path); const path resolve_link(const path& path); sftp_filesystem_item stat(const path& path, bool follow_links); private: session_reservation m_ticket; }; CProvider::CProvider(BOOST_RV_REF(session_reservation) session_ticket) { m_provider = make_shared(boost::ref(session_ticket)); } directory_listing CProvider::listing(const path& directory) { return m_provider->listing(directory); } comet::com_ptr CProvider::get_file(const path& file_path, std::ios_base::openmode open_mode) { return m_provider->get_file(file_path, open_mode); } VARIANT_BOOL CProvider::rename(ISftpConsumer* consumer, const path& from_path, const path& to_path) { return m_provider->rename(consumer, from_path, to_path); } void CProvider::remove_all(const path& path) { m_provider->remove_all(path); } void CProvider::create_new_directory(const path& path) { m_provider->create_new_directory(path); } path CProvider::resolve_link(const path& path) { return m_provider->resolve_link(path); } sftp_filesystem_item CProvider::stat(const path& path, bool follow_links) { return m_provider->stat(path, follow_links); } /** * Create libssh2-based data provider. */ provider::provider(BOOST_RV_REF(session_reservation) ticket) : m_ticket(ticket) { } namespace { bool not_special_file(const sftp_file& file) { return file.path().filename() != "." && file.path().filename() != ".."; } } /** * Retrieves a file listing, @c ls, of a given directory. * * @param directory Absolute path of the directory to list. */ directory_listing provider::listing(const path& directory) { if (directory.empty()) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); sftp_filesystem& channel = m_ticket.session().get_sftp_filesystem(); vector files; transform( make_filter_iterator(not_special_file, channel.directory_iterator(directory)), make_filter_iterator(not_special_file, channel.directory_iterator()), back_inserter(files), libssh2_sftp_filesystem_item::create_from_libssh2_file); return files; } com_ptr provider::get_file(const path& file_path, std::ios_base::openmode mode) { if (file_path.empty()) BOOST_THROW_EXCEPTION(invalid_argument("File cannot be empty")); sftp_filesystem& channel = m_ticket.session().get_sftp_filesystem(); if (mode & std::ios_base::out && mode & std::ios_base::in) { return adapt_stream_pointer( make_shared(boost::ref(channel), file_path, mode), file_path.filename().wstring()); } else if (mode & std::ios_base::out) { return adapt_stream_pointer( make_shared(boost::ref(channel), file_path, mode), file_path.filename().wstring()); } else if (mode & std::ios_base::in) { return adapt_stream_pointer( make_shared(boost::ref(channel), file_path, mode), file_path.filename().wstring()); } else { BOOST_THROW_EXCEPTION( std::invalid_argument("Stream must be input, output or both")); } } namespace { /** * Rename file or directory and overwrite any obstruction non-atomically. * * This involves renaming the obstruction at the target to a temporary file, * renaming the source file to the target and then deleting the renamed * obstruction. As this is not an atomic operation it is possible to fail * between any of these stages and is not a prefect solution. It may, for * instance, leave the temporary file behind. * * @param from * Absolute path of the file or directory to be renamed. * @param to * Absolute path to rename `from` to. * * @throws ssh_error if the operation fails. */ void rename_non_atomic_overwrite(authenticated_session& session, const string& from, const string& to) { string temporary = to + ".swish_rename_temp"; rename(session.get_sftp_filesystem(), to, temporary, overwrite_behaviour::prevent_overwrite); try { rename(session.get_sftp_filesystem(), from, to, overwrite_behaviour::prevent_overwrite); } catch (const exception&) { // Rename failed, rename our temporary back to its old name try { rename(session.get_sftp_filesystem(), from, to, overwrite_behaviour::prevent_overwrite); } catch (const exception&) { /* Suppress to avoid nested exception */ } throw; } // We ignore any failure to clean up the temporary backup as the rename // has succeeded, whether or not cleanup fails. // // XXX: We could inform the user of this here. Might make UI // separation messy though. try { remove_all(session.get_sftp_filesystem(), temporary); } catch (const exception&) { } } /** * Retry renaming after seeking permission to overwrite the obstruction at * the target. * * If this fails the file or directory really can't be renamed and the error * message from libssh2 is returned in @a error_out. * * @param pConsumer * Callback for user confirmation. * * @param previous_error * Error code of the previous rename attempt in order to determine if an * overwrite has any chance of being successful. * * @param from * Absolute path of the file or directory to be renamed. * * @param to * Absolute path to rename @a from to. * * @returns `true` if the the rename operation succeeds as a result of * retrying it, * `false` if the the rename operation needed user permission for * something and the user chose to abort the renaming. * * @throws `previous_error` if the situation is not caused by an obstruction * at the target. Retrying renaming is not going to help here. * * @bug The strings aren't converted from UTF-8 to UTF-16 before displaying * to the user. Any unicode filenames will produce gibberish in the * confirmation dialogues. */ bool rename_retry_with_overwrite(authenticated_session& session, ISftpConsumer* pConsumer, const system_error& previous_error, const string& from, const string& to) { assert(previous_error.code() && "Previous attempt succeeded; why retry?"); if (previous_error.code() == errc::file_exists) { HRESULT hr = pConsumer->OnConfirmOverwrite(bstr_t(from).in(), bstr_t(to).in()); if (FAILED(hr)) return false; // Attempt rename again this time allowing it to atomically overwrite // any obstruction. // This will only work on a server supporting SFTP version 5 or above. try { rename(session.get_sftp_filesystem(), from, to, overwrite_behaviour::atomic_overwrite); return true; } catch (const system_error& e) { if (e.code() == errc::operation_not_supported) { rename_non_atomic_overwrite(session, from, to); return true; } else { throw; } } } else { // The failure is an unspecified one. This isn't the end of the world. // SFTP servers < v5 (i.e. most of them) return this error code if the // file already exists as they don't explicitly support overwriting. // We need to stat() the file to find out if this is the case and if // the user confirms the overwrite we will have to explicitly delete // the target file first (via a temporary) and then repeat the rename. // // NOTE: this is not a perfect solution due to the possibility // for race conditions. // We used to test for FX_FAILURE here, because that's what OpenSSH // returns, but changed it because the v3 standard (v5 handled above) // doesn't promise any particular error code so we might as well // treat them all this way. if (exists(session.get_sftp_filesystem(), to)) { HRESULT hr = pConsumer->OnConfirmOverwrite(bstr_t(from).in(), bstr_t(to).in()); if (FAILED(hr)) return false; rename_non_atomic_overwrite(session, from, to); return true; } else { // Rethrow the last exception because it wasn't caused by an // obstruction. // // RACE CONDITION: It might have been caused by an obstruction // which was then cleared by the time we did the existence check // above. The result it just that we would fail when we could // have succeeded. Such an edge case that it doesn't matter. throw previous_error; } } } } /** * Renames a file or directory. * * The source and target file or directory must be specified using absolute * paths for the remote filesystem. The results of passing relative paths are * not guaranteed (though, libssh2 seems to default to operating in the home * directory) and may be dangerous. * * If a file or folder already exists at the target path, @a to_path, * we inform the front-end consumer through a call to OnConfirmOverwrite. * If confirmation is given, we attempt to overwrite the * obstruction with the source path, @a from_path, and if successful we * return @c VARIANT_TRUE. This can be used by the caller to decide whether * or not to update a directory view. * * @remarks * Due to the limitations of SFTP versions 4 and below, most servers will not * allow atomic overwrite. We attempt to do this non-atomically by: * -# appending @c ".swish_renaming_temp" to the obstructing target's filename * -# renaming the source file to the old target name * -# deleting the renamed target * If step 2 fails, we try to rename the temporary file back to its old name. * It is possible that this last step may fail, in which case the temporary file * would remain in place. It could be recovered by manually renaming it back. * * @warning * If either of the paths are not absolute, this function may cause files * in whichever directory libssh2 considers 'current' to be renamed or deleted * if they happen to have matching filenames. * * @param consumer UI callback. * @param from_path Absolute path of the file or directory to be renamed. * @param to_path Absolute path that @a from_path should be renamed to. * * @returns Whether or not we needed to overwrite an existing file or * directory at the target path. */ VARIANT_BOOL provider::rename(com_ptr consumer, const path& from, const path& to) { if (from.empty()) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); if (to.empty()) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); // NOP if filenames are equal if (from == to) return VARIANT_FALSE; // Attempt to rename old path to new path try { ssh::filesystem::rename(m_ticket.session().get_sftp_filesystem(), from, to, overwrite_behaviour::prevent_overwrite); // Rename was successful without overwrite return VARIANT_FALSE; } catch (const system_error& e) { if (rename_retry_with_overwrite(m_ticket.session(), consumer.get(), e, from, to)) { return VARIANT_TRUE; } else { BOOST_THROW_EXCEPTION(com_error(E_ABORT)); } } } void provider::remove_all(const path& target) { if (target.empty()) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); ssh::filesystem::remove_all(m_ticket.session().get_sftp_filesystem(), target); } void provider::create_new_directory(const path& path) { if (path.empty()) BOOST_THROW_EXCEPTION(com_error( "Cannot create a directory without a name", E_INVALIDARG)); create_directory(m_ticket.session().get_sftp_filesystem(), path); } const path provider::resolve_link(const path& path) { sftp_filesystem& channel = m_ticket.session().get_sftp_filesystem(); bstr_t target = channel.canonical_path(path).wstring(); return target.detach(); } /** * Get the details of a file by path. * * The item returned by this function doesn't include a long entry or * owner and group names as string (these being derived from the long entry). */ sftp_filesystem_item provider::stat(const path& path, bool follow_links) { sftp_filesystem& channel = m_ticket.session().get_sftp_filesystem(); file_attributes stat_result = channel.attributes(path, follow_links != FALSE); return libssh2_sftp_filesystem_item::create_from_libssh2_attributes( path, stat_result); } } } // namespace swish::provider ================================================ FILE: swish/provider/Provider.hpp ================================================ /** @file libssh2-based SFTP provider component. @if license Copyright (C) 2008, 2009, 2010, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #ifndef SWISH_PROVIDER_PROVIDER_HPP #define SWISH_PROVIDER_PROVIDER_HPP #pragma once #include "swish/connection/session_manager.hpp" // session_reservation #include "swish/provider/sftp_provider.hpp" #include // BOOST_RV_REF #include // shared_ptr namespace swish { namespace provider { class provider; class CProvider : public sftp_provider { public: explicit CProvider( BOOST_RV_REF(swish::connection::session_reservation) session_ticket); virtual directory_listing listing(const ssh::filesystem::path& directory); virtual comet::com_ptr get_file( const ssh::filesystem::path& file_path, std::ios_base::openmode open_mode); virtual VARIANT_BOOL rename( ISftpConsumer* consumer, const ssh::filesystem::path& from_path, const ssh::filesystem::path& to_path); virtual void remove_all(const ssh::filesystem::path& path); virtual void create_new_directory(const ssh::filesystem::path& path); virtual ssh::filesystem::path resolve_link( const ssh::filesystem::path& link_path); virtual sftp_filesystem_item stat( const ssh::filesystem::path& path, bool follow_links); private: boost::shared_ptr m_provider; }; }} // namespace swish::provider #endif ================================================ FILE: swish/provider/libssh2_sftp_filesystem_item.cpp ================================================ /** @file SFTP filesystem item using libssh2 backend. @if license Copyright (C) 2012, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . If you modify this Program, or any covered work, by linking or combining it with the OpenSSL project's OpenSSL library (or a modified version of that library), containing parts covered by the terms of the OpenSSL or SSLeay licenses, the licensors of this Program grant you additional permission to convey the resulting work. @endif */ #include "libssh2_sftp_filesystem_item.hpp" #include "swish/utils.hpp" // Utf8StringToWideString #include // file_attributes, sftp_file #include // Regular expressions #include using swish::utils::Utf8StringToWideString; using comet::datetime_t; using ssh::filesystem::file_attributes; using ssh::filesystem::path; using ssh::filesystem::sftp_file; using boost::optional; using boost::shared_ptr; using boost::uint64_t; using std::string; using std::wstring; namespace { const boost::regex regex("\\S{10,}\\s+\\d+\\s+(\\S+)\\s+(\\S+)\\s+.+"); const unsigned int USER_MATCH = 1; const unsigned int GROUP_MATCH = 2; /** * Get the username part of an SFTP 'ls -l'-style long entry. * * According to the specification * (http://www.openssh.org/txt/draft-ietf-secsh-filexfer-02.txt): * * The recommended format for the longname field is as follows: * * -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer * 1234567890 123 12345678 12345678 12345678 123456789012 * * where the second line shows the *minimum* number of characters. * * @warning * The spec specifically forbids parsing this long entry by it is the * only way to get the user @b name rather than the user @b ID. */ optional parse_user_from_long_entry(const string& long_entry) { boost::smatch match; if (regex_match(long_entry, match, regex) && match[USER_MATCH].matched) { return Utf8StringToWideString(match[USER_MATCH].str()); } else { return optional(); } } /** * Get the group name part of an SFTP 'ls -l'-style long entry. * * @see parse_user_from_long_entry() for more information. */ optional parse_group_from_long_entry(const string& long_entry) { boost::smatch match; if (regex_match(long_entry, match, regex) && match[GROUP_MATCH].matched) { return Utf8StringToWideString(match[GROUP_MATCH].str()); } else { return optional(); } } } namespace swish { namespace provider { sftp_filesystem_item libssh2_sftp_filesystem_item::create_from_libssh2_attributes( const path& file_name, const file_attributes& attributes) { return sftp_filesystem_item( shared_ptr( new libssh2_sftp_filesystem_item(file_name, attributes))); } sftp_filesystem_item libssh2_sftp_filesystem_item::create_from_libssh2_file(const sftp_file& file) { return sftp_filesystem_item( shared_ptr( new libssh2_sftp_filesystem_item(file))); } void libssh2_sftp_filesystem_item::common_init( const path& file_name, const file_attributes& attributes) { m_path = file_name; switch (attributes.type()) { case file_attributes::normal_file: m_type = type::file; break; case file_attributes::directory: m_type = type::directory; break; case file_attributes::symbolic_link: m_type = type::link; break; default: m_type = type::unknown; } if (attributes.permissions()) { m_permissions = *attributes.permissions(); } if (attributes.size()) { m_size = *attributes.size(); } if (attributes.uid()) { m_uid = *attributes.uid(); } if (attributes.gid()) { m_gid = *attributes.gid(); } if (attributes.last_accessed()) { m_accessed.from_unixtime( static_cast(*attributes.last_accessed()), datetime_t::utc_convert_mode::none); } if (attributes.last_modified()) { m_modified.from_unixtime( static_cast(*attributes.last_modified()), datetime_t::utc_convert_mode::none); } } libssh2_sftp_filesystem_item::libssh2_sftp_filesystem_item( const sftp_file& file) : m_type(type::unknown), m_permissions(0U), m_uid(0U), m_gid(0U), m_size(0U) { file_attributes attributes = file.attributes(); common_init(file.path().filename(), attributes); // Naughtily, we parse the long (ls -l) form of the file's attributes // for the username and group. The standard says we shouldn't but // there's no other way to get them as text. Although it contains a copy // the filename, which may not be in UTF-8 encoding, we treat this // long form as a UTF-8 string as the other info /should/ be UTF-8 and we // don't use the filename. // To be on the safe side assume that the long entry doesn't hold // valid owner and group info if the UID and GID aren't valid string utf8_long_entry = file.long_entry(); if (attributes.uid()) { m_owner = parse_user_from_long_entry(utf8_long_entry); } if (attributes.gid()) { m_group = parse_group_from_long_entry(utf8_long_entry); } } libssh2_sftp_filesystem_item::libssh2_sftp_filesystem_item( const path& file_name, const file_attributes& attributes) : m_type(type::unknown), m_permissions(0U), m_uid(0U), m_gid(0U), m_size(0U) { common_init(file_name, attributes); } BOOST_SCOPED_ENUM(sftp_filesystem_item_interface::type) libssh2_sftp_filesystem_item::type() const { return m_type; } ssh::filesystem::path libssh2_sftp_filesystem_item::filename() const { return m_path.filename(); } unsigned long libssh2_sftp_filesystem_item::permissions() const { return m_permissions; } optional libssh2_sftp_filesystem_item::owner() const { return m_owner; } unsigned long libssh2_sftp_filesystem_item::uid() const { return m_uid; } optional libssh2_sftp_filesystem_item::group() const { return m_group; } unsigned long libssh2_sftp_filesystem_item::gid() const { return m_gid; } uint64_t libssh2_sftp_filesystem_item::size_in_bytes() const { return m_size; } datetime_t libssh2_sftp_filesystem_item::last_accessed() const { return m_accessed; } datetime_t libssh2_sftp_filesystem_item::last_modified() const { return m_modified; } /* bool libssh2_sftp_filesystem_item::operator<(const libssh2_sftp_filesystem_item& other) const { if (bstrFilename == 0) return other.bstrFilename != 0; if (other.bstrFilename == 0) return false; return ::VarBstrCmp( bstrFilename, other.bstrFilename, ::GetThreadLocale(), 0) == VARCMP_LT; } bool libssh2_sftp_filesystem_item::operator==(const libssh2_sftp_filesystem_item& other) const { if (bstrFilename == 0 && other.bstrFilename == 0) return true; return ::VarBstrCmp( bstrFilename, other.bstrFilename, ::GetThreadLocale(), 0) == VARCMP_EQ; } bool libssh2_sftp_filesystem_item::operator==(const comet::bstr_t& name) const { return bstrFilename == name; } */ }} ================================================ FILE: swish/provider/libssh2_sftp_filesystem_item.hpp ================================================ /** @file SFTP filesystem item using libssh2 backend. @if license Copyright (C) 2012 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . If you modify this Program, or any covered work, by linking or combining it with the OpenSSL project's OpenSSL library (or a modified version of that library), containing parts covered by the terms of the OpenSSL or SSLeay licenses, the licensors of this Program grant you additional permission to convey the resulting work. @endif */ #ifndef SWISH_PROVIDER_LIBSSH2_SFTP_FILESYSTEM_ITEM_HPP #define SWISH_PROVIDER_LIBSSH2_SFTP_FILESYSTEM_ITEM_HPP #include "swish/provider/sftp_filesystem_item.hpp" #include // uint64_t #include #include // datetime_t #include namespace ssh { namespace filesystem { class file_attributes; class sftp_file; } } namespace swish { namespace provider { /** * An entry in an SFTP directory retrieved by the libssh2 backend. */ class libssh2_sftp_filesystem_item : public sftp_filesystem_item_interface { public: /** * Create filesystem entry from libssh2 filesystem item representation. */ static sftp_filesystem_item create_from_libssh2_file( const ssh::filesystem::sftp_file& file); /** * Create filesystem entry from libssh2 filesystem item representation using * only the attributes and filename. * * This constructor is for use with a stat-style situation where the full * file info isn't available. * * Items created with this constructor will *not* be able to return the * user name or group name as a string. * * @param file_name * Filename. * * @param attributes * Object containing the file's details. */ static sftp_filesystem_item create_from_libssh2_attributes( const ssh::filesystem::path& file_name, const ssh::filesystem::file_attributes& attributes); virtual BOOST_SCOPED_ENUM(type) type() const; virtual ssh::filesystem::path filename() const; virtual unsigned long permissions() const; virtual boost::optional owner() const; virtual unsigned long uid() const; virtual boost::optional group() const; virtual unsigned long gid() const; virtual boost::uint64_t size_in_bytes() const; virtual comet::datetime_t last_accessed() const; virtual comet::datetime_t last_modified() const; private: libssh2_sftp_filesystem_item( const ssh::filesystem::sftp_file& file); libssh2_sftp_filesystem_item( const ssh::filesystem::path& file_name, const ssh::filesystem::file_attributes& attributes); void common_init( const ssh::filesystem::path& file_name, const ssh::filesystem::file_attributes& attributes); BOOST_SCOPED_ENUM(type) m_type; ssh::filesystem::path m_path; unsigned long m_permissions; boost::optional m_owner; boost::optional m_group; unsigned long m_uid; unsigned long m_gid; boost::uint64_t m_size; comet::datetime_t m_modified; comet::datetime_t m_accessed; }; }} #endif ================================================ FILE: swish/provider/sftp_filesystem_item.hpp ================================================ /** @file SFTP backend filesystem item interface. @if license Copyright (C) 2012 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . If you modify this Program, or any covered work, by linking or combining it with the OpenSSL project's OpenSSL library (or a modified version of that library), containing parts covered by the terms of the OpenSSL or SSLeay licenses, the licensors of this Program grant you additional permission to convey the resulting work. @endif */ #ifndef SWISH_PROVIDER_SFTP_FILESYSTEM_ITEM_HPP #define SWISH_PROVIDER_SFTP_FILESYSTEM_ITEM_HPP #include #include // uint64_t #include // BOOST_SCOPED_ENUM #include #include #include // datetime_t #include namespace swish { namespace provider { /** * Interface to Swish's representation of an SFTP file's properties. * * All attributes are technically optional according to the SFTP standard * (i.e. the server could set the flags to say the returned value isn't valid), * but, to simplify things inside Swish, we only make this optionality * explicit for `owner` and `group` as they are the only ones with a realistic * prospect of not being supported. The others have sensible defaults. */ class sftp_filesystem_item_interface { public: BOOST_SCOPED_ENUM_START(type) { /// File that can be opened and whose contents can be accessed /// (permissions permitting). file, /// This filesystem item can be listed for items under it. directory, /// This file is a link to another item link, /// An item of a type we don't recognise or the server didn't send any /// information about the type unknown }; BOOST_SCOPED_ENUM_END(); /// Type of item represented by this object. virtual BOOST_SCOPED_ENUM(type) type() const = 0; /// Filename relative to directory (e.g. `README.txt`). virtual ssh::filesystem::path filename() const = 0; /// Unix file permissions. virtual unsigned long permissions() const = 0; /// The user name of the file's owner. /// This may not exist if the server doesn't report named users. This may /// also be incorrect if the server responds in an unusual way so should /// only be used for information. virtual boost::optional owner() const = 0; /// Numeric ID of file's owner. virtual unsigned long uid() const = 0; /// The name of the user group to which the file belongs /// This may not exist if the server doesn't report named groups. This may /// also be incorrect if the server responds in an unusual way so should /// only be used for information. virtual boost::optional group() const = 0; /// Numeric ID of group to which the file belongs. virtual unsigned long gid() const = 0; /// The file's size in bytes. virtual boost::uint64_t size_in_bytes() const = 0; /// The date and time at which the file was last accessed. virtual comet::datetime_t last_accessed() const = 0; /// The date and time at which the file was last modified. virtual comet::datetime_t last_modified() const = 0; }; /** * Type erasure interface to SFTP representation implementations. */ class sftp_filesystem_item : public sftp_filesystem_item_interface { public: BOOST_SCOPED_ENUM(type) type() const { return m_inner->type(); } ssh::filesystem::path filename() const { return m_inner ->filename(); } unsigned long permissions() const { return m_inner->permissions(); } boost::optional owner() const { return m_inner->owner(); } unsigned long uid() const { return m_inner->uid(); } boost::optional group() const { return m_inner->group(); } unsigned long gid() const { return m_inner->gid(); } boost::uint64_t size_in_bytes() const { return m_inner->size_in_bytes(); } comet::datetime_t last_accessed() const { return m_inner->last_accessed(); } comet::datetime_t last_modified() const { return m_inner->last_modified(); } explicit sftp_filesystem_item( boost::shared_ptr inner) : m_inner(inner) {} private: boost::shared_ptr m_inner; }; }} #endif ================================================ FILE: swish/provider/sftp_provider.hpp ================================================ /** @file SFTP backend interfaces. @if license Copyright (C) 2010, 2011, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_PROVIDER_SFTP_PROVIDER_H #define SWISH_PROVIDER_SFTP_PROVIDER_H #pragma once #include "swish/provider/sftp_filesystem_item.hpp" #include #include #include //#include USE ONCE WE UPGRADE BOOST #include // comtype #include // com_ptr #include // wstring #include // pair #include class ISftpConsumer : public IUnknown { public: /** * Get password from the user. * * @return * Uninitialised optional if authentication should be aborted. * String containing password, otherwise. */ virtual boost::optional prompt_for_password() = 0; /** * Get files containing private and public keys for public-key * authentication. * * @return * Uninitialised optional if public-key authentication should not be * performed using file-based keys. * Pair of paths: private-key file first, public-key file second. */ virtual boost::optional< std::pair> key_files() = 0; /** * Perform a challenge-response interaction with the user. * * @return * Uninitialised optional if authentication should be aborted. * As many responses as there were prompts, otherwise. */ virtual boost::optional> challenge_response( const std::string& title, const std::string& instructions, const std::vector>& prompts) = 0; virtual HRESULT OnConfirmOverwrite( BSTR bstrOldFile, BSTR bstrNewFile ) = 0; virtual HRESULT OnHostkeyMismatch( BSTR bstrHostName, BSTR bstrHostKey, BSTR bstrHostKeyType ) = 0; virtual HRESULT OnHostkeyUnknown( BSTR bstrHostName, BSTR bstrHostKey, BSTR bstrHostKeyType ) = 0; }; namespace swish { namespace provider { //typedef boost::any_range< // sftp_filesystem_item, boost::forward_traversal_tag, // sftp_filesystem_item&, std::ptrdiff_t> // directory_listing; typedef std::vector directory_listing; class sftp_provider { public: virtual ~sftp_provider() {} virtual directory_listing listing( const ssh::filesystem::path& directory) = 0; virtual comet::com_ptr get_file( const ssh::filesystem::path& file_path, std::ios_base::openmode mode) = 0; virtual VARIANT_BOOL rename( ISftpConsumer* consumer, const ssh::filesystem::path& from_path, const ssh::filesystem::path& to_path) = 0; virtual void remove_all(const ssh::filesystem::path& path) = 0; virtual void create_new_directory(const ssh::filesystem::path& path) = 0; /** * Return the canonical path of the given non-canonical path. * * While generally used to resolve symlinks, it can also be used to * convert paths relative to the startup directory into absolute paths. */ virtual ssh::filesystem::path resolve_link( const ssh::filesystem::path& link_path) = 0; virtual sftp_filesystem_item stat( const ssh::filesystem::path& path, bool follow_links) = 0; }; }} namespace comet { template<> struct comtype { static const IID& uuid() throw() { static comet::uuid_t iid("304982B4-4FB1-4C2E-A892-3536DF59ACF5"); return iid; } typedef IUnknown base; }; } #endif ================================================ FILE: swish/provider/ticketed_stream.hpp ================================================ /** @file Session-holding IStream. @if license Copyright (C) 2013 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . If you modify this Program, or any covered work, by linking or combining it with the OpenSSL project's OpenSSL library (or a modified version of that library), containing parts covered by the terms of the OpenSSL or SSLeay licenses, the licensors of this Program grant you additional permission to convey the resulting work. @endif */ #ifndef SWISH_PROVIDER_TICKETED_STREAM_HPP #define SWISH_PROVIDER_TICKETED_STREAM_HPP #include // session_reservation #include // simple_object #include // BOOST_RV_REF namespace swish { namespace provider { /** * IStream holding a session reservation ticket. * * The ticket ensures the session remains active for at least as long as the * IStream. */ class ticketed_stream : public comet::simple_object { public: ticketed_stream( com_ptr stream, BOOST_RV_REF(swish::connection::session_reservation) ticket) : m_ticket(ticket), m_inner(stream) {} virtual HRESULT STDMETHODCALLTYPE Read( void* buffer, ULONG buffer_size, ULONG* read_count_out) { return m_inner->Read(buffer, buffer_size, read_count_out); } virtual HRESULT STDMETHODCALLTYPE Write( const void* data, ULONG data_size, ULONG* written_count_out) { return m_inner->Write(data, data_size, written_count_out); } virtual HRESULT STDMETHODCALLTYPE Seek( LARGE_INTEGER offset, DWORD origin, ULARGE_INTEGER* new_position_out) { return m_inner->Seek(offset, origin, new_position_out); } virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER new_size) { return m_inner->SetSize(new_size); } virtual HRESULT STDMETHODCALLTYPE CopyTo( IStream* destination, ULARGE_INTEGER amount, ULARGE_INTEGER* bytes_read_out, ULARGE_INTEGER* bytes_written_out) { return m_inner->CopyTo( destination, amount, bytes_read_out, bytes_written_out); } virtual HRESULT STDMETHODCALLTYPE Commit(DWORD commit_flags) { return m_inner->Commit(commit_flags); } virtual HRESULT STDMETHODCALLTYPE Revert() { return m_inner->Revert(); } virtual HRESULT STDMETHODCALLTYPE LockRegion( ULARGE_INTEGER offset, ULARGE_INTEGER extent, DWORD lock_type) { return m_inner->LockRegion(offset, extent, lock_type); } virtual HRESULT STDMETHODCALLTYPE UnlockRegion( ULARGE_INTEGER offset, ULARGE_INTEGER extent, DWORD lock_type) { return m_inner->UnlockRegion(offset, extent, lock_type); } virtual HRESULT STDMETHODCALLTYPE Stat( STATSTG* attributes_out, DWORD stat_flag) { return m_inner->Stat(attributes_out, stat_flag); } virtual HRESULT STDMETHODCALLTYPE Clone(IStream** stream_out) { return m_inner->Clone(stream_out); } private: swish::connection::session_reservation m_ticket; comet::com_ptr m_stream; }; }} // namespace swish::provider #endif ================================================ FILE: swish/remote_folder/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(COMMAND_SOURCES commands/commands.cpp commands/commands.hpp commands/delete.cpp commands/delete.hpp commands/NewFolder.cpp commands/NewFolder.hpp) set(SOURCES columns.cpp context_menu_callback.cpp filemode.c Mode.cpp pidl_connection.cpp properties.cpp ViewCallback.cpp columns.hpp context_menu_callback.hpp filemode.h Mode.h pidl_connection.hpp properties.hpp remote_pidl.hpp swish_pidl.hpp ViewCallback.hpp ${COMMAND_SOURCES}) add_library(remote_folder ${SOURCES}) target_compile_definitions(remote_folder PRIVATE ISOLATION_AWARE_ENABLED=1) hunter_add_package(Comet) hunter_add_package(Washer) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) target_link_libraries(remote_folder PRIVATE connection provider nse frontend shell_folder shell PUBLIC Washer::washer Comet::comet ${Boost_LIBRARIES}) ================================================ FILE: swish/remote_folder/Mode.cpp ================================================ /* File permissions processing functions Copyright (C) 2006, 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "Mode.h" #include using namespace swish::remote_folder::mode; Mode::Mode(mode_t mode) : mode(mode) {} bool Mode::isSymLink() { assert(S_ISLNK(mode) ^ ( S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISLNK(mode); } bool Mode::isRegular() { assert(S_ISREG(mode) ^ ( S_ISLNK(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISREG(mode); } bool Mode::isDirectory() { assert(S_ISDIR(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISDIR(mode); } bool Mode::isCharacter() { assert(S_ISLNK(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISLNK(mode); } bool Mode::isBlock() { assert(S_ISBLK(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISBLK(mode); } bool Mode::isFifo() { assert(S_ISFIFO(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISFIFO(mode); } bool Mode::isSocket() { assert(S_ISSOCK(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISSOCK(mode); } bool Mode::isDoor() /* Solaris door 'D' */ { assert(S_ISDOOR(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISDOOR(mode); } bool Mode::isNamed() /* XENIX named file 'x' */ { assert(S_ISNAM(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISNAM(mode); } bool Mode::isMultiplexedBlock() /* multiplexed block special 'B' */ { assert(S_ISMPB(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISMPB(mode); } bool Mode::isMultiplexedChar() /* multiplexed char special 'm' */ { assert(S_ISMPC(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISMPC(mode); } bool Mode::isWhiteout() /* BSD whiteout 'w' */ { assert(S_ISWHT(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISWHT(mode); } bool Mode::isNetwork() /* HP-UX network special 'n' */ { assert(S_ISNWK(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISNWK(mode); } bool Mode::isContiguous() /* contiguous - returns false 'C' */ { assert(S_ISCTG(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) || S_ISOFL(mode) )); return S_ISCTG(mode); } bool Mode::isOffline() /* Cray DMF offline no data - returns false 'M' */ { assert(S_ISOFL(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFD(mode) )); return S_ISOFL(mode); } bool Mode::isOfflineData() /* Cray DMF offline + data - returns false 'M' */ { assert(S_ISOFD(mode) ^ ( S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) || S_ISDOOR(mode) || S_ISNAM(mode) || S_ISMPB(mode) || S_ISMPC(mode) || S_ISWHT(mode) || S_ISNWK(mode) || S_ISCTG(mode) || S_ISOFL(mode) )); return S_ISOFD(mode); } bool Mode::isSUID() { return ((mode & S_ISUID) == S_ISUID); } bool Mode::isSGID() { return ((mode & S_ISGID) == S_ISGID); } bool Mode::isSticky() { return ((mode & S_ISVTX) == S_ISVTX); } std::string Mode::toString() { const int MODE_STR_BUFFER_SIZE = 10; char buf[MODE_STR_BUFFER_SIZE]; ::mode_string(mode, buf); return std::string(buf, MODE_STR_BUFFER_SIZE); } ================================================ FILE: swish/remote_folder/Mode.h ================================================ /* File permissions processing functions (header). Copyright (C) 2006, 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef SWISH_REMOTE_FOLDER_MODE_H #define SWISH_REMOTE_FOLDER_MODE_H #pragma once #include "filemode.h" #include namespace swish { namespace remote_folder { namespace mode { class Mode { public: Mode(mode_t mode); bool isRegular(); ///< regular '-' bool isSymLink(); ///< Symbolic link 'l' bool isDirectory(); ///< directory 'd' bool isCharacter(); ///< character special 'c' bool isBlock(); ///< block special 'b' bool isFifo(); ///< fifo 'p' bool isSocket(); ///< socket 's' bool isDoor(); ///< Solaris door 'D' bool isNamed(); ///< XENIX named file 'x' bool isMultiplexedBlock(); ///< multiplexed block special 'B' bool isMultiplexedChar(); ///< multiplexed char special 'm' bool isWhiteout(); ///< BSD whiteout 'w' bool isNetwork(); ///< HP-UX network special 'n' bool isContiguous(); ///< contiguous 'C' bool isOffline(); ///< Cray DMF offline no data 'M' bool isOfflineData(); ///< Cray DMF offline + data 'M' bool isSUID(); bool isSGID(); bool isSticky(); std::string toString(); private: mode_t mode; }; }}} // namespace swish::remote_folder::mode #endif ================================================ FILE: swish/remote_folder/ViewCallback.cpp ================================================ /* Handler for remote folder's interaction with Explorer Shell Folder View. Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ViewCallback.hpp" #include "swish/frontend/UserInteraction.hpp" // CUserInteraction #include "swish/remote_folder/commands/commands.hpp" // NewFolder #include "swish/remote_folder/pidl_connection.hpp" // provider_from_pidl #include // last_error #include // bind #include #include // errinfo_api_function #include // BOOST_THROW_EXCEPTION #include // assert using swish::frontend::CUserInteraction; using swish::nse::IEnumUICommand; using swish::nse::IUIElement; using swish::remote_folder::provider_from_pidl; using swish::remote_folder::commands::remote_folder_task_pane_tasks; using swish::remote_folder::commands::remote_folder_task_pane_titles; using comet::com_ptr; using washer::last_error; using washer::shell::pidl::apidl_t; using washer::window::window; using washer::window::window_handle; using boost::bind; using boost::enable_error_info; using boost::errinfo_api_function; using std::pair; namespace swish { namespace remote_folder { namespace { bool is_vista_or_greater() { OSVERSIONINFO version = OSVERSIONINFO(); version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (::GetVersionEx(&version) == FALSE) BOOST_THROW_EXCEPTION( enable_error_info(last_error()) << errinfo_api_function("GetVersionEx")); return version.dwMajorVersion > 5; } } /** * Create customisation callback object for Explorer default shell view. * * @param folder_pidl Absolute PIDL to the folder for whom we are * creating this callback object. */ CViewCallback::CViewCallback(const apidl_t& folder_pidl) : m_folder_pidl(folder_pidl) {} /** * The folder window is being created. * * The shell is notifying us of the folder view's window handle. */ bool CViewCallback::on_window_created(HWND hwnd_view) { if (hwnd_view) { m_owning_folder_view = window( window_handle::foster_handle(hwnd_view)); } return true; } /** * Tell the shell that we might notify it of update events that apply to this * folder (specified using our absolute PIDL). * * We are notified via SFVM_FSNOTIFY if any events indicated here occurr. * * @TODO: It's possible that @a events already has events set in its bitmask * so maybe we should 'or' our extra events with it. */ bool CViewCallback::on_get_notify( PCIDLIST_ABSOLUTE& pidl_monitor, LONG& events) { events = SHCNE_CREATE | SHCNE_DELETE | SHCNE_MKDIR | SHCNE_RMDIR | SHCNE_UPDATEITEM | SHCNE_UPDATEDIR | SHCNE_RENAMEITEM | SHCNE_RENAMEFOLDER; pidl_monitor = m_folder_pidl.get(); // Owned by us return true; } /** * The shell is telling us that an event (probably a SHChangeNotify of some * sort) has affected one of our item. Just nod. If we don't it doesn't work. */ bool CViewCallback::on_fs_notify( PCIDLIST_ABSOLUTE /*pidl*/, LONG /*event*/) { return true; } bool CViewCallback::on_get_webview_content( SFV_WEBVIEW_CONTENT_DATA& content_out) { assert(content_out.pFolderTasksExpando == NULL); assert(content_out.pExtraTasksExpando == NULL); assert(content_out.pEnumRelatedPlaces == NULL); // HACK: webview conflicts with ExplorerCommands so we disable it if // ExplorerCommands are likely to be used. if (is_vista_or_greater()) return false; pair< com_ptr, com_ptr > tasks = remote_folder_task_pane_titles(m_folder_pidl); content_out.pExtraTasksExpando = tasks.first.detach(); content_out.pFolderTasksExpando = tasks.second.detach(); return true; } namespace { com_ptr consumer(HWND hwnd) { return new CUserInteraction(hwnd); } } bool CViewCallback::on_get_webview_tasks( SFV_WEBVIEW_TASKSECTION_DATA& tasks_out) { //for some reason this fails on 64-bit //assert(tasks_out.pEnumExtraTasks == NULL); assert(tasks_out.pEnumFolderTasks == NULL); // HACK: webview conflicts with ExplorerCommands so we disable it if // ExplorerCommands are likely to be used. if (is_vista_or_greater()) return false; HWND nasty_old_hwnd = (m_owning_folder_view) ? m_owning_folder_view->hwnd() : NULL; pair< com_ptr, com_ptr > commands = remote_folder_task_pane_tasks( m_folder_pidl, ole_site(), bind(provider_from_pidl, m_folder_pidl, _1, _2), bind(&consumer, nasty_old_hwnd)); tasks_out.pEnumExtraTasks = commands.first.detach(); tasks_out.pEnumFolderTasks = commands.second.detach(); return true; } }} // namespace swish::remote_folder ================================================ FILE: swish/remote_folder/ViewCallback.hpp ================================================ /* Handler for remote folder's interaction with Explorer Shell Folder View. Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_REMOTE_FOLDER_VIEW_CALLBACK_HPP #define SWISH_REMOTE_FOLDER_VIEW_CALLBACK_HPP #pragma once #include "swish/nse/view_callback.hpp" // CViewCallback #include // object_with_site #include // apidl_t #include #include // simple_object #include // optional namespace swish { namespace remote_folder { class CViewCallback : public comet::simple_object { public: CViewCallback(const washer::shell::pidl::apidl_t& folder_pidl); private: /// @name SFVM_* message handlers // @{ virtual bool on_window_created(HWND hwnd_view); virtual bool on_get_notify(PCIDLIST_ABSOLUTE& pidl_monitor, LONG& events); virtual bool on_fs_notify(PCIDLIST_ABSOLUTE pidl, LONG event); virtual bool on_get_webview_content(SFV_WEBVIEW_CONTENT_DATA& content_out); virtual bool on_get_webview_tasks(SFV_WEBVIEW_TASKSECTION_DATA& tasks_out); // @} boost::optional> m_owning_folder_view; washer::shell::pidl::apidl_t m_folder_pidl; }; }} // namespace remote::host_folder #endif ================================================ FILE: swish/remote_folder/columns.cpp ================================================ /** @file Host folder detail columns. @if license Copyright (C) 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "columns.hpp" #include // format_date_time, // format_filesize_kilobytes #pragma warning(push) #pragma warning(disable: 4510 4610) // Cannot generate default constructor #include // array #pragma warning(pop) #include // translate #include #include // LVCFMT_* #include // PKEY_ * using washer::shell::format_date_time; using washer::shell::format_filesize_kilobytes; using washer::shell::property_key; using comet::variant_t; using boost::array; using boost::locale::translate; using std::wstring; namespace swish { namespace remote_folder { namespace { /** * Convert the variant to a date string in the format normal for the shell. */ wstring date_formatter(const variant_t& val) { return format_date_time(val); } /** * Format the number in the variant as a file size in KB. */ wstring size_formatter(const variant_t& val) { return format_filesize_kilobytes(val); } /** * Static column information. * Order of entries must correspond to the indices in columnIndices. */ const boost::array column_key_index = { { { PKEY_ItemNameDisplay, translate("Property (filename/label)", "Name"), SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30 }, { PKEY_Size, translate("Property", "Size"), SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT, LVCFMT_RIGHT, 15, size_formatter }, { PKEY_ItemTypeText, translate("Property", "Type"), SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 20 }, { PKEY_DateModified, translate("Property", "Date Modified"), SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 20, date_formatter }, { PKEY_DateAccessed, translate("Property", "Date Accessed"), SHCOLSTATE_TYPE_DATE, LVCFMT_LEFT, 20, date_formatter }, { PKEY_Permissions, translate("Property", "Permissions"), SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 12 }, { PKEY_FileOwner, translate("Property", "Owner"), SHCOLSTATE_TYPE_STR, LVCFMT_LEFT, 12 }, { PKEY_Group, translate("Property", "Group"), SHCOLSTATE_TYPE_STR, LVCFMT_LEFT, 12 }, { PKEY_OwnerId, translate("Property", "Owner ID"), SHCOLSTATE_TYPE_INT, LVCFMT_LEFT, 10 }, { PKEY_GroupId, translate("Property", "Group ID"), SHCOLSTATE_TYPE_INT, LVCFMT_LEFT, 10 } } }; } /** * Return a column entry. */ const column_entry& RemoteColumnEntries::entry(size_t index) const { return column_key_index.at(index); } /** * Convert index to a corresponding PROPERTYKEY. */ const property_key& property_key_from_column_index(size_t index) { return column_key_index.at(index).m_key; } }} // namespace swish::remote_folder ================================================ FILE: swish/remote_folder/columns.hpp ================================================ /** @file Host folder detail columns. @if license Copyright (C) 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_REMOTE_FOLDER_COLUMNS_HPP #define SWISH_REMOTE_FOLDER_COLUMNS_HPP #pragma once #include "properties.hpp" // property_from_pidl, compare_pidls_by_property #include "swish/nse/StaticColumn.hpp" // StaticColumn #include // cpidl_t #include // property_key #include // variant_t #include // utf_to_utf #include // message #include // function #include // SHCOLSTATEF #include namespace swish { namespace remote_folder { #pragma warning(push) #pragma warning(disable: 4510 4610) // Cannot generate default constructor struct column_entry { /** * @name Fields. * * These can be set using an aggregate initialiser: { val1, val2, ... } */ // @{ washer::shell::property_key m_key; boost::locale::message m_title; SHCOLSTATEF m_flags; int m_format; int m_avg_char_width; boost::function m_stringifier; // @} std::wstring title() const { return boost::locale::conv::utf_to_utf(m_title.str()); } SHCOLSTATEF flags() const { return m_flags; } int format() const { return m_format; } int avg_char_width() const { return m_avg_char_width; } /** * Convert the column's property variant to a string. * * Transforms the output using m_stringifier, if any, otherwise performs * simple wstring conversion. */ std::wstring detail(const washer::shell::pidl::cpidl_t& pidl) const { comet::variant_t var = property_from_pidl(pidl, m_key); if (m_stringifier) return m_stringifier(var); else return var; } int compare( const washer::shell::pidl::cpidl_t& lhs, const washer::shell::pidl::cpidl_t& rhs) const { return compare_pidls_by_property(lhs, rhs, m_key); } }; #pragma warning(pop) /** * StaticColumn-compatible interface to the static column data. */ class RemoteColumnEntries { protected: const column_entry& entry(size_t index) const; }; typedef swish::nse::StaticColumn Column; const washer::shell::property_key& property_key_from_column_index( size_t index); }} // namespace swish::remote_folder #endif ================================================ FILE: swish/remote_folder/commands/NewFolder.cpp ================================================ /* Copyright (C) 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "NewFolder.hpp" #include "swish/frontend/announce_error.hpp" // announce_last_exception #include "swish/provider/sftp_filesystem_item.hpp" #include "swish/remote_folder/swish_pidl.hpp" // absolute_path_from_swish_pidl #include "swish/shell_folder/SftpDirectory.h" // CSftpDirectory #include "swish/shell/shell.hpp" // put_view_item_into_rename_mode #include #include // shell_browser, shell_view #include // trace #include // com_error #include // uuid_t #include // lexical_cast #include // translate #include // BOOST_FOREACH #include // wformat #include // wregex, smatch, regex_match #include // BOOST_THROW_EXCEPTION #include // assert #include #include #include using swish::frontend::announce_last_exception; using swish::nse::Command; using swish::nse::command_site; using swish::provider::sftp_filesystem_item; using swish::provider::sftp_provider; using swish::remote_folder::absolute_path_from_swish_pidl; using swish::shell::put_view_item_into_rename_mode; using ssh::filesystem::path; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::shell_browser; using washer::shell::shell_view; using washer::window::window; using washer::trace; using comet::com_error; using comet::com_error_from_interface; using comet::com_ptr; using comet::uuid_t; using boost::function; using boost::shared_ptr; using boost::lexical_cast; using boost::locale::translate; using boost::optional; using boost::regex_match; using boost::wformat; using boost::wregex; using boost::wsmatch; using std::exception; using std::vector; using std::wstring; namespace { /** * Find the first non-existent directory name that begins with @a initial_name. * * This may simply be initial_name, however, if an item of this name already * exists in the directory, return a name that begins with @a initial_name * followed by a space and a digit in brackets. The digit should be the lowest * digit that will create a name that doesn't already exits. * * @todo Investigate whether other locales require something other than an * Arabic digit being appended or if it should be appended in a different * place. */ wstring prefix_if_necessary( const wstring& initial_name, shared_ptr provider, const path& directory) { wregex new_folder_pattern( str(wformat(L"%1%|%1% \\((\\d+)\\)") % initial_name)); wsmatch digit_suffix_match; bool collision = false; vector suffixes; BOOST_FOREACH( const sftp_filesystem_item& lt, provider->listing(directory)) { wstring filename = lt.filename().wstring(); if (regex_match(filename, digit_suffix_match, new_folder_pattern)) { assert(digit_suffix_match.size() == 2); // complete + capture group assert(digit_suffix_match[0].matched); // why are we here otherwise? // We record whether an exact match was found with "initial_name" // but keep going regardless: if it was, we will need to find // the next available digit suffix; if not, it might be found on // a future iteration so we still need the next available digit if (digit_suffix_match[1].matched) { unsigned long suffix = lexical_cast( wstring(digit_suffix_match[1])); suffixes.push_back(suffix); } else if (digit_suffix_match[0].matched) { collision = true; } else { assert(false && "Invariant violation: should never reach here"); collision = false; break; } } } if (!collision) return initial_name; else { unsigned long lowest = 2; std::sort(suffixes.begin(), suffixes.end()); BOOST_FOREACH(unsigned long suffix, suffixes) { if (suffix == 1) // skip "New Folder (1)" as Windows doesn't use it continue; // so we won't either if (suffix == lowest) ++lowest; if (suffix > lowest) break; } return str(wformat(L"%1% (%2%)") % initial_name % lowest); } } } namespace swish { namespace remote_folder { namespace commands { namespace { const uuid_t NEW_FOLDER_COMMAND_ID(L"b816a882-5022-11dc-9153-0090f5284f85"); } NewFolder::NewFolder( const apidl_t& folder_pidl, const provider_factory& provider, const consumer_factory& consumer) : Command( translate(L"New &folder"), NEW_FOLDER_COMMAND_ID, translate(L"Create a new, empty folder in the folder you have open."), L"shell32.dll,-258", L"", translate(L"Make a new folder")), m_folder_pidl(folder_pidl), m_provider_factory(provider), m_consumer_factory(consumer) {} BOOST_SCOPED_ENUM(Command::state) NewFolder::state( com_ptr, bool /*ok_to_be_slow*/) const { return state::enabled; } void NewFolder::operator()( com_ptr, const command_site& site, com_ptr) const { try { shared_ptr provider = m_provider_factory( m_consumer_factory(), translate("Name of a running task", "Creating new folder")); CSftpDirectory directory(m_folder_pidl, provider); // The default New Folder name may already exist in the folder. If it // does, we append a number to it to make it unique wstring initial_name = translate(L"Initial name", L"New folder"); initial_name = prefix_if_necessary( initial_name, provider, absolute_path_from_swish_pidl(m_folder_pidl)); cpidl_t pidl = directory.CreateDirectory(initial_name); try { // A failure after this point is not worth reporting. The folder // was created even if we didn't allow the user a chance to pick a // name. com_ptr view = shell_view(shell_browser(site.ole_site())); if (view) { put_view_item_into_rename_mode(view, pidl); } } catch (const exception& e) { trace("WARNING: Couldn't put folder into rename mode: %s") % e.what(); } } catch (...) { try { optional> view_window = site.ui_owner(); if (view_window) { announce_last_exception( view_window->hwnd(), translate(L"Could not create a new folder"), translate(L"You might not have permission.")); } } catch (...) {} throw; } } }}} // namespace swish::remote_folder::commands ================================================ FILE: swish/remote_folder/commands/NewFolder.hpp ================================================ /* Copyright (C) 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_REMOTE_FOLDER_COMMANDS_NEW_FOLDER_HPP #define SWISH_REMOTE_FOLDER_COMMANDS_NEW_FOLDER_HPP #pragma once #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include "swish/nse/Command.hpp" // Command #include // apidl_t #include #include namespace swish { namespace remote_folder { namespace commands { typedef boost::function< boost::shared_ptr( comet::com_ptr, const std::string& task_name)> provider_factory; typedef boost::function()> consumer_factory; class NewFolder : public swish::nse::Command { public: NewFolder( const washer::shell::pidl::apidl_t& folder_pidl, const provider_factory& provider, const consumer_factory& consumer); virtual BOOST_SCOPED_ENUM(state) state( comet::com_ptr selection, bool ok_to_be_slow) const; void operator()( comet::com_ptr selection, const swish::nse::command_site& site, comet::com_ptr bind_ctx) const; private: washer::shell::pidl::apidl_t m_folder_pidl; provider_factory m_provider_factory; consumer_factory m_consumer_factory; }; }}} // namespace swish::remote_folder::commands #endif ================================================ FILE: swish/remote_folder/commands/commands.cpp ================================================ /* Copyright (C) 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "commands.hpp" #include "swish/nse/explorer_command.hpp" // CExplorerCommand* #include "swish/nse/task_pane.hpp" // CUIElementErrorAdapter, CUICommand #include "swish/remote_folder/commands/NewFolder.hpp" #include // simple_object #include // make_smart_enumeration #include // bind, _1 #include // translate #include // make_shared #include // BOOST_THROW_EXCEPTION #include // assert #include #include #include // make_pair #include using swish::nse::CExplorerCommandProvider; using swish::nse::CExplorerCommand; using swish::nse::CUIElementErrorAdapter; using swish::nse::CUICommand; using swish::nse::IEnumUICommand; using swish::nse::IUICommand; using swish::nse::IUIElement; using swish::nse::WebtaskCommandTitleAdapter; using swish::provider::sftp_provider; using washer::shell::pidl::apidl_t; using comet::com_ptr; using comet::make_smart_enumeration; using comet::simple_object; using boost::bind; using boost::function; using boost::locale::translate; using boost::make_shared; using boost::shared_ptr; using std::make_pair; using std::vector; using std::wstring; namespace swish { namespace remote_folder { namespace commands { com_ptr remote_folder_command_provider( const apidl_t& folder_pidl, const provider_factory& provider, const consumer_factory& consumer) { CExplorerCommandProvider::ordered_commands commands; commands.push_back( new CExplorerCommand( folder_pidl, provider, consumer)); return new CExplorerCommandProvider(commands); } class CSftpTasksTitle : public simple_object { public: virtual std::wstring title( const comet::com_ptr& /*items*/) const { return translate(L"File and Folder Tasks"); } virtual std::wstring icon( const comet::com_ptr& /*items*/) const { return L"shell32.dll,-319"; } virtual std::wstring tool_tip( const comet::com_ptr& /*items*/) const { return translate(L"These tasks help you manage your remote files."); } }; std::pair, com_ptr > remote_folder_task_pane_titles(const apidl_t& /*folder_pidl*/) { return make_pair(new CSftpTasksTitle(), com_ptr()); } std::pair, com_ptr > remote_folder_task_pane_tasks( const apidl_t& folder_pidl, com_ptr ole_site, const provider_factory& provider, const consumer_factory& consumer) { typedef shared_ptr< vector< com_ptr > > shared_command_vector; shared_command_vector commands = make_shared< vector< com_ptr > >(); com_ptr new_folder = new CUICommand>( folder_pidl, bind( provider, _1, translate("Name of a running task", "Creating new folder")), consumer); com_ptr object_with_site = com_cast(new_folder); // Explorer doesn't seem to call SetSite on the command object which is odd // because any command that needs to change the view would need it. We do // it instead. // XXX: We never unset the site. Explorer normally does if it sets it. // I don't know if this is a problem. if (object_with_site) object_with_site->SetSite(ole_site.get()); commands->push_back(new_folder); com_ptr e = make_smart_enumeration(commands); return make_pair(e, com_ptr()); } }}} // namespace swish::remote_folder::commands ================================================ FILE: swish/remote_folder/commands/commands.hpp ================================================ /* Copyright (C) 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_REMOTE_FOLDER_COMMANDS_HPP #define SWISH_REMOTE_FOLDER_COMMANDS_HPP #pragma once #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include "swish/nse/UICommand.hpp" // IUIElement #include // apidl_t #include // com_ptr #include #include #include // IExplorerCommandProvider namespace swish { namespace remote_folder { namespace commands { typedef boost::function< boost::shared_ptr( comet::com_ptr, const std::string& task_name)> provider_factory; typedef boost::function()> consumer_factory; comet::com_ptr remote_folder_command_provider( const washer::shell::pidl::apidl_t& folder_pidl, const provider_factory& provider, const consumer_factory& consumer); std::pair, comet::com_ptr > remote_folder_task_pane_titles( const washer::shell::pidl::apidl_t& folder_pidl); std::pair< comet::com_ptr, comet::com_ptr > remote_folder_task_pane_tasks( const washer::shell::pidl::apidl_t& folder_pidl, comet::com_ptr ole_site, const provider_factory& provider, const consumer_factory& consumer); }}} // namespace swish::remote_folder::commands #endif ================================================ FILE: swish/remote_folder/commands/delete.cpp ================================================ /** @file Remote item deletion. @if license Copyright (C) 2011, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/remote_folder/commands/delete.hpp" #include "swish/frontend/announce_error.hpp" // announce_last_exception #include "swish/shell_folder/SftpDirectory.h" // CSftpDirectory #include "swish/shell/shell_item_array.hpp" #include "swish/shell/parent_and_item.hpp" #include // apidl_t, cpidl_t, pidl_cast #include // translate #include // wformat #include // BOOST_THROW_EXCEPTION #include // assert #include #include #include // IsolationAwareMessageBox using swish::frontend::announce_last_exception; using swish::provider::sftp_provider; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::pidl_cast; using comet::com_ptr; using boost::function; using boost::locale::translate; using boost::shared_ptr; using boost::wformat; using std::vector; using std::wstring; namespace swish { namespace remote_folder { namespace commands { namespace { /** * Deletes files or folders. * * The list of items to delete is supplied as a list of PIDLs and may * contain a mix of files and folder. */ template void do_delete( HWND hwnd_view, const vector>& death_row, ProviderFactory provider_factory, ConsumerFactory consumer_factory) { com_ptr consumer = consumer_factory(hwnd_view); shared_ptr provider = provider_factory( consumer, translate("Name of a running task", "Deleting files")); // Delete each item and notify shell vector>::const_iterator it = death_row.begin(); while (it != death_row.end()) { CSftpDirectory directory((*it)->parent_pidl(), provider); directory.Delete((*it)->item_pidl()); ++it; } } /** * Displays dialog seeking confirmation from user to delete a single item. * * The dialog differs depending on whether the item is a file or a folder. * * @param hwnd_view Handle to the window used for UI. * @param filename Name of the file or folder being deleted. * @param is_folder Is the item in question a file or a folder? * * @returns Whether confirmation was given or denied. */ bool confirm_deletion( HWND hwnd_view, const wstring& filename, bool is_folder) { if (hwnd_view == NULL) return false; wstring message; if (!is_folder) { message = L"Are you sure you want to permanently delete '"; message += filename; message += L"'?"; } else { message = L"Are you sure you want to permanently delete the " L"folder '"; message += filename; message += L"' and all of its contents?"; } int ret = ::IsolationAwareMessageBoxW( hwnd_view, message.c_str(), (is_folder) ? L"Confirm Folder Delete" : L"Confirm File Delete", MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON1); return (ret == IDYES); } /** * Displays dialog seeking confirmation from user to delete multiple items. * * @param hwnd_view Handle to the window used for UI. * @param item_count Number of items selected for deletion. * * @returns Whether confirmation was given or denied. */ bool confirm_multiple_deletion(HWND hwnd_view, size_t item_count) { if (hwnd_view == NULL) return false; wstring message = str(wformat( L"Are you sure you want to permanently delete these %d items?") % item_count); int ret = ::IsolationAwareMessageBoxW( hwnd_view, message.c_str(), L"Confirm Multiple Item Delete", MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON1); return (ret == IDYES); } /** * Deletes files or directories after seeking confirmation from user. * * The list of items to delete is supplied as a list of PIDLs and may * contain a mix of files and folders. * * If just one item is chosen, a specific confirmation message for that * item is shown. If multiple items are to be deleted, a general * confirmation message is displayed asking if the number of items are * to be deleted. */ template void execute_death_row( HWND hwnd_view, const vector>& death_row, ProviderFactory provider_factory, ConsumerFactory consumer_factory) { size_t item_count = death_row.size(); BOOL go_ahead = false; if (item_count == 1) { remote_itemid_view itemid(death_row[0]->item_pidl()); go_ahead = confirm_deletion( hwnd_view, itemid.filename(), itemid.is_folder()); } else if (item_count > 1) { go_ahead = confirm_multiple_deletion(hwnd_view, item_count); } else { assert(false); return; // do nothing because no items were given } if (go_ahead) { do_delete(hwnd_view, death_row, provider_factory, consumer_factory); } } } Delete::Delete( my_provider_factory provider_factory, my_consumer_factory consumer_factory) : m_provider_factory(provider_factory), m_consumer_factory(consumer_factory) {} void Delete::operator()(HWND hwnd_view, com_ptr selection) const { try { vector> death_row; comet::wrap_t::iterator_type it = selection->begin(); comet::wrap_t::iterator_type end = selection->end(); while(it++ != end) { com_ptr item = *it; com_ptr parent_and_item = try_cast(item); death_row.push_back(parent_and_item); } execute_death_row( hwnd_view, death_row, m_provider_factory, m_consumer_factory); } catch (...) { announce_last_exception( hwnd_view, translate(L"Unable to delete the item"), translate(L"You might not have permission.")); throw; } } }}} // namespace swish::remote_folder::commands ================================================ FILE: swish/remote_folder/commands/delete.hpp ================================================ /** @file Swish remote folder commands. @if license Copyright (C) 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_REMOTE_FOLDER_COMMANDS_DELETE_HPP #define SWISH_REMOTE_FOLDER_COMMANDS_DELETE_HPP #pragma once #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include // com_ptr #include #include #include // IShellItemArray namespace swish { namespace remote_folder { namespace commands { class Delete { typedef boost::function< boost::shared_ptr( comet::com_ptr, const std::string& task_name) > my_provider_factory; typedef boost::function(HWND)> my_consumer_factory; public: Delete( my_provider_factory provider_factory, my_consumer_factory consumer_factory); void operator()( HWND hwnd_view, comet::com_ptr selection) const; private: my_provider_factory m_provider_factory; my_consumer_factory m_consumer_factory; }; }}} // namespace swish::remote_folder::commands #endif ================================================ FILE: swish/remote_folder/context_menu_callback.cpp ================================================ /** @file RemoteFolder context menu implementation (basically that what it does). @if license Copyright (C) 2011, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/frontend/announce_error.hpp" // announce_last_exception #include "swish/shell_folder/SftpDirectory.h" // CSftpDirectory #include "swish/shell/shell.hpp" // ui_object_of_item #include "swish/remote_folder/commands/delete.hpp" // Delete #include "swish/remote_folder/pidl_connection.hpp" // provider_from_pidl #include "swish/remote_folder/context_menu_callback.hpp" // context_menu_callback #include "swish/shell/parent_and_item.hpp" // parent_and_item #include "swish/shell/shell_item_array.hpp" // shell_item_array_from_data_object #include // last_error #include // apidl_t, cpidl_t #include // pidl_from_parsing_name #include // uuid_t #include // translate #include // errinfo_file_name #include // path #include // numeric_cast #include // BOOST_THROW_EXCEPTION #include // runtime_error #include #include // ShellExecuteEx #include // InsertMenu, SetMenuDefaultItem using swish::frontend::announce_last_exception; using swish::provider::sftp_provider; using swish::shell::shell_item_array_from_data_object; using swish::shell::ui_object_of_item; using washer::last_error; using washer::shell::pidl_from_parsing_name; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::pidl_cast; using comet::com_error; using comet::com_ptr; using comet::uuid_t; using boost::enable_error_info; using boost::errinfo_api_function; using boost::filesystem::path; using boost::function; using boost::locale::translate; using boost::numeric_cast; using boost::shared_ptr; using std::runtime_error; using std::string; using std::vector; using std::wstring; namespace comet { template<> struct comtype { static const IID& uuid() throw() { return IID_IDropTarget; } typedef IUnknown base; }; } // namespace comet namespace swish { namespace remote_folder { namespace { bool is_single_link(com_ptr selection) { if (selection->size() != 1) { return false; } else { com_ptr parent_and_item = com_cast(selection->at(0)); if (!parent_and_item) { return false; } return remote_itemid_view(parent_and_item->item_pidl()).is_link(); } } bool are_normal_files(com_ptr selection) { if (selection->size() < 1U) return false; // TODO: fix comet enum_iterator so == is defined. == is OK with // read-once iteration (see istream_iterator for example). Then we can // use ranges comet::wrap_t::iterator_type it = selection->begin(); comet::wrap_t::iterator_type end = selection->end(); while(it++ != end) { com_ptr item = *it; com_ptr parent_and_item = com_cast(item); if (!parent_and_item) { return false; } cpidl_t item_pidl = parent_and_item->item_pidl(); if (remote_itemid_view(item_pidl).is_link() || remote_itemid_view(item_pidl).is_folder()) { return false; } } // FIXME: failure to be a folder or a link does not mean you're a // normal file. return true; } const UINT MENU_OFFSET_OPEN = 0; } context_menu_callback::context_menu_callback( my_provider_factory provider_factory, my_consumer_factory consumer_factory) : m_provider_factory(provider_factory), m_consumer_factory(consumer_factory) {} /** * @todo Take account of allowed changes flags. */ bool context_menu_callback::merge_context_menu( HWND hwnd_view, com_ptr selection_data_object, HMENU hmenu, UINT first_item_index, UINT& minimum_id, UINT maximum_id, UINT allowed_changes_flags) { com_ptr selection = shell_item_array_from_data_object(selection_data_object); if (is_single_link(selection)) { BOOL success = ::InsertMenuW( hmenu, first_item_index, MF_BYPOSITION, minimum_id + MENU_OFFSET_OPEN, wstring(translate(L"Open &link")).c_str()); if (!success) BOOST_THROW_EXCEPTION( enable_error_info(last_error()) << errinfo_api_function("InsertMenuW")); // It's not worth aborting menu creation just because we can't // set default so ignore return val ::SetMenuDefaultItem(hmenu, minimum_id + MENU_OFFSET_OPEN, FALSE); ++minimum_id; // Return false so that Explorer won't add its own 'open' // and 'explore' menu items. // TODO: Find out what else we lose. return false; } else if (are_normal_files(selection)) { BOOL success = ::InsertMenuW( hmenu, first_item_index, MF_BYPOSITION, minimum_id + MENU_OFFSET_OPEN, wstring(translate(L"&Open")).c_str()); if (!success) BOOST_THROW_EXCEPTION( enable_error_info(last_error()) << errinfo_api_function("InsertMenuW")); // It's not worth aborting menu creation just because we can't // set default so ignore return val ::SetMenuDefaultItem(hmenu, minimum_id + MENU_OFFSET_OPEN, FALSE); ++minimum_id; // Return false so that Explorer won't add its own 'open' // and 'explore' menu items. // TODO: Find out what else we lose. return false; } else { return true; // Let Explorer provide standard verbs } } void context_menu_callback::verb( HWND hwnd_view, com_ptr /*selection*/, UINT command_id_offset, wstring& verb_out) { if (command_id_offset != MENU_OFFSET_OPEN) BOOST_THROW_EXCEPTION( com_error("Unrecognised menu command ID", E_FAIL)); verb_out = L"open"; } void context_menu_callback::verb( HWND hwnd_view, com_ptr /*selection*/, UINT command_id_offset, string& verb_out) { if (command_id_offset != MENU_OFFSET_OPEN) BOOST_THROW_EXCEPTION( com_error("Unrecognised menu command ID", E_FAIL)); verb_out = "open"; } namespace { template bool do_invoke_command( ProviderFactory provider_factory, ConsumerFactory consumer_factory, HWND hwnd_view, com_ptr selection_data_object, UINT item_offset, const wstring& /*arguments*/, int window_mode) { com_ptr selection = shell_item_array_from_data_object(selection_data_object); if (item_offset == DFM_CMD_DELETE) { commands::Delete deletion_command( provider_factory, consumer_factory); deletion_command(hwnd_view, selection); return true; } else if (item_offset == MENU_OFFSET_OPEN && is_single_link(selection)) { try { com_ptr item = selection->at(0); com_ptr folder_and_pidl = try_cast(item); apidl_t item_pidl = folder_and_pidl->absolute_item_pidl(); // Create SFTP Consumer for this HWNDs lifetime com_ptr consumer = consumer_factory(hwnd_view); shared_ptr provider = provider_from_pidl( folder_and_pidl->parent_pidl(), consumer, translate("Name of a running task", "Resolving link")); CSftpDirectory directory(folder_and_pidl->parent_pidl(), provider); apidl_t target = directory.ResolveLink(folder_and_pidl->item_pidl()); SHELLEXECUTEINFO sei = SHELLEXECUTEINFO(); sei.cbSize = sizeof(SHELLEXECUTEINFO); sei.fMask = SEE_MASK_IDLIST; sei.hwnd = hwnd_view; sei.nShow = window_mode; sei.lpIDList = const_cast( reinterpret_cast(target.get())); sei.lpVerb = L"open"; if (!::ShellExecuteEx(&sei)) BOOST_THROW_EXCEPTION( enable_error_info(last_error()) << errinfo_api_function("ShellExecuteEx")); } catch (...) { announce_last_exception( hwnd_view, translate(L"Unable to open the link"), translate(L"You might not have permission.")); throw; } return true; // Even if the above fails, we don't want to invoke // any default action provided by Explorer } // TODO: handle links so that links to files are resolved and the // targets are opened // // FIXME: what if the selection contains a mix of items? else if (item_offset == MENU_OFFSET_OPEN && are_normal_files(selection)) { try { com_ptr item = selection->at(0); com_ptr folder_and_pidl = try_cast(item); apidl_t item_pidl = folder_and_pidl->absolute_item_pidl(); // XXX: We're only opening the first file even though we copy all // of them. Is this what we want? vector system_temp_dir(MAX_PATH + 1, L'\0'); DWORD rc = ::GetTempPathW( numeric_cast(system_temp_dir.size()), &system_temp_dir[0]); if (rc < 1) BOOST_THROW_EXCEPTION(last_error()); // We're using drag-and-drop to do the copy, so we don't // want collisions to be possible as they will throw up // confirmation dialogues. We create a unique directory to // copy the file into by generating a GUID. If this already // exists, the universe may be close to collapse in which case // we should probably find our loved ones and stop worrying // about file transfers. path temp_dir = &system_temp_dir[0]; temp_dir /= uuid_t::create().w_str(); if (!create_directory(temp_dir)) BOOST_THROW_EXCEPTION( runtime_error( "Temporary download location already exists")); com_ptr drop_target = ui_object_of_item( pidl_from_parsing_name(temp_dir.wstring()).get()); POINTL pt = { 0, 0 }; DWORD dwEffect = DROPEFFECT_COPY; HRESULT hr = drop_target->DragEnter( selection_data_object.get(), MK_LBUTTON, pt, &dwEffect); if (FAILED(hr)) BOOST_THROW_EXCEPTION( com_error_from_interface(drop_target, hr)); dwEffect &= DROPEFFECT_COPY; if (dwEffect) { hr = drop_target->Drop( selection_data_object.get(), MK_LBUTTON, pt, &dwEffect); if (FAILED(hr)) BOOST_THROW_EXCEPTION( com_error_from_interface(drop_target, hr)); } else { hr = drop_target->DragLeave(); if (FAILED(hr)) BOOST_THROW_EXCEPTION( com_error_from_interface(drop_target, hr)); BOOST_THROW_EXCEPTION( runtime_error( "Permission refused to copy remote file to " "temporary location")); } path target = temp_dir; target /= remote_itemid_view(folder_and_pidl->item_pidl()).filename(); wstring target_windows_path = target.wstring(); // Before opening the file we make it read-only to discourage // users from making changes and saving it to the temporary // location - they're likely to forget and then lose their data. // This should force most apps to invoke Save As. ::SetFileAttributes( target_windows_path.c_str(), ::GetFileAttributes(target_windows_path.c_str()) | FILE_ATTRIBUTE_READONLY); // It isn't worth aborting the operation if this fails so we // ignore any error SHELLEXECUTEINFO sei = SHELLEXECUTEINFO(); sei.cbSize = sizeof(SHELLEXECUTEINFO); //sei.fMask = SEE_ sei.hwnd = hwnd_view; sei.nShow = window_mode; sei.lpFile = target_windows_path.c_str(); sei.lpVerb = L"open"; if (!::ShellExecuteEx(&sei)) BOOST_THROW_EXCEPTION( enable_error_info(last_error()) << errinfo_api_function("ShellExecuteEx")); } catch (...) { announce_last_exception( hwnd_view, translate(L"Unable to open the file"), translate(L"You might not have permission.")); throw; } return true; // Even if the above fails, we don't want to invoke // any default action provided by Explorer } else { return false; } } } bool context_menu_callback::invoke_command( HWND hwnd_view, com_ptr selection, UINT item_offset, const wstring& arguments) { return do_invoke_command( m_provider_factory, m_consumer_factory, hwnd_view, selection, item_offset, arguments, SW_NORMAL); } /** * @todo Take account of the behaviour flags. */ bool context_menu_callback::invoke_command( HWND hwnd_view, com_ptr selection, UINT item_offset, const wstring& arguments, DWORD /*behaviour_flags*/, UINT /*minimum_id*/, UINT /*maximum_id*/, const CMINVOKECOMMANDINFO& invocation_details, com_ptr /*context_menu_site*/) { return do_invoke_command( m_provider_factory, m_consumer_factory, hwnd_view, selection, item_offset, arguments, invocation_details.nShow); } bool context_menu_callback::default_menu_item( HWND /*hwnd_view*/, com_ptr selection_data_object, UINT& default_command_id) { com_ptr selection = shell_item_array_from_data_object(selection_data_object); if (is_single_link(selection)) { default_command_id = MENU_OFFSET_OPEN; return true; } else { return false; } } }} // namespace swish::remote_folder ================================================ FILE: swish/remote_folder/context_menu_callback.hpp ================================================ /** @file RemoteFolder context menu implementation (basically that what it does). @if license Copyright (C) 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_REMOTE_FOLDER_CONTEXT_MENU_CALLBACK_HPP #define SWISH_REMOTE_FOLDER_CONTEXT_MENU_CALLBACK_HPP #pragma once #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include "swish/nse/default_context_menu_callback.hpp" // default_context_menu_callback #include // com_ptr #include #include #include #include // IDataObject #include // CMINVOKECOMMANDINFO #include // HWND, HMENU namespace swish { namespace remote_folder { class context_menu_callback : public swish::nse::default_context_menu_callback { typedef boost::function< boost::shared_ptr( comet::com_ptr, const std::string&) > my_provider_factory; typedef boost::function(HWND)> my_consumer_factory; public: context_menu_callback( my_provider_factory provider_factory, my_consumer_factory consumer_factory); private: bool merge_context_menu( HWND hwnd_view, comet::com_ptr selection, HMENU hmenu, UINT first_item_index, UINT& minimum_id, UINT maximum_id, UINT allowed_changes_flags); void verb( HWND hwnd_view, comet::com_ptr selection, UINT command_id_offset, std::wstring& verb_out); void verb( HWND hwnd_view, comet::com_ptr selection, UINT command_id_offset, std::string& verb_out); bool invoke_command( HWND hwnd_view, comet::com_ptr selection, UINT item_offset, const std::wstring& arguments); bool invoke_command( HWND hwnd_view, comet::com_ptr selection, UINT item_offset, const std::wstring& arguments, DWORD behaviour_flags, UINT minimum_id, UINT maximum_id, const CMINVOKECOMMANDINFO& invocation_details, comet::com_ptr context_menu_site); bool default_menu_item( HWND hwnd_view, comet::com_ptr selection, UINT& default_command_id); my_provider_factory m_provider_factory; my_consumer_factory m_consumer_factory; }; }} // namespace swish::remote_folder #endif ================================================ FILE: swish/remote_folder/filemode.c ================================================ /* filemode.c -- make a string describing file modes Copyright (C) 1985, 1990, 1993, 1998-2000, 2004 Free Software Foundation, Inc. Copyright (C) 2006, 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Removed by Alexander Lamaison for swish project #if HAVE_CONFIG_H # include #endif #include #include #include "filemode.h" #include "stat-macros.h" Removed 2006.08.20 */ #include "filemode.h" /* Set the 's' and 't' flags in file attributes string CHARS, according to the file mode BITS. */ static void setst (mode_t bits, char *chars) { if (bits & S_ISUID) { if (chars[3] != 'x') /* Set-uid, but not executable by owner. */ chars[3] = 'S'; else chars[3] = 's'; } if (bits & S_ISGID) { if (chars[6] != 'x') /* Set-gid, but not executable by group. */ chars[6] = 'S'; else chars[6] = 's'; } if (bits & S_ISVTX) { if (chars[9] != 'x') /* Sticky, but not executable by others. */ chars[9] = 'T'; else chars[9] = 't'; } } /* Return a character indicating the type of file described by file mode BITS: 'd' for directories 'D' for doors 'b' for block special files 'c' for character special files 'n' for network special files 'm' for multiplexor files 'M' for an off-line (regular) file 'l' for symbolic links 's' for sockets 'p' for fifos 'C' for contigous data files 'w' for BSD whiteout - Added by Alexander Lamaison for Swish project 'B' for multiplexor (block) - Added by Alexander Lamaison for Swish project 'x' for Xenix named files - Added by Alexander Lamaison for Swish project '-' for regular files '?' for any other file type. */ static char ftypelet (mode_t bits) { if (S_ISBLK (bits)) return 'b'; if (S_ISCHR (bits)) return 'c'; if (S_ISDIR (bits)) return 'd'; if (S_ISREG (bits)) return '-'; if (S_ISFIFO (bits)) return 'p'; if (S_ISLNK (bits)) return 'l'; if (S_ISSOCK (bits)) return 's'; if (S_ISMPC (bits)) return 'm'; if (S_ISNWK (bits)) return 'n'; if (S_ISDOOR (bits)) return 'D'; if (S_ISCTG (bits)) return 'C'; /* Added by Alexander Lamaison for Swish project */ if (S_ISWHT (bits)) return 'w'; if (S_ISMPB (bits)) return 'B'; if (S_ISNAM (bits)) return 'x'; /* Added 2006.08.20 */ /* The following two tests are for Cray DMF (Data Migration Facility), which is a HSM file system. A migrated file has a `st_dm_mode' that is different from the normal `st_mode', so any tests for migrated files should use the former. */ if (S_ISOFD (bits)) /* off line, with data */ return 'M'; /* off line, with no data */ if (S_ISOFL (bits)) return 'M'; return '?'; } /* Like filemodestring, but only the relevant part of the `struct stat' is given as an argument. */ void mode_string (mode_t mode, char *str) { str[0] = ftypelet (mode); str[1] = mode & S_IRUSR ? 'r' : '-'; str[2] = mode & S_IWUSR ? 'w' : '-'; str[3] = mode & S_IXUSR ? 'x' : '-'; str[4] = mode & S_IRGRP ? 'r' : '-'; str[5] = mode & S_IWGRP ? 'w' : '-'; str[6] = mode & S_IXGRP ? 'x' : '-'; str[7] = mode & S_IROTH ? 'r' : '-'; str[8] = mode & S_IWOTH ? 'w' : '-'; str[9] = mode & S_IXOTH ? 'x' : '-'; setst (mode, str); } /* filemodestring - fill in string STR with an ls-style ASCII representation of the st_mode field of file stats block STATP. 10 characters are stored in STR; no terminating null is added. The characters stored in STR are: 0 File type. 'd' for directory, 'c' for character special, 'b' for block special, 'm' for multiplex, 'l' for symbolic link, 's' for socket, 'p' for fifo, '-' for regular, '?' for any other file type 1 'r' if the owner may read, '-' otherwise. 2 'w' if the owner may write, '-' otherwise. 3 'x' if the owner may execute, 's' if the file is set-user-id, '-' otherwise. 'S' if the file is set-user-id, but the execute bit isn't set. 4 'r' if group members may read, '-' otherwise. 5 'w' if group members may write, '-' otherwise. 6 'x' if group members may execute, 's' if the file is set-group-id, '-' otherwise. 'S' if it is set-group-id but not executable. 7 'r' if any user may read, '-' otherwise. 8 'w' if any user may write, '-' otherwise. 9 'x' if any user may execute, 't' if the file is "sticky" (will be retained in swap space after execution), '-' otherwise. 'T' if the file is sticky but not executable. */ /* Removed by Alexander Lamaison for swish project void filemodestring (struct stat *statp, char *str) { mode_string (statp->st_mode, str); } Removed 2006.08.20 */ ================================================ FILE: swish/remote_folder/filemode.h ================================================ /* Make a string describing file modes. Copyright (C) 1998, 1999, 2003 Free Software Foundation, Inc. Copyright (C) 2006, 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.*/ #ifndef FILEMODE_H_ #define S_IFMT 0170000 /* type of file */ #define S_IFSOCK 0140000 /* socket 's' */ #define S_IFLNK 0120000 /* symbolic link 'l' */ #define S_IFREG 0100000 /* regular '-' */ #define S_IFBLK 0060000 /* block special 'b' */ #define S_IFDIR 0040000 /* directory 'd' */ #define S_IFCHR 0020000 /* character special 'c' */ #define S_IFIFO 0010000 /* fifo 'p' */ #define S_IFDOOR 0150000 /* Solaris door 'D' */ #define S_IFNAM 0050000 /* XENIX named file 'x' */ #define S_IFMPB 0070000 /* multiplexed block special 'B' */ #define S_IFMPC 0030000 /* multiplexed char special 'm' */ #define S_IFWHT 0160000 /* BSD whiteout 'w' */ #define S_IFNWK 110000 /* HP-UX network special 'n' */ /* TODO: Add support for other obscure types * S_IFCNT Contiguous file 'C' * S_IFSHAD 130000 Solaris shadow inode for ACL (not seen by userspace) * S_IFEVC UNOS eventcount * S_ISOFD Cray DMF: off line with data 'M' * S_ISOFL Cray DMF: off line with no data 'M' */ #define S_ISUID 0004000 /* set user id on execution */ #define S_ISGID 0002000 /* set group id on execution */ #define S_ISVTX 0001000 /* save swapped text even after use */ #define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) #define S_IRUSR 0000400 /* read permission, owner */ #define S_IWUSR 0000200 /* write permission, owner */ #define S_IXUSR 0000100 /* execute/search permission, owner */ #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) #define S_IRGRP 0000040 /* read permission, group */ #define S_IWGRP 0000020 /* write permission, grougroup */ #define S_IXGRP 0000010 /* execute/search permission, group */ #define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) #define S_IROTH 0000004 /* read permission, other */ #define S_IWOTH 0000002 /* write permission, other */ #define S_IXOTH 0000001 /* execute/search permission, other */ #define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) #define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) #define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) #define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) #define S_ISDOOR(m) (((m) & S_IFMT) == S_IFDOOR) /* Solaris 2.5 and up */ #define S_ISNAM(m) (((m) & S_IFMT) == S_IFNAM) /* Xenix */ #define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB) /* V7 */ #define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC) /* V7 */ #define S_ISWHT(m) (((m) & S_IFMT) == S_IFWHT) /* BSD whiteout */ #define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK) /* HP/UX */ /* contiguous */ # ifndef S_ISCTG # define S_ISCTG(m) ((void)(m), 0) # endif /* Cray DMF (data migration facility): offline, with data */ # ifndef S_ISOFD # define S_ISOFD(m) ((void)(m), 0) # endif /* Cray DMF (data migration facility): offline, with no data */ # ifndef S_ISOFL # define S_ISOFL(m) ((void)(m), 0) # endif /* typedef u32 mode_t; Changed by Alex Lamaison 2009.02.01 */ typedef unsigned long mode_t; #ifdef __cplusplus extern "C" void mode_string (mode_t mode, char *str); #else extern void mode_string (mode_t mode, char *str); #endif #endif ================================================ FILE: swish/remote_folder/pidl_connection.cpp ================================================ /** @file Relates PIDLs to SFTP connections. @if license Copyright (C) 2007, 2008, 2009, 2010, 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "pidl_connection.hpp" #include "swish/connection/session_manager.hpp" #include "swish/host_folder/host_pidl.hpp" // find_host_itemid, host_itemid_view #include "swish/provider/Provider.hpp" // CProvider #include #include using swish::connection::connection_spec; using swish::connection::session_manager; using swish::host_folder::find_host_itemid; using swish::host_folder::host_itemid_view; using swish::provider::CProvider; using swish::provider::sftp_provider; using washer::shell::pidl::apidl_t; using comet::com_ptr; using boost::shared_ptr; using std::string; using std::wstring; namespace swish { namespace remote_folder { namespace { void params_from_pidl( const apidl_t& pidl, wstring& user, wstring& host, int& port) { // Find HOSTPIDL part of this folder's absolute pidl to extract server // info host_itemid_view host_itemid(*find_host_itemid(pidl)); assert(host_itemid.valid()); user = host_itemid.user(); host = host_itemid.host(); port = host_itemid.port(); assert(!user.empty()); assert(!host.empty()); } } connection_spec connection_from_pidl(const apidl_t& pidl) { // Extract connection info from PIDL wstring user, host, path; int port; params_from_pidl(pidl, user, host, port); return connection_spec(host, user, port); } shared_ptr provider_from_pidl( const apidl_t& pidl, com_ptr consumer, const string& task_name) { connection_spec specification = connection_from_pidl(pidl); return shared_ptr( new CProvider( session_manager().reserve_session( specification, consumer, task_name))); } }} // namespace swish::remote_folder ================================================ FILE: swish/remote_folder/pidl_connection.hpp ================================================ /** @file Relates PIDLs to SFTP connections. @if license Copyright (C) 2007, 2008, 2009, 2010, 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_REMOTE_FOLDER_PIDL_CONNECTION_HPP #define SWISH_REMOTE_FOLDER_PIDL_CONNECTION_HPP #pragma once #include "swish/connection/connection_spec.hpp" #include "swish/provider/sftp_provider.hpp" #include // apidl_t #include #include #include namespace swish { namespace remote_folder { /** * Converts a host PIDL into a connection specification. */ swish::connection::connection_spec connection_from_pidl( const washer::shell::pidl::apidl_t& pidl); /** * Creates lazy-connecting provider primed to connect for given PIDL. * * The session will be created from the information stored in this * folder's PIDL, @a pidl, if connection is required. */ boost::shared_ptr provider_from_pidl( const washer::shell::pidl::apidl_t& pidl, comet::com_ptr consumer, const std::string& task_name); }} // namespace swish::remote_folder #endif ================================================ FILE: swish/remote_folder/properties.cpp ================================================ /** @file Host folder property columns. @if license Copyright (C) 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "properties.hpp" #include "Mode.h" // Unix-style permissions #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view #include // map_list_of #include #include // errinfo_api_function #include // function #include // translate #include // BOOST_THROW_EXCEPTION #include // com_error #include // autocoinit #include // assert #include #include // Make DEFINE_PROPERTYKEY() actually define a key #include // PKEY_ * #include // SHGetFileInfo using washer::shell::pidl::cpidl_t; using washer::shell::property_key; using comet::auto_coinit; using comet::variant_t; using comet::com_error; using boost::assign::map_list_of; using boost::enable_error_info; using boost::errinfo_api_function; using boost::locale::translate; using std::map; using std::wstring; namespace swish { namespace remote_folder { DEFINE_PROPERTYKEY( PKEY_Group, \ 0xb816a851, 0x5022, 0x11dc, 0x91, 0x53, 0x00, 0x90, 0xf5, 0x28, \ 0x4f, 0x85, PID_FIRST_USABLE); DEFINE_PROPERTYKEY( PKEY_Permissions, \ 0xb816a851, 0x5022, 0x11dc, 0x91, 0x53, 0x00, 0x90, 0xf5, 0x28, \ 0x4f, 0x85, PID_FIRST_USABLE + 1); DEFINE_PROPERTYKEY( PKEY_OwnerId, \ 0xb816a851, 0x5022, 0x11dc, 0x91, 0x53, 0x00, 0x90, 0xf5, 0x28, \ 0x4f, 0x85, PID_FIRST_USABLE + 2); DEFINE_PROPERTYKEY( PKEY_GroupId, \ 0xb816a851, 0x5022, 0x11dc, 0x91, 0x53, 0x00, 0x90, 0xf5, 0x28, \ 0x4f, 0x85, PID_FIRST_USABLE + 3); namespace { /** * Find the Windows friendly type name for the file given as a PIDL. * * This type name is the one used in Explorer details. For example, * something.txt is given the type name "Text Document" and a directory * is called a "File Folder" regardless of its name. */ std::wstring lookup_friendly_typename(const cpidl_t& pidl) { // Must call CoInitialize before SHGetFileInfo, otherwise it returns // the wrong typename (something like "TXT file" instead of // "Text Document") auto_coinit com_scope; DWORD dwAttributes = (remote_itemid_view(pidl).is_folder()) ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL; UINT uInfoFlags = SHGFI_USEFILEATTRIBUTES | SHGFI_TYPENAME; SHFILEINFOW shfi = SHFILEINFOW(); if (!::SHGetFileInfoW( remote_itemid_view(pidl).filename().c_str(), dwAttributes, &shfi, sizeof(shfi), uInfoFlags)) { BOOST_THROW_EXCEPTION( enable_error_info(com_error(E_FAIL)) << errinfo_api_function("SHGetFileInfoW")); } return shfi.szTypeName; } class unknown_property_error : public std::runtime_error { public: unknown_property_error() : std::runtime_error("Unknown property") {} }; typedef map< property_key, boost::function > remote_property_map; variant_t label_getter(const cpidl_t& pidl) { return remote_itemid_view(pidl).filename(); } variant_t owner_getter(const cpidl_t& pidl) { return remote_itemid_view(pidl).owner(); } variant_t group_getter(const cpidl_t& pidl) { return remote_itemid_view(pidl).group(); } variant_t owner_id_getter(const cpidl_t& pidl) { return remote_itemid_view(pidl).owner_id(); } variant_t group_id_getter(const cpidl_t& pidl) { return remote_itemid_view(pidl).group_id(); } variant_t size_getter(const cpidl_t& pidl) { return remote_itemid_view(pidl).size(); } variant_t modified_date_getter(const cpidl_t& pidl) { return remote_itemid_view(pidl).date_modified(); } variant_t accessed_date_getter(const cpidl_t& pidl) { return remote_itemid_view(pidl).date_accessed(); } variant_t type_getter(const cpidl_t& pidl) { return lookup_friendly_typename(pidl); } variant_t permissions_getter(const cpidl_t& pidl) { DWORD dwPerms = remote_itemid_view(pidl).permissions(); return mode::Mode(dwPerms).toString(); } const remote_property_map remote_property_getters = map_list_of (PKEY_ItemNameDisplay, label_getter) // Display name (Label) (PKEY_FileOwner, owner_getter) // Owner (PKEY_Group, group_getter) // Group (PKEY_OwnerId, owner_id_getter) // Owner ID (UID) (PKEY_GroupId, group_id_getter) // Group ID (GID) (PKEY_Permissions, permissions_getter) // File permissions: drwxr-xr-x (PKEY_Size, size_getter) // File size in bytes (PKEY_DateModified, modified_date_getter) // Last modified date (PKEY_DateAccessed, accessed_date_getter) // Last accessed date (PKEY_ItemTypeText, type_getter); // Friendly type name } /** * Get the requested property for a file based on its PIDL. * * Many of these will be standard system properties but some are custom * to Swish if an appropriate one did not already exist. */ variant_t property_from_pidl(const cpidl_t& pidl, const property_key& key) { remote_property_map::const_iterator pos = remote_property_getters.find(key); if (pos == remote_property_getters.end()) BOOST_THROW_EXCEPTION(unknown_property_error()); return (pos->second)(pidl.get()); } /** * Compare two PIDLs by one of their properties. * * @param left First PIDL in the comparison. * @param right Second PIDL in the comparison. * @param key Property on which to compare the two PIDLs. * * @retval -1 if left < right for chosen property. * @retval 0 if left == right for chosen property. * @retval 1 if left > right for chosen property. */ int compare_pidls_by_property( const cpidl_t& left, const cpidl_t& right, const property_key& key) { if (property_from_pidl(left, key) == property_from_pidl(right, key)) return 0; else if (property_from_pidl(left, key) < property_from_pidl(right, key)) return -1; assert(property_from_pidl(left, key) > property_from_pidl(right, key)); return 1; } }} // namespace swish::remote_folder ================================================ FILE: swish/remote_folder/properties.hpp ================================================ /** @file Properties available for items in a host folder. @if license Copyright (C) 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_REMOTE_FOLDER_PROPERTIES_HPP #define SWISH_REMOTE_FOLDER_PROPERTIES_HPP #pragma once #include // cpidl_t #include // property_key #include // variant_t #include // PROPERTYKEY namespace swish { namespace remote_folder { comet::variant_t property_from_pidl( const washer::shell::pidl::cpidl_t& pidl, const washer::shell::property_key& key); int compare_pidls_by_property( const washer::shell::pidl::cpidl_t& pidl_left, const washer::shell::pidl::cpidl_t& pidl_right, const washer::shell::property_key& key); /** * Custom properties (PKEYs) for Swish remote folder. * * Ideally, we want as few of these as possible. If an appropriate * one already exists in propkey.h, that should be used instead. * * The Swish remote folder FMTID GUID which collects all the custom * properties together is @c {b816a851-5022-11dc-9153-0090f5284f85}. */ // @{ extern "C" const PROPERTYKEY PKEY_Group; extern "C" const PROPERTYKEY PKEY_Permissions; extern "C" const PROPERTYKEY PKEY_OwnerId; extern "C" const PROPERTYKEY PKEY_GroupId; // @} }} // namespace swish::host_folder #endif ================================================ FILE: swish/remote_folder/remote_pidl.hpp ================================================ /** @file PIDL access particular to remote folder PIDLs. @if license Copyright (C) 2011, 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_REMOTE_FOLDER_REMOTE_PIDL_HPP #define SWISH_REMOTE_FOLDER_REMOTE_PIDL_HPP #pragma once #include "swish/remotelimits.h" // Text field limits #include #include // datetime_t #include // pidl_t #include // raw_pidl_iterator #include // BOOST_STATIC_ASSERT #include // BOOST_THROW_EXCEPTION #ifndef STRICT_TYPED_ITEMIDS #error Currently, swish requires strict PIDL types: define STRICT_TYPED_ITEMIDS #endif #include // Raw PIDL types #include // ua_wcslen, ua_wcscpy_s #include // memset #include #include #include namespace swish { namespace remote_folder { namespace detail { #include /** * Internal structure of the PIDLs representing items on the remote filesystem. */ struct remote_item_id { USHORT cb; DWORD dwFingerprint; bool fIsFolder; bool fIsLink; WCHAR wszFilename[MAX_FILENAME_LENZ]; WCHAR wszOwner[MAX_USERNAME_LENZ]; WCHAR wszGroup[MAX_USERNAME_LENZ]; ULONG uUid; ULONG uGid; DWORD dwPermissions; //WORD wPadding; ULONGLONG uSize; DATE dateModified; DATE dateAccessed; static const DWORD FINGERPRINT = 0x533aaf69; }; #include BOOST_STATIC_ASSERT((sizeof(remote_item_id) % sizeof(DWORD)) == 0); inline std::wstring copy_unaligned_string(const wchar_t __unaligned* source) { std::vector buffer(::ua_wcslen(source) + 1); ::ua_wcscpy_s(&buffer[0], buffer.size(), source); return std::wstring(&buffer[0]); } } /** * View internal fields of remote folder PIDLs. * * The viewer doesn't take ownership of the PIDL it's passed so it must remain * valid for the duration of the viewer's use. */ class remote_itemid_view { public: // We have to take the PIDL as a template, rather than that as a pidl_t // as the PIDL passed might be a cpidl_t or an apidl_t. In this case // the pidl would be converted to a pidl_t using a temporary which is // destroyed immediately after the constructor returns, thereby // invalidating the PIDL we've stored a reference to. template explicit remote_itemid_view( const washer::shell::pidl::basic_pidl& pidl) : m_itemid(reinterpret_cast(pidl.get())) {} explicit remote_itemid_view(PCUIDLIST_RELATIVE pidl) : m_itemid(reinterpret_cast(pidl)) {} bool valid() const { if (m_itemid == NULL) return false; return ((m_itemid->cb == sizeof(detail::remote_item_id)) && (m_itemid->dwFingerprint == detail::remote_item_id::FINGERPRINT)); } std::wstring filename() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return detail::copy_unaligned_string(m_itemid->wszFilename); } std::wstring owner() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return detail::copy_unaligned_string(m_itemid->wszOwner); } std::wstring group() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return detail::copy_unaligned_string(m_itemid->wszGroup); } ULONG owner_id() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return m_itemid->uUid; } ULONG group_id() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return m_itemid->uGid; } bool is_folder() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return m_itemid->fIsFolder; } bool is_link() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return m_itemid->fIsLink; } DWORD permissions() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return m_itemid->dwPermissions; } ULONGLONG size() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return m_itemid->uSize; } comet::datetime_t date_modified() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return comet::datetime_t(m_itemid->dateModified); } comet::datetime_t date_accessed() const { if (!valid()) BOOST_THROW_EXCEPTION(std::exception("PIDL is not a remote item")); return comet::datetime_t(m_itemid->dateAccessed); } private: const detail::remote_item_id __unaligned* m_itemid; }; namespace detail { #include struct remote_item_template { remote_item_id id; SHITEMID terminator; }; #include } /** * Create a new wrapped PIDL holding a remote_item_id with given parameters. * * @param filename Name of file or directory on the remote filesystem. * @param is_folder Is file a folder? * @param is_link Is file a symlink? * @param owner Name of file owner on remote system. * @param group Name of file group on remote system. * @param owner_id UID of file owner on remote system. * @param group_id GID of file group on remote system. * @param permissions The file's Unix permissions bits. * @param size Size of file in bytes. * @param date_modified Date that file was last modified. * @param date_accessed Date that file was last accessed. */ inline washer::shell::pidl::cpidl_t create_remote_itemid( const std::wstring& filename, bool is_folder, bool is_link, const std::wstring& owner, const std::wstring& group, ULONG owner_id, ULONG group_id, DWORD permissions, ULONGLONG size, const comet::datetime_t date_modified, const comet::datetime_t date_accessed) { // We create the item on the stack and then clone it into // a CoTaskMemAllocated pidl when we return it as a cpidl_t detail::remote_item_template item; std::memset(&item, 0, sizeof(item)); item.id.cb = sizeof(item.id); item.id.dwFingerprint = detail::remote_item_id::FINGERPRINT; #pragma warning(push) #pragma warning(disable:4996) filename.copy(item.id.wszFilename, MAX_FILENAME_LENZ); item.id.wszFilename[MAX_HOSTNAME_LENZ - 1] = wchar_t(); owner.copy(item.id.wszOwner, MAX_USERNAME_LENZ); item.id.wszOwner[MAX_USERNAME_LENZ - 1] = wchar_t(); group.copy(item.id.wszGroup, MAX_USERNAME_LENZ); item.id.wszGroup[MAX_USERNAME_LENZ - 1] = wchar_t(); #pragma warning(pop) item.id.fIsFolder = is_folder; item.id.fIsLink = is_link; item.id.uUid = owner_id; item.id.uGid = group_id; item.id.dwPermissions = permissions; item.id.uSize = size; item.id.dateModified = date_modified.get(); item.id.dateAccessed = date_accessed.get(); assert(item.terminator.cb == 0); return washer::shell::pidl::cpidl_t( reinterpret_cast(&item)); } /** * Return the relative path made by the items in this PIDL. * e.g. * - A child PIDL returns: "filename.ext" * - A relative PIDL returns: "dir2/dir2/dir3/filename.ext" * - An absolute PIDL returns: "dir2/dir2/dir3/filename.ext" */ inline ssh::filesystem::path path_from_remote_pidl( const washer::shell::pidl::pidl_t& remote_pidl) { // Walk over RemoteItemIds and append each filename to form the path washer::shell::pidl::raw_pidl_iterator it(remote_pidl.get()); ssh::filesystem::path path; while (it != washer::shell::pidl::raw_pidl_iterator()) { remote_itemid_view itemid(*it); if (!itemid.valid()) break; // should never happen path /= itemid.filename(); ++it; } return path; } }} // namespace swish::remote_folder #endif ================================================ FILE: swish/remote_folder/swish_pidl.hpp ================================================ /** @file Operations over complete Swish PIDLs. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_REMOTE_FOLDER_SWISH_PIDL_HPP #define SWISH_REMOTE_FOLDER_SWISH_PIDL_HPP #pragma once #include "swish/host_folder/host_pidl.hpp" // find_host_itemid, host_itemid_view #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view #include // apidl_t #include // raw_pidl_iterator #include namespace swish { namespace remote_folder { /** * Return the absolute path made by the items in this PIDL. * e.g. "/path/dir2/dir2/dir3/filename.ext" * The PIDL must contain a host itemid an after that can contain any number of * remote item ids, but doesn't have to. */ inline ssh::filesystem::path absolute_path_from_swish_pidl( const washer::shell::pidl::apidl_t& pidl) { washer::shell::pidl::raw_pidl_iterator item_pos = swish::host_folder::find_host_itemid(pidl); ssh::filesystem::path path = swish::host_folder::host_itemid_view(*item_pos).path(); if (++item_pos != washer::shell::pidl::raw_pidl_iterator()) { if (remote_itemid_view(*item_pos).valid()) { path /= path_from_remote_pidl(*item_pos); } } return path; } }} // namespace swish::remote_folder #endif ================================================ FILE: swish/remotelimits.h ================================================ /* Defines constant limits given that resources are not local and therefore do not follow OS-defined constants Copyright (C) 2007 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef REMOTELIMITS_H #define REMOTELIMITS_H #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // String limits should not include the terminating NULL // TODO: Not sure if this is the case currently #define MAX_USERNAME_LEN 32 #define MAX_USERNAME_LENZ 33 // Under libc-5 the utmp and wtmp files only allow 8 chars for username #define MAX_USERNAME_LEN_UNIX 8 // Under libc-6 this is increased to 32 characters #define MAX_USERNAME_LEN_LINUX 32 // Apparently Win2k can have names up to 104 characters: // http://www.microsoft.com/technet/prodtechnol/windows2000serv/deploy/confeat/08w2kada.mspx #define MAX_USERNAME_LEN_WIN 20 #define MAX_HOSTNAME_LEN 255 // http://en.wikipedia.org/wiki/Hostname #define MAX_HOSTNAME_LENZ 256 #define MAX_PATH_LEN 1023 #define MAX_PATH_LENZ 1024 #define MAX_PATH_LEN_WIN 248 // 260 including filename #define MAX_PATH_LEN_LINUX 4096 #define MAX_FILENAME_LEN 255 // Choosing lower val as Windows FAT is #define MAX_FILENAME_LENZ 256 // also limited to 255. Makes things easier #define MAX_FILENAME_LEN_WIN 256 #define MAX_FILENAME_LEN_LINUX 255 #define MIN_PORT 0 #define MAX_PORT 65535 #define MAX_PORT_STR_LEN 5 // length of '65535' as a string #define PROTOCOL_LEN 7 // length of 'sftp://' as a string // Complete connection description of the form: // sftp://username@hostname:port/path #define MAX_CANONICAL_LEN \ (PROTOCOL_LEN + MAX_USERNAME_LEN + MAX_HOSTNAME_LEN \ + MAX_PATH_LEN + MAX_PORT_STR_LEN + 2) #define MAX_LABEL_LEN 30 // Arbitrary - chosen to be easy to display #define MAX_LABEL_LENZ 31 #define SFTP_DEFAULT_PORT 22 #define MAX_LONGENTRY_LEN 127 // 128 should be long enough to fit any #define MAX_LONGENTRY_LENZ 128 // long entry (certainly what we need) #endif REMOTELIMITS_H ================================================ FILE: swish/shell/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES parent_and_item.hpp shell.hpp shell.cpp shell_item.hpp shell_item_array.hpp) add_library(shell ${SOURCES}) hunter_add_package(Comet) hunter_add_package(Washer) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) target_link_libraries(shell PUBLIC ${Boost_LIBRARIES} Washer::washer Comet::comet) ================================================ FILE: swish/shell/parent_and_item.hpp ================================================ /* Copyright (C) 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_SHELL_PARENT_AND_ITEM_HPP #define SWISH_SHELL_PARENT_AND_ITEM_HPP #include #include #include namespace comet { template<> struct comtype { static const IID& uuid() throw() { return IID_IParentAndItem; } typedef IUnknown base; }; template<> struct wrap_t { washer::shell::pidl::apidl_t parent_pidl() { washer::shell::pidl::apidl_t parent; HRESULT hr = raw(this)->GetParentAndItem(parent.out(), NULL, NULL); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(raw(this), hr)); return parent; } washer::shell::pidl::cpidl_t item_pidl() { washer::shell::pidl::cpidl_t item; HRESULT hr = raw(this)->GetParentAndItem(NULL, NULL, item.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(raw(this), hr)); return item; } washer::shell::pidl::apidl_t absolute_item_pidl() { washer::shell::pidl::apidl_t parent; washer::shell::pidl::cpidl_t item; HRESULT hr = raw(this)->GetParentAndItem(parent.out(), NULL, item.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(raw(this), hr)); return parent + item; } comet::com_ptr parent_folder() { comet::com_ptr folder; HRESULT hr = raw(this)->GetParentAndItem(NULL, folder.out(), NULL); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(raw(this), hr)); return folder; } }; } #endif ================================================ FILE: swish/shell/shell.cpp ================================================ /** @file Utility functions to work with the Windows Shell Namespace. @if license Copyright (C) 2009, 2011, 2012, 2015 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "shell.hpp" #include // shell_browser, shell_view #include // pidl_shell_item #include // com_error #include // BOOST_THROW_EXCEPTION #include #include // SHILCreateFromPath, ILFree #include // FAILED using washer::shell::pidl::cpidl_t; using washer::shell::pidl_shell_item; using washer::shell::shell_browser; using washer::shell::shell_view; using washer::window::window; using washer::window::window_handle; using comet::com_error; using comet::com_error_from_interface; using comet::com_ptr; using boost::filesystem::path; using boost::filesystem::directory_iterator; using boost::optional; using boost::shared_ptr; using std::invalid_argument; namespace swish { namespace shell { path path_from_pidl(PIDLIST_ABSOLUTE pidl) { return pidl_shell_item(pidl).parsing_name(); } shared_ptr pidl_from_path( const path& filesystem_path) { PIDLIST_ABSOLUTE pidl; HRESULT hr = ::SHILCreateFromPath( filesystem_path.wstring().c_str(), &pidl, NULL); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); return shared_ptr(pidl, ::ILFree); } com_ptr data_object_for_file(const path& file) { return data_object_for_files(&file, &file + 1); } com_ptr data_object_for_directory(const path& directory) { if (!is_directory(directory)) BOOST_THROW_EXCEPTION( invalid_argument("The path must be to a directory.")); return data_object_for_files( directory_iterator(directory), directory_iterator()); } void put_view_item_into_rename_mode( comet::com_ptr view, const washer::shell::pidl::cpidl_t& item) { HRESULT hr = view->SelectItem( item.get(), SVSI_EDIT | SVSI_SELECT | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE | SVSI_FOCUSED); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(view, hr)); } optional> window_for_ole_site(com_ptr ole_site) { try { com_ptr view = shell_view(shell_browser(ole_site)); HWND hwnd = NULL; if (view->GetWindow(&hwnd) == S_OK && hwnd) { return window(window_handle::foster_handle(hwnd)); } } catch(const std::exception&) {} return optional>(); } }} // namespace swish::shell ================================================ FILE: swish/shell/shell.hpp ================================================ /** @file Utility functions to work with the Windows Shell Namespace. @if license Copyright (C) 2009, 2011, 2012, 2015 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/windows_api.hpp" // SHBindToParent #include // cpidl_t #include #include // com_error #include // uuidof, comtype #include // com_ptr #include // path #include // numeric_cast #include #include #include // BOOST_THROW_EXCEPTION #include #include // IShellFolder #include // IDataObject #include #include // transform #include // invalid_argument namespace { // private /** * Function adapter for ILFindLastID to work with transform algorithm * in ui_object_of_items. */ PUITEMID_CHILD find_last_ID(const ITEMIDLIST_RELATIVE& idl) { return ::ILFindLastID(&idl); } } namespace comet { template<> struct comtype { static const IID& uuid() throw() { return IID_IDataObject; } typedef IUnknown base; }; } namespace swish { namespace shell { /** * Return the filesystem path represented by the given PIDL. * * @warning * The PIDL must be a PIDL to a filesystem item. If it isn't this * function is likely but not guaranteed to throw an exception * when it converts the parsing name to a path. If the parsing * name looks sufficiently path-like, however, it may silently * succeed and return a bogus path. */ boost::filesystem::path path_from_pidl(PIDLIST_ABSOLUTE pidl); /** * Return an absolute PIDL to the item in the filesystem at the given * path. */ boost::shared_ptr pidl_from_path( const boost::filesystem::path& filesystem_path); /** * Return an IDataObject representing several files in the same folder. * * The files are passed as a half-open range of fully-qualified paths to * each file. * * @templateparam It An iterator type whose items are convertible to path * by the path constructor (e.g. could be paths or * strings). */ template comet::com_ptr data_object_for_files(It begin, It end) { std::vector > pidls; transform(begin, end, back_inserter(pidls), pidl_from_path); return ui_object_of_items(pidls.begin(), pidls.end()); } /** * Return an IDataObject representing a file on the local filesystem. */ comet::com_ptr data_object_for_file( const boost::filesystem::path& file); /** * Return an IDataObject representing all the files in a directory. */ comet::com_ptr data_object_for_directory( const boost::filesystem::path& directory); /** * Return the associated object of several items. * * This is a convenience function that binds to the items' parent and then * asks the parent for the associated object. The items are passed as a * half-open range of absolute PIDLs. * * Analogous to GetUIObjectOf(). * * @warning * In order for this to work all items MUST HAVE THE SAME PARENT (i.e. they * must all be in the same folder). */ template comet::com_ptr ui_object_of_items(It begin, It end) { // // All the items we're passed have to have the same parent folder so // we just bind to the parent of the *first* item in the collection. // if (begin == end) BOOST_THROW_EXCEPTION(std::invalid_argument("Empty range given")); comet::com_ptr parent; HRESULT hr = swish::windows_api::SHBindToParent( // &* strips smart pointer, if any &**begin, comet::uuidof(parent.in()), reinterpret_cast(parent.out()), NULL); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); std::vector child_pidls; std::transform( boost::make_indirect_iterator(begin), boost::make_indirect_iterator(end), back_inserter(child_pidls), find_last_ID); comet::com_ptr ui_object; hr = parent->GetUIObjectOf( NULL, boost::numeric_cast(child_pidls.size()), (child_pidls.empty()) ? NULL : &child_pidls[0], comet::uuidof(), NULL, reinterpret_cast(ui_object.out())); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error_from_interface(parent, hr)); return ui_object; } /** * Return the associated object of an item. * * This is a convenience function that binds to the item's parent * and then asks the parent for the associated object. The type of * associated object is determined by the template parameter. * * Analogous to GetUIObjectOf(). * * @templateparam T Type of associated object to return. */ template comet::com_ptr ui_object_of_item(PCIDLIST_ABSOLUTE pidl) { return ui_object_of_items(&pidl, &pidl + 1); } void put_view_item_into_rename_mode( comet::com_ptr view, const washer::shell::pidl::cpidl_t& item); /** * Get the window for the give OLE site, if available. */ boost::optional> window_for_ole_site( comet::com_ptr ole_site); }} // namespace swish::shell ================================================ FILE: swish/shell/shell_item.hpp ================================================ /* Copyright (C) 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_SHELL_SHELL_ITEM_HPP #define SWISH_SHELL_SHELL_ITEM_HPP #include #include namespace swish { namespace shell { template struct comet::wrap_t { }; }} #endif ================================================ FILE: swish/shell/shell_item_array.hpp ================================================ /* Copyright (C) 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SWISH_SHELL_SHELL_ITEM_ARRAY_HPP #define SWISH_SHELL_SHELL_ITEM_ARRAY_HPP #include #include #include #include #include // numeric_cast #include namespace comet { template<> struct comtype { static const IID& uuid() throw() { return IID_IShellItemArray; } typedef IUnknown base; }; template<> struct comtype { static const IID& uuid() throw() { return IID_IShellItem; } typedef IUnknown base; }; template<> struct comet::impl::type_policy { static void init(IShellItem*& destination, IShellItem* source) { destination = source; destination->AddRef(); } static void clear(IShellItem*& p) { p->Release(); } }; template<> struct enumerated_type_of { typedef IShellItem* is; }; template<> struct wrap_t { typedef comet::enum_iterator iterator_type; size_t size() { DWORD array_size = 0; HRESULT hr = raw(this)->GetCount(&array_size); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(raw(this), hr)); return array_size; } comet::com_ptr at(size_t index) { comet::com_ptr item; HRESULT hr = raw(this)->GetItemAt( boost::numeric_cast(index), item.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(raw(this), hr)); return item; } comet::com_ptr operator[](size_t index) { return at(index); } iterator_type begin() { com_ptr enumerator; HRESULT hr = raw(this)->EnumItems(enumerator.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(raw(this), hr)); return iterator_type(enumerator); } iterator_type end() { return iterator_type(); } }; } namespace swish { namespace shell { inline comet::com_ptr shell_item_array_from_folder_items( comet::com_ptr parent_folder, UINT pidl_count, PCUITEMID_CHILD_ARRAY item_pidls) { comet::com_ptr item_array; // Not passing the folder PIDL, so this relies on the folder // implementing IPersistFolder2 HRESULT hr = ::SHCreateShellItemArray( NULL, parent_folder.in(), pidl_count, item_pidls, item_array.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); return item_array; } inline comet::com_ptr shell_item_array_from_folder_pidl_and_items( PCIDLIST_ABSOLUTE parent_folder_pidl, UINT pidl_count, PCUITEMID_CHILD_ARRAY item_pidls) { comet::com_ptr item_array; HRESULT hr = ::SHCreateShellItemArray( parent_folder_pidl, NULL, pidl_count, item_pidls, item_array.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); return item_array; } inline comet::com_ptr shell_item_array_from_pidls( UINT cidl, PCIDLIST_ABSOLUTE_ARRAY rgpidl) { comet::com_ptr item_array; HRESULT hr = ::SHCreateShellItemArrayFromIDLists( cidl, rgpidl, item_array.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); return item_array; } inline comet::com_ptr shell_item_array_from_data_object( comet::com_ptr data_object) { comet::com_ptr item_array; HRESULT hr = ::SHCreateShellItemArrayFromDataObject( data_object.in(), item_array.iid(), reinterpret_cast(item_array.out())); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); return item_array; } }} #endif ================================================ FILE: swish/shell_folder/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(GENERATED_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/shell_folder.dir/${CMAKE_CFG_INTDIR}/Swish.h ${CMAKE_CURRENT_BINARY_DIR}/shell_folder.dir/${CMAKE_CFG_INTDIR}/Swish_i.c) set_source_files_properties(${GENERATED_SOURCES} PROPERTIES GENERATED TRUE) set(SOURCES DataObject.h Folder.h IconExtractor.h KbdInteractiveDialog.h HostFolder.h locale_setup.hpp Pidl.h Registry.h RemoteFolder.h resource.h SftpDataObject.h SftpDirectory.h SnitchingDataObject.hpp SwishFolder.hpp wtl.hpp DataObject.cpp HostFolder.cpp IconExtractor.cpp KbdInteractiveDialog.cpp Registry.cpp RemoteFolder.cpp SftpDataObject.cpp SftpDirectory.cpp data_object/FileGroupDescriptor.hpp data_object/GlobalLocker.hpp data_object/ShellDataObject.hpp data_object/ShellDataObject.cpp data_object/StorageMedium.hpp Swish.idl ${GENERATED_SOURCES} shell_folder.rc) add_library(shell_folder ${SOURCES}) # This exposes the include path of the directory that CMake tells midl # to output the header file into. Ideally, it would be # "${CMAKE_CURRENT_BINARY_DIR}/\$(IntDir)", but that is no good if # used by another project because $(IntDir) expands to the # intermediate directory of that project, not this one. target_include_directories(shell_folder PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/shell_folder.dir/${CMAKE_CFG_INTDIR}) hunter_add_package(Comet) hunter_add_package(Washer) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) target_link_libraries(shell_folder PRIVATE host_folder remote_folder frontend nse drop_target urlmon ${Boost_LIBRARIES} PUBLIC Washer::washer Comet::comet) ================================================ FILE: swish/shell_folder/DataObject.cpp ================================================ /** @file Wrapper around shell-created IDataObject adding support for FILECONTENTS. @if license Copyright (C) 2009, 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "DataObject.h" #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // com_error_from_interface #include // com_ptr #include // BOOST_THROW_EXCEPTION using comet::com_error; using comet::com_error_from_interface; using comet::com_ptr; namespace comet { template<> struct comtype { static const IID& uuid() { return IID_IDataObject; } typedef ::IUnknown base; }; } namespace { com_ptr shell_data_object_from_pidls( UINT cPidl, PCUITEMID_CHILD_ARRAY aPidl, PCIDLIST_ABSOLUTE pidlCommonParent) { // Create the default shell IDataObject implementation which we // are wrapping. com_ptr data_object; HRESULT hr = ::CIDLData_CreateFromIDArray( pidlCommonParent, cPidl, reinterpret_cast(aPidl), data_object.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); return data_object; } } /** * Construct the DataObject with the top-level PIDLs. * * These PIDLs represent, for instance, the current group of files and * directories which have been selected in an Explorer window. This list * should not include any sub-items of any of the directories. * * @param cPidl Number of PIDLs in the selection. * @param aPidl The selected PIDLs. * @param pidlCommonParent PIDL to the common parent of all the PIDLs. */ CDataObject::CDataObject( UINT cPidl, PCUITEMID_CHILD_ARRAY aPidl, PCIDLIST_ABSOLUTE pidlCommonParent) : m_cfFileDescriptor(static_cast( ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR))), m_cfFileContents(static_cast( ::RegisterClipboardFormat(CFSTR_FILECONTENTS))), m_inner(shell_data_object_from_pidls(cPidl, aPidl, pidlCommonParent)) { } /*----------------------------------------------------------------------------* * IDataObject methods *----------------------------------------------------------------------------*/ STDMETHODIMP CDataObject::GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium) { HRESULT hr = S_OK; try { if (pformatetcIn->cfFormat == m_cfFileContents) { // Validate FORMATETC if (!(pformatetcIn->tymed & TYMED_ISTREAM)) BOOST_THROW_EXCEPTION(com_error(DV_E_TYMED)); if (pformatetcIn->dwAspect != DVASPECT_CONTENT) BOOST_THROW_EXCEPTION(com_error(DV_E_DVASPECT)); if (pformatetcIn->ptd) BOOST_THROW_EXCEPTION(com_error(DV_E_DVTARGETDEVICE)); long lindex = pformatetcIn->lindex; // Handle incorrect lindex if possible if (lindex == -1) { if (m_streams.size() != 1) BOOST_THROW_EXCEPTION(com_error(DV_E_LINDEX)); lindex = 0; } // Ensure that the item is actually in our (sparse) local store if (m_streams.find(lindex) == m_streams.end()) BOOST_THROW_EXCEPTION(com_error(DV_E_LINDEX)); // Fill STGMEDIUM with IStream pmedium->pUnkForRelease = NULL; m_streams[lindex].CopyTo(&(pmedium->pstm)); pmedium->tymed = TYMED_ISTREAM; } else { // Delegate all other requests to the inner IDataObject hr = m_inner->GetData(pformatetcIn, pmedium); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(m_inner, hr)); } } WASHER_COM_CATCH_AUTO_INTERFACE(); return hr; } /** * Set a format in the DataObject. * * Which item to set is specified as a FORMATETC and the item is passed in * a STGMEDIUM. If an item already exists with the specified parameters, it * is replaced. * * @param pformatetc Pointer to FORMATETC specifying the format type and, for * CFSTR_FILECONTENTS formats only, the index of the item * in the DataObject. * @param pmedium Pointer to STGMEDIUM holding the item to be added to the * DataObject. * @param fRelease Indicates who owns the contents of the STGMEDIUM after a * call to this method. If true, this object does. If * false, the caller retains ownership. */ STDMETHODIMP CDataObject::SetData( FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease) { HRESULT hr = S_OK; try { if (pformatetc->cfFormat == m_cfFileContents) { // Validate FORMATETC if (pformatetc->tymed != TYMED_ISTREAM) BOOST_THROW_EXCEPTION(com_error(DV_E_TYMED)); if (pformatetc->dwAspect != DVASPECT_CONTENT) BOOST_THROW_EXCEPTION(com_error(DV_E_DVASPECT)); if (pformatetc->ptd) BOOST_THROW_EXCEPTION(com_error(DV_E_DVTARGETDEVICE)); if (pformatetc->lindex < 0) BOOST_THROW_EXCEPTION(com_error(DV_E_LINDEX)); // Validate STGMEDIUM if (pmedium->tymed != pformatetc->tymed) BOOST_THROW_EXCEPTION(com_error(DV_E_TYMED)); if (!pmedium->pstm) BOOST_THROW_EXCEPTION(com_error(DV_E_STGMEDIUM)); // Add IStream to our local store m_streams[pformatetc->lindex] = pmedium->pstm; if (fRelease) // Release STGMEDIUM if we own it now { ::ReleaseStgMedium(pmedium); } // Prod inner IDataObject with empty CFSTR_FILECONTENTS format hr = ProdInnerWithFormat(pformatetc->cfFormat, pformatetc->tymed); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(m_inner, hr)); } else { // Delegate all other requests to the inner IDataObject hr = m_inner->SetData(pformatetc, pmedium, fRelease); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(m_inner, hr)); } } WASHER_COM_CATCH_AUTO_INTERFACE(); return hr; } STDMETHODIMP CDataObject::GetDataHere(FORMATETC *pformatetc, STGMEDIUM *pmedium) { return m_inner->GetDataHere(pformatetc, pmedium); } STDMETHODIMP CDataObject::QueryGetData(FORMATETC *pformatetc) { return m_inner->QueryGetData(pformatetc); } STDMETHODIMP CDataObject::GetCanonicalFormatEtc( FORMATETC *pformatetcIn, FORMATETC *pformatetcOut) { return m_inner->GetCanonicalFormatEtc(pformatetcIn, pformatetcOut); } STDMETHODIMP CDataObject::EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc) { return m_inner->EnumFormatEtc(dwDirection, ppenumFormatEtc); } STDMETHODIMP CDataObject::DAdvise( FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection) { return m_inner->DAdvise(pformatetc, advf, pAdvSink, pdwConnection); } STDMETHODIMP CDataObject::DUnadvise(DWORD dwConnection) { return m_inner->DUnadvise(dwConnection); } STDMETHODIMP CDataObject::EnumDAdvise(IEnumSTATDATA **ppenumAdvise) { return m_inner->EnumDAdvise(ppenumAdvise); } /*----------------------------------------------------------------------------* * Protected methods *----------------------------------------------------------------------------*/ /** * Prod the inner DataObject with the given format. * * This sets an empty item in the inner DataObject which causes it to register * the existence of the format. This ensures that calls to QueryGetData() and * the IEnumFORMATETC enumeration---both of which are delegated to the inner * object---respond correctly. */ HRESULT CDataObject::ProdInnerWithFormat(CLIPFORMAT nFormat, DWORD tymed) throw() { CFormatEtc fetc(nFormat, tymed); STGMEDIUM stgEmpty = STGMEDIUM(); // The tymeds in the FORMATETC and STGMEDIUM must match. This was tripping // up Windows 8 (Consumer Preview) which is stricter about this and was // returning E_OUTOFMEMORY from SetData. stgEmpty.tymed = tymed; return m_inner->SetData(&fetc, &stgEmpty, true); } ================================================ FILE: swish/shell_folder/DataObject.h ================================================ /** @file Wrapper around shell-created IDataObject adding support for FILECONTENTS. @if license Copyright (C) 2009, 2011, 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "swish/atl.hpp" #include // com_ptr #include // simple_object #include // Windows Shell API #include // Associative container for IStream store /** * Pseudo-subclass of IDataObject created by CIDLData_CreateFromIDArray(). * * The shell-created DataObject is lacking in one respect: it doesn't * allow the storage of more than one item with the same format but * different @p lindex value. This rules out using it as-is for the common * shell scenario where the contents of a number of selected files * are stored in the same IDataObject: only the last file is stored regardless * of the value of @p lindex passed in the FORMATETC into SetData(). * * This class works around the problem by intercepting calls to the * shell DataObject (stored in @p m_inner) and performing custom * processing for CFSTR_FILECONTENTS formats. All other requests are simply * forwarded to the inner IDataObject. * * As the locally stored CFSTR_FILECONTENTS formats may be set with any * @p lindex value (not necessarily a continuous series), a std::map is * used as a sparse array. */ class CDataObject : public comet::simple_object { public: typedef IDataObject interface_is; CDataObject( UINT cPidl, __in_ecount_opt(cPidl) PCUITEMID_CHILD_ARRAY aPidl, __in PCIDLIST_ABSOLUTE pidlCommonParent); public: // IDataObject methods IFACEMETHODIMP GetData( __in FORMATETC *pformatetcIn, __out STGMEDIUM *pmedium); IFACEMETHODIMP GetDataHere( __in FORMATETC *pformatetc, __inout STGMEDIUM *pmedium); IFACEMETHODIMP QueryGetData( __in_opt FORMATETC *pformatetc); IFACEMETHODIMP GetCanonicalFormatEtc( __in_opt FORMATETC *pformatetcIn, __out FORMATETC *pformatetcOut); IFACEMETHODIMP SetData( __in FORMATETC *pformatetc, __in STGMEDIUM *pmedium, __in BOOL fRelease); IFACEMETHODIMP EnumFormatEtc( __in DWORD dwDirection, __deref_out_opt IEnumFORMATETC **ppenumFormatEtc); IFACEMETHODIMP DAdvise( __in FORMATETC *pformatetc, __in DWORD advf, __in_opt IAdviseSink *pAdvSink, __out DWORD *pdwConnection); IFACEMETHODIMP DUnadvise( __in DWORD dwConnection); IFACEMETHODIMP EnumDAdvise( __deref_out_opt IEnumSTATDATA **ppenumAdvise); protected: HRESULT ProdInnerWithFormat(CLIPFORMAT nFormat, DWORD tymed) throw(); private: /** @name Stores */ // @{ typedef std::map > StreamStore; StreamStore m_streams; ///< Local FILECONTENTS IStream store comet::com_ptr m_inner; ///< Wrapped inner DataObject // @} /** @name Explicitly recognised CLIPFORMATS */ // @{ CLIPFORMAT m_cfFileDescriptor; ///< CFSTR_FILEDESCRIPTOR CLIPFORMAT m_cfFileContents; ///< CFSTR_FILECONTENTS // @} }; class CStorageMedium : public STGMEDIUM { public: CStorageMedium() throw() { tymed = TYMED_NULL; hBitmap = 0; hMetaFilePict = 0; hEnhMetaFile = 0; hGlobal = 0; lpszFileName = NULL; pstm = NULL; pstg = NULL; pUnkForRelease = NULL; } ~CStorageMedium() throw() { ::ReleaseStgMedium(this); } }; class CFormatEtc : public FORMATETC { public: CFormatEtc( CLIPFORMAT cfFormat, DWORD tymed = TYMED_HGLOBAL, LONG lIndex = -1, DWORD dwAspect = DVASPECT_CONTENT, DVTARGETDEVICE *ptd = NULL) throw() { _Construct(cfFormat, tymed, lIndex, dwAspect, ptd); } CFormatEtc( UINT nFormat, DWORD tymed = TYMED_HGLOBAL, LONG lIndex = -1, DWORD dwAspect = DVASPECT_CONTENT, DVTARGETDEVICE *ptd = NULL) throw() { _Construct(static_cast(nFormat), tymed, lIndex, dwAspect, ptd); } CFormatEtc( PCWSTR pszFormat, DWORD tymed = TYMED_HGLOBAL, LONG lIndex = -1, DWORD dwAspect = DVASPECT_CONTENT, DVTARGETDEVICE *ptd = NULL) throw(...) { UINT nFormat = ::RegisterClipboardFormatW(pszFormat); ATLENSURE_THROW(nFormat, E_INVALIDARG); _Construct(static_cast(nFormat), tymed, lIndex, dwAspect, ptd); } private: inline void _Construct( CLIPFORMAT cfFormat, DWORD tymed, LONG lIndex, DWORD dwAspect, DVTARGETDEVICE *ptd) throw() { this->cfFormat = cfFormat; this->tymed = tymed; this->lindex = lIndex; this->dwAspect = dwAspect; this->ptd = ptd; } }; class CGlobalLock { public: CGlobalLock() throw() : m_hGlobal(NULL), m_pMem(NULL) {} CGlobalLock(__in HGLOBAL hGlobal) throw() : m_hGlobal(hGlobal), m_pMem(::GlobalLock(m_hGlobal)) {} ~CGlobalLock() throw() { _Clear(); } /** * Disable copy constructor. If the object were copied, the old one would * be destroyed which unlocks the global memory but the new copy would * not be re-locked. */ CGlobalLock(const CGlobalLock& lock) throw(); CGlobalLock& operator=(const CGlobalLock&) throw(); void Attach(__in HGLOBAL hGlobal) throw() { _Clear(); m_hGlobal = hGlobal; m_pMem = ::GlobalLock(m_hGlobal); } HGLOBAL Detach() throw() { HGLOBAL hGlobal = m_hGlobal; _Clear(); return hGlobal; } CIDA* GetCida() { return static_cast(m_pMem); } FILEGROUPDESCRIPTOR& GetFileGroupDescriptor() { return *static_cast(m_pMem); } DWORD& GetDword() { return *static_cast(m_pMem); } private: void _Clear() throw() { m_pMem = NULL; if (m_hGlobal) ::GlobalUnlock(m_hGlobal); m_hGlobal = NULL; } HGLOBAL m_hGlobal; PVOID m_pMem; }; ================================================ FILE: swish/shell_folder/Folder.h ================================================ /** @file Base class for IShellFolder implementations. @if license Copyright (C) 2008, 2009, 2010, 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "Pidl.h" #include "swish/atl.hpp" // Common ATL setup #include "swish/debug.hpp" // METHOD_TRACE #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // folder2_error_adapter #include // apidl_t, cpidl_t #include // property_key #include // string_to_strret #include // com_error #include // variant_t #include // BOOST_THROW_EXCEPTION #include // Windows Shell API #include // memset, memcpy #include // range_error #ifndef __IPersistIDList_INTERFACE_DEFINED__ #define __IPersistIDList_INTERFACE_DEFINED__ EXTERN_C const IID IID_IPersistIDList; MIDL_INTERFACE("1079acfc-29bd-11d3-8e0d-00c04f6837d5") IPersistIDList : public IPersist { public: virtual HRESULT STDMETHODCALLTYPE SetIDList( /* [in] */ __RPC__in PCIDLIST_ABSOLUTE pidl) = 0; virtual HRESULT STDMETHODCALLTYPE GetIDList( /* [out] */ __RPC__deref_out_opt PIDLIST_ABSOLUTE *ppidl) = 0; }; #endif namespace comet { template<> struct comtype { static const IID& uuid() { return IID_IPersistFolder; } typedef IPersistFolder base; }; } namespace swish { namespace shell_folder { namespace folder { namespace detail { /** * Return the parent IShellFolder of the last item in the PIDL. * * Optionally, return the a pointer to the last item as well. This * function emulates the Vista-specific SHBindToFolderIDListParent API * call. */ inline HRESULT BindToParentFolderOfPIDL( IShellFolder *psfRoot, PCUIDLIST_RELATIVE pidl, REFIID riid, __out void **ppvParent, __out_opt PCUITEMID_CHILD *ppidlChild) { *ppvParent = NULL; if (ppidlChild) *ppidlChild = NULL; // Equivalent to // ::SHBindToFolderIDListParent( // psfRoot, pidl, riid, ppvParent, ppidlChild); // Create PIDL to penultimate item (parent) PIDLIST_RELATIVE pidlParent = ::ILClone(pidl); ATLVERIFY(::ILRemoveLastID(pidlParent)); // Bind to the penultimate PIDL's folder (parent folder) HRESULT hr = psfRoot->BindToObject(pidlParent, NULL, riid, ppvParent); ::ILFree(pidlParent); if (SUCCEEDED(hr) && ppidlChild) { *ppidlChild = ::ILFindLastID(pidl); } return hr; } } template class CFolder : public ATL::CComObjectRoot, public washer::shell::folder2_error_adapter, public washer::shell::shell_details_error_adapter, public IPersistFolder3, public IPersistIDList { public: BEGIN_COM_MAP(CFolder) COM_INTERFACE_ENTRY(IPersistFolder3) COM_INTERFACE_ENTRY(IShellFolder2) COM_INTERFACE_ENTRY(IShellDetails) COM_INTERFACE_ENTRY(IPersistIDList) COM_INTERFACE_ENTRY2(IPersist, IPersistFolder3) COM_INTERFACE_ENTRY2(IPersistFolder, IPersistFolder3) COM_INTERFACE_ENTRY2(IPersistFolder2, IPersistFolder3) COM_INTERFACE_ENTRY2(IShellFolder, IShellFolder2) END_COM_MAP() CFolder() {} virtual ~CFolder() {} const washer::shell::pidl::apidl_t& root_pidl() const { return m_root_pidl; } public: // IPersist methods /** * Get the class identifier (CLSID) of the object. * * @implementing IPersist * * @param[in] pClassID Location in which to return the CLSID. */ IFACEMETHODIMP GetClassID(CLSID* pClassID) { ATLENSURE_RETURN_HR(pClassID, E_POINTER); *pClassID = clsid(); return S_OK; } public: // IPersistFolder methods /** * Assign an @b absolute PIDL to be the root of this folder. * * @implementing IPersistFolder * * This function tells a folder its place in the system namespace. * If the folder implementation needs to construct a fully qualified PIDL * to elements that it contains, the PIDL passed to this method is * used to construct these. * * @param pidl PIDL that specifies the absolute location of this folder. */ IFACEMETHODIMP Initialize(PCIDLIST_ABSOLUTE pidl) { ATLENSURE_RETURN_HR(!::ILIsEmpty(pidl), E_INVALIDARG); ATLENSURE_RETURN_HR(!m_root_pidl, E_UNEXPECTED); // Multiple init m_root_pidl = pidl; return S_OK; } public: // IPersistFolder2 methods /** * Get the root of this folder - the absolute PIDL relative to the * desktop. * * @implementing IPersistFolder2 * * @param[out] ppidl Location in which to return the copied PIDL. * If the folder hasn't been initialised yet, this * value will be NULL. * * @returns S_FALSE if Initialize() hasn't been called. */ IFACEMETHODIMP GetCurFolder(PIDLIST_ABSOLUTE* ppidl) { ATLENSURE_RETURN_HR(ppidl, E_POINTER); *ppidl = NULL; try { if (!root_pidl()) // Legal to call this before Initialize() return S_FALSE; // Copy the PIDL that was passed to us in Initialize() root_pidl().copy_to(*ppidl); } WASHER_COM_CATCH_INTERFACE(IPersistFolder2); return S_OK; } public: // IPersistFolder3 methods IFACEMETHODIMP InitializeEx( IBindCtx* /*pbc*/, PCIDLIST_ABSOLUTE pidlRoot, const PERSIST_FOLDER_TARGET_INFO* /*ppfti*/) { ATLENSURE_RETURN_HR(pidlRoot, E_POINTER); return Initialize(pidlRoot); } IFACEMETHODIMP GetFolderTargetInfo(PERSIST_FOLDER_TARGET_INFO* ppfti) { ATLENSURE_RETURN_HR(ppfti, E_POINTER); ::ZeroMemory(ppfti, sizeof(PERSIST_FOLDER_TARGET_INFO)); return E_NOTIMPL; } public: // IPersistIDList methods IFACEMETHODIMP SetIDList(PCIDLIST_ABSOLUTE pidl) { return Initialize(pidl); } IFACEMETHODIMP GetIDList(PIDLIST_ABSOLUTE* ppidl) { return GetCurFolder(ppidl); } public: // IShellFolder methods virtual void bind_to_storage( PCUIDLIST_RELATIVE /*pidl*/, IBindCtx* /*bind_ctx*/, const IID& /*iid*/, void** /*interface_out*/) { BOOST_THROW_EXCEPTION(comet::com_error(E_NOTIMPL)); } /** * Caller is requesting a subobject of this folder. * * @implementing folder_error_adapter * * Create and initialise an instance of the subitem represented by @a pidl * and return the interface asked for in @a iid. * * Typically this is an IShellFolder although it may be an IStream. * Whereas create_view_object() and get_ui_object_of() request 'associated * objects' of items in the hierarchy, calls to bind_to_object() * are for the objects representing the items themselves. E.g., * IShellFolder for folders and IStream for files. * * @todo Find out more about how IStreams are dealt with and what it * gains us. * * @param[in] pidl PIDL to the requested object @b relative to * this folder. * @param[in] bind_ctx Binding context. * @param[in] iid IID of the interface being requested. * @param[out] interface_out Location in which to return the requested * interface. * * @note Corresponds to IShellFolder::BindToObject. */ virtual void bind_to_object( PCUIDLIST_RELATIVE pidl, IBindCtx* bind_ctx, const IID& iid, void** interface_out) { if (::ILIsEmpty(pidl)) BOOST_THROW_EXCEPTION(comet::com_error(E_INVALIDARG)); // TODO: We can optimise this function by immediately throwing // E_NOTIMPL for any riid that we know our children and grandchildren // don't provide. This is not in the spirit of COM QueryInterface but // it may be a performance boost. // First item in pidl must be of our type validate_pidl(pidl); if (::ILIsChild(pidl)) // Our child subfolder is the target { comet::com_ptr folder = subfolder( reinterpret_cast(pidl)); // Create absolute PIDL to the subfolder by combining with // our root HRESULT hr = folder->QueryInterface(iid, interface_out); if (FAILED(hr)) comet::throw_com_error(folder.get(), hr); } else // One of our grandchildren is the target - delegate to its parent { comet::com_ptr folder; PCUITEMID_CHILD pidl_grandchild = NULL; HRESULT hr = detail::BindToParentFolderOfPIDL( this, pidl, IID_PPV_ARGS(folder.out()), &pidl_grandchild); if (FAILED(hr)) comet::throw_com_error( comet::com_ptr(this).get(), hr); hr = folder->BindToObject( pidl_grandchild, bind_ctx, iid, interface_out); if (FAILED(hr)) comet::throw_com_error(folder.get(), hr); } } /** * Determine the relative order of two items in or below this folder. * * @implementing folder_error_adapter * * Given their item identifier lists, compare the two objects and return a * value indicating the result of the comparison: * - Negative: pidl1 < pidl2 * - Positive: pidl1 > pidl2 * - Zero: pidl1 == pidl2 * * @note Corresponds to IShellFolder::CompareIDs. */ int compare_ids( LPARAM lparam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2) { int column = LOWORD(lparam); bool compare_all_fields = (HIWORD(lparam) == SHCIDS_ALLFIELDS); bool canonical = (HIWORD(lparam) == SHCIDS_CANONICALONLY); assert(!compare_all_fields || column == 0); assert(!canonical || !compare_all_fields); // Handle empty PIDL cases if (::ILIsEmpty(pidl1) && ::ILIsEmpty(pidl2)) // Both empty - equal return 0; else if (::ILIsEmpty(pidl1)) // Only pidl1 empty < return -1; else if (::ILIsEmpty(pidl2)) // Only pidl2 empty > return 1; // Explorer can pass us invalid PIDLs from its cache if our PIDL // representation changes. We catch that here to stop us asserting // later. validate_pidl(pidl1); validate_pidl(pidl2); // The casts here are OK. We are aware compare_pidls only compares // a single item. We recurse later if needed. int result = compare_pidls( static_cast(pidl1), static_cast(pidl2), column, compare_all_fields, canonical); if ((::ILIsChild(pidl1) && ::ILIsChild(pidl2)) || result != 0) { return result; } else { // The first items are equal and there are more items to // compare. // Delegate the rest of the comparison to our child. comet::com_ptr folder; washer::shell::pidl::cpidl_t child; child.attach(::ILCloneFirst(pidl1)); bind_to_object(child.get(), NULL, IID_PPV_ARGS(folder.out())); HRESULT hr = folder->CompareIDs( lparam, ::ILNext(pidl1), ::ILNext(pidl2)); if (FAILED(hr)) comet::throw_com_error(folder.get(), hr); return (short)HRESULT_CODE(hr); } } /** * Create an object associated with @b this folder. * * @implementing folder_error_adapter * * The types of object which can be requested include IShellView, * IContextMenu, IExtractIcon, IQueryInfo, IShellDetails or IDropTarget. * This method is in contrast to get_ui_object_of() which performs the same * task but for an item contained *within* the current folder rather than * the folder itself. * * @param[in] hwnd Handle to the parent window, if any, of any * UI that may be needed to complete the * request. * @param[in] iid Interface UUID for the object being * requested. * @param[out] interface_out Return value. * * @note Corresponds to IShellFolder::CreateViewObject. */ void create_view_object(HWND hwnd_owner, REFIID iid, void** interface_out) { comet::com_ptr object = folder_object(hwnd_owner, iid); HRESULT hr = object.get()->QueryInterface(iid, interface_out); if (FAILED(hr)) comet::throw_com_error(object.get(), hr); } /** * Create an object associated with an item in the current folder. * * @implementing folder_error_adapter * * Callers will request an associated object, such as a context menu, for * items in the folder by calling this method with the IID of the object * they want and the PIDLs of the items they want it for. In addition, * if the don't pass any PIDLs then they are requesting an associated * object of this folder. * * We deal with the request as follows: * - If the request is for an object associated with this folder, we * call folder_object() with the requested IID. You should override * that method if your folder supports any associated objects. * * - If the request is for items in this in this folder we call * folder_item_object() with the IID and the PIDLs. Again, you should * override that method if you want to support associated objects for * your folder's contents. * * - If the previous step fails to find the associated object and there is * only a single PIDL, we attempt to bind to the item in the PIDL as * an IShellFolder. If this succeeds we delegate the object lookup to * this subfolder by calling its IShellFolder::CreateViewObject() method. * * The idea is that a given folder implementation answers object queries * for itself and the non-folder items within it. Additionally, it can * answer queries for sub-folders if it chooses but it doesn't have to. If * it doesn't, the request will be delegated to the subfolder * implementation. * * create_view_object() performs the same task as get_ui_object_of() but * only for the folder, not for items within it. * * @param[in] hwnd_owner Handle to the parent window, if any, of any * UI that may be needed to complete the * request. * @param[in] riid Interface UUID for the object being * requested. * @param[out] interface_out Return value. * * @note Corresponds to IShellFolder::GetUIObjectIf. */ void get_ui_object_of( HWND hwnd_owner, UINT pidl_count, PCUITEMID_CHILD_ARRAY pidl_array, const IID& iid, void** interface_out) { comet::com_ptr object; if (pidl_count == 0) { // Equivalent to CreateViewObject object = folder_object(hwnd_owner, iid); } else { try { object = folder_item_object( hwnd_owner, iid, pidl_count, pidl_array); } catch (const comet::com_error& e) { if (e.hr() == E_NOINTERFACE && pidl_count == 1) object = _delegate_object_lookup_to_subfolder( hwnd_owner, iid, pidl_array[0]); else throw; } } HRESULT hr = object.get()->QueryInterface(iid, interface_out); if (FAILED(hr)) comet::throw_com_error(object.get(), hr); } public: // IShellDetails methods /** * Sort by a given column of the folder view. * * @implementing shell_details_base_interface * * @return false to instruct the shell to perform the sort itself. */ bool column_click(UINT /*column_index*/) { return false; } public: // IShellFolder2 methods /** * Detailed information about an item in a folder. * * The desired detail is specified by a column index. * * @implementing folder_base_interface2 * * This function operates in two distinctly different ways: * - if pidl is NULL: * Retrieve the the names of the columns themselves. * - if pidl is not NULL: * Retrieve information for the item in the given pidl. * * The caller indicates which detail they want by specifying a column index * in column_index. If this column does not exist, throw an error. * * @retval A SHELLDETAILS structure holding the requested detail as a * string along with various metadata. * * @note Typically, a folder view calls this method repeatedly, * incrementing the column index each time. The first column for * which we throw an error, marks the end of the columns in this * folder. */ SHELLDETAILS get_details_of(PCUITEMID_CHILD pidl, UINT column_index) { ColumnType col(column_index); SHELLDETAILS details; std::memset(&details, 0, sizeof(SHELLDETAILS)); if (!pidl) { details.cxChar = col.average_width_in_chars(); details.fmt = col.format(); details.str = washer::shell::string_to_strret(col.header()); } else { details.str = washer::shell::string_to_strret(col.detail(pidl)); } return details; } /** * GUID of the search to invoke when the user clicks on the search * toolbar button. * * @implementing folder_base_interface2 * * We do not support search objects so this method is not implemented. */ GUID get_default_search_guid() { BOOST_THROW_EXCEPTION(comet::com_error(E_NOTIMPL)); return GUID(); } /** * Enumeration of all searches supported by this folder * * @implementing folder_base_interface2 * * We do not support search objects so this method is not implemented. */ IEnumExtraSearch* enum_searches() { BOOST_THROW_EXCEPTION(comet::com_error(E_NOTIMPL)); return NULL; } /** * Default sorting and display columns. * * @implementing folder_base_interface2 * * This is a default implementation which simply return the 1st (zeroth) * column for both sorting and display. Derived classes can override this * if they need custom behaviour. */ void get_default_column(ULONG* sort_out, ULONG* display_out) { *sort_out = 0; *display_out = 0; } /** * Default UI state (hidden etc.) and type (string, integer, etc.) for the * column specified by column_index. * * @implementing folder_base_interface2 */ SHCOLSTATEF get_default_column_state(UINT column_index) { ColumnType col(column_index); return col.state(); } /** * Detailed information about an item in a folder. * * The desired detail is specified by PROPERTYKEY. * * @implementing folder_base_interface2 */ VARIANT get_details_ex(PCUITEMID_CHILD pidl, const SHCOLUMNID* pscid) { if (::ILIsEmpty(pidl)) BOOST_THROW_EXCEPTION(comet::com_error(E_INVALIDARG)); return property(*pscid, pidl).detach(); } protected: /** * Return the folder implementation's CLSID. * * This allows callers to identify the type of folder for persistence * etc. */ virtual CLSID clsid() const = 0; /** * Check if a PIDL is recognised. * * Explorer has a tendency to pass our folders PIDLs that don't belong to * them to see if we are paying attention (or, more likely, to see if it * can use some PIDL data that it cached earlier. We need to disbelieve * everything Explorer tells us an validate each PIDL it gives us. * * Implementation should sniff the PIDLs passed to this method and * throw an exception of they don't recognise them. */ virtual void validate_pidl(PCUIDLIST_RELATIVE pidl) const = 0; /** * Determine the relative order of two file objects or folders. * * @returns * - Negative: pidl1 < pidl2 * - Positive: pidl1 > pidl2 * - Zero: pidl1 == pidl2 * * This is one of the most important methods to get right. When * implementing it, take care that if two PIDLs don't represent the * same filesystem item you don't return 0 from this method! Explorer * uses this to test if an item it has cached and if you falsely * claim that it is Explorer is likely not to bother looking at your * item becuase it thinks it already has it. * * If compare_all_fields is false, the PIDLs are compared by the * data that corresponds to the given column index. Otherwise, the PIDLs * are compared by all the data the contain. * * @todo We aren't actually comparing raw PIDLs here when * compare_all_fields is true. We should be. * * @todo Do something with canonical flag. */ virtual int compare_pidls( PCUITEMID_CHILD pidl1, PCUITEMID_CHILD pidl2, int column, bool compare_all_fields, bool /*canonical*/) const // TODO: This should be a PURE virtual method. We're putting the // impl here temporarily. Move it somewhere better! { if (compare_all_fields) { // FIXME: This should ignore columns completely and do a raw PIDL // comparison try { int i = 0; while (true) { int result = compare_pidls(pidl1, pidl2, i, false, false); if (result != 0) return result; } } catch (const std::range_error&) { return 0; } } else { ColumnType col(column); return col.compare(pidl1, pidl2); } } /** * The caller is requesting an object associated with the current folder. * * The default implementation throws an E_NOINTERFACE exception which * indicates to the caller that the object doesn't exist. You almost * certainly want to override this method in your folder. * * Examples of the type of object that Explorer may request include * - IShellView, the GUI representation of your folder * - IDropTarget, in order to support drag-and-drop into your GUI window * - IContextMenu, if your folder has a context menu * * This method corresponds roughly to CreateViewObject() but also * deals with the unusual case where GetUIObjectOf() is called without * any PIDLs. */ virtual ATL::CComPtr folder_object(HWND hwnd, REFIID riid) = 0; /** * The caller is requesting an object associated with one of more items * in the current folder. * * The default implementation throws an E_NOINTERFACE exception which * indicates to the caller that the object doesn't exist. You almost * certainly want to override this method in your folder to return * associated objects for @b non-folder item. It is also common to * handle some objects for sub-folder items too. For example, handling * IDropTarget requests here avoids the need to bind to an instance of * the subfolder implementation first. * * If a request isn't handled here (this method throws E_NOINTERFACE) * and it's possible to bind to the item's IShellFolder interface then * the request is delegated to the folder's CreateViewObject method and, * assuming that folder is implemented with this class, will end up at * the subfolder's folder_object() method. However if this method throws * a different error, the request is not delegated. * * This method corresponds roughly to GetUIObjectOf(). */ virtual ATL::CComPtr folder_item_object( HWND hwnd, REFIID riid, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl) = 0; /** * The caller is asking for an IShellFolder handler for a subfolder. * * The pidl passed to this method will be the @b child item whose folder * is being requested. It may not end in one of our own PIDLs if this * is the root folder of our own hierarchy. In that case it will be * the PIDL of the external hosting folder such as 'Desktop' or * 'My Computer' * * Respond to the request by creating an instance of the subfolder * handler object (which may well be another instance of your folder * class) and initialise it with the PIDL. * * This method corresonds to BindToFolder() where the item is directly * in the current folder (not a grandchild). */ virtual ATL::CComPtr subfolder( const washer::shell::pidl::cpidl_t& pidl) = 0; /** * The caller is asking for some property of an item in this folder. * * Which property is indicated by the given PROPERTYKEY (a GUID, aka * SHCOLUMNID). Common PROPERTYKEYs are in Propkey.h. * * The return value is a variant so can be any type that VARIANTs * support. */ virtual comet::variant_t property( const washer::shell::property_key& key, const washer::shell::pidl::cpidl_t& pidl) = 0; private: /** * Delegate associated object lookup to subfolder item's CreateViewObject. * * Attempts to bind to the item, given in the PIDL, as an IShellFolder. * If this succeeds, that folder is queried for its associated object by * a call to IShellFolder::CreateViewObject(). */ ATL::CComPtr CFolder::_delegate_object_lookup_to_subfolder( HWND hwnd, REFIID riid, PCUITEMID_CHILD pidl) { HRESULT hr; ATL::CComPtr subfolder; hr = BindToObject(pidl, NULL, IID_PPV_ARGS(&subfolder)); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); ATL::CComPtr object; hr = subfolder->CreateViewObject( hwnd, riid, reinterpret_cast(&object)); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); return object; } washer::shell::pidl::apidl_t m_root_pidl; }; }}} // namespace swish::shell_folder::folder ================================================ FILE: swish/shell_folder/HostFolder.cpp ================================================ /* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "HostFolder.h" #include "RemoteFolder.h" #include "Registry.h" // For saved connection details #include "swish/debug.hpp" #include "swish/frontend/UserInteraction.hpp" // CUserInteraction #include "swish/host_folder/columns.hpp" // property_key_from_column_index #include "swish/host_folder/commands/Rename.hpp" #include "swish/host_folder/commands/Remove.hpp" #include "swish/host_folder/commands/commands.hpp" // host_folder_commands #include "swish/host_folder/context_menu_callback.hpp" #include "swish/host_folder/extract_icon.hpp" #include "swish/host_folder/host_management.hpp" #include "swish/host_folder/host_pidl.hpp" // host_itemid_view, // find_host_itemid // url_from_host_itemid #include "swish/host_folder/overlay_icon.hpp" #include "swish/host_folder/properties.hpp" // property_from_pidl #include "swish/host_folder/host_management.hpp" // RemoveConnectionFromRegistry #include "swish/host_folder/ViewCallback.hpp" // CViewCallback #include "swish/remotelimits.h" // Text field limits #include "swish/shell/shell_item_array.hpp" // shell_item_array_from_folder_items #include "swish/windows_api.hpp" // SHBindToParent #include "swish/trace.hpp" // trace #include // WASHER_COM_CATCH_INTERFACE #include // strret_to_string #include #include #include // make_smart_enumeration #include // com_error #include // For StringCchCopy #include // translate #include // make_shared #include #include // shared_ptr #include // BOOST_THROW_EXCEPTION #include // assert #include // memset #include #include using ATL::CComPtr; using ATL::CComObject; using comet::com_error; using comet::com_error_from_interface; using comet::com_ptr; using comet::make_smart_enumeration; using comet::throw_com_error; using comet::variant_t; using boost::locale::translate; using boost::make_shared; using boost::optional; using boost::shared_ptr; using std::vector; using std::wstring; using swish::frontend::CUserInteraction; using swish::host_folder::CViewCallback; using swish::host_folder::commands::host_folder_command_provider; using swish::host_folder::commands::Rename; using swish::host_folder::commands::Remove; using swish::host_folder::context_menu_callback; using swish::host_folder::create_host_itemid; using swish::host_folder::extract_icon_co; using swish::host_folder::find_host_itemid; using swish::host_folder::host_itemid_view; using swish::host_folder::host_management::FindConnectionInRegistry; using swish::host_folder::host_management::LoadConnectionsFromRegistry; using swish::host_folder::host_management::RenameConnectionInRegistry; using swish::host_folder::overlay_icon; using swish::host_folder::property_from_pidl; using swish::host_folder::property_key_from_column_index; using swish::host_folder::url_from_host_itemid; using swish::nse::Command; using swish::shell::shell_item_array_from_folder_items; using swish::tracing::trace; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::apidl_t; using washer::shell::pidl::pidl_t; using washer::shell::property_key; using washer::shell::strret_to_string; using washer::shell::string_to_strret; using washer::window::window; using washer::window::window_handle; namespace comet { template<> struct comtype<::IEnumIDList> { static const ::IID& uuid() throw() { return ::IID_IEnumIDList; } typedef ::IUnknown base; }; template<> struct enumerated_type_of { typedef PITEMID_CHILD is; }; template<> struct comtype { static const IID& uuid() { return IID_IQueryAssociations; } typedef ::IUnknown base; }; template<> struct comtype<::IExtractIconW> { static const ::IID& uuid() throw() { return ::IID_IExtractIconW; } typedef ::IUnknown base; }; /** * Copy policy used to create IEnumIDList from cpidl_t. */ template<> struct impl::type_policy { static void init(PITEMID_CHILD& raw_pidl, const cpidl_t& pidl) { pidl.copy_to(raw_pidl); } static void clear(PITEMID_CHILD& raw_pidl) { ::CoTaskMemFree(raw_pidl); } }; template<> struct comtype<::IShellIconOverlay> { static const ::IID& uuid() throw() { return ::IID_IShellIconOverlay; } typedef ::IUnknown base; }; } /*--------------------------------------------------------------------------*/ /* Functions implementing IShellFolder via folder_error_adapter. */ /*--------------------------------------------------------------------------*/ /** * Create an IEnumIDList which enumerates the items in this folder. * * @implementing folder_error_adapter * * @param hwnd Optional window handle used if enumeration requires user * input. * @param flags Flags specifying which types of items to include in the * enumeration. Possible flags are from the @c SHCONT enum. * * @retval S_FALSE if the are no matching items to enumerate. */ IEnumIDList* CHostFolder::enum_objects(HWND hwnd, SHCONTF flags) { UNREFERENCED_PARAMETER(hwnd); // No UI required to access registry // This folder only contains folders if (!(flags & SHCONTF_FOLDERS) || (flags & (SHCONTF_NETPRINTERSRCH | SHCONTF_SHAREABLE))) return NULL; // Load connections from HKCU\Software\Swish\Connections return make_smart_enumeration( make_shared< vector >(LoadConnectionsFromRegistry())).detach(); } /** * Convert path string relative to this folder into a PIDL to the item. * * @implementing folder_error_adapter * * @todo Handle the attributes parameter. Should just return * GetAttributesOf() the PIDL we create but it is a bit hazy where the * host PIDL's responsibilities end and the remote PIDL's start because * of the path embedded in the host PIDL. */ PIDLIST_RELATIVE CHostFolder::parse_display_name( HWND hwnd, IBindCtx* bind_ctx, const wchar_t* display_name, ULONG* attributes_inout) { trace(__FUNCTION__" called (display_name=%s)") % display_name; // The string we are trying to parse should be of the form: // sftp://username@hostname:port/path wstring strDisplayName(display_name); if (strDisplayName.empty()) { PIDLIST_RELATIVE pidl; root_pidl().copy_to(pidl); return pidl; } // Must start with sftp:// if (strDisplayName.substr(0, 7) != L"sftp://") BOOST_THROW_EXCEPTION(com_error(E_FAIL)); // Must have @ to separate username from hostname wstring::size_type nAt = strDisplayName.find_first_of(L'@', 7); if (nAt == wstring::npos) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); // Must have : to separate hostname from port number wstring::size_type nColon = strDisplayName.find_first_of(L':', 7); if (nAt == wstring::npos) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); if (nColon <= nAt) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); // Must have / to separate port number from path wstring::size_type nSlash = strDisplayName.find_first_of(L'/', 7); if (nAt == wstring::npos) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); if (nColon <= nAt) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); wstring strUser = strDisplayName.substr(7, nAt - 7); wstring strHost = strDisplayName.substr(nAt+1, nColon - (nAt+1)); wstring strPort = strDisplayName.substr(nColon+1, nAt - (nSlash+1)); wstring strPath = strDisplayName.substr(nSlash+1); if (strUser.empty() || strHost.empty() || strPort.empty() || strPath.empty()) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); int nPort = _wtoi(strPort.c_str()); if (nPort < MIN_PORT || nPort > MAX_PORT) BOOST_THROW_EXCEPTION(com_error(E_FAIL)); // Create child PIDL for this path segment cpidl_t pidl = create_host_itemid(strHost, strUser, strPath, nPort); com_ptr subfolder; bind_to_object( pidl.get(), bind_ctx, subfolder.iid(), reinterpret_cast(subfolder.out())); wchar_t wszPath[MAX_PATH]; ::StringCchCopyW(wszPath, ARRAYSIZE(wszPath), strPath.c_str()); pidl_t pidl_path; HRESULT hr = subfolder->ParseDisplayName( hwnd, bind_ctx, wszPath, NULL, pidl_path.out(), attributes_inout); if (FAILED(hr)) throw_com_error(subfolder.get(), hr); pidl_t pidl_out = root_pidl() + pidl_path; return pidl_out.detach(); } /** * Retrieve the display name for the specified file object or subfolder. * * @implementing folder_error_adapter */ STRRET CHostFolder::get_display_name_of(PCUITEMID_CHILD pidl, SHGDNF flags) { if (::ILIsEmpty(pidl)) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); wstring name; if (flags & SHGDN_FORPARSING) { if (!(flags & SHGDN_INFOLDER)) { // Bind to parent com_ptr parent; PCUITEMID_CHILD pidlThisFolder = NULL; HRESULT hr = swish::windows_api::SHBindToParent( root_pidl().get(), parent.iid(), reinterpret_cast(parent.out()), &pidlThisFolder); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); STRRET strret; std::memset(&strret, 0, sizeof(strret)); hr = parent->GetDisplayNameOf(pidlThisFolder, flags, &strret); if (FAILED(hr)) throw_com_error(parent.get(), hr); name = strret_to_string( strret, pidlThisFolder) + L'\\'; } name += url_from_host_itemid(pidl, true); } else if (flags == SHGDN_NORMAL || flags & SHGDN_FORADDRESSBAR) { name = url_from_host_itemid(pidl, false); } else if (flags == SHGDN_INFOLDER || flags & SHGDN_FOREDITING) { name = host_itemid_view(pidl).label(); } else { UNREACHABLE; BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); } return string_to_strret(name); } namespace { void notify_shell_that_rename_occurred( const apidl_t& old_pidl, const apidl_t& new_pidl) { ::SHChangeNotify( SHCNE_RENAMEFOLDER, SHCNF_IDLIST, old_pidl.get(), new_pidl.get()); } } /** * Rename item. */ PITEMID_CHILD CHostFolder::set_name_of( HWND /*hwnd*/, PCUITEMID_CHILD pidl, const wchar_t* new_label, SHGDNF /*flags*/) { wstring from_label = host_itemid_view(pidl).label(); RenameConnectionInRegistry(from_label, new_label); optional connection = FindConnectionInRegistry(new_label); if (connection) { notify_shell_that_rename_occurred( root_pidl() + pidl, root_pidl() + *connection); return connection->detach(); } else { return NULL; } } /** * Returns the attributes for the items whose PIDLs are passed in. * * @implementing folder_error_adapter */ void CHostFolder::get_attributes_of( UINT pidl_count, PCUITEMID_CHILD_ARRAY pidl_array, SFGAOF* attributes_inout) { com_ptr selection = shell_item_array_from_folder_items(this, pidl_count, pidl_array); DWORD dwAttribs = 0; dwAttribs |= SFGAO_FOLDER; dwAttribs |= SFGAO_HASSUBFOLDER; // This adds a 'rename' item to the default context menu that SetNameOf // directly on the IShellFolder Rename rename_command; if (rename_command.state(selection, false) == Command::state::enabled) { dwAttribs |= SFGAO_CANRENAME; } // This adds an 'delete' item to the default context menu that calls the // menu handler with ID DFM_CMD_DELETE Remove remove_command(root_pidl()); if (remove_command.state(selection, false) == Command::state::enabled) { dwAttribs |= SFGAO_CANDELETE; } *attributes_inout &= dwAttribs; } /*--------------------------------------------------------------------------*/ /* Functions implementing IShellFolder2 via folder2_error_adapter. */ /*--------------------------------------------------------------------------*/ /** * Convert column index to matching PROPERTYKEY, if any. * * @implementing folder2_error_adapter */ SHCOLUMNID CHostFolder::map_column_to_scid(UINT column_index) { return property_key_from_column_index(column_index).get(); } /*--------------------------------------------------------------------------*/ /* Functions implementing IShellIconOverlay */ /*--------------------------------------------------------------------------*/ STDMETHODIMP CHostFolder::GetOverlayIndex(PCUITEMID_CHILD item, int* index) { try { overlay_icon overlay(item); if (overlay.has_overlay()) { *index = overlay.index(); return S_OK; } else { return S_FALSE; } } WASHER_COM_CATCH_INTERFACE(IShellIconOverlay) } STDMETHODIMP CHostFolder::GetOverlayIconIndex( PCUITEMID_CHILD item, int* icon_index) { try { overlay_icon overlay(item); if (overlay.has_overlay()) { *icon_index = overlay.icon_index(); return S_OK; } else { return S_FALSE; } } WASHER_COM_CATCH_INTERFACE(IShellIconOverlay) } /*--------------------------------------------------------------------------*/ /* CFolder NVI internal interface. */ /* These method implement the internal interface of the CFolder abstract */ /* class */ /*--------------------------------------------------------------------------*/ /** * Return the folder's registered CLSID * * @implementing CFolder */ CLSID CHostFolder::clsid() const { return __uuidof(this); } /** * Sniff PIDLs to determine if they are of our type. Throw if not. * * @implementing CFolder */ void CHostFolder::validate_pidl(PCUIDLIST_RELATIVE pidl) const { if (pidl == NULL) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); if (!host_itemid_view(pidl).valid()) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); } namespace { com_ptr consumer_factory(HWND hwnd) { return new CUserInteraction(hwnd); } } /** * Create and initialise new folder object for subfolder. * * @implementing CFolder * * Create CRemoteFolder initialised with its root PIDL. CHostFolders * don't have any other types of subfolder. */ CComPtr CHostFolder::subfolder(const cpidl_t& pidl) { CComPtr folder = CRemoteFolder::Create( (root_pidl() + pidl).get(), consumer_factory); ATLENSURE_THROW(folder, E_NOINTERFACE); return folder; } /** * Return a property, specified by PROERTYKEY, of an item in this folder. */ variant_t CHostFolder::property(const property_key& key, const cpidl_t& pidl) { return property_from_pidl(pidl, key); } /*--------------------------------------------------------------------------*/ /* CSwishFolder internal interface. */ /* These method override the (usually no-op) implementations of some */ /* in the CSwishFolder base class */ /*--------------------------------------------------------------------------*/ /** * Create a toolbar command provider for the folder. */ CComPtr CHostFolder::command_provider( HWND /*owning_hwnd*/) { TRACE("Request: IExplorerCommandProvider"); return host_folder_command_provider(root_pidl()).get(); } /** * Create an icon extraction helper object for the selected item. * * @implementing CSwishFolder */ CComPtr CHostFolder::extract_icon_w( HWND hwnd_view, PCUITEMID_CHILD pidl) { optional> owning_view; if (hwnd_view) owning_view = window(window_handle::foster_handle(hwnd_view)); return new extract_icon_co(owning_view, pidl); } /** * Create a file association handler for host items. * * @implementing CSwishFolder * * We don't need to look at the PIDLs as all host items are the same. */ CComPtr CHostFolder::query_associations( HWND /*hwnd*/, UINT /*cpidl*/, PCUITEMID_CHILD_ARRAY /*apidl*/) { TRACE("Request: IQueryAssociations"); CComPtr spAssoc; HRESULT hr = ::AssocCreate( CLSID_QueryAssociations, IID_PPV_ARGS(&spAssoc)); ATLENSURE_SUCCEEDED(hr); // Get CLSID in {DWORD-WORD-WORD-WORD-WORD.DWORD} form LPOLESTR posz; ::StringFromCLSID(__uuidof(CHostFolder), &posz); shared_ptr clsid(posz, ::CoTaskMemFree); posz = NULL; // Initialise default assoc provider to use Swish CLSID key for data. // This is necessary to pick up properties and TileInfo etc. hr = spAssoc->Init(0, clsid.get(), NULL, NULL); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(spAssoc.p, hr)); return spAssoc; } namespace { HRESULT CALLBACK menu_callback( IShellFolder* folder, HWND hwnd_view, IDataObject* selection, UINT message_id, WPARAM wparam, LPARAM lparam) { CRemoteFolder* remote_folder = static_cast(folder); context_menu_callback callback(remote_folder->root_pidl()); return callback(hwnd_view, selection, message_id, wparam, lparam); } } /** * Create a context menu for the selected items. * * @implementing CSwishFolder */ CComPtr CHostFolder::context_menu( HWND hwnd, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl) { TRACE("Request: IContextMenu"); assert(cpidl > 0); // Get keys associated with filetype from registry. // We only take into account the item that was right-clicked on // (the first array element) even if this was a multi-selection. // // This article says that we don't need to specify the keys: // http://groups.google.com/group/microsoft.public.platformsdk.shell/ // browse_thread/thread/6f07525eaddea29d/ // but we do for the context menu to appear in versions of Windows // earlier than Vista. HKEY *akeys; UINT ckeys; ATLENSURE_THROW(SUCCEEDED( CRegistry::GetHostFolderAssocKeys(&ckeys, &akeys)), E_UNEXPECTED // Might fail if registry is corrupted ); CComPtr spThisFolder = this; ATLENSURE_THROW(spThisFolder, E_OUTOFMEMORY); // Create default context menu from list of PIDLs CComPtr spMenu; HRESULT hr = ::CDefFolderMenu_Create2( root_pidl().get(), hwnd, cpidl, apidl, spThisFolder, menu_callback, ckeys, akeys, &spMenu); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); return spMenu; } /** * Create a data object for the selected items. * * @implementing CSwishFolder */ CComPtr CHostFolder::data_object( HWND /*hwnd*/, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl) { TRACE("Request: IDataObject"); assert(cpidl > 0); // A DataObject is required in order for the call to // CDefFolderMenu_Create2 (above) to succeed on versions of Windows // earlier than Vista CComPtr spdo; HRESULT hr = ::CIDLData_CreateFromIDArray( root_pidl().get(), cpidl, reinterpret_cast(apidl), &spdo); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); return spdo; } /** * Create an instance of our Shell Folder View callback handler. * * @implementing CSwishFolder */ CComPtr CHostFolder::folder_view_callback(HWND /*hwnd*/) { return new CViewCallback(root_pidl()); } ================================================ FILE: swish/shell_folder/HostFolder.h ================================================ /** @file Explorer folder to handle SFTP connection items. @if license Copyright (C) 2007, 2008, 2009, 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "SwishFolder.hpp" // Superclass #include "swish/host_folder/columns.hpp" // Column #include "resource.h" // main symbols #include "Swish.h" // For CHostFolder UUID #include "swish/CoFactory.hpp" // CComObject factory #include "swish/atl.hpp" // Common ATL setup #include class ATL_NO_VTABLE CHostFolder : public IShellIconOverlay, public swish::shell_folder::folder::CSwishFolder< swish::host_folder::Column> { public: BEGIN_COM_MAP(CHostFolder) COM_INTERFACE_ENTRY(IShellIconOverlay) COM_INTERFACE_ENTRY_CHAIN(CSwishFolder) END_COM_MAP() protected: CLSID clsid() const; void validate_pidl(PCUIDLIST_RELATIVE pidl) const; ATL::CComPtr subfolder( const washer::shell::pidl::cpidl_t& pidl); comet::variant_t property( const washer::shell::property_key& key, const washer::shell::pidl::cpidl_t& pidl); ATL::CComPtr command_provider(HWND hwnd); ATL::CComPtr extract_icon_w( HWND hwnd, PCUITEMID_CHILD pidl); ATL::CComPtr query_associations( HWND hwnd, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl); ATL::CComPtr context_menu( HWND hwnd, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl); ATL::CComPtr data_object( HWND hwnd, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl); ATL::CComPtr folder_view_callback(HWND hwnd); public: // IShellFolder (via folder_error_adapter) virtual IEnumIDList* enum_objects(HWND hwnd, SHCONTF flags); virtual void get_attributes_of( UINT pidl_count, PCUITEMID_CHILD_ARRAY pidl_array, SFGAOF* flags_inout); virtual STRRET get_display_name_of( PCUITEMID_CHILD pidl, SHGDNF uFlags); virtual PIDLIST_RELATIVE parse_display_name( HWND hwnd, IBindCtx* bind_ctx, const wchar_t* display_name, ULONG* attributes_inout); virtual PITEMID_CHILD set_name_of( HWND hwnd, PCUITEMID_CHILD pidl, const wchar_t* name, SHGDNF flags); // IShellFolder2 (via folder_error_adapter2) virtual SHCOLUMNID map_column_to_scid(UINT column_index); // IShellIconOverlay STDMETHOD(GetOverlayIndex)(PCUITEMID_CHILD item, int* index); STDMETHOD(GetOverlayIconIndex)(PCUITEMID_CHILD item, int* icon_index); }; ================================================ FILE: swish/shell_folder/IconExtractor.cpp ================================================ /* Implementation of icon extraction handler. Copyright (C) 2008 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "IconExtractor.h" #include // For SHGetFileInfo #include // For StringCchCopy using ATL::CComPtr; /*static*/ CComPtr CIconExtractor::Create( PCTSTR szFilename, bool fIsFolder) { CComPtr sp = CIconExtractor::CreateCoObject(); sp->Initialize(szFilename, fIsFolder); return sp.p; } /** * Sets file or folder that this IconExtractor is being used for. * * @param szFilename The filename of the file or folder whose icon we want. * @param fIsFolder Flag whether this is a file (@c false) or folder (@c true). */ void CIconExtractor::Initialize(PCTSTR szFilename, bool fIsFolder) { m_fForFolder = fIsFolder; m_strFilename = szFilename; } /** * Retrieves location of the appropriate icon as an index into the system list. * * @implementing IExtractIconW * * @param[in] uFlags Flags that determine what type of icon is being * requested. * @param[out] pwszIconFile The name of the file to find the icon in. In our * case we return '*' to indicate that the icon is in * the system index and the value returned in * @p piIndex is the index to it. * @param[in] cchMax The size of the buffer passed as @p szIconFile. * @param[out] piIndex The index to the icon in the system list. * @param[out] pwFlags Output flags. In our case set to indicate that * @p szIconFile is not a real filename. * */ STDMETHODIMP CIconExtractor::GetIconLocation( UINT uFlags, PWSTR pwszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags ) { ATLTRACE("CIconExtractor::GetIconLocation called\n"); // Look for icon's index into system list int index = _GetIconIndex(uFlags); if (index < 0) return S_FALSE; // None found - make shell use Unknown // Output * as filename to indicate icon is in system list ATLVERIFY(SUCCEEDED( ::StringCchCopyW(pwszIconFile, cchMax, L"*"))); *pwFlags = GIL_NOTFILENAME; *piIndex = index; return S_OK; } /** * @overload * @implementing IExtractIconA */ STDMETHODIMP CIconExtractor::GetIconLocation( UINT uFlags, LPSTR szIconFile, UINT cchMax, int *piIndex, UINT *pwFlags) { ATLTRACE("CIconExtractor::GetIconLocation called\n"); // Look for icon's index into system list int index = _GetIconIndex(uFlags); if (index < 0) return S_FALSE; // None found - make shell use Unknown // Output * as filename to indicate icon is in system list ATLVERIFY(SUCCEEDED( ::StringCchCopyA(szIconFile, cchMax, "*"))); *pwFlags = GIL_NOTFILENAME; *piIndex = index; return S_OK; } /** * Extract an icon bitmap given the information passed. * * @implementing IExtractIconW * * @returns S_FALSE to tell the shell to extract the icons itself. */ STDMETHODIMP CIconExtractor::Extract(PCWSTR, UINT, HICON *, HICON *, UINT) { ATLTRACE("CIconExtractor::Extract called\n"); return S_FALSE; } /** * @overload * @implementing IExtractIconA */ STDMETHODIMP CIconExtractor::Extract(PCSTR, UINT, HICON *, HICON *, UINT) { ATLTRACE("CIconExtractor::Extract (A) called\n"); return S_FALSE; } int CIconExtractor::_GetIconIndex(UINT uFlags) { if (uFlags & GIL_DEFAULTICON) return 0; DWORD dwAttributes = (m_fForFolder) ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL; UINT uInfoFlags = SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX; if (uFlags & GIL_OPENICON) uInfoFlags |= SHGFI_OPENICON; // Look up index to default icon in current system list { // Get index to default icon in system list SHFILEINFO shfi; if(::SHGetFileInfo(m_strFilename, dwAttributes, &shfi, sizeof(shfi), uInfoFlags)) { return shfi.iIcon; } else return -1; // None found } } // CIconExtractor ================================================ FILE: swish/shell_folder/IconExtractor.h ================================================ /** @file Component allowing icon extraction based on file extension. @if license Copyright (C) 2008, 2009, 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "swish/CoFactory.hpp" // CoObject factory mixin #include "swish/atl.hpp" // Common ATL setup #include // CString #include // Windows Shell API class ATL_NO_VTABLE CIconExtractor : public IExtractIconW, public IExtractIconA, public ATL::CComObjectRoot, public swish::CCoFactory { public: BEGIN_COM_MAP(CIconExtractor) COM_INTERFACE_ENTRY(IExtractIconW) COM_INTERFACE_ENTRY(IExtractIconA) END_COM_MAP() CIconExtractor() : m_fForFolder(false) {} static ATL::CComPtr Create( PCTSTR szFilename, bool fIsFolder); void Initialize(PCTSTR szFilename, bool fIsFolder); // IExtractIconW IFACEMETHODIMP GetIconLocation( UINT uFlags, __out_ecount(cchMax) PWSTR pwszIconFile, UINT cchMax, __out int *piIndex, __out UINT *pwFlags); IFACEMETHODIMP Extract( PCWSTR pszFile, UINT nIconIndex, __out_opt HICON *phiconLarge, __out_opt HICON *phiconSmall, UINT nIconSize); // IExtractIconA IFACEMETHODIMP GetIconLocation( UINT uFlags, __out_ecount(cchMax) LPSTR szIconFile, UINT cchMax, __out int *piIndex, __out UINT *pwFlags); IFACEMETHODIMP Extract( PCSTR pszFile, UINT nIconIndex, __out_opt HICON *phiconLarge, __out_opt HICON *phiconSmall, UINT nIconSize); private: int _GetIconIndex(UINT uFlags); bool m_fForFolder; ///< Are we trying to extract the icon ///< for a Folder? ATL::CString m_strFilename; ///< File to get default icon for. }; ================================================ FILE: swish/shell_folder/KbdInteractiveDialog.cpp ================================================ /* WTL dialog box for keyboard-interactive requests. Copyright (C) 2008, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "KbdInteractiveDialog.h" #include "swish/utils.hpp" #include // BOOST_FOREACH #include // translate #include "wtl.hpp" // WTL #include // WTL control wrappers #include // CString using swish::utils::Utf8StringToWideString; using swish::utils::WideStringToUtf8String; using boost::locale::translate; using WTL::CStatic; using WTL::CEdit; using WTL::CButton; using WTL::CClientDC; using WTL::CFontHandle; using ATL::CString; using std::pair; using std::string; using std::vector; #define SEPARATION 10 #define MINI_SEPARATION 3 #define RESPONSE_BOX_HEIGHT 22 CKbdInteractiveDialog::CKbdInteractiveDialog( const std::string& title, const std::string& instructions, const std::vector>& prompts) : m_title(title), m_instructions(instructions), m_prompts(prompts) {} vector CKbdInteractiveDialog::GetResponses() { return m_responses; } /*----------------------------------------------------------------------------* * Event Handlers *----------------------------------------------------------------------------*/ LRESULT CKbdInteractiveDialog::OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) { // If server specifies a title use it as the dialogue title if (!m_title.empty()) this->SetWindowText( Utf8StringToWideString(m_title).c_str()); else this->SetWindowText( translate(L"Keyboard-interactive request").str().c_str()); // Get size of this dialogue box CRect rectDialog; this->GetClientRect(rectDialog); // Control drawing 'cursor' - increment each time we move down the window CPoint point(0,0); // Draw instruction label CRect rect = _DrawInstruction(rectDialog); point.Offset(rect.left, rect.Height()+2*SEPARATION); // Draw prompts and response boxes typedef pair prompt_pair; BOOST_FOREACH(const prompt_pair& prompt, m_prompts) { CRect rectPrompt = _DrawPrompt(prompt.first, point, rectDialog); // Increment point by height of prompt text plus small separation point.Offset(0, rectPrompt.Height() + MINI_SEPARATION); CRect rectResponse = _DrawResponseBox( !prompt.second, point, rectDialog); // Increment point by height of response box plus separation point.Offset(0, rectResponse.Height() + SEPARATION); } // Move OK and Cancel below prompts CRect rectOKCancel = _DrawOKCancel(point, rectDialog); // Expand dialogue downward to include all controls rectDialog.bottom = rectOKCancel.bottom + SEPARATION; ATLVERIFY( this->ResizeClient(rectDialog.Width(), rectDialog.Height()) ); // Place dialogue and set focus CenterWindow(); if (m_vecResponseWindows.size() && m_vecResponseWindows[0]) ::SetFocus(m_vecResponseWindows[0]); return 0; } LRESULT CKbdInteractiveDialog::OnOK(WORD, WORD wID, HWND, BOOL&) { _ExchangeData(); EndDialog(wID); return 0; } LRESULT CKbdInteractiveDialog::OnCancel(WORD, WORD wID, HWND, BOOL&) { EndDialog(wID); return 0; } /*----------------------------------------------------------------------------* * Private functions *----------------------------------------------------------------------------*/ CRect CKbdInteractiveDialog::_DrawInstruction(CRect rectDialog) { // Fix instruction text's width to 20px fewer than the dialog and centre CRect rect(0, 0, rectDialog.Width()-20, 0); rect.OffsetRect(10, 10); // Always draw the instruction label even if empty to override resource text CStatic instruction(GetDlgItem(IDC_INSTRUCTION)); instruction.SetWindowText(Utf8StringToWideString(m_instructions).c_str()); CFontHandle font, fontOld; font = instruction.GetFont(); // Calculate neccessary size of instruction label CClientDC dc(instruction); fontOld = dc.SelectFont(font); dc.DrawText(Utf8StringToWideString(m_instructions).c_str(), -1, rect, DT_CALCRECT | DT_WORDBREAK | DT_NOPREFIX); dc.SelectFont(fontOld); // Set instruction size, position and text instruction.MoveWindow(rect); instruction.SetWindowText(Utf8StringToWideString(m_instructions).c_str()); // Return instruction's rectangle return rect; } CRect CKbdInteractiveDialog::_DrawPrompt( const string& prompt, CPoint point, CRect rectDialog) { // Use same font as instruction label CStatic instruction(GetDlgItem(IDC_INSTRUCTION)); CFontHandle font, fontOld; font = instruction.GetFont(); // Prompt label CStatic prompt_label; prompt_label.Create(*this, NULL, NULL, WS_VISIBLE | WS_CHILD | SS_WORDELLIPSIS | SS_NOPREFIX); // Fix prompt text's width to 20px fewer than the dialog CRect rect(0, 0, rectDialog.Width()-20, 0); // Calculate necessary (vertical) size of prompt label CClientDC dcPrompt(prompt_label); fontOld = dcPrompt.SelectFont(font); dcPrompt.DrawText( Utf8StringToWideString(prompt).c_str(), -1, rect, DT_CALCRECT | DT_WORD_ELLIPSIS | DT_NOPREFIX); dcPrompt.SelectFont(fontOld); // Set prompt size, position, font and text rect.OffsetRect(point); prompt_label.MoveWindow(rect); prompt_label.SetFont(font); prompt_label.SetWindowText(Utf8StringToWideString(prompt).c_str()); // Return prompt's rectangle return rect; } CRect CKbdInteractiveDialog::_DrawResponseBox( bool fHideResponse, CPoint point, CRect rectDialog) { // Use same font as instruction label CStatic instruction(GetDlgItem(IDC_INSTRUCTION)); CFontHandle font = instruction.GetFont(); // Response text box CEdit edit; DWORD dwWindowFlags = WS_VISIBLE | WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL; if (fHideResponse) { dwWindowFlags |= ES_PASSWORD; } DWORD dwWindowFlagsEx = WS_EX_CLIENTEDGE; edit.Create(*this, NULL, NULL, dwWindowFlags, dwWindowFlagsEx); m_vecResponseWindows.push_back(edit); // Push onto list of response boxes // Fix response box's width to 20px fewer than the dialog CRect rect(0, 0, rectDialog.Width()-20, RESPONSE_BOX_HEIGHT); // Set response size, position and font rect.MoveToXY(point.x, point.y); edit.MoveWindow(rect); edit.SetFont(font); // Return response box's rectangle return rect; } CRect CKbdInteractiveDialog::_DrawOKCancel( CPoint point, CRect rectDialog) { CButton btnOK(GetDlgItem(IDOK)), btnCancel(GetDlgItem(IDCANCEL)); CRect rectOK, rectCancel; ATLVERIFY( btnOK.GetClientRect(rectOK) ); ATLVERIFY( btnCancel.GetClientRect(rectCancel) ); rectCancel.MoveToXY( rectDialog.right - rectCancel.Width() - SEPARATION, point.y + SEPARATION); rectOK.MoveToXY( rectDialog.right - rectCancel.Width() - rectOK.Width() - 2*SEPARATION, point.y + SEPARATION); ATLVERIFY( btnOK.MoveWindow(rectOK) ); ATLVERIFY( btnCancel.MoveWindow(rectCancel) ); // Return the union of the two buttons' rectangles return rectOK | rectCancel; } /** * Copy data from response Win32 edit boxes into member variable. * * This is necessary as the dialogue and its text boxed are destroyed when OK * or Cancel is clicked. Therefore, this function must be called in the * OK button click event handler. The responses can be retrieved from the * member variable using the GetResonses() function after the * dialogue window has been destroyed. */ void CKbdInteractiveDialog::_ExchangeData() { m_responses.clear(); for each (HWND hwnd in m_vecResponseWindows) { CEdit edit(hwnd); CString strResponse; edit.GetWindowText(strResponse); m_responses.push_back(WideStringToUtf8String(strResponse.GetString())); } } ================================================ FILE: swish/shell_folder/KbdInteractiveDialog.h ================================================ /** @file WTL dialog box for keyboard-interactive requests. @if license Copyright (C) 2008, 2009, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "resource.h" // main symbols #include "swish/atl.hpp" // Common ATL setup #include // ATL windowing classes #include // For CRect and CPoint #include #include // pair #include class CKbdInteractiveDialog : public ATL::CDialogImpl { public: /** Dialog box resource identifier */ enum { IDD = IDD_KBDINTERACTIVEDIALOG }; CKbdInteractiveDialog( const std::string& title, const std::string& instructions, const std::vector>& prompts); std::vector GetResponses(); BEGIN_MSG_MAP(CKbdInteractiveDialog) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_HANDLER(IDOK, BN_CLICKED, OnOK) COMMAND_HANDLER(IDCANCEL, BN_CLICKED, OnCancel) END_MSG_MAP() private: /** @name Message handlers */ // @{ LRESULT __callback OnInitDialog(UINT, WPARAM, LPARAM, BOOL&); // @} /** @name Command handlers */ // @{ LRESULT __callback OnOK(WORD, WORD, HWND, BOOL&); LRESULT __callback OnCancel(WORD, WORD, HWND, BOOL&); // @} /** @name GUI drawing */ // @{ CRect _DrawInstruction(__in CRect rectDialog); CRect _DrawPrompt( const std::string&, __in CPoint point, __in CRect rectDialog); CRect _DrawResponseBox( bool fHideResponse, __in CPoint point, __in CRect rectDialog); CRect _DrawOKCancel(__in CPoint point, __in CRect rectDialog); // @} void _ExchangeData(); // Input std::string m_title; std::string m_instructions; std::vector> m_prompts; // Output std::vector m_vecResponseWindows; std::vector m_responses; }; ================================================ FILE: swish/shell_folder/Pidl.h ================================================ /** @file PIDL wrapper classes. @if license Copyright (C) 2008, 2009, 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include // com_error #include // Native PIDL handling /** * Base class of all PIDL classes which wrap a non-const PIDL. */ template class CPidlData { protected: /** @name PIDL Types */ // @{ typedef IdListType *PidlType; ///< @b Non-const PIDL type typedef const IdListType *ConstPidlType; ///< @b Const PIDL type typedef PidlType Type; ///< Which of const or non-const @b this PIDL is // @} CPidlData() : m_pidl(NULL) {} explicit CPidlData( __in_opt Type pidl ) throw() : m_pidl(pidl) {} explicit CPidlData( __in const CPidlData& pidl ) throw() : m_pidl(pidl) {} public: inline bool operator!() const throw() { return m_pidl == NULL; } /** * Implicitly convert wrapped PIDL to another type. * * This template delegates to Type (the type of the raw PIDL) decisions * about which types this PIDL can be converted to. The code will not * compile if @p IdListSupertype is not compatible with Type. Used in: * @code const IdListSupertype p = Type m_pidl @endcode * * @tparam IdListSupertype Target type of conversion. Does not have to be * supertype of raw PIDL type. May be same type. */ template operator const IdListSupertype() const throw() { return m_pidl; } Type m_pidl; }; /** * Base class of all PIDL classes which wrap a const PIDL. */ template class CPidlConstData { protected: /** @name PIDL Types */ // @{ typedef IdListType *PidlType; ///< @b Non-const PIDL type typedef const IdListType *ConstPidlType; ///< @b Const PIDL type typedef ConstPidlType Type; ///< Which of const or non-const @b this PIDL is // @} CPidlConstData() : m_pidl(NULL) {} CPidlConstData( __in_opt Type pidl ) throw() : m_pidl(pidl) {} CPidlConstData( __in const CPidlConstData& pidl ) throw() : m_pidl(pidl) {} public: inline bool operator!() const throw() { return m_pidl == NULL; } /** * Implicitly convert wrapped PIDL to another type. * * This template delegates to Type (the type of the raw PIDL) decisions * about which types this PIDL can be converted to. The code will not * compile if @p IdListSupertype is not compatible with Type. Used in: * @code const IdListSupertype p = Type m_pidl @endcode * * @tparam IdListSupertype Target type of conversion. Does not have to be * supertype of raw PIDL type. May be same type. */ template operator const IdListSupertype() const throw() { return m_pidl; } Type m_pidl; }; /** * Class adding operations common to all types of PIDL: const or non-const, * relative, absolute or child. * * The template takes the type of the ITEMIDLIST; either relative * (ITEMIDLIST_RELATIVE), absolute (ITEMIDLIST_ABSOLUTE) or child (ITEMID_CHILD). * This enhances type-safety when using the PIDL with functions, etc. The only * state stored by this wrapper is the PIDL itself and so can be used anywhere * a PIDL can. * * @param IdListType The type of ITEMIDLIST whose pointer to be wrapped; * either ITEMIDLIST_RELATIVE, ITEMIDLIST_ABSOLUTE or * ITEMID_CHILD. * * @attention This class requires STRICT_TYPED_ITEMIDS to be defined in order * to make the different types of PIDL distinct. Without it, all * three PIDL types appear to the compiler as LPITEMIDLIST. */ template class CPidlBase : public DataT { protected: typedef typename DataT::PidlType PidlType; typedef typename DataT::ConstPidlType ConstPidlType; public: CPidlBase() {} CPidlBase( __in_opt typename DataT::Type pidl ) throw() : DataT(pidl) {} CPidlBase( __in const CPidlBase& pidl ) throw() : DataT(pidl) {} PidlType CopyTo() const throw(...) { return Clone(m_pidl); } PidlType CopyParent() const throw(...) { PidlType pidl = CopyTo(); if (!::ILRemoveLastID(pidl)) { assert(false); throw comet::com_error(E_FAIL); } return pidl; } PCUIDLIST_RELATIVE GetNext() const throw() { if (m_pidl == NULL) return NULL; PCUIDLIST_RELATIVE pidl = ::ILNext(m_pidl); return (::ILIsEmpty(pidl)) ? NULL : pidl; } PCUITEMID_CHILD GetLast() const throw(...) { return ::ILFindLastID(m_pidl); } inline bool IsEmpty() const throw() { return ((m_pidl == NULL) || (m_pidl->mkid.cb == 0)); } static PidlType Clone( __in_opt ConstPidlType pidl ) throw(...) { if (pidl == NULL) return NULL; PidlType pidlOut = static_cast(::ILClone(pidl)); if (pidlOut == NULL) AtlThrow(E_OUTOFMEMORY); return pidlOut; } }; /** @name PIDL Handle type declarations. * * These types create PIDL wrappers with @b unmanaged lifetimes. The PIDLs * are also const so cannot be modified or destroyed. * * This effect is achieved by deriving the common PIDL operations base * class from a const-PIDL data wrapper. */ typedef CPidlBase< CPidlConstData > CRelativePidlHandle; typedef CPidlBase< CPidlConstData > CAbsolutePidlHandle; typedef CPidlBase< CPidlConstData > CChildPidlHandle; /** * Wrapper for a PIDL with a @b managed lifetime. * * This class augments the commmon operations provided in CPidlBase with * those that are specific to non-const PIDLs such as creation and destruction * as well as concatenation. * * The template (and those from which it is derived) takes the type of * the ITEMIDLIST; either relative (ITEMIDLIST_RELATIVE), absolute *(ITEMIDLIST_ABSOLUTE) or child (ITEMID_CHILD). This enhances * type-safety when using the PIDL with functions, etc. The only state * stored by this wrapper is the PIDL itself and so can be used anywhere * a PIDL can. * * Most methods that take a PIDL argument, including the constructors, make a * copy of the PIDL first, although the class can take ownership of an * exisiting PIDL with Attach(). * * Several of the methods return a reference to the current CPidl so that * operations can be chained, e.g. * @code pidl.Attach(old).Append(item).Detach() @endcode * * @param IdListType The type of ITEMIDLIST whose pointer to be wrapped; * either ITEMIDLIST_RELATIVE, ITEMIDLIST_ABSOLUTE or * ITEMID_CHILD. * * @attention This class requires STRICT_TYPED_ITEMIDS to be defined in order * to make the different types of PIDL distinct. Without it, all * three PIDL types appear to the compiler as LPITEMIDLIST. */ template class CPidl : public CPidlBase< CPidlData > { public: CPidl() {} CPidl( __in_opt ConstPidlType pidl ) throw(...) : CPidlBase(Clone(pidl)) {} CPidl( __in const CPidl& pidl ) throw(...) : CPidlBase(Clone(pidl)) {} CPidl& operator=( __in const CPidl& pidl ) throw(...) { if (m_pidl != pidl.m_pidl) { CopyFrom(pidl); } return *this; } /** * Concatenation constructor. */ explicit CPidl( __in_opt ConstPidlType pidl1, __in_opt PCUIDLIST_RELATIVE pidl2 ) throw(...) { if (::ILIsEmpty(pidl1) && ::ILIsEmpty(pidl2)) return; m_pidl = reinterpret_cast(::ILCombine( reinterpret_cast(pidl1), pidl2)); if (!m_pidl) throw comet::com_error(E_OUTOFMEMORY); } /** * Return raw address of PIDL to allow use as an out-parameter. * * Any existing PIDL will first be destroyed. */ PidlType* operator&() throw() { Delete(); return &m_pidl; } ~CPidl() throw() { Delete(); } CPidl& Attach( __in_opt PidlType pidl ) throw() { Delete(); m_pidl = pidl; return *this; } CPidl& CopyFrom( __in_opt ConstPidlType pidl ) throw(...) { return Attach(Clone(pidl)); } PidlType Detach() throw() { PidlType pidl = m_pidl; m_pidl = NULL; return pidl; } void Delete() { ::ILFree(m_pidl); m_pidl = NULL; } CPidl& Append(__in_opt PCUIDLIST_RELATIVE pidl) throw(...) { if (::ILIsEmpty(pidl)) return *this; return Attach(CPidl(m_pidl, pidl).Detach()); } }; /** * Wrapper around a @b relative PIDL with a @b managed lifetime. */ typedef CPidl CRelativePidl; /** * Wrapper around an @b absolute PIDL with a @b managed lifetime. */ typedef CPidl CAbsolutePidl; /** * Wrapper around a @b child PIDL with a @b managed lifetime. */ typedef CPidl CChildPidl; ================================================ FILE: swish/shell_folder/Registry.cpp ================================================ /** @file Helper class for Swish registry access. @if license Copyright (C) 2008, 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "Registry.h" #include "swish/debug.hpp" #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // path #include using swish::remote_folder::remote_itemid_view; using ATL::CRegKey; using ATL::CString; using boost::filesystem::path; using std::vector; using std::wstring; /** * Get registry keys for HostFolder connection association info. * * This list is not required for Windows Vista but, on any earlier * version, it must be passed to CDefFolderMenu_Create2 in order to display * the default context menu. * * Host connection items are treated as folders so the list of keys is: * HKCU\\Directory * HKCU\\Directory\\Background * HKCU\\Folder * HKCU\\* * HKCU\\AllFileSystemObjects * * @param[out] pcKeys Number of HKEYS allocated in the array @p paKeys. * @param[out] paKeys Location in which to return th allocated array. */ /* static */ HRESULT CRegistry::GetHostFolderAssocKeys( UINT *pcKeys, HKEY **paKeys) throw() { try { return _GetHKEYArrayFromKeynames( _GetHostFolderAssocKeynames(), pcKeys, paKeys); } WASHER_COM_CATCH(); } /** * Get registry keys for HostFolder connection association info. * * This list is not required for Windows Vista but, on any earlier * version, it must be passed to CDefFolderMenu_Create2 in order to display * the default context menu. * * A (ficticious) example might include: * HKCU\\.ppt * HKCU\\PowerPoint.Show * HKCU\\PowerPoint.Show.12 * HKCU\\SystemFileAssociations\\.ppt * HKCU\\SystemFileAssociations\\presentation * HKCU\\* * HKCU\\AllFileSystemObjects * for a file and: * HKCU\\Directory * HKCU\\Directory\\Background * HKCU\\Folder * HKCU\\AllFileSystemObjects * for a folder. * * @param[in] itemid Remote PIDL representing the file whose association * information is being requested. * @param[out] pcKeys Number of HKEYS allocated in the array @p paKeys. * @param[out] paKeys Location in which to return th allocated array. */ HRESULT CRegistry::GetRemoteFolderAssocKeys( remote_itemid_view itemid, UINT *pcKeys, HKEY **paKeys) { try { return _GetHKEYArrayFromKeynames( _GetRemoteFolderAssocKeynames(itemid), pcKeys, paKeys); } WASHER_COM_CATCH(); } namespace { CRegistry::KeyNames remote_folder_background_key_names() { CRegistry::KeyNames names; names.push_back(L"Directory\\Background"); return names; } } HRESULT CRegistry::GetRemoteFolderBackgroundAssocKeys( UINT *pcKeys, HKEY **paKeys) { try { return _GetHKEYArrayFromKeynames( remote_folder_background_key_names(), pcKeys, paKeys); } WASHER_COM_CATCH(); } /*----------------------------------------------------------------------------* * Private functions *----------------------------------------------------------------------------*/ /** * Get names of registry keys which provide association info for Folder items. * * Such a list is required by Windows XP and earlier in order to display the * default context menu. Only 'HKCR\\Folder' is relevant as the Swish hosts are * virtual folder items with no filesystem parallel. 'HKCR\\Directory' and * 'HKCR\AllFileSystemObjects' are for real filesystem items. 'HKCR\\*' is * not for folders at all. */ /* static */ CRegistry::KeyNames CRegistry::_GetHostFolderAssocKeynames() throw() { KeyNames vecNames; // Add virtual folder specific items vecNames.push_back(L"Folder"); return vecNames; } /** * Get a list names of registry keys for the types of the selected file. * * A (ficticious) example might include: * HKCU\\.ppt * HKCU\\PowerPoint.Show * HKCU\\PowerPoint.Show.12 * HKCU\\SystemFileAssociations\\.ppt * HKCU\\SystemFileAssociations\\presentation * HKCU\\* * HKCU\\AllFileSystemObjects * for a file and: * HKCU\\Folder * HKCU\\Directory * HKCU\\Directory\\Background * HKCU\\AllFileSystemObjects * for a folder. * * @param itemid Remote PIDL representing the file whose association * information is being requested. */ /* static */ CRegistry::KeyNames CRegistry::_GetRemoteFolderAssocKeynames( remote_itemid_view itemid) throw(...) { KeyNames vecNames; // If this is a directory, add directory-specific items if (itemid.is_folder()) { vecNames = _GetKeynamesForFolder(); } else { wstring extension = path(itemid.filename()).extension().wstring(); wstring::size_type dot_index = extension.find(L"."); if (dot_index != wstring::npos) extension.erase(dot_index); // Get extension-specific keys // We don't want to add the {.ext} key itself to the list // of keys but rather, we should use it's default value to // look up its file class. // e.g: // HKCR\.txt => (Default) txtfile // so we look up the following key // HKCR\txtfile vecNames = _GetKeynamesForExtension(extension.c_str()); } // Add names of keys that apply to items of all types KeyNames vecCommon = _GetKeynamesCommonToAll(); vecNames.insert(vecNames.end(), vecCommon.begin(), vecCommon.end()); return vecNames; } /** * Get list of directory-specific association key names. */ /* static */ CRegistry::KeyNames CRegistry::_GetKeynamesForFolder() throw() { KeyNames vecKeynames; vecKeynames.push_back(L"Folder"); vecKeynames.push_back(L"Directory"); vecKeynames.push_back(L"Directory\\Background"); return vecKeynames; } /** * Get list of directory-specific association key names. */ /* static */ CRegistry::KeyNames CRegistry::_GetKeynamesCommonToAll() throw() { KeyNames vecKeynames; vecKeynames.push_back(L"AllFilesystemObjects"); return vecKeynames; } /** * Get the list of names of registry keys related to a specific file extension. * * @param pwszExtension File extension whose keys will be returned. * * @todo Some files, e.g. PDFs, need * HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ext */ /* static */ CRegistry::KeyNames CRegistry::_GetKeynamesForExtension( __in PCWSTR pwszExtension) throw() { KeyNames vecKeynames; CString strExtension = CString(L".") + pwszExtension; // Start digging at HKCR\.{szExtension} CRegKey reg; if (reg.Open(HKEY_CLASSES_ROOT, strExtension, KEY_READ) == ERROR_SUCCESS) { vecKeynames.push_back(strExtension); // Try to get registered file class key (extensions's default val) wchar_t wszClass[2048]; ULONG cchClass = 2048; if (reg.QueryStringValue(L"", wszClass, &cchClass) == ERROR_SUCCESS && cchClass > 1 && reg.Open(HKEY_CLASSES_ROOT, wszClass, KEY_READ) == ERROR_SUCCESS) { vecKeynames.push_back(wszClass); // Does this class contain a CurVer subkey pointing to another // version of this file. // e.g.: PowerPoint.Show\CurVer => PowerPoint.Show.12 CString strCurVer = wszClass; strCurVer += L"\\CurVer"; if (reg.Open(HKEY_CLASSES_ROOT, strCurVer, KEY_READ) == ERROR_SUCCESS) { // Does this CurVer exist? wchar_t wszCurVer[2048]; ULONG cchCurVer = 2048; if (reg.QueryStringValue(L"", wszCurVer, &cchCurVer) == ERROR_SUCCESS && cchCurVer > 1 && reg.Open(HKEY_CLASSES_ROOT, wszCurVer, KEY_READ) == ERROR_SUCCESS) { vecKeynames.push_back(wszCurVer); } } } } // Dig again at HKCR\SystemFileAssociations\.{sxExtension} CString strSysFileAssocExt = L"SystemFileAssociations\\" + strExtension; if (reg.Open(HKEY_CLASSES_ROOT, strSysFileAssocExt, KEY_READ) == ERROR_SUCCESS) { vecKeynames.push_back(strSysFileAssocExt); } // Dig again at HKCR\.{szExtension}\PerceivedType if (reg.Open(HKEY_CLASSES_ROOT, strExtension, KEY_READ) == ERROR_SUCCESS) { wchar_t wszPerceivedType[2048]; ULONG cchPerceivedType = 2048; if (reg.QueryStringValue( L"PerceivedType", wszPerceivedType, &cchPerceivedType) == ERROR_SUCCESS && cchPerceivedType > 1) { CString strPerceivedType = CString(L"SystemFileAssociations\\") + wszPerceivedType; if (reg.Open(HKEY_CLASSES_ROOT, strPerceivedType, KEY_READ) == ERROR_SUCCESS) vecKeynames.push_back(strPerceivedType); } } if (!vecKeynames.size()) vecKeynames.push_back(L"Unknown"); vecKeynames.push_back(L"*"); ATLASSERT( vecKeynames.size() <= 6 ); return vecKeynames; } /** * Create SHAlloced array of HKEYs from a list of registry key names. */ /* static */ HRESULT CRegistry::_GetHKEYArrayFromKeynames( const KeyNames vecNames, UINT *pcKeys, HKEY **paKeys) throw() { vector vecKeys = _GetKeysFromKeynames(vecNames); return _GetHKEYArrayFromVector(vecKeys, pcKeys, paKeys); } /** * Create SHAlloced array of HKEYs from a list of HKEYs. */ /* static */ HRESULT CRegistry::_GetHKEYArrayFromVector( const vector vecKeys, UINT *pcKeys, HKEY **paKeys) throw() { ATLASSERT( vecKeys.size() <= 16 ); // CDefFolderMenu_Create2's maximum HKEY *aKeys = (HKEY *)::SHAlloc(vecKeys.size() * sizeof HKEY); for (UINT i = 0; i < vecKeys.size(); i++) aKeys[i] = vecKeys[i]; *pcKeys = static_cast(vecKeys.size()); *paKeys = aKeys; return S_OK; } /** * Create list of registry handles from list of keys names. */ /* static */ vector CRegistry::_GetKeysFromKeynames( const KeyNames vecKeynames) throw() { LSTATUS rc = ERROR_SUCCESS; vector vecKeys; for each (CString strKeyname in vecKeynames) { CRegKey reg; rc = reg.Open(HKEY_CLASSES_ROOT, strKeyname, KEY_READ); ATLASSERT(rc == ERROR_SUCCESS); if (rc == ERROR_SUCCESS) vecKeys.push_back(reg.Detach()); } return vecKeys; } ================================================ FILE: swish/shell_folder/Registry.h ================================================ /** @file Helper class for Swish registry access. @if license Copyright (C) 2008, 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "swish/atl.hpp" #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view #include #include // CString class CRegistry { public: static HRESULT GetHostFolderAssocKeys( __out UINT *pcKeys, __deref_out_ecount(pcKeys) HKEY **paKeys); static HRESULT GetRemoteFolderAssocKeys( swish::remote_folder::remote_itemid_view itemid, __out UINT *pcKeys, __deref_out_ecount(pcKeys) HKEY **paKeys); static HRESULT GetRemoteFolderBackgroundAssocKeys( UINT *pcKeys, HKEY **paKeys); typedef std::vector KeyNames; private: static KeyNames _GetHostFolderAssocKeynames(); static KeyNames _GetRemoteFolderAssocKeynames( swish::remote_folder::remote_itemid_view itemid); static KeyNames _GetKeynamesForFolder(); static KeyNames _GetKeynamesCommonToAll(); static KeyNames _GetKeynamesForExtension(__in PCWSTR pwszExtension) throw(); static HRESULT _GetHKEYArrayFromKeynames( __in const KeyNames vecNames, __out UINT *pcKeys, __deref_out_ecount(pcKeys) HKEY **paKeys); static HRESULT _GetHKEYArrayFromVector( __in const std::vector vecKeys, __out UINT *pcKeys, __deref_out_ecount(pcKeys) HKEY **paKeys); static std::vector _GetKeysFromKeynames( __in const KeyNames vecKeynames); }; ================================================ FILE: swish/shell_folder/RemoteFolder.cpp ================================================ /* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RemoteFolder.h" #include "SftpDirectory.h" #include "SftpDataObject.h" #include "IconExtractor.h" #include "Registry.h" #include "swish/debug.hpp" #include "swish/drop_target/DropTarget.hpp" // CDropTarget #include "swish/drop_target/DropUI.hpp" // DropUI #include "swish/frontend/announce_error.hpp" // announce_last_exception #include "swish/remote_folder/columns.hpp" // property_key_from_column_index #include "swish/remote_folder/commands/commands.hpp" // remote_folder_command_provider #include "swish/remote_folder/pidl_connection.hpp" // provider_from_pidl #include "swish/remote_folder/context_menu_callback.hpp" // context_menu_callback #include "swish/remote_folder/properties.hpp" // property_from_pidl #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view // create_remote_itemid #include "swish/remote_folder/ViewCallback.hpp" // CViewCallback #include "swish/shell_folder/SnitchingDataObject.hpp" // CSnitchingDataObject #include "swish/trace.hpp" // trace #include "swish/windows_api.hpp" // SHBindToParent #include // string_to_strret #include #include // datetime_t #include #include // bind #include // diagnostic_information #include // path #include // translate #include // make_shared #include // BOOST_THROW_EXCEPTION #include // assert #include using swish::drop_target::CDropTarget; using swish::drop_target::DropUI; using swish::frontend::announce_last_exception; using swish::provider::sftp_provider; using swish::remote_folder::CViewCallback; using swish::remote_folder::commands::remote_folder_command_provider; using swish::remote_folder::context_menu_callback; using swish::remote_folder::create_remote_itemid; using swish::remote_folder::property_from_pidl; using swish::remote_folder::property_key_from_column_index; using swish::remote_folder::provider_from_pidl; using swish::remote_folder::remote_itemid_view; using swish::tracing::trace; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::pidl_t; using washer::shell::property_key; using washer::shell::string_to_strret; using washer::window::window; using washer::window::window_handle; using comet::com_ptr; using comet::com_error; using comet::datetime_t; using comet::regkey; using comet::throw_com_error; using comet::variant_t; using boost::bind; using boost::filesystem::path; using boost::locale::translate; using boost::make_shared; using boost::optional; using boost::shared_ptr; using ATL::CComPtr; using std::string; using std::wstring; namespace comet { template<> struct comtype<::IContextMenu> { static const ::IID& uuid() throw() { return ::IID_IContextMenu; } typedef ::IUnknown base; }; } namespace { cpidl_t create_filename_only_pidl(const wstring& filename) { return create_remote_itemid( filename, false, false, L"", L"", 0, 0, 0, 0, datetime_t(), datetime_t()); } /** * Remove the extension from the remote item's filename *if appropriate*. */ wstring filename_without_extension(const cpidl_t remote_item) { remote_itemid_view itemid(remote_item); wstring full_name = itemid.filename(); if (full_name.empty() || itemid.is_folder()) { return full_name; } else { if (full_name[0] != L'.') { return path(full_name).stem().wstring(); } else { // File might look something like '.hidden.txt' or it might // just be '.hidden'. In the first case we only want to remove // the '.txt' extension. In the second case we don't want // to remove anything. wstring bit_after_initial_dot = full_name.substr(1); return L'.' + path(bit_after_initial_dot).stem().wstring(); } } } } /*--------------------------------------------------------------------------*/ /* Functions implementing IShellFolder via folder_error_adapter. */ /*--------------------------------------------------------------------------*/ /** * Create an IEnumIDList which enumerates the items in this folder. * * @implementing folder_error_adapter * * @param hwnd Optional window handle used if enumeration requires user * input. * @param flags Flags specifying which types of items to include in the * enumeration. Possible flags are from the @c SHCONT enum. */ IEnumIDList* CRemoteFolder::enum_objects(HWND hwnd, SHCONTF flags) { try { com_ptr consumer = m_consumer_factory(hwnd); // TODO: get the name of the directory and embed in the task name shared_ptr provider = provider_from_pidl( root_pidl(), consumer, translate( "Name of a running task", "Reading a directory")); // Create directory handler and get listing as PIDL enumeration CSftpDirectory directory(root_pidl(), provider); return directory.GetEnum(flags).detach(); } catch (...) { announce_last_exception( hwnd, translate(L"Unable to access the directory"), translate(L"You might not have permission.")); throw; } } /** * Convert path string relative to this folder into a PIDL to the item. * * @implementing folder_error_adapter * * @todo Handle the attributes parameter. Will need to contact server * as the PIDL we create is fake and will not have correct folderness, etc. */ PIDLIST_RELATIVE CRemoteFolder::parse_display_name( HWND hwnd, IBindCtx* bind_ctx, const wchar_t* display_name, ULONG* attributes_inout) { try { trace(__FUNCTION__" called (display_name=%s)") % display_name; if (*display_name == L'\0') BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); // The string we are trying to parse should be of the form: // directory/directory/filename // or // filename wstring strDisplayName(display_name); // May have / to separate path segments wstring::size_type nSlash = strDisplayName.find_first_of(L'/'); wstring strSegment; if (nSlash == 0) // Unix machine - starts with folder called / { strSegment = strDisplayName.substr(0, 1); } else { strSegment = strDisplayName.substr(0, nSlash); } // Create child PIDL for this path segment cpidl_t pidl = create_filename_only_pidl(strSegment); // Bind to subfolder and recurse if there were other path segments if (nSlash != wstring::npos) { wstring strRest = strDisplayName.substr(nSlash+1); com_ptr subfolder; bind_to_object( pidl.get(), bind_ctx, subfolder.iid(), reinterpret_cast(subfolder.out())); wchar_t wszRest[MAX_PATH]; ::wcscpy_s(wszRest, ARRAYSIZE(wszRest), strRest.c_str()); pidl_t rest; HRESULT hr = subfolder->ParseDisplayName( hwnd, bind_ctx, wszRest, NULL, rest.out(), attributes_inout); if (FAILED(hr)) throw_com_error(subfolder.get(), hr); return (pidl + rest).detach(); } else { return pidl.detach(); } } catch (...) { announce_last_exception( hwnd, translate(L"Path not recognised"), translate(L"Check that the path was entered correctly.")); throw; } } namespace { bool extension_hiding_disabled_in_registry() { if (regkey user_settings = regkey(HKEY_CURRENT_USER).open_nothrow( L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\" L"Advanced")) { regkey::mapped_type extension_setting = user_settings[L"HideFileExt"]; if (extension_setting.exists()) { return extension_setting == 0U; } } // We only reach here if the user settings didn't exist, not if // they just said "no". This means the global settings don't // override the user settings, which seems the right way round. if (regkey global_settings = regkey(HKEY_LOCAL_MACHINE).open_nothrow( L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\" L"Advanced\\Folder\\HideFileExt")) { regkey::mapped_type extension_setting = global_settings[L"DefaultValue"]; if (extension_setting.exists()) { return extension_setting == 0U; } } // It's unlikely that neither will be set but we're prepared for it // anyway return false; } } bool CRemoteFolder::show_extension(PCUITEMID_CHILD pidl) { if (extension_hiding_disabled_in_registry()) return true; HKEY raw_class_key = NULL; com_ptr associations = query_associations( NULL, 1, &pidl); HRESULT hr = associations->GetKey( 0, ASSOCKEY_CLASS, NULL, &raw_class_key); regkey class_key(raw_class_key); // Failing to find the key indicates an unknown file type. As the // user setting say 'Hide extensions for *known* filetypes' we // show the extension if the file is unknown. if FAILED(hr) { return true; } else { // In practice, Explorer returns the "Unknown" key for unregistered // file types. But that's ok; it contains an AlwaysShowExt value // so we obey that and it all comes out in the wash. return class_key[L"AlwaysShowExt"].exists(); } } /** * Retrieve the display name for the specified file object or subfolder. * * @implementing folder_error_adapter */ STRRET CRemoteFolder::get_display_name_of(PCUITEMID_CHILD pidl, SHGDNF flags) { if (::ILIsEmpty(pidl)) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); wstring name; bool fForParsing = (flags & SHGDN_FORPARSING) != 0; if (fForParsing || (flags & SHGDN_FORADDRESSBAR)) { if (!(flags & SHGDN_INFOLDER)) { // Bind to parent com_ptr parent; PCUITEMID_CHILD pidlThisFolder = NULL; HRESULT hr = swish::windows_api::SHBindToParent( root_pidl().get(), parent.iid(), reinterpret_cast(parent.out()), &pidlThisFolder); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); STRRET strret; std::memset(&strret, 0, sizeof(strret)); hr = parent->GetDisplayNameOf(pidlThisFolder, flags, &strret); if (FAILED(hr)) throw_com_error(parent.get(), hr); ATLASSERT(strret.uType == STRRET_WSTR); name += strret.pOleStr; name += L'/'; } // Add child path - include extension if FORPARSING if (fForParsing) name += remote_itemid_view(pidl).filename(); else name += filename_without_extension(pidl); } else if (flags & SHGDN_FOREDITING) { name = remote_itemid_view(pidl).filename(); } else { ATLASSERT(flags == SHGDN_NORMAL || flags == SHGDN_INFOLDER); if (show_extension(pidl)) { // The table of SHGDN examples on MSDN implies that the // presence of the SHGDN_FORPARSING flag means include the file // extension and its absence means remove it. // But that's not the full story. The SHGDN_FORPARSING flag // indeed means include the file extension, but its absence means // do what the user wants. In other words, remove the extension // if their Explorer settings say that's what they want. // Checking the Explorer settings is up to the individual // namespace extension. name = remote_itemid_view(pidl).filename(); } else { name = filename_without_extension(pidl); } } return string_to_strret(name); } /** * Rename item. * * @implementing folder_error_adapter */ PITEMID_CHILD CRemoteFolder::set_name_of( HWND hwnd, PCUITEMID_CHILD pidl, const wchar_t* name, SHGDNF /*flags*/) { try { // TODO: embed the name of the file in the task name com_ptr consumer = m_consumer_factory(hwnd); shared_ptr provider = provider_from_pidl(root_pidl(), consumer, translate( "Name of a running task", "Renaming a file")); // Rename file CSftpDirectory directory(root_pidl(), provider); bool fOverwritten = directory.Rename(pidl, name, consumer); // Create new PIDL from old one with new filename remote_itemid_view itemid(pidl); cpidl_t new_file = create_remote_itemid( name, itemid.is_folder(), itemid.is_link(), itemid.owner(), itemid.group(), itemid.owner_id(), itemid.group_id(), itemid.permissions(), itemid.size(), itemid.date_modified(), itemid.date_accessed()); // A failure to notify the shell shouldn't prevent us returning the PIDL try { // Make PIDLs absolute apidl_t old_pidl = root_pidl() + pidl; apidl_t new_pidl = root_pidl() + new_file; // Update the shell by passing both PIDLs if (fOverwritten) { ::SHChangeNotify( SHCNE_DELETE, SHCNF_IDLIST | SHCNF_FLUSH, new_pidl.get(), NULL); } ::SHChangeNotify( (remote_itemid_view(pidl).is_folder()) ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM, SHCNF_IDLIST | SHCNF_FLUSH, old_pidl.get(), new_pidl.get()); } catch (const std::exception& e) { trace("Exception thrown while notifying shell of rename:"); trace("%s") % boost::diagnostic_information(e); } return new_file.detach(); } catch (...) { announce_last_exception( hwnd, translate(L"Unable to rename the item"), translate(L"You might not have permission.")); throw; } } /** * Returns the attributes for the items whose PIDLs are passed in. * * @implementing folder_error_adapter */ void CRemoteFolder::get_attributes_of( UINT pidl_count, PCUITEMID_CHILD_ARRAY pidl_array, SFGAOF* attributes_inout) { // Search through all PIDLs and check if they are all folders bool fAllAreFolders = true; for (UINT i = 0; i < pidl_count; i++) { if (!remote_itemid_view(pidl_array[i]).is_folder()) { fAllAreFolders = false; break; } } // Search through all PIDLs and check if they are all links bool fAreAllLinks = true; for (UINT i = 0; i < pidl_count; i++) { if (!remote_itemid_view(pidl_array[i]).is_link()) { fAreAllLinks = false; break; } } // Search through all PIDLs and check if they are all 'dot' files bool fAllAreDotFiles = true; for (UINT i = 0; i < pidl_count; i++) { wstring filename = remote_itemid_view(pidl_array[i]).filename(); if (filename[0] != L'.') { fAllAreDotFiles = false; break; } } DWORD dwAttribs = 0; if (fAllAreFolders) { dwAttribs |= SFGAO_FOLDER; dwAttribs |= SFGAO_HASSUBFOLDER; dwAttribs |= SFGAO_DROPTARGET; } if (fAllAreDotFiles) { dwAttribs |= SFGAO_GHOSTED; dwAttribs |= SFGAO_HIDDEN; } if (fAreAllLinks) { dwAttribs |= SFGAO_LINK; } dwAttribs |= SFGAO_CANRENAME; dwAttribs |= SFGAO_CANDELETE; dwAttribs |= SFGAO_CANCOPY; *attributes_inout &= dwAttribs; } /*--------------------------------------------------------------------------*/ /* Functions implementing IShellFolder2 via folder2_error_adapter. */ /*--------------------------------------------------------------------------*/ /** * Convert column index to matching PROPERTYKEY, if any. * * @implementing folder2_error_adapter */ SHCOLUMNID CRemoteFolder::map_column_to_scid(UINT column_index) { return property_key_from_column_index(column_index).get(); } /*--------------------------------------------------------------------------*/ /* CFolder NVI internal interface. */ /* These method implement the internal interface of the CFolder abstract */ /* class */ /*--------------------------------------------------------------------------*/ /** * Return the folder's registered CLSID * * @implementing CFolder */ CLSID CRemoteFolder::clsid() const { return __uuidof(this); } /** * Sniff PIDLs to determine if they are of our type. Throw if not. * * @implementing CFolder */ void CRemoteFolder::validate_pidl(PCUIDLIST_RELATIVE pidl) const { if (pidl == NULL) BOOST_THROW_EXCEPTION(com_error(E_POINTER)); if (!remote_itemid_view(pidl).valid()) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); } /** * Create and initialise new folder object for subfolder. * * @implementing CFolder * * Create new CRemoteFolder initialised with its root PIDL. CRemoteFolder * only have instances of themselves as subfolders. */ CComPtr CRemoteFolder::subfolder(const cpidl_t& pidl) { apidl_t new_root = root_pidl() + pidl; // Create CRemoteFolder initialised with its root PIDL CComPtr folder = CRemoteFolder::Create( new_root.get(), m_consumer_factory); ATLENSURE_THROW(folder, E_NOINTERFACE); return folder; } /** * Return a property, specified by PROERTYKEY, of an item in this folder. */ variant_t CRemoteFolder::property(const property_key& key, const cpidl_t& pidl) { return property_from_pidl(pidl, key); } /*--------------------------------------------------------------------------*/ /* CSwishFolder internal interface. */ /* These method override the (usually no-op) implementations of some */ /* in the CSwishFolder base class */ /*--------------------------------------------------------------------------*/ /** * Create a toolbar command provider for the folder. */ CComPtr CRemoteFolder::command_provider( HWND owning_hwnd) { TRACE("Request: IExplorerCommandProvider"); return remote_folder_command_provider( root_pidl(), bind(&provider_from_pidl, root_pidl(), _1, _2), bind(m_consumer_factory, owning_hwnd)).get(); } /** * Create an icon extraction helper object for the selected item. * * @implementing CSwishFolder */ CComPtr CRemoteFolder::extract_icon_w( HWND /*hwnd*/, PCUITEMID_CHILD pidl) { TRACE("Request: IExtractIconW"); remote_itemid_view itemid(pidl); return CIconExtractor::Create( itemid.filename().c_str(), itemid.is_folder()); } /** * Create a file association handler for the selected items. * * @implementing CSwishFolder */ CComPtr CRemoteFolder::query_associations( HWND hwnd, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl) { TRACE("Request: IQueryAssociations"); ATLENSURE(cpidl > 0); CComPtr spAssoc; HRESULT hr = ::AssocCreate( CLSID_QueryAssociations, IID_PPV_ARGS(&spAssoc)); ATLENSURE_SUCCEEDED(hr); remote_itemid_view itemid(apidl[0]); if (itemid.is_folder()) { // Initialise default assoc provider for Folders hr = spAssoc->Init( ASSOCF_INIT_DEFAULTTOFOLDER, L"Folder", NULL, hwnd); ATLENSURE_SUCCEEDED(hr); } else { // Initialise default assoc provider for given file extension wstring extension = path(itemid.filename()).extension().wstring(); if (extension.empty()) extension = L"."; hr = spAssoc->Init( ASSOCF_INIT_DEFAULTTOSTAR, extension.c_str(), NULL, hwnd); ATLENSURE_SUCCEEDED(hr); } return spAssoc; } HRESULT CALLBACK CRemoteFolder::menu_callback( IShellFolder* folder, HWND hwnd_view, IDataObject* selection, UINT message_id, WPARAM wparam, LPARAM lparam) { assert(folder); if (!folder) return E_POINTER; return static_cast(folder)->MenuCallback( hwnd_view, selection, message_id, wparam, lparam); } /** * Create a context menu for the selected items. * * @implementing CSwishFolder */ CComPtr CRemoteFolder::context_menu( HWND hwnd, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl) { TRACE("Request: IContextMenu"); assert(cpidl > 0); // Get keys associated with filetype from registry. // We only take into account the item that was right-clicked on // (the first array element) even if this was a multi-selection. // // This article says that we don't need to specify the keys: // http://groups.google.com/group/microsoft.public.platformsdk.shell/ // browse_thread/thread/6f07525eaddea29d/ // but we do for the context menu to appear in versions of Windows // earlier than Vista. HKEY *akeys = NULL; UINT ckeys = 0; if (cpidl > 0) { ATLENSURE_THROW(SUCCEEDED( CRegistry::GetRemoteFolderAssocKeys( remote_itemid_view(apidl[0]), &ckeys, &akeys)), E_UNEXPECTED // Might fail if registry is corrupted ); } CComPtr spThisFolder = this; ATLENSURE_THROW(spThisFolder, E_OUTOFMEMORY); // Create default context menu from list of PIDLs CComPtr spMenu; HRESULT hr = ::CDefFolderMenu_Create2( root_pidl().get(), hwnd, cpidl, apidl, spThisFolder, menu_callback, ckeys, akeys, &spMenu); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); return spMenu; } CComPtr CRemoteFolder::background_context_menu(HWND hwnd) { TRACE("Request: IContextMenu"); // Get keys associated with directory background menus from registry. // // This article says that we don't need to specify the keys: // http://groups.google.com/group/microsoft.public.platformsdk.shell/ // browse_thread/thread/6f07525eaddea29d/ // but we do for the context menu to appear in versions of Windows // earlier than Vista. HKEY *akeys; UINT ckeys; ATLENSURE_THROW(SUCCEEDED( CRegistry::GetRemoteFolderBackgroundAssocKeys(&ckeys, &akeys)), E_UNEXPECTED // Might fail if registry is corrupted ); CComPtr spThisFolder = this; ATLENSURE_THROW(spThisFolder, E_OUTOFMEMORY); // Create default context menu from list of PIDLs CComPtr spMenu; HRESULT hr = ::CDefFolderMenu_Create2( root_pidl().get(), hwnd, 0, NULL, spThisFolder, menu_callback, ckeys, akeys, &spMenu); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); return spMenu; } /** * Create a data object for the selected items. * * @implementing CSwishFolder */ CComPtr CRemoteFolder::data_object( HWND hwnd, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl) { TRACE("Request: IDataObject"); assert(cpidl > 0); try { // TODO: pass a provider factory instead of the provider to the // data object and create more specific reservations when needed com_ptr consumer = m_consumer_factory(hwnd); shared_ptr provider = provider_from_pidl(root_pidl(), consumer, translate( "Name of a running task", "Accessing files")); return new swish::shell_folder::CSnitchingDataObject( new CSftpDataObject( cpidl, apidl, root_pidl().get(), provider)); } catch (...) { announce_last_exception( hwnd, (cpidl > 1) ? translate(L"Unable to access the item") : translate(L"Unable to access the items"), translate(L"You might not have permission.")); throw; } } /** * Create a drop target handler for the folder. * * @implementing CSwishFolder */ CComPtr CRemoteFolder::drop_target(HWND hwnd) { TRACE("Request: IDropTarget"); try { // TODO: pass a provider factory instead of the provider to the // drop target and create more specific reservations when needed com_ptr consumer = m_consumer_factory(hwnd); shared_ptr provider = provider_from_pidl(root_pidl(), consumer, translate( "Name of a running task", "Copying to directory")); optional< window > owner; if (hwnd) owner = window(window_handle::foster_handle(hwnd)); // HACKish: // UI happens via the given owner window given here. We used to do it // via the window of the OLE site instead, but this is incompatible // with asynchronous operations because the shell clears the site // when Drop returns (at which point the operation is still running // and may need an owner window for UI). // // We could hang on to a copy of the site but that seems .. impolite. // After all, the shell presumably cleared the site for a reason. // // That said, what we're doing now seems pretty naughty too. We use the // window we were passed as an owner window when we were created. This // window is probably the one the shell passed to our folder's // GetUIObjectOf or CreateViewObject methods. MSDN documents this // window as the 'owner' to be used for UI but doesn't make clear how // long the window is guarantted to remain alive: until the // GetUIObjectOf/CreateViewObject call returns or for as long as this // drop target is in use. Nevertheless, this seems to work so it's // what we're doing for now. return new CDropTarget( provider, root_pidl(), make_shared(owner)); } catch (...) { announce_last_exception( hwnd, translate(L"Unable to access the folder"), translate(L"You might not have permission.")); throw; } } /** * Create an instance of our Shell Folder View callback handler. */ CComPtr CRemoteFolder::folder_view_callback(HWND /*hwnd*/) { return new CViewCallback(root_pidl()); } HRESULT CRemoteFolder::MenuCallback( HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam ) { context_menu_callback callback( bind(&provider_from_pidl, root_pidl(), _1, _2), m_consumer_factory); return callback(hwnd, pdtobj, uMsg, wParam, lParam); } ================================================ FILE: swish/shell_folder/RemoteFolder.h ================================================ /* Explorer folder handling remote files and folders in a directory. Copyright (C) 2007, 2008, 2009, 2010, 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "SwishFolder.hpp" // Superclass #include "swish/CoFactory.hpp" // CComObject factory #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include "swish/remote_folder/columns.hpp" // Column #include "Swish.h" // For CRemoteFolder UUID #include "swish/atl.hpp" // Common ATL setup #include // cpidl_t #include // com_ptr #include // function #include class ATL_NO_VTABLE CRemoteFolder : public swish::shell_folder::folder::CSwishFolder< swish::remote_folder::Column>, private swish::CCoFactory { public: BEGIN_COM_MAP(CRemoteFolder) COM_INTERFACE_ENTRY(IShellFolder) COM_INTERFACE_ENTRY_CHAIN(CSwishFolder) END_COM_MAP() /* We can assume that the PIDLs contained in this folder (i.e. any PIDL relative to it) contain one or more REMOTEPIDLs representing the file-system hierarchy of the target file or folder and may be a child of either a HOSTPIDL or another REMOTEPIDL: /REMOTEPIDL[/REMOTEPIDL]* */ /** * Create initialized instance of the CRemoteFolder class. * * @param pidl Absolute PIDL at which to root the folder instance (passed * to Initialize). * @param consumer_factory Callable that returns a consumer instance to * use for a single request. * * @returns Smart pointer to the CRemoteFolder's IShellFolder interface. * @throws com_error if creation fails. */ static ATL::CComPtr Create( PCIDLIST_ABSOLUTE pidl, boost::function(HWND)> consumer_factory) throw(...) { ATL::CComPtr spObject = spObject->CreateCoObject(); spObject->set_consumer_factory(consumer_factory); HRESULT hr = spObject->Initialize(pidl); ATLENSURE_SUCCEEDED(hr); return spObject.p; } protected: CLSID clsid() const; void validate_pidl(PCUIDLIST_RELATIVE pidl) const; ATL::CComPtr subfolder( const washer::shell::pidl::cpidl_t& pidl); comet::variant_t property( const washer::shell::property_key& key, const washer::shell::pidl::cpidl_t& pidl); ATL::CComPtr command_provider(HWND hwnd); ATL::CComPtr background_context_menu(HWND hwnd); ATL::CComPtr extract_icon_w( HWND hwnd, PCUITEMID_CHILD pidl); ATL::CComPtr query_associations( HWND hwnd, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl); ATL::CComPtr context_menu( HWND hwnd, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl); ATL::CComPtr data_object( HWND hwnd, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl); ATL::CComPtr drop_target(HWND hwnd); ATL::CComPtr folder_view_callback(HWND hwnd); public: // IShellFolder (via folder_error_adapter) virtual IEnumIDList* enum_objects(HWND hwnd, SHCONTF flags); virtual void get_attributes_of( UINT pidl_count, PCUITEMID_CHILD_ARRAY pidl_array, SFGAOF* flags_inout); virtual STRRET get_display_name_of( PCUITEMID_CHILD pidl, SHGDNF uFlags); virtual PIDLIST_RELATIVE parse_display_name( HWND hwnd, IBindCtx* bind_ctx, const wchar_t* display_name, ULONG* attributes_inout); virtual PITEMID_CHILD set_name_of( HWND hwnd, PCUITEMID_CHILD pidl, const wchar_t* name, SHGDNF flags); // IShellFolder2 (via folder_error_adapter2) virtual SHCOLUMNID map_column_to_scid(UINT column_index); private: boost::function(HWND)> m_consumer_factory; comet::com_ptr m_consumer; void set_consumer_factory( boost::function(HWND)> consumer_factory) { m_consumer_factory = consumer_factory; } /** @name Default Context Menu event handler */ // @{ static HRESULT CALLBACK menu_callback( IShellFolder* folder, HWND hwnd_view, IDataObject* selection, UINT message_id, WPARAM wparam, LPARAM lparam); HRESULT MenuCallback( HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam ); // @} bool CRemoteFolder::show_extension(PCUITEMID_CHILD pidl); }; ================================================ FILE: swish/shell_folder/SftpDataObject.cpp ================================================ /* DataObject creating FILE_DESCRIPTOR/FILE_CONTENTS formats from remote data. Copyright (C) 2009, 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "SftpDataObject.h" #include "SftpDirectory.h" #include "data_object/StorageMedium.hpp" // StorageMedium #include "swish/provider/sftp_provider.hpp" #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view // path_from_remote_pidl #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // cpidl_t, pidl_t #include // raw_pidl_iterator #pragma warning(push) #pragma warning(disable:4244) // conversion from uint64_t to uint32_t #include // from_ftime #pragma warning(pop) #include // transform_iterator #include // mem_fn #include #include // next #include // replace #include // runtime_error #include using swish::provider::sftp_provider; using swish::remote_folder::path_from_remote_pidl; using swish::remote_folder::remote_itemid_view; using swish::shell_folder::data_object::FileGroupDescriptor; using swish::shell_folder::data_object::Descriptor; using swish::shell_folder::data_object::StorageMedium; using swish::shell_folder::data_object::group_descriptor_from_range; using washer::shell::pidl::basic_pidl; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::pidl_t; using washer::shell::pidl::raw_pidl_iterator; using comet::com_error; using comet::com_ptr; using boost::make_transform_iterator; using boost::mem_fn; using boost::next; using boost::shared_ptr; using std::replace; using std::runtime_error; using std::wstring; namespace comet { template<> struct comtype { static const IID& uuid() { return IID_IDataObject; } typedef ::IUnknown base; }; } /** * Create the DataObject with the top-level PIDLs. * * These PIDLs represent, for instance, the current group of files and * directories which have been selected in an Explorer window. This list * should not include any sub-items of any of the directories. * * @param cPidl Number of PIDLs in the selection. * @param aPidl The selected PIDLs. * @param pidlCommonParent PIDL to the common parent of all the PIDLs. * @param pProvider Backend to communicate with remote server. */ CSftpDataObject::CSftpDataObject( UINT cPidl, PCUITEMID_CHILD_ARRAY aPidl, PCIDLIST_ABSOLUTE pidlCommonParent, shared_ptr provider) : CDataObject(cPidl, aPidl, pidlCommonParent), // Make a copy of the PIDLs. These are used to delay-render the // CFSTR_FILEDESCRIPTOR and CFSTR_FILECONTENTS format in GetData(). m_pidlCommonParent(pidlCommonParent), m_provider(provider), m_fExpandedPidlList(false), m_fRenderedDescriptor(false), m_cfPreferredDropEffect(static_cast( ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT))), m_cfFileDescriptor(static_cast( ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR))), m_cfFileContents(static_cast( ::RegisterClipboardFormat(CFSTR_FILECONTENTS))) { std::copy(aPidl, aPidl + cPidl, std::back_inserter(m_pidls)); // Prod the inner object with the formats whose data we will delay- // render in GetData() if (cPidl > 0) { HRESULT hr; hr = ProdInnerWithFormat(m_cfFileDescriptor, TYMED_HGLOBAL); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); hr = ProdInnerWithFormat(m_cfFileContents, TYMED_ISTREAM); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); } // Set preferred drop effect. This prevents any calls to GetData of FGD or // FILECONTENTS until drag is complete, thereby preventing interruptions // caused by delay-rendering. _RenderCfPreferredDropEffect(); } /*----------------------------------------------------------------------------* * IDataObject methods *----------------------------------------------------------------------------*/ STDMETHODIMP CSftpDataObject::GetData( FORMATETC *pformatetcIn, STGMEDIUM *pmedium) { ::ZeroMemory(pmedium, sizeof(STGMEDIUM)); // Delay-render data if necessary try { if (pformatetcIn->cfFormat == m_cfFileDescriptor) { // Delay-render CFSTR_FILEDESCRIPTOR format into this IDataObject _DelayRenderCfFileGroupDescriptor(); } else if (pformatetcIn->cfFormat == m_cfFileContents) { // Delay-render CFSTR_FILECONTENTS format directly. Do not store. *pmedium = _DelayRenderCfFileContents(pformatetcIn->lindex); return S_OK; } // Delegate all non-FILECONTENTS requests to the superclass return __super::GetData(pformatetcIn, pmedium); } WASHER_COM_CATCH_AUTO_INTERFACE(); } /*----------------------------------------------------------------------------* * Private methods *----------------------------------------------------------------------------*/ void CSftpDataObject::_RenderCfPreferredDropEffect() throw(...) { // Create DROPEFFECT_COPY in global memory HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD)); CGlobalLock glock(hGlobal); DWORD &dwDropEffect = glock.GetDword(); dwDropEffect = DROPEFFECT_COPY; // Save in IDataObject CFormatEtc fetc(m_cfPreferredDropEffect); STGMEDIUM stg; stg.tymed = TYMED_HGLOBAL; stg.hGlobal = glock.Detach(); stg.pUnkForRelease = NULL; HRESULT hr = SetData(&fetc, &stg, true); if (FAILED(hr)) { ::ReleaseStgMedium(&stg); } ATLENSURE_SUCCEEDED(hr); } /** * Delay render CFSTR_FILEDESCRIPTOR format for PIDLs passed to Initialize(). * * Unlike the CFSTR_SHELLIDLIST format, the file group descriptor should * include not only the top-level items but also any subitems within and * directories. This enables Explorer to copy or move an entire directory * tree. * * As this operation can be very expensive when the directory tree is deep, * it isn't appropriate to do this when the IDataObject is created. This * would lead to large delays when simply opening a directory---an operation * that also requires an IDataObject. Instead, this format is delay-rendered * from the list of PIDLs cached during Initialize() the first time it is * requested. * * @see _DelayRenderCfFileContents() * * @throws com_error on error. */ void CSftpDataObject::_DelayRenderCfFileGroupDescriptor() throw(...) { if (!m_fRenderedDescriptor && !m_pidls.empty()) { // Create FILEGROUPDESCRIPTOR format from the cached PIDL list HGLOBAL hglobal = _CreateFileGroupDescriptor(); #ifdef DEBUG FileGroupDescriptor fgd(hglobal); ATLASSERT(fgd.size() > 0); #endif // Insert the descriptor into the IDataObject CFormatEtc fetc(m_cfFileDescriptor); STGMEDIUM stg; stg.tymed = TYMED_HGLOBAL; stg.hGlobal = hglobal; stg.pUnkForRelease = NULL; HRESULT hr = SetData(&fetc, &stg, true); if (FAILED(hr)) { ::ReleaseStgMedium(&stg); } ATLENSURE_SUCCEEDED(hr); m_fRenderedDescriptor = true; } } /** * Delay-render a CFSTR_FILECONTENTS format for a PIDL passed to Initialize(). * * Unlike the CFSTR_SHELLIDLIST format, the file contents formats should * include not only the top-level items but also any subitems within any * directories. This enables Explorer to copy or move an entire directory * tree. * * As this operation can be very expensive when the directory tree is deep, * it isn't appropriate to do this when the IDataObject is created. This * would lead to large delays when simply opening a directory---an operation * that also requires an IDataObject. Instead, these formats are individually * delay-rendered from the list of PIDLs cached during Initialize() each time * one is requested. * * @see _DelayRenderCfFileGroupDescriptor() * * @throws com_error on error. */ STGMEDIUM CSftpDataObject::_DelayRenderCfFileContents(long lindex) throw(...) { STGMEDIUM stg; ::ZeroMemory(&stg, sizeof(STGMEDIUM)); if (!m_pidls.empty()) { // Create an IStream from the cached PIDL list com_ptr stream = _CreateFileContentsStream(lindex); ATLENSURE(stream); // Pack into a STGMEDIUM which will be returned to the client stg.tymed = TYMED_ISTREAM; stg.pstm = stream.detach(); } else { AtlThrow(DV_E_LINDEX); } return stg; } /** * Create CFSTR_FILEDESCRIPTOR format from cached PIDLs. */ HGLOBAL CSftpDataObject::_CreateFileGroupDescriptor() { ExpandedList descriptors; _ExpandPidlsInto(descriptors); typedef ExpandedList::value_type descriptor_type; return group_descriptor_from_range( make_transform_iterator( descriptors.begin(), mem_fn(&descriptor_type::get)), make_transform_iterator( descriptors.end(), mem_fn(&descriptor_type::get))); } /** * Create an IStream for relative path stored in the lindexth FILEDESCRIPTOR. * * @p lindex corresponds to an item in the File Group Descriptor (which * we created in _DelayRenderCfFileGroupDescriptor) with the same index. * * @note Asking for an IStream to folder may not break (libssh2 can do * this) but it is a waste of effort. Explorer won't use it, nor should it. */ com_ptr CSftpDataObject::_CreateFileContentsStream(long lindex) throw(...) { ATLENSURE(m_fRenderedDescriptor); // Pull the FILEGROUPDESCRIPTOR we made earlier out of the DataObject CFormatEtc fetc(m_cfFileDescriptor); StorageMedium medium; HRESULT hr = GetData(&fetc, medium.out()); ATLENSURE_SUCCEEDED(hr); FileGroupDescriptor fgd(medium.get().hGlobal); // Get stream from relative path stored in the lindexth FILEDESCRIPTOR CSftpDirectory dir(m_pidlCommonParent, m_provider); // UNOBVIOUS: FGDs store paths with backslashes so we need to convert // those here wstring path = fgd[lindex].path(); replace(path.begin(), path.end(), L'\\', L'/'); return dir.GetFileByPath(path, false); } /** * Expand all top-level PIDLs into a list of Descriptors with relative paths. * * There should be a file descriptor for every item in the directory * heirarchies. Once expanded, this should not need to be done again for this * DataObject as the descriptors will be saved in the superclass. * * In an attempt to reduce the memory footprint of this very expensive * operation to an absolute minimum, all expansion is done by appending to a * single container by reference. */ void CSftpDataObject::_ExpandPidlsInto(ExpandedList& descriptors) const throw(...) { for (UINT i = 0; i < m_pidls.size(); ++i) { _ExpandTopLevelPidlInto(m_pidls[i], descriptors); } } namespace { using namespace boost::posix_time; const int SHOW_PROGRESS_THRESHOLD = 10000; template remote_itemid_view view_of_last_item(const basic_pidl& pidl) { raw_pidl_iterator it(pidl.get()); while (it != raw_pidl_iterator()) { if (next(it) == raw_pidl_iterator()) return remote_itemid_view(*it); else ++it; } BOOST_THROW_EXCEPTION(runtime_error("Empty iterator")); } template Descriptor make_descriptor(const basic_pidl& pidl, bool dialogue) { Descriptor d; // Filename d.path(path_from_remote_pidl(pidl).wstring()); // The PIDL we have been passed may be multilevel, representing a // path to the file. Get last item in PIDL to get properties of the // file itself. remote_itemid_view itemid = view_of_last_item(pidl); // Size d.file_size(itemid.size()); // Date SYSTEMTIME st; ATLVERIFY(itemid.date_modified().to_systemtime(&st)); FILETIME ftLastWriteTime; ATLVERIFY(::SystemTimeToFileTime(&st, &ftLastWriteTime)); d.last_write_time(from_ftime(ftLastWriteTime)); // Show progress UI? if (d.file_size() > SHOW_PROGRESS_THRESHOLD || dialogue) d.want_progress(true); // Attributes DWORD dwFileAttributes = 0; if (itemid.is_folder()) dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY; else dwFileAttributes |= FILE_ATTRIBUTE_NORMAL; if (itemid.filename()[0] == L'.') dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN; d.attributes(dwFileAttributes); return d; } } /** * Expand one of the selected PIDLs to include any descendents. * * If the given PIDL is a simple item, the returned list just contains this * PIDL. However, if it a directory it will contain the PIDL followed by * all the items in and below the directory. */ void CSftpDataObject::_ExpandTopLevelPidlInto( const TopLevelPidl& pidl, ExpandedList& descriptors) const throw(...) { // Add file descriptor from PIDL - common case ATLENSURE_THROW( descriptors.size() < descriptors.max_size() - 1, E_OUTOFMEMORY); descriptors.push_back(make_descriptor(pidl, _WantProgressDialogue())); // Explode the contents of subfolders into the list if (remote_itemid_view(pidl).is_folder()) { _ExpandDirectoryTreeInto(m_pidlCommonParent, pidl.get(), descriptors); } } /** * Flattens the filesystem tree rooted at this directory into a list of PIDLs. * * The list includes this directory, all the items in this directory and all * items below any of those which are directories. * * Although called 'flat', all the PIDL are returned relative to this * directory's parent and therefore, actually do maintain a record of the * directory structure. *//* vector CSftpDataObject::FlattenDirectoryTree() throw(...) { vector pidls; _FlattenDirectoryTreeInto(pidls, NULL); ATLASSERT(pidls.size() > 0); return pidls; }*/ /** * Return a list of all the PIDLs in this directory and below as a single list. * * The PIDLs are returned appended to the end of the @p vecPidls inout * parameter which reduces the amount of copying. * * All the PIDL (which are relative to this directory's parent) are prefixed with * a given parent PIDL. This allows this method to be used recursively and still * produce a list of PIDLs relative to a common root. * * @param[in,out] vecPidl List of flattened PIDLs to append our flattened * PIDLs. * @param[in] pidlPrefix PIDL with which to prefix the PIDLs below this * folder. If NULL, the returned list is relative to * this folder. */ void CSftpDataObject::_ExpandDirectoryTreeInto( const CAbsolutePidl& pidlParent, const CRelativePidl& pidlDirectory, ExpandedList& descriptors) const throw(...) { com_ptr listing = _GetEnumAll( CAbsolutePidl(pidlParent, pidlDirectory)); // Add all items below this directory (this directory added by caller) HRESULT hr; while(true) { cpidl_t pidl; hr = listing->Next(1, pidl.out(), NULL); if (hr != S_OK) break; // Create version of pidl relative to the common root (pidlParent) pidl_t relative_pidl = pidlDirectory.m_pidl + pidl; // Add simple item - common case ATLENSURE_THROW( descriptors.size() < descriptors.max_size() - 1, E_OUTOFMEMORY); descriptors.push_back(make_descriptor(relative_pidl, true)); // Explode the contents of subfolders into the list if (remote_itemid_view(pidl).is_folder()) { pidl = NULL; // Reduce recursion footprint _ExpandDirectoryTreeInto( pidlParent, relative_pidl.get(), descriptors); } } ATLENSURE(hr == S_FALSE); } com_ptr CSftpDataObject::_GetEnumAll(const CAbsolutePidl& pidl) const throw(...) { CSftpDirectory dir(pidl, m_provider); return dir.GetEnum( SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN); } /** * We want a progress dialogue unless the entire selection consists of a * single non-directory. This is for the FD_PROGRESSUI flag. * * @rant WHY did MS put this flag in the descriptors!!?? */ inline bool CSftpDataObject::_WantProgressDialogue() const throw() { return m_pidls.size() > 1 || (m_pidls.size() == 1 && remote_itemid_view(m_pidls[0]).is_folder()); } ================================================ FILE: swish/shell_folder/SftpDataObject.h ================================================ /** @file DataObject creating FILE_DESCRIPTOR/FILE_CONTENTS formats from remote data. @if license Copyright (C) 2009, 2010, 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "DataObject.h" #include "data_object/FileGroupDescriptor.hpp" // FileGroupDescriptor #include "swish/provider/sftp_provider.hpp" // sftp_provider #include "swish/shell_folder/Pidl.h" #include // cpidl_t #include // com_ptr #include #include /** * Subclass of CDataObject which, additionally, creates CFSTR_FILEDESCRIPTOR * and CFSTR_FILECONTENTS from remote data on demand. * * This class creates the CFSTR_FILEDESCRIPTOR HGLOBAL data and delegates its * storage to the superclass (which will, in turn, delegate it to the inner * object provided by the system). * * This class also creates CFSTR_FILECONTENTS data (as IStreams) as they are * requested. Although the superclass (CDataObject) can---as with the file * group descriptor---store these for later, we no longer use this as doing * so keeps a file-handle open to every file ever requested. This would * cause a large transfer to fail part way through. Instead, we create the * IStreams afresh on every request. These file-handles will close when the * client Releases the IStream. * * These operations are expensive---they require the DataObject to contact * the remote server via an swish::provider::sftp_provider to retrieve file data---and may not * be needed if the client simply wants, say, a CFSTR_SHELLIDLIST format, * so delay-rendering is employed to postpone this expense until we are sure * it is required (GetData() is called with for one of the two formats). * * If the CFSTR_FILEDESCRIPTOR format is requested and any of the initial * PIDLs are directories, the PIDLs are expanded to include every item * anywhere with those directory trees. Unfortunately, this is a @b very * expensive operation but the shell design doesn't give any way to provide * a partial file group descriptor. */ class CSftpDataObject : public CDataObject { public: CSftpDataObject( UINT cPidl, __in_ecount_opt(cPidl) PCUITEMID_CHILD_ARRAY aPidl, __in PCIDLIST_ABSOLUTE pidlCommonParent, boost::shared_ptr provider); public: // IDataObject methods IFACEMETHODIMP GetData( __in FORMATETC *pformatetcIn, __out STGMEDIUM *pmedium); private: /** * Top-level PIDL types. These represent currently-selected items * and will always be single-level children of m_pidlCommonParent. */ // @{ typedef washer::shell::pidl::cpidl_t TopLevelPidl; typedef std::vector TopLevelList; // @} /** * Expanded types. The are the types that the top-level PIDLs are expanded * into when a file group descriptor is requested. They can represent all * the items in or below the top-level and are needed in order to store * entire directory trees in an IDataObject. */ // @{ typedef swish::shell_folder::data_object::Descriptor ExpandedItem; typedef std::vector ExpandedList; // @} boost::shared_ptr m_provider; ///< Connection to backend /** @name Cached PIDLs */ // @{ CAbsolutePidl m_pidlCommonParent; ///< Parent of PIDLs in m_pidls TopLevelList m_pidls; ///< Top-level PIDLs (the selection) // @} /** @name Registered CLIPFORMATS */ // @{ CLIPFORMAT m_cfPreferredDropEffect; ///< CFSTR_PREFERREDDROPEFFECT CLIPFORMAT m_cfFileDescriptor; ///< CFSTR_FILEDESCRIPTOR CLIPFORMAT m_cfFileContents; ///< CFSTR_FILECONTENTS // @} void _RenderCfPreferredDropEffect() throw(...); /** @name Delay-rendering */ //@{ bool m_fExpandedPidlList; ///< Have we expanded top-level PIDLs? bool m_fRenderedDescriptor; ///< Have we rendered FileGroupDescriptor void _DelayRenderCfFileGroupDescriptor() throw(...); STGMEDIUM _DelayRenderCfFileContents(long lindex) throw(...); HGLOBAL _CreateFileGroupDescriptor(); comet::com_ptr _CreateFileContentsStream(long lindex) throw(...); void _ExpandPidlsInto(__inout ExpandedList& descriptors) const throw(...); void _ExpandTopLevelPidlInto( const TopLevelPidl& pidl, __inout ExpandedList& descriptors) const throw(...); void _ExpandDirectoryTreeInto( const CAbsolutePidl& pidlParent, const CRelativePidl& pidlDirectory, __inout ExpandedList& descriptors) const throw(...); comet::com_ptr _GetEnumAll(const CAbsolutePidl& pidl) const throw(...); inline bool _WantProgressDialogue() const throw(); // @} }; ================================================ FILE: swish/shell_folder/SftpDirectory.cpp ================================================ /* Manage remote directory as a collection of PIDLs. Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "SftpDirectory.h" #include "swish/host_folder/host_pidl.hpp" // host_itemid_view, create_host_item #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view, // create_remote_itemid #include "swish/remote_folder/swish_pidl.hpp" // absolute_path_from_swish_pidl #include // pidl_iterator, find_host_itemid #include // trace #include // datetime_t #include // com_error #include // comtype #include // make_smart_enumeration #include // BOOST_FOREACH #include #include #include // _1 #include // make_shared #include #include #include #include // shared_ptr #include // BOOST_THROW_EXCEPTION #include // transform #include // exception #include using ssh::filesystem::path; using swish::provider::sftp_provider; using swish::remote_folder::absolute_path_from_swish_pidl; using swish::remote_folder::create_remote_itemid; using swish::remote_folder::remote_itemid_view; using swish::provider::sftp_filesystem_item; using swish::host_folder::create_host_itemid; using swish::host_folder::find_host_itemid; using swish::host_folder::host_itemid_view; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::pidl_iterator; using washer::shell::pidl::raw_pidl_iterator; using washer::trace; using comet::auto_attach; using comet::com_error; using comet::com_error_from_interface; using comet::com_ptr; using comet::datetime_t; using comet::make_smart_enumeration; using boost::adaptors::filtered; using boost::adaptors::transformed; using boost::function; using namespace boost::lambda; using boost::make_shared; using boost::ref; using boost::shared_ptr; using std::exception; using std::vector; using std::wstring; namespace comet { template<> struct comtype { static const IID& uuid() throw() { return IID_IEnumIDList; } typedef IUnknown base; }; template<> struct enumerated_type_of { typedef PITEMID_CHILD is; }; /** * Copy-policy for use by enumerators of child PIDLs. */ template<> struct impl::type_policy { static void init(PITEMID_CHILD& t, const cpidl_t& s) { s.copy_to(t); } static void clear(PITEMID_CHILD& t) { ::ILFree(t); } }; } /** * Creates and initialises directory instance from a PIDL. * * @param directory_pidl PIDL to the directory this object represents. Must * start at or before a HostItemId. */ CSftpDirectory::CSftpDirectory( const apidl_t& directory_pidl, shared_ptr provider) : m_provider(provider), m_directory_pidl(directory_pidl), m_directory(absolute_path_from_swish_pidl(directory_pidl)) {} namespace { bool is_link(const sftp_filesystem_item& lt) { return lt.type() == sftp_filesystem_item::type::link; } bool is_directory( const sftp_filesystem_item& file, const path& directory, sftp_provider& provider) { if (is_link(file)) { // Links don't indicate anything about their target such as // whether it is a file or folder so we have to interrogate // its target path link_path = directory / file.filename(); try { sftp_filesystem_item target = provider.stat(link_path, TRUE); // TODO: consider what other properties we might want to // take from the target instead of the link. Currently // we only take on folderness. return target.type() == sftp_filesystem_item::type::directory; } catch(const exception&) { // Broken links are treated like files. There isn't really // anything else sensible to do with them. return false; } } else { return file.type() == sftp_filesystem_item::type::directory; } } bool is_dotted(const sftp_filesystem_item& file) { wstring filename = file.filename().wstring(); return filename[0] == L'.'; } cpidl_t convert_directory_entry_to_pidl( const sftp_filesystem_item& file, const path& directory, sftp_provider& provider) { return create_remote_itemid( file.filename().wstring(), is_directory(file, directory, provider), is_link(file), (file.owner()) ? *file.owner() : wstring(), (file.group()) ? *file.group() : wstring(), file.uid(), file.gid(), file.permissions(), file.size_in_bytes(), file.last_modified(), file.last_accessed()); } /** * Notify the shell that a new directory was created. * * Primarily, this will cause Explorer to show the new folder in any * windows displaying the parent folder. * * IMPORTANT: this will only happen if the parent folder is listening for * SHCNE_MKDIR notifications. * * We wait for the event to flush because setting the edit text afterwards * depends on this. */ void notify_shell_created_directory(const apidl_t& folder_pidl) { assert(folder_pidl); ::SHChangeNotify( SHCNE_MKDIR, SHCNF_IDLIST | SHCNF_FLUSH, folder_pidl.get(), NULL); } /** * Notify the shell that a file or directory was deleted. * * Primarily, this will cause Explorer to remove the item from the parent * folder view. * * This function works out whether it was a file or folder removed by * extracting the flag from the remote item ID. */ void notify_shell_of_deletion( const apidl_t& parent_folder, const cpidl_t& file_or_folder) { bool is_folder = remote_itemid_view(file_or_folder).is_folder(); ::SHChangeNotify( (is_folder) ? SHCNE_RMDIR : SHCNE_DELETE, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT, (parent_folder + file_or_folder).get(), NULL); } } /** * Retrieve an IEnumIDList to enumerate this directory's contents. * * This function returns an enumerator which can be used to iterate through * the contents of this directory as a series of PIDLs. This listing is a * @b copy of the one obtained from the server and will not update to reflect * changes. In order to obtain an up-to-date listing, this function must be * called again to get a new enumerator. * * @param flags Flags specifying nature of files to fetch. * * @returns Smart pointer to the IEnumIDList. * @throws com_error if an error occurs. */ com_ptr CSftpDirectory::GetEnum(SHCONTF flags) { // Interpret supported SHCONTF flags bool include_folders = (flags & SHCONTF_FOLDERS) != 0; bool include_non_folders = (flags & SHCONTF_NONFOLDERS) != 0; bool include_hidden = (flags & SHCONTF_INCLUDEHIDDEN) != 0; vector directory_enum = m_provider->listing( m_directory); // XXX: PERFORMANCE: // For a link, we look its target details up 3 times! Once to see if it is // a directory, once to see if it isn't a directory and once to see if its // a directory whilst converting to PIDL. This info should be cached in // the sftp_filesystem_item function hidden_filter = include_hidden || !bind(is_dotted, _1); function directory_filter = include_folders || !bind(is_directory, _1, m_directory, ref(*m_provider)); function non_directory_filter = include_non_folders || bind(is_directory, _1, m_directory, ref(*m_provider)); function pidl_converter = bind( convert_directory_entry_to_pidl, _1, m_directory, ref(*m_provider)); shared_ptr< vector > pidls = make_shared< vector >(); boost::copy( directory_enum | filtered(hidden_filter) | filtered(directory_filter) | filtered(non_directory_filter) | transformed(pidl_converter), back_inserter(*pidls)); return make_smart_enumeration(pidls); } /** * Get instance of CSftpDirectory for a subdirectory of this directory. * * @param directory Child PIDL of directory within this directory. * * @returns Instance of the subdirectory. * @throws com_error if error. */ CSftpDirectory CSftpDirectory::GetSubdirectory(const cpidl_t& directory) { if (!remote_itemid_view(directory).is_folder()) BOOST_THROW_EXCEPTION(com_error(E_INVALIDARG)); apidl_t sub_directory = m_directory_pidl + directory; return CSftpDirectory(sub_directory, m_provider); } namespace { std::ios_base::openmode writeable_to_openmode(bool writeable) { if (writeable) { return std::ios_base::out; } else { return std::ios_base::in; } } } /** * Get IStream interface to the remote file specified by the given PIDL. * * This 'file' may also be a directory but the IStream does not give access * to its subitems. * * @param file Child PIDL of item within this directory. * * @returns Smart pointer of an IStream interface to the file. * @throws com_error if error. */ com_ptr CSftpDirectory::GetFile(const cpidl_t& file, bool writeable) { path file_path = m_directory / remote_itemid_view(file).filename(); return m_provider->get_file(file_path, writeable_to_openmode(writeable)); } /** * Get IStream interface to the remote file specified by a relative path. * * This 'file' may also be a directory but the IStream does not give access * to its subitems. * * @param file Path of item relative to this directory (may be at a level * below this directory). * * @returns Smart pointer of an IStream interface to the file. * @throws com_error if error. */ com_ptr CSftpDirectory::GetFileByPath( const path& file, bool writeable) { return m_provider->get_file( m_directory / file, writeable_to_openmode(writeable)); } bool CSftpDirectory::exists(const cpidl_t& file) { path file_path = m_directory / remote_itemid_view(file).filename(); try { // std::ios_base::in makes it fail if file doesn't exist m_provider->get_file(file_path, std::ios_base::in); } catch (const exception&) { return false; } return true; } bool CSftpDirectory::Rename( const cpidl_t& old_file, const wstring& new_filename, com_ptr consumer) { path old_file_path = m_directory / remote_itemid_view(old_file).filename(); path new_file_path = m_directory / new_filename; return m_provider->rename(consumer.in(), old_file_path, new_file_path) == VARIANT_TRUE; } void CSftpDirectory::Delete(const cpidl_t& file) { path target_path = m_directory / remote_itemid_view(file).filename(); m_provider->remove_all(target_path); try { // Must not report a failure after this point. The item was deleted // even if notifying the shell fails. notify_shell_of_deletion(m_directory_pidl, file); } catch (const exception& e) { trace("WARNING: Couldn't notify shell of deletion: %s") % e.what(); } } cpidl_t CSftpDirectory::CreateDirectory(const wstring& name) { path target_path = m_directory / name; cpidl_t sub_directory = create_remote_itemid( name, true, false, L"", L"", 0, 0, 0, 0, datetime_t::now(), datetime_t::now()); m_provider->create_new_directory(target_path); try { // Must not report a failure after this point. The folder was created // even if notifying the shell fails. // TODO: stat new folder for actual parameters notify_shell_created_directory(m_directory_pidl + sub_directory); } catch (const exception& e) { trace("WARNING: Couldn't notify shell of new folder: %s") % e.what(); } return sub_directory; } apidl_t CSftpDirectory::ResolveLink(const cpidl_t& item) { remote_itemid_view symlink(item); path link_path = m_directory / symlink.filename(); path target_path = m_provider->resolve_link(link_path); // XXX: HACK: // Currently, we create the new PIDL for the resolved path by copying all // the items up to (not including) the host itemid, then appending a new // host itemid containing the full resolved path. This is a horrible hack // and is likely to fail miserably if the resolved target is a file rather // than a directory. // // The proper solution would be to have three types of Item ID (PIDL items): // - Server items that just maintain the details of the server connection. // They don't store any path information. // - Remote items that hold the details of one segment of the remote path. // Combined in a list after a server item, they identify an absolute // path to a file or directory on a remote server. // - Host items that are just shortcuts that resolve to a server item and // one or more remote items. They hold server information and a starting // path. // ... or something like this. A host item could, in some magical, way hold // an absolute PIDL that contains a server items followed by several remote // items. Symlink items could even be a fourth type of item. pidl_iterator itemids(m_directory_pidl); apidl_t pidl_to_link_target; raw_pidl_iterator host_itemid = find_host_itemid(m_directory_pidl); while (itemids != host_itemid) { pidl_to_link_target += *itemids++; } host_itemid_view old_item(*host_itemid); cpidl_t new_host_item = create_host_itemid( old_item.host(), old_item.user(), L"", old_item.port(), old_item.label()); apidl_t resolved_target = pidl_to_link_target + new_host_item; BOOST_FOREACH(const path& segment, target_path) { resolved_target += create_remote_itemid( segment.filename().wstring(), true, false, L"", L"", 0, 0, 0, 0, datetime_t(), datetime_t()); } return resolved_target; } ================================================ FILE: swish/shell_folder/SftpDirectory.h ================================================ /** @file Manage remote directory as a collection of PIDLs. @if license Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view #include // apidl_t #include // com_ptr #include #include #include class CSftpDirectory { public: CSftpDirectory( const washer::shell::pidl::apidl_t& directory, boost::shared_ptr provider); comet::com_ptr GetEnum(SHCONTF flags); CSftpDirectory GetSubdirectory( const washer::shell::pidl::cpidl_t& directory); comet::com_ptr GetFile( const washer::shell::pidl::cpidl_t& file, bool writeable); comet::com_ptr GetFileByPath( const ssh::filesystem::path& file, bool writeable); bool exists(const washer::shell::pidl::cpidl_t& file); bool Rename( const washer::shell::pidl::cpidl_t& old_file, const std::wstring& new_filename, comet::com_ptr consumer); void Delete( const washer::shell::pidl::cpidl_t& file); washer::shell::pidl::cpidl_t CreateDirectory(const std::wstring& name); washer::shell::pidl::apidl_t ResolveLink( const washer::shell::pidl::cpidl_t& item); private: boost::shared_ptr m_provider; ///< Backend data provider ssh::filesystem::path m_directory; ///< Absolute path to this directory. const washer::shell::pidl::apidl_t m_directory_pidl; ///< Absolute PIDL to this directory. }; ================================================ FILE: swish/shell_folder/SnitchingDataObject.hpp ================================================ /** @file Wrap a data object to show errors to user. @if license Copyright (C) 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_SHELL_FOLDER_SNITCHING_DATA_OBJECT_HPP #define SWISH_SHELL_FOLDER_SNITCHING_DATA_OBJECT_HPP #pragma once #include "swish/frontend/announce_error.hpp" // announce_last_exception #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // com_error_from_interface #include // com_ptr #include // simple_object #include // translate #include // BOOST_THROW_EXCEPTION #include // IDataObject namespace comet { template<> struct comtype<::IDataObject> { static const ::IID& uuid() throw() { return ::IID_IDataObject; } typedef ::IUnknown base; }; } namespace swish { namespace shell_folder { /** * Layer around a data object that reports errors to the user. * * This keeps UI out of CDropTarget. */ class CSnitchingDataObject : public comet::simple_object { public: CSnitchingDataObject(comet::com_ptr wrapped_data_object) : m_inner(wrapped_data_object), m_file_descriptor_format_w(static_cast( ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW))), m_file_descriptor_format_a(static_cast( ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA))), m_file_contents_format(static_cast( ::RegisterClipboardFormat(CFSTR_FILECONTENTS))), m_error_cycle_marker(FORMATETC()) { } public: // IDataObject methods virtual HRESULT STDMETHODCALLTYPE GetData( FORMATETC *pformatetcIn, STGMEDIUM *pmedium) { HRESULT hr = m_inner->GetData(pformatetcIn, pmedium); try { try { // Only capture the delay rendered formats. Everything else // should have been caught when this data object was created // and for some formats an error from GetData is standard // operating procedure, not something that we should report. if (pformatetcIn->cfFormat == m_file_descriptor_format_w || pformatetcIn->cfFormat == m_file_descriptor_format_a || pformatetcIn->cfFormat == m_file_contents_format) { if (FAILED(hr)) BOOST_THROW_EXCEPTION( comet::com_error_from_interface(m_inner, hr)); } } catch (...) { // DV_E_FORMATETC is used when we might have the data, just // not in the requested format. It should not be reported. if (hr == DV_E_FORMATETC) throw; // HACK: // The shell asks for different versions of the same format // (such as CFSTR_FILEDESCRIPTORA/CFSTR_FILEDESCRIPTORW) and // different DVASPECTs. As one fails it tries the next. // However, we only want to report the error once // so we record what the first failing case was and won't // show the error message again unless we see that exact format // requested again. // The theory being that the calling code is not going to try // a format again that we already said no to unless the user // initiated the operation again in which case we *do* want to // show the error message again. // Yes, this is a hack; a different sequence of format requests // might cause some weird behaviour. However, it we mustn't // display the error message repeatedly and this approach is a // slight improvement on showing the message strictly once only. if (m_error_cycle_marker.cfFormat == 0) { m_error_cycle_marker = *pformatetcIn; } else if ( pformatetcIn->cfFormat != m_error_cycle_marker.cfFormat || pformatetcIn->dwAspect != m_error_cycle_marker.dwAspect || pformatetcIn->lindex != m_error_cycle_marker.lindex || pformatetcIn->ptd != m_error_cycle_marker.ptd || pformatetcIn->tymed != m_error_cycle_marker.tymed) { throw; } // HACK HACK HACK: // Yes, we are creating a dialogue here even though we don't know // if UI is even allowed. Yes, our UI won't have a proper // parent window. Yes, it is disgusting. No, there doesn't // seem to be an alternative if we want to report // a drag-and-drop error to the user. // The shell doesn't give us an HWND when creating this data object. // It doesn't do anything with IObjectWithSite while using this // data object. SFVM_DIDDRAGDROP is only called is the // drag-and-drop *succeeded*. // I'm out of options. Let's just hope the shell doesn't often // need no-UI drag-and-drop. swish::frontend::announce_last_exception( NULL, boost::locale::translate(L"Unable to access the item"), boost::locale::translate(L"You might not have permission."), true); throw; } } WASHER_COM_CATCH_AUTO_INTERFACE(); return hr; } virtual HRESULT STDMETHODCALLTYPE GetDataHere( FORMATETC *pformatetc, STGMEDIUM *pmedium) { return m_inner->GetDataHere(pformatetc, pmedium); } virtual HRESULT STDMETHODCALLTYPE QueryGetData(FORMATETC *pformatetc) { return m_inner->QueryGetData(pformatetc); } virtual HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc( FORMATETC *pformatectIn, FORMATETC *pformatetcOut) { return m_inner->GetCanonicalFormatEtc(pformatectIn, pformatetcOut); } virtual HRESULT STDMETHODCALLTYPE SetData( FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease) { return m_inner->SetData(pformatetc, pmedium, fRelease); } virtual HRESULT STDMETHODCALLTYPE EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc) { return m_inner->EnumFormatEtc(dwDirection, ppenumFormatEtc); } virtual HRESULT STDMETHODCALLTYPE DAdvise( FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection) { return m_inner->DAdvise(pformatetc, advf, pAdvSink, pdwConnection); } virtual HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) { return m_inner->DUnadvise(dwConnection); } virtual HRESULT STDMETHODCALLTYPE EnumDAdvise(IEnumSTATDATA **ppenumAdvise) { return m_inner->EnumDAdvise(ppenumAdvise); } private: comet::com_ptr m_inner; /** @name Registered CLIPFORMATS */ // @{ CLIPFORMAT m_file_descriptor_format_w; ///< CFSTR_FILEDESCRIPTORW CLIPFORMAT m_file_descriptor_format_a; ///< CFSTR_FILEDESCRIPTORA CLIPFORMAT m_file_contents_format; ///< CFSTR_FILECONTENTS // @} FORMATETC m_error_cycle_marker; }; }} // namespace swish::shell_folder #endif ================================================ FILE: swish/shell_folder/Swish.idl ================================================ import "oaidl.idl"; import "ocidl.idl"; import "shobjidl.idl"; [ uuid(b816a838-5022-11dc-9153-0090f5284f85), version(0.3), helpstring("Swish 0.3 Type Library") ] library SwishLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(b816a83a-5022-11dc-9153-0090f5284f85), helpstring("HostFolder Class") ] coclass CHostFolder { [default] interface IShellFolder2; }; [ uuid(b816a83c-5022-11dc-9153-0090f5284f85), helpstring("RemoteFolder Class") ] coclass CRemoteFolder { [default] interface IShellFolder2; }; } ================================================ FILE: swish/shell_folder/SwishFolder.hpp ================================================ /** @file Base-class implementing functionality common to all Swish folders @if license Copyright (C) 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "Folder.h" // Superclass #include "swish/atl.hpp" // Common ATL setup #include "swish/debug.hpp" // METHOD_TRACE #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // com_error #include // BOOST_THROW_EXCEPTION #include // assert namespace swish { namespace shell_folder { namespace folder { template class CSwishFolder : public swish::shell_folder::folder::CFolder { public: BEGIN_COM_MAP(CSwishFolder) COM_INTERFACE_ENTRY(IShellFolder) COM_INTERFACE_ENTRY_CHAIN(CFolder) END_COM_MAP() protected: /** * Create one of the objects associated with the current folder. * * Currently, only requests for the current objects are dispatched to the * subclasses: * - IShellView * - IShellDetails * - IDropTarget * - IExplorerCommandProvider * - IContextMenu */ ATL::CComPtr folder_object(HWND hwnd, REFIID riid) { ATL::CComPtr object; if (riid == __uuidof(IShellView)) { object = folder_view(hwnd); } else if (riid == __uuidof(IShellDetails)) { object = shell_details(hwnd); } else if (riid == __uuidof(IDropTarget)) { object = drop_target(hwnd); } else if (riid == __uuidof(IExplorerCommandProvider)) { object = command_provider(hwnd); } else if (riid == __uuidof(IContextMenu)) { object = background_context_menu(hwnd); } // QueryInterface could fail at any point above and it *doesn't* throw // an exception. We have to check for NULL once we are sure it can't // fail again: IUnknown returned as IUnknown shouldn't be able to fail. if (!object) BOOST_THROW_EXCEPTION(comet::com_error(E_NOINTERFACE)); return object; } /** * Create one of the objects associated with an item in the current folder. * * Currently, only requests for the current objects are displatched to the * subclasses: * - IContextMenu * - IDataObject * - IQueryAssociations * - IExtractIconW/IExtractIconA */ ATL::CComPtr folder_item_object( HWND hwnd, REFIID riid, UINT cpidl, PCUITEMID_CHILD_ARRAY apidl) { assert(cpidl > 0); ATL::CComPtr object; if (riid == __uuidof(IContextMenu)) { object = context_menu(hwnd, cpidl, apidl); } else if (riid == __uuidof(IDataObject)) { object = data_object(hwnd, cpidl, apidl); } else if (riid == __uuidof(IQueryAssociations)) { object = query_associations(hwnd, cpidl, apidl); } else if (riid == __uuidof(IExtractIconW)) { assert(cpidl == 1); if (cpidl == 1) object = extract_icon_w(hwnd, apidl[0]); } else if (riid == __uuidof(IExtractIconA)) { assert(cpidl == 1); if (cpidl == 1) object = extract_icon_a(hwnd, apidl[0]); } // QueryInterface could fail at any point above and it *doesn't* throw // an exception. We have to check for NULL once we are sure it can't // fail again: IUnknown returned as IUnknown shouldn't be able to fail. if (!object) BOOST_THROW_EXCEPTION(comet::com_error(E_NOINTERFACE)); return object; } /** @name Objects associated with the current folder. */ // @{ /** * Caller has requested the IShellView object associated with this folder. */ virtual ATL::CComPtr folder_view(HWND hwnd) { TRACE("Request: IShellView"); SFV_CREATE sfvdata = { sizeof(sfvdata), 0 }; // Create a pointer to this IShellFolder to pass to view ATL::CComPtr this_folder = this; ATLENSURE_THROW(this_folder, E_NOINTERFACE); sfvdata.pshf = this_folder; // Get the callback object for this folder view, if any. // Must hold reference to it in this CComPtr over the // ::SHCreateShellFolderView() call in case folder_view_callback() // also creates it (hands back the only pointer to it). ATL::CComPtr callback = folder_view_callback(hwnd); sfvdata.psfvcb = callback; // Create Default Shell Folder View object ATL::CComPtr view; HRESULT hr = ::SHCreateShellFolderView(&sfvdata, &view); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); return view; } /** * Caller has requested the IShellDetail object associated with this * folder. * * By default, that is this folder itself. */ virtual ATL::CComPtr shell_details(HWND /*hwnd*/) { TRACE("Request: IShellDetails"); return this; } /** Create a drop target handler for the folder. */ virtual ATL::CComPtr drop_target(HWND /*hwnd*/) { TRACE("Request: IDropTarget"); BOOST_THROW_EXCEPTION(comet::com_error(E_NOINTERFACE)); return NULL; } /** Create a toolbar command provider for the folder. */ virtual ATL::CComPtr command_provider( HWND /*hwnd*/) { TRACE("Request: IExplorerCommandProvider"); BOOST_THROW_EXCEPTION(comet::com_error(E_NOINTERFACE)); return NULL; } /** * Create a context menu for the folder background. * Pasting into a Swish window requires this. */ virtual ATL::CComPtr background_context_menu(HWND /*hwnd*/) { TRACE("Request: IContextMenu"); BOOST_THROW_EXCEPTION(comet::com_error(E_NOINTERFACE)); return NULL; } // @} /** @name Objects associated with items contained in folder. */ // @{ /** Create an icon extraction helper object for the selected item. */ virtual ATL::CComPtr extract_icon_w( HWND /*hwnd*/, PCUITEMID_CHILD /*pidl*/) { TRACE("Request: IExtractIconW"); BOOST_THROW_EXCEPTION(comet::com_error(E_NOINTERFACE)); return NULL; } /** * Create an icon extraction helper object for the selected item. * This is the ASCII version of the interface and, by default, requests are * delegated to the same object as IExtractIconW. Override this to * change the behaviour. */ virtual ATL::CComPtr extract_icon_a( HWND hwnd, PCUITEMID_CHILD pidl) { TRACE("Request: IExtractIconA"); ATL::CComPtr extractor; extractor = extract_icon_w(hwnd, pidl); return extractor; } /** Create a context menu for the selected items. */ virtual ATL::CComPtr context_menu( HWND /*hwnd*/, UINT /*cpidl*/, PCUITEMID_CHILD_ARRAY /*apidl*/) { TRACE("Request: IContextMenu"); BOOST_THROW_EXCEPTION(comet::com_error(E_NOINTERFACE)); return NULL; } /** Create a file association handler for the selected items. */ virtual ATL::CComPtr query_associations( HWND /*hwnd*/, UINT /*cpidl*/, PCUITEMID_CHILD_ARRAY /*apidl*/) { TRACE("Request: IQueryAssociations"); BOOST_THROW_EXCEPTION(comet::com_error(E_NOINTERFACE)); return NULL; } /** Create a data object for the selected items. */ virtual ATL::CComPtr data_object( HWND /*hwnd*/, UINT /*cpidl*/, PCUITEMID_CHILD_ARRAY /*apidl*/) { TRACE("Request: IDataObject"); BOOST_THROW_EXCEPTION(comet::com_error(E_NOINTERFACE)); return NULL; } /** * Return any folder view callback object that should be used when creating * the default view. */ virtual ATL::CComPtr folder_view_callback( HWND /*hwnd*/) { return NULL; } // @} }; }}} // namespace swish::shell_folder::folder ================================================ FILE: swish/shell_folder/com_dll/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(RESOURCES com_dll.rc HostFolder.rgs RemoteFolder.rgs Swish.rgs) set(SOURCES resource.h com_dll.def SwishCoClasses.cpp SwishModule.cpp ${RESOURCES}) add_library(shell_folder-com_dll SHARED ${SOURCES}) hunter_add_package(WTL) target_link_libraries(shell_folder-com_dll PRIVATE shell_folder versions WTL::wtl) # WTL needed for atlres.h install(TARGETS shell_folder-com_dll RUNTIME DESTINATION .) # This step is a dev-time convenience and not guaranteed to produce the same # result as the installer add_custom_target(RegisterDllFromBuildTree COMMAND regsvr32 $ COMMENT "Registering Swish DLL from build tree" VERBATIM) ================================================ FILE: swish/shell_folder/com_dll/HostFolder.rgs ================================================ HKCR { NoRemove CLSID { ForceRemove {b816a83a-5022-11dc-9153-0090f5284f85} = s 'Swish' { ProgID = s 'Swish.HostFolder.1' VersionIndependentProgID = s 'Swish.HostFolder' InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } val AppID = s '%APPID%' TypeLib = s '%APPID%' val InfoTip = s 'Remote file-system access via SFTP' val TileInfo = s 'prop:{28636AA6-953D-11D2-B5D6-00C04FD918D0} 5;{b816a850-5022-11dc-9153-0090f5284f85} 2;{E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD} 7' ShellFolder { val Attributes = d '2684354560' } DefaultIcon = s 'shell32.dll,9' } } Swish.HostFolder.1 = s 'CHostFolder Class' { CLSID = s '{b816a83a-5022-11dc-9153-0090f5284f85}' } Swish.HostFolder = s 'CHostFolder Class' { CLSID = s '{b816a83a-5022-11dc-9153-0090f5284f85}' CurVer = s 'Swish.HostFolder.1' } } HKLM { NoRemove Software { NoRemove Microsoft { NoRemove Windows { NoRemove CurrentVersion { NoRemove Explorer { NoRemove MyComputer { NoRemove NameSpace { ForceRemove {b816a83a-5022-11dc-9153-0090f5284f85} = s 'Swish' { val 'Removal Message' = s 'Please don''t remove Swish this way - uninstall it.' } } } } NoRemove 'Shell Extensions' { NoRemove Approved { val {b816a83a-5022-11dc-9153-0090f5284f85} = s 'Swish HostFolder' } } } } } } } ================================================ FILE: swish/shell_folder/com_dll/RemoteFolder.rgs ================================================ HKCR { NoRemove CLSID { ForceRemove {b816a83c-5022-11dc-9153-0090f5284f85} = s 'CRemoteFolder Class' { ProgID = s 'Swish.RemoteFolder.1' VersionIndependentProgID = s 'Swish.RemoteFolder' InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } val AppID = s '%APPID%' TypeLib = s '%APPID%' val InfoTip = s 'Remote file-system access via SFTP' val TileInfo = s 'prop:{B725F130-47EF-101A-A5F1-02608C9EEBAC}, 12;{B725F130-47EF-101A-A5F1-02608C9EEBAC, 14}' ShellFolder { val Attributes = d '2684354560' } DefaultIcon = s 'shell32.dll,9' } } Swish.RemoteFolder.1 = s 'CRemoteFolder Class' { CLSID = s '{b816a83c-5022-11dc-9153-0090f5284f85}' } Swish.RemoteFolder = s 'CRemoteFolder Class' { CLSID = s '{b816a83c-5022-11dc-9153-0090f5284f85}' CurVer = s 'Swish.RemoteFolder.1' } } HKLM { NoRemove Software { NoRemove Microsoft { NoRemove Windows { NoRemove CurrentVersion { NoRemove 'Shell Extensions' { NoRemove Approved { val {b816a83c-5022-11dc-9153-0090f5284f85} = s 'Swish SFTP Folder' } } } } } } } ================================================ FILE: swish/shell_folder/com_dll/Swish.rgs ================================================ HKCR { NoRemove AppID { '%APPID%' = s 'Swish' 'Swish.DLL' { val AppID = s '%APPID%' } } } ================================================ FILE: swish/shell_folder/com_dll/SwishCoClasses.cpp ================================================ /** @file Externally COM-creatable aspects of Swish. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/shell_folder/HostFolder.h" #include "swish/shell_folder/RemoteFolder.h" #include "Swish.h" // CHost/RemoteFolder UUIDs #include "resource.h" namespace swish { namespace shell_folder { namespace com_dll { using namespace ATL; /** * COM factory for externally created instances of CHostFolder. */ class ATL_NO_VTABLE CHostFolderCoClass : public CHostFolder, public CComCoClass { public: DECLARE_REGISTRY_RESOURCEID(IDR_HOSTFOLDER) BEGIN_COM_MAP(CHostFolderCoClass) COM_INTERFACE_ENTRY(IShellFolder) COM_INTERFACE_ENTRY_CHAIN(CHostFolder) END_COM_MAP() }; OBJECT_ENTRY_AUTO(__uuidof(CHostFolder), CHostFolderCoClass) /** * COM factory for externally created instances of CRemoteFolder. */ class ATL_NO_VTABLE CRemoteFolderCoClass : public CRemoteFolder, public CComCoClass { public: DECLARE_REGISTRY_RESOURCEID(IDR_REMOTEFOLDER) BEGIN_COM_MAP(CRemoteFolderCoClass) COM_INTERFACE_ENTRY(IShellFolder) COM_INTERFACE_ENTRY_CHAIN(CRemoteFolder) END_COM_MAP() }; OBJECT_ENTRY_AUTO(__uuidof(CRemoteFolder), CRemoteFolderCoClass) }}} // namespace swish::shell_folder::com_dll ================================================ FILE: swish/shell_folder/com_dll/SwishModule.cpp ================================================ /** @file DLL exports for in-proc COM server. @if license Copyright (C) 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "resource.h" // main symbols #include // LocaleSetup lifetime manager #include "Swish.h" // Swish type-library #include "swish/atl.hpp" namespace swish { namespace shell_folder { namespace com_dll { using namespace ATL; /** * ATL module needed to use ATL-based objects. * Also provides implementation of in-proc server functions. */ class CSwishModule : public CAtlDllModuleT< CSwishModule > { public : DECLARE_LIBID(LIBID_SwishLib) DECLARE_REGISTRY_APPID_RESOURCEID( IDR_SWISH, "{b816a838-5022-11dc-9153-0090f5284f85}") }; }}} // namespace swish::shell_folder::com_dll swish::shell_folder::com_dll::CSwishModule _Module; /** DLL Entry Point. */ extern "C" BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { static swish::shell_folder::LocaleSetup m_locale; ///< Boost.Locale manager (void)hInstance; return _Module.DllMain(dwReason, lpReserved); } /** Used to determine whether the DLL can be unloaded by OLE. */ STDAPI DllCanUnloadNow() { return _Module.DllCanUnloadNow(); } /** Return a class factory to create an object of the requested type. */ STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { return _Module.DllGetClassObject(rclsid, riid, ppv); } /** * Add entries to the system registry. * * Registers object, typelib and all interfaces in typelib. */ STDAPI DllRegisterServer() { HRESULT hr = _Module.DllRegisterServer(); return hr; } /** Remove entries from the system registry. */ STDAPI DllUnregisterServer() { HRESULT hr = _Module.DllUnregisterServer(); return hr; } ================================================ FILE: swish/shell_folder/com_dll/com_dll.def ================================================ LIBRARY EXPORTS DllCanUnloadNow PRIVATE DllGetClassObject PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE ================================================ FILE: swish/shell_folder/com_dll/com_dll.rc ================================================ // Microsoft Visual C++ generated resource script. // #include "resource.h" #include "swish/versions/metadata.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "atlres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // Neutral (Sys. Default) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEUSD) #ifdef _WIN32 LANGUAGE LANG_NEUTRAL, SUBLANG_SYS_DEFAULT #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""atlres.h""\r\0" END 3 TEXTINCLUDE BEGIN "#include ""shell_folder.rc""\r\n" "1 TYPELIB ""Swish.tlb""\r\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION SWISH_MAJOR_VERSION,SWISH_MINOR_VERSION,SWISH_BUGFIX_VERSION,0 PRODUCTVERSION SWISH_MAJOR_VERSION,SWISH_MINOR_VERSION,SWISH_BUGFIX_VERSION,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x3L #else FILEFLAGS 0x2L #endif FILEOS 0x4L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080004b0" BEGIN VALUE "Comments", SWISH_DESCRIPTION VALUE "FileDescription", "Swish Explorer plugin DLL" VALUE "FileVersion", SWISH_VERSION_STRING VALUE "InternalName", "swish-com_dll" VALUE "LegalCopyright", SWISH_COPYRIGHT VALUE "OriginalFilename", "shell_folder-com_dll.dll" VALUE "ProductName", SWISH_PROGRAM_NAME VALUE "ProductVersion", SWISH_VERSION_STRING END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x800, 1200 END END ///////////////////////////////////////////////////////////////////////////// // // REGISTRY // IDR_SWISH REGISTRY "Swish.rgs" ///////////////////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE BEGIN IDS_PROJNAME "Swish" END #endif // Neutral (Sys. Default) resources ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // English (U.K.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // REGISTRY // IDR_HOSTFOLDER REGISTRY "HostFolder.rgs" IDR_REMOTEFOLDER REGISTRY "RemoteFolder.rgs" #endif // English (U.K.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // #include "swish/shell_folder/shell_folder.rc" 1 TYPELIB "Swish.tlb" ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: swish/shell_folder/com_dll/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by com_dll.rc // #define IDS_PROJNAME 100 #define IDR_SWISH 100 #define IDR_HOSTFOLDER 206 #define IDR_REMOTEFOLDER 207 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 208 #define _APS_NEXT_COMMAND_VALUE 32775 #define _APS_NEXT_CONTROL_VALUE 1023 #define _APS_NEXT_SYMED_VALUE 106 #endif #endif ================================================ FILE: swish/shell_folder/data_object/FileGroupDescriptor.hpp ================================================ /** @file FILEDESCRIPTORW clipboard format wrapper. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "GlobalLocker.hpp" // Manage global memory storing FGD #include // BOOST_THROW_EXCEPTION #include // shared_ptr #pragma warning(push) #pragma warning(disable:4244) // conversion from uint64_t to uint32_t #include // ptime #include // date #include // from_ftime #pragma warning(pop) #include // numeric_cast #include // system_error #include // BOOST_STATIC_ASSERT #include // unint64_t #include // out_of_range, length_error, logic_error #include #include // FILEDESCRIPTOR, FILEGROUPDESCRIPTOR #include // HGLOBAL, GetLastError #pragma warning(push) #pragma warning(disable:4996) // std::copy ... may be unsafe namespace swish { namespace shell_folder { namespace data_object { namespace { inline boost::uint32_t lo_dword(boost::uint64_t qword) { return static_cast(qword & 0xFFFFFFFF); } inline boost::uint32_t hi_dword(boost::uint64_t qword) { return static_cast((qword >> 32) & 0xFFFFFFFF); } /** * Convert a ptime to a Windows FILETIME. */ void ptime_to_filetime(const boost::posix_time::ptime& time, FILETIME& ft) { boost::posix_time::ptime filetime_epoch( boost::gregorian::date(1601,1,1)); boost::posix_time::time_duration duration = time - filetime_epoch; boost::uint64_t hundred_nanosecs = duration.total_nanoseconds(); hundred_nanosecs /= 100UL; ft.dwLowDateTime = lo_dword(hundred_nanosecs); ft.dwHighDateTime = hi_dword(hundred_nanosecs); } /** * Convert a Windows FILETIME to a ptime. */ boost::posix_time::ptime filetime_to_ptime(const FILETIME& ft) { return boost::posix_time::from_ftime(ft); } } /** * Exception thrown when trying to access a field that has not been set to * a value. */ class field_error : public std::logic_error { public: explicit field_error(const std::string message) : std::logic_error(message) {} }; /** * C++ interface to the FILEDESCRIPTORW structure. */ class Descriptor : private FILEDESCRIPTORW { public: Descriptor() : FILEDESCRIPTORW() {} Descriptor(const FILEDESCRIPTORW& d) : FILEDESCRIPTORW(d) {} // default copy, assign and destruct OK const FILEDESCRIPTORW& get() const { return *this; } /** * Return the stored filename or relative path. */ std::wstring path() const { return cFileName; } /** * Save given path as the descriptor filename/path. * * FGD paths are relative paths using backslashes as separators. We allow * the path argument to use forward slashes, andthey will be converted * accordingly. */ void path(std::wstring path) { std::replace(path.begin(), path.end(), L'/', L'\\'); static const size_t BUFFER_SIZE = sizeof(cFileName) / sizeof(cFileName[0]); if (path.size() >= BUFFER_SIZE) BOOST_THROW_EXCEPTION( std::length_error("Path greater than MAX_PATH")); size_t count = path.copy(cFileName, BUFFER_SIZE - 1); cFileName[count] = L'\0'; } /** * Get the size of the item described by the descriptor. * * If the corresponding FILECONTENTS format is stored in an HGLOBAL this * is also the size of the allocated memory. */ boost::uint64_t file_size() const { if (!_valid_field(FD_FILESIZE)) BOOST_THROW_EXCEPTION(field_error("File size not available.")); boost::uint64_t s = nFileSizeHigh; s = s << 32; s += nFileSizeLow; return s; } /** * Set the size of the item described by the descriptor. * * If the corresponding FILECONTENTS format is stored in an HGLOBAL this * is also the size of the allocated memory. */ void file_size(boost::uint64_t size) { nFileSizeLow = lo_dword(size); nFileSizeHigh = hi_dword(size); _set_field_valid(FD_FILESIZE); } /** * The date and time that the item was created. */ boost::posix_time::ptime creation_time() const { if (!_valid_field(FD_CREATETIME)) BOOST_THROW_EXCEPTION(field_error("Creation time not available.")); return filetime_to_ptime(ftCreationTime); } /** * Set the date and time that the item was created. */ void creation_time(const boost::posix_time::ptime& time) { ptime_to_filetime(time, ftLastWriteTime); _set_field_valid(FD_CREATETIME); } /** * The date and time that the item was last accessed. */ boost::posix_time::ptime last_access_time() const { if (!_valid_field(FD_ACCESSTIME)) BOOST_THROW_EXCEPTION( field_error("Last access time not available.")); return filetime_to_ptime(ftLastAccessTime); } /** * Set the date and time that the item was last accessed. */ void last_access_time(const boost::posix_time::ptime& time) { ptime_to_filetime(time, ftLastAccessTime); _set_field_valid(FD_ACCESSTIME); } /** * The date and time that the item was last modified. */ boost::posix_time::ptime last_write_time() const { if (!_valid_field(FD_WRITESTIME)) BOOST_THROW_EXCEPTION( field_error("Last write time not available.")); return filetime_to_ptime(ftLastWriteTime); } /** * Set the date and time that the item was last modified. */ void last_write_time(const boost::posix_time::ptime& time) { ptime_to_filetime(time, ftLastWriteTime); _set_field_valid(FD_WRITESTIME); } /** * Should shell show progress UI when copying items? */ bool want_progress() const { return _valid_field(FD_PROGRESSUI); } /** * Set whether the shell should show progress UI when copying items. */ void want_progress(bool show) { if (show) _set_field_valid(FD_PROGRESSUI); else _unset_field_valid(FD_PROGRESSUI); } /** * FILE_ATTRIBUTE* bit values of item. */ DWORD attributes() const { if (!_valid_field(FD_ATTRIBUTES)) BOOST_THROW_EXCEPTION(field_error("Attributes not available.")); return dwFileAttributes; } /** * Set FILE_ATTRIBUTE* bit values for item. */ void attributes(DWORD attrs) { dwFileAttributes = attrs; _set_field_valid(FD_ATTRIBUTES); } private: /** * Is the field with the given field flag valid? */ bool _valid_field(DWORD field) const { return !!(dwFlags & field); } /** * Set the validity of the given field. */ void _set_field_valid(DWORD field) { dwFlags = dwFlags | field; } /** * Unset the validity of the given field. */ void _unset_field_valid(DWORD field) { dwFlags = (dwFlags & (~field)); } }; BOOST_STATIC_ASSERT(sizeof(Descriptor) == sizeof(FILEDESCRIPTORW)); /** * Wrapper around the FILEGROUPDESCRIPTORW structure. * * This wrapper adds construction as well as access to the FILEDESCRIPTORS * contained within it. */ class FileGroupDescriptor { public: /** * Create wrapper around an existing FILEGROUPDESCRIPTORW in global memory. */ FileGroupDescriptor(HGLOBAL hglobal) : m_lock(hglobal) {} // default copy, assign and destruct OK /** * Number of FILEDESCRIPTORS in the FILEGROUPDESCRIPTORW. */ size_t size() const { return m_lock.get()->cItems; } /** * Return a reference to the ith FILEDESCRIPTORW as a Descriptor. */ Descriptor& operator[](size_t i) const { if (i >= size()) BOOST_THROW_EXCEPTION( std::out_of_range( "Attempt to access FILEDESCRIPTORW out of range")); return *static_cast(&m_lock.get()->fgd[i]); } private: GlobalLocker m_lock; }; /** * Allocate a FILEGROUPDESCRIPTORW in global memory holding the given * descriptors. * * The descriptor are give as a [first, last) range who element type is * copyable to FILEDESCRIPTORW. * * @returns HGLOBAL handle to the allocated global memory. Caller must free. */ template HGLOBAL group_descriptor_from_range(It first, It last) { size_t count = std::distance(first, last); if (count < 1) BOOST_THROW_EXCEPTION( std::length_error("Range must have at least one descriptor.")); size_t bytes = sizeof(FILEGROUPDESCRIPTORW ) + (count * sizeof(FILEDESCRIPTORW)); HGLOBAL hglobal = ::GlobalAlloc(GMEM_MOVEABLE, bytes); if (!hglobal) BOOST_THROW_EXCEPTION( boost::system::system_error( ::GetLastError(), boost::system::get_system_category())); try { GlobalLocker lock(hglobal); lock.get()->cItems = boost::numeric_cast(count); std::copy(first, last, &lock.get()->fgd[0]); // last arg above: decay array to stop false +ive by checked iterator } catch (...) { ::GlobalFree(hglobal); throw; } return hglobal; } }}} // namespace swish::shell_folder::data_object #pragma warning(pop) ================================================ FILE: swish/shell_folder/data_object/GlobalLocker.hpp ================================================ /** @file Resource-managed HGLOBAL locking. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include // system_error #include // GlobalLock/Unlock, HGLOBAL, GetLastError namespace swish { namespace shell_folder { namespace data_object { template class GlobalLocker; /** * Swap two GlobalLocker instances. * * This operation cannot fail and offers the strong guarantee. */ template inline void swap(GlobalLocker& lhs, GlobalLocker& rhs) throw() { std::swap(lhs.m_hglobal, rhs.m_hglobal); std::swap(lhs.m_mem, rhs.m_mem); } /** * Resource-management (RAII) container handling locking on an HGLOBAL. * * @templateparam Type of object the HGLOBAL points to. The get() method * returns a pointer to an object of this type. */ template class GlobalLocker { public: /** * Lock the given HGLOBAL. * * The HGLOBAL remains locked for the lifetime of the object. * * @throws system_error if locking fails. */ explicit GlobalLocker(HGLOBAL hglobal) : m_hglobal(hglobal), m_mem(::GlobalLock(hglobal)) { if (!m_mem) throw boost::system::system_error( ::GetLastError(), boost::system::get_system_category()); } /** * Unlock the HGLOBAL. * * As the global lock functions maintain a lock-count for each * HGLOBAL, our HGLOBAL may remain locked after this object is * destroyed if it has been locked elsewhere. For example, if the * GlobalLocker is copied, that will increment the lock-count. */ ~GlobalLocker() throw() { m_mem = NULL; if (m_hglobal) { BOOL result = ::GlobalUnlock(m_hglobal); (void) result; assert(result || ::GetLastError() == NO_ERROR); // Too many unlocks } m_hglobal = NULL; } /** * Copy the lock. * * Global locking is maintains a lock-count per HGLOBAL that holds the * number of outstanding locks. It increases every time the HGLOBAL * is locked and decreases on each call the GlobalUnlock(). When it * reaches zero, the global memory is actually unlocked and free to be * moved. * * Instances of the GlobalLocker object can be copied safely as the * operation increments the lock count and so destruction of one * GlobalLocker instance can't accidentally unlock the memory held by * another. * * @throws system_error if locking fails. */ GlobalLocker(const GlobalLocker& lock) : m_hglobal(lock.m_hglobal), m_mem(::GlobalLock(m_hglobal)) { if (!m_mem) throw boost::system::system_error( ::GetLastError(), boost::system::get_system_category()); } /** * Copy-assign one lock to another. * * @see the copy constructor for more info on copying locks. */ GlobalLocker& operator=(GlobalLocker lock) throw() { swap(*this, lock); return *this; } /** * Return a pointer to the item held in the HGLOBAL. */ T* get() const { return static_cast(m_mem); } private: HGLOBAL m_hglobal; void* m_mem; friend void swap<>(GlobalLocker&, GlobalLocker&) throw(); }; }}} // namespace swish::shell_folder::data_object ================================================ FILE: swish/shell_folder/data_object/ShellDataObject.cpp ================================================ /** @file Classes to handle the typical Explorer 'Shell DataObject'. @if license Copyright (C) 2008, 2009, 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "ShellDataObject.hpp" #include "GlobalLocker.hpp" // GlobalLocker #include "StorageMedium.hpp" // StorageMedium #include // register_format #include // com_error #include // com_cast #include // share_ptr #include // BOOST_THROW_EXCEPTION #include // IAsyncOperation using washer::clipboard::register_format; using washer::shell::pidl::pidl_t; using washer::shell::pidl::apidl_t; using boost::shared_ptr; using comet::com_error; using comet::com_error_from_interface; using comet::com_ptr; template<> struct comet::comtype { static const IID& uuid() throw() { return IID_IAsyncOperation; } typedef IUnknown base; }; namespace swish { namespace shell_folder { namespace data_object { namespace { // private /** * Lifetime-management class for a CIDA held in global memory in a * STGMEDIUM. * * The lifetimes of a STGMEDIUM holding an HGLOBAL, a lock on that * HGLOBAL and the pointer to the memory it contains. The pointer * is only valid for the duration of the lock which, in turn, can only * exist while the global memory in the STGMEDIUM is allocated. * * Therefore, this class exists to make it easy to manage the lifetimes * of these three items together. A caller to get() is free to use the * CIDA returned as long as the instance of this class remains in scope. * Copying is explicity prevented as that reallocates the STGMEDIUM * invalidating both the lock and the pointer to the original memory. */ class GlobalCida { public: GlobalCida(const StorageMedium& medium) : m_medium(medium), m_lock(m_medium.get().hGlobal) { } const CIDA& get() const { CIDA* pcida = m_lock.get(); if (!pcida) BOOST_THROW_EXCEPTION(com_error(E_UNEXPECTED)); return *pcida; } private: GlobalCida(const GlobalCida& cida); // Disabled GlobalCida& operator=(const GlobalCida& cida); // Disabled StorageMedium m_medium; GlobalLocker m_lock; }; /** * Return a STGMEDIUM with a list of PIDLs in global memory. */ StorageMedium cfstr_shellidlist_from_data_object( const com_ptr data_object) { FORMATETC fetc = { register_format(CFSTR_SHELLIDLIST), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; StorageMedium medium; HRESULT hr = data_object->GetData(&fetc, medium.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error(hr)); assert(medium.get().hGlobal); return medium; } #pragma region CIDA Accessors /** * Return a pointer to the ith PIDL in the CIDA. */ pidl_t pidl_from_cida(const CIDA& cida, int i) { unsigned int offset = cida.aoffset[i]; const BYTE* position = reinterpret_cast(&cida) + offset; return reinterpret_cast(position); } /** * Return a pointer to the PIDL corresponding to the parent folder of the * other PIDLs. */ apidl_t parent_from_cida(const CIDA& cida) { return washer::shell::pidl::pidl_cast(pidl_from_cida(cida, 0)); } /** * Return a pointer to the ith child PIDL in the CIDA (i+1th PIDL). */ pidl_t child_from_cida(const CIDA& cida, int i) { return pidl_from_cida(cida, i + 1); } #pragma endregion } #pragma region ShellDataObject implementation ShellDataObject::ShellDataObject(com_ptr data_object) : m_data_object(data_object) { } ShellDataObject::~ShellDataObject() { } /** * Can the data_object be used asynchronously? */ bool ShellDataObject::supports_async() const { com_ptr async = (comet::com_cast)(m_data_object); if (!async) return false; BOOL support; HRESULT hr = async->GetAsyncMode(&support); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(async, hr)); // Ignoring what MSDN says: the result is *not* a VARIANT_BOOL // and should *not* be compared with VARIANT_TRUE. WTF? return support != FALSE; } com_ptr ShellDataObject::async() const { com_ptr async = try_cast(m_data_object); return async; } /** * Does the data object have the CFSTR_SHELLIDLIST format? * * This must not call GetData() on the data object in order to make this * operation cheap and to prevent premature rendering of delay-rendered data. * We require the format to be in an HGLOBAL for a positive result. No other * storage medium is allowed. */ bool ShellDataObject::has_pidl_format() const { FORMATETC fetc = { register_format(CFSTR_SHELLIDLIST), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; return m_data_object->QueryGetData(&fetc) == S_OK; } /** * Does the data object have the CF_HDROP format? * * This must not call GetData() on the data object in order to make this * operation cheap and to prevent premature rendering of delay-rendered data. * We require the format to be in an HGLOBAL for a positive result. No other * storage medium is allowed. */ bool ShellDataObject::has_hdrop_format() const { FORMATETC fetc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; return m_data_object->QueryGetData(&fetc) == S_OK; } /** * Does the data object have a CFSTR_FILEDESCRIPTORA or CFSTR_FILEDESCRIPTORW * format? * * This must not call GetData() on the data object in order to make this * operation cheap and to prevent premature rendering of delay-rendered data. * We require the format to be in an HGLOBAL for a positive result. No other * storage medium is allowed. */ bool ShellDataObject::has_file_group_descriptor_format() const { return has_unicode_file_group_descriptor_format() || has_ansi_file_group_descriptor_format(); } /** * Does the data object have the CFSTR_FILEDESCRIPTORW format? * * This must not call GetData() on the data object in order to make this * operation cheap and to prevent premature rendering of delay-rendered data. * We require the format to be in an HGLOBAL for a positive result. No other * storage medium is allowed. */ bool ShellDataObject::has_unicode_file_group_descriptor_format() const { FORMATETC fetc = { register_format(CFSTR_FILEDESCRIPTORW), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; return m_data_object->QueryGetData(&fetc) == S_OK; } /** * Does the data object have the CFSTR_FILEDESCRIPTORA format? * * This must not call GetData() on the data object in order to make this * operation cheap and to prevent premature rendering of delay-rendered data. * We require the format to be in an HGLOBAL for a positive result. No other * storage medium is allowed. */ bool ShellDataObject::has_ansi_file_group_descriptor_format() const { FORMATETC fetc = { register_format(CFSTR_FILEDESCRIPTORA), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; return m_data_object->QueryGetData(&fetc) == S_OK; } #pragma endregion #pragma region PidlFormat implementation PidlFormat::PidlFormat(const com_ptr& data_object) : m_data_object(data_object) {} PidlFormat::~PidlFormat() {} /** * The absolute PIDL to the common parent of the items in the SHELLIDLIST * format. */ apidl_t PidlFormat::parent_folder() const { if (!m_data_object) BOOST_THROW_EXCEPTION(std::logic_error("Empty (NULL) Data Object")); GlobalCida global_cida(cfstr_shellidlist_from_data_object(m_data_object)); apidl_t pidl = parent_from_cida(global_cida.get()); return pidl; } /** * The absolute PIDL of the ith item in the SHELLIDLIST format. */ apidl_t PidlFormat::file(UINT i) const { if (pidl_count() == 0) BOOST_THROW_EXCEPTION(std::range_error("Empty (NULL) Data Object")); return parent_folder() + relative_file(i); } /** * The ith relative PIDL in the SHELLIDLIST format. */ pidl_t PidlFormat::relative_file(UINT i) const { if (!m_data_object) BOOST_THROW_EXCEPTION(std::range_error("Empty (NULL) Data Object")); GlobalCida global_cida(cfstr_shellidlist_from_data_object(m_data_object)); if (i >= global_cida.get().cidl) BOOST_THROW_EXCEPTION(std::range_error( "The index is greater than the number of PIDLs in the " "Data Object")); pidl_t pidl = child_from_cida(global_cida.get(), i); return pidl; } /** * Return the number of PIDLs in the CFSTR_SHELLIDLIST format of the data * object. */ UINT PidlFormat::pidl_count() const { if (!m_data_object) return 0; GlobalCida global_cida(cfstr_shellidlist_from_data_object(m_data_object)); return global_cida.get().cidl; } #pragma endregion }}} // namespace swish::shell_folder::data_object ================================================ FILE: swish/shell_folder/data_object/ShellDataObject.hpp ================================================ /** @file Classes to handle the typical Explorer 'Shell DataObject'. @if license Copyright (C) 2008, 2009, 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include // pidl_t, PIDL wrapper types #include // com_ptr #include // IAsyncOperation namespace swish { namespace shell_folder { namespace data_object { /** * Wrapper around an IDataObject pointer providing access to the usual * shell formats. */ class ShellDataObject { public: ShellDataObject(comet::com_ptr data_object); ~ShellDataObject(); bool supports_async() const; comet::com_ptr async() const; bool has_pidl_format() const; bool has_hdrop_format() const; bool has_file_group_descriptor_format() const; bool has_unicode_file_group_descriptor_format() const; bool has_ansi_file_group_descriptor_format() const; private: comet::com_ptr m_data_object; }; /** * Access wrapper for the items in a DataObject's SHELL_IDLIST format. */ class PidlFormat { public: PidlFormat(const comet::com_ptr& data_object); ~PidlFormat(); washer::shell::pidl::apidl_t parent_folder() const; washer::shell::pidl::apidl_t file(UINT i) const; washer::shell::pidl::pidl_t relative_file(UINT i) const; UINT pidl_count() const; private: comet::com_ptr m_data_object; }; }}} // namespace swish::shell_folder::data_object ================================================ FILE: swish/shell_folder/data_object/StorageMedium.hpp ================================================ /** @file Wrapper around STGMEDIUM structure adding lifetime management. @if license Copyright (C) 2008, 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "swish/shell_folder/Pidl.h" #include // com_error #include // BOOST_THROW_EXCEPTION namespace swish { namespace shell_folder { namespace data_object { class StorageMedium { public: StorageMedium() throw() { ::ZeroMemory(&m_medium, sizeof(m_medium)); } StorageMedium(const StorageMedium& medium) { HRESULT hr = ::CopyStgMedium(&(medium.m_medium), &m_medium); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); } StorageMedium& operator=(StorageMedium medium) throw() { std::swap(m_medium, medium.m_medium); } ~StorageMedium() throw() { ::ReleaseStgMedium(&m_medium); } /** * Return address of STGMEDIUM to use as an out-parameter. * * This should only be used on an empty STGMEDIUM as modifying a * STGMEDIUM with allocated resources can lead to memory leaks. * * @todo Instead of asserting on non-empty, release previous to make * empty. */ STGMEDIUM* out() { assert(empty() || "Taking address of non-empty STGMEDIUM"); return &m_medium; } /** * Read-only access to STGMEDIUM. */ const STGMEDIUM& get() const { assert(!empty() || "Accessing empty STGMEDIUM."); return m_medium; } /** * Does the STGMEDIUM hold an allocated resource? */ bool empty() const { return m_medium.tymed == NULL; } private: STGMEDIUM m_medium; }; }}} // namespace swish::shell_folder::data_object ================================================ FILE: swish/shell_folder/locale_setup.hpp ================================================ /** @file Static setup for Boost.Locale. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_SHELL_FOLDER_LOCALE_SETUP_HPP #define SWISH_SHELL_FOLDER_LOCALE_SETUP_HPP #pragma once #include "swish/atl.hpp" #include // module_path #include // path #include // boost::locale::generator namespace swish { namespace shell_folder { namespace detail { /** * Initialise Boost.Locale translation mechanism. */ inline std::locale switch_to_boost_locale() { using boost::filesystem::path; using boost::locale::generator; try { generator gen; path module_directory = washer::module_path( ATL::_AtlBaseModule.GetModuleInstance()).parent_path(); gen.add_messages_path(module_directory.string()); gen.add_messages_domain("swish"); return std::locale::global(gen("")); // default locale } catch (std::exception) { // fall-back return std::locale::global(std::locale::classic()); } } } /** * Switch Boost.Locale for the duration of this module's existence. * * Resets locale to original when the module is unloaded. */ class LocaleSetup { public: LocaleSetup() : m_old_locale(detail::switch_to_boost_locale()) {} ~LocaleSetup() { try { std::locale::global(m_old_locale); } catch (std::exception) { // fall-back std::locale::global(std::locale::classic()); } } private: std::locale m_old_locale; }; }} // namespace swish::shell_folder #endif ================================================ FILE: swish/shell_folder/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by shell_folder.rc // #define IDOK 1 #define IDCANCEL 2 #define IDR_SWISH 100 #define IDS_COLUMN_FILENAME 101 #define IDS_COLUMN_SIZE 102 #define IDS_COLUMN_TYPE 103 #define IDS_COLUMN_PERMISSIONS 104 #define IDS_COLUMN_MODIFIED 105 #define IDD_KBDINTERACTIVEDIALOG 105 #define IDS_COLUMN_OWNER 106 #define IDS_COLUMN_GROUP 107 #define IDS_COLUMN_OWNER_ID 108 #define IDS_COLUMN_GROUP_ID 109 #define IDS_COLUMN_ACCESSED 110 #define IDS_COPYING_TITLE 117 #define IDC_INSTRUCTION 1022 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 207 #define _APS_NEXT_COMMAND_VALUE 32775 #define _APS_NEXT_CONTROL_VALUE 1025 #define _APS_NEXT_SYMED_VALUE 106 #endif #endif ================================================ FILE: swish/shell_folder/shell_folder.rc ================================================ // Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "atlres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""atlres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n\0" END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // English (U.K.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_KBDINTERACTIVEDIALOG DIALOGEX 0, 0, 186, 95 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Dialog" FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN DEFPUSHBUTTON "OK",IDOK,73,73,50,16 PUSHBUTTON "Cancel",IDCANCEL,129,73,50,16 LTEXT "The instruction from the server which may be multiline, include embedded newlines or even be empty",IDC_INSTRUCTION,6,6,174,8 END #endif // English (U.K.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: swish/shell_folder/wtl.hpp ================================================ /** @file Set up WTL. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ /** * @file * * This file must be included before any other WTL headers as they depend * on already being included. Also, any macros defined * in this file must be allowed to affect the behaviour of other parts of * ATL. This is contrary to the usual top-down include order. */ #pragma once #define _WTL_NO_AUTOMATIC_NAMESPACE #include "swish/atl.hpp" // Common ATL setup #include // WTL ================================================ FILE: swish/trace.hpp ================================================ /** @file Debug tracing. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #pragma once #ifdef _DEBUG #include // _CrtDebugReport #include #include #include // va_start, va_list, va_end #include // _vsnprintf et al #include // format #pragma warning(push) #pragma warning(disable:4996) // unsafe function wctomb #include // mb_from_wchar #pragma warning(pop) namespace swish { namespace tracing { namespace { /** * Debug tracer. */ class Tracer { public: Tracer() { ::_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); ::_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); } /** * Output the trace message and break to a new line. */ void trace(const std::string& message) const { std::string line = message + "\n"; ::_CrtDbgReport(_CRT_WARN, NULL, 0, NULL, line.c_str()); } }; /** * Helper class to give same usage for boost-style formatting as printf. * * I.e: * trace("%s %d") % "argument" % 42; * behaves indentically to: * trace_f("%s %d", "argument", 42); * * This works because the temporary TraceFormatter returned by trace() is * destroyed only after the final operator% call is made. On destruction, * the formatter outputs the fed values to the tracer. * * @see swish::tracing::trace() */ class TraceFormatter { public: typedef boost::archive::iterators::mb_from_wchar< std::wstring::const_iterator> converter; TraceFormatter(const std::string& format) : m_format(format) {} ~TraceFormatter() throw() { try { static Tracer tracer; tracer.trace(m_format.str()); } catch (...) {} } /** * Feeding operator that narrows wstring values for output. */ TraceFormatter& operator%(const std::wstring& value) { m_format % std::string( converter(value.begin()), converter(value.end()));; return *this; } template TraceFormatter& operator%(const T& value) { m_format % value; return *this; } private: boost::format m_format; }; } /** * Output trace message. * * Can be, optionally, fed with values in boost-format style: * trace("%s %d") % "argument" % 42; * or: * trace("%1% %2%") % "argument" % 42; */ inline TraceFormatter trace(const std::string& format) { return TraceFormatter(format); } /** * Output trace message. * * Can be, optionally, passed values in printf style: * trace_f("%s %d", "argument", 42); */ inline void trace_f(std::string format, ...) { // // WARNING: mustn't change format to a reference - this breaks varargs // std::va_list arglist; va_start(arglist, format); try { int cch = ::_vscprintf(format.c_str(), arglist) + 1; std::vector buffer(cch); #pragma warning(push) #pragma warning(disable:4996) // unsafe function ::_vsnprintf(&buffer[0], buffer.size(), format.c_str(), arglist); #pragma warning(pop) trace(&buffer[0]); } catch (...) { va_end(arglist); throw; } va_end(arglist); } }} // namespace swish::trace #else namespace swish { namespace tracing { namespace { class DummyFormatter { public: template DummyFormatter& operator%(const T&) { return *this; } }; } inline DummyFormatter trace(const std::string&) { return DummyFormatter(); } inline void trace_f(std::string, ...) {} }} // namespace swish::trace #endif ================================================ FILE: swish/utils.hpp ================================================ /** @file Miscellanious Windows API utility code. @if license Copyright (C) 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #pragma once #include // known_folder_path #include // com_error #include // com_ptr #include // basic_path #include #include // numeric_cast #include // BOOST_THROW_EXCEPTION #include #include // GetRunningObjectTable #include #include #include #pragma region WideCharToMultiByte/MultiByteToWideChar wrappers namespace { template struct Converter { typedef _FromElem FromElem; typedef _ToElem ToElem; typedef std::basic_string FromType; typedef std::basic_string ToType; }; struct Narrow : Converter { int operator()( const FromElem* pszWide, int cchWide, ToElem* pszNarrow, int cbNarrow) { return ::WideCharToMultiByte( CP_UTF8, 0, pszWide, cchWide, pszNarrow, cbNarrow, NULL, NULL); } }; struct Widen : Converter { int operator()( const FromElem* pszNarrow, int cbNarrow, ToElem* pszWide, int cchWide) { return ::MultiByteToWideChar( CP_UTF8, 0, pszNarrow, cbNarrow, pszWide, cchWide); } }; } namespace swish { namespace utils { /** * Convert a basic_string-style string from one element type to another. * * @templateparam T Converter functor to perform the actual conversion. */ template inline typename T::ToType ConvertString(const typename T::FromType& from) { const int size = boost::numeric_cast(from.size()); if (size == 0) return T::ToType(); // Calculate necessary buffer size int len = T()(from.data(), size, NULL, 0); // Perform actual conversion if (len > 0) { std::vector buffer(len); len = T()( from.data(), size, &buffer[0], static_cast(buffer.size())); if (len > 0) { assert(len == boost::numeric_cast(buffer.size())); return T::ToType(&buffer[0], len); } } throw boost::system::system_error( ::GetLastError(), boost::system::get_system_category()); } /** * Convert a Windows wide string to a UTF-8 (multi-byte) string. */ inline std::string WideStringToUtf8String(const std::wstring& wide) { return swish::utils::ConvertString(wide); } /** * Convert a UTF-8 (multi-byte) string to a Windows wide string. */ inline std::wstring Utf8StringToWideString(const std::string& narrow) { return swish::utils::ConvertString(narrow); } }} // namespace swish::utils #pragma endregion #pragma region GetUserName wrapper namespace { struct NarrowUserTraits { typedef char element_type; typedef std::basic_string return_type; inline static BOOL get_user_name( element_type* out_buffer, DWORD* pcb_buffer) { return ::GetUserNameA(out_buffer, pcb_buffer); } }; struct WideUserTraits { typedef wchar_t element_type; typedef std::basic_string return_type; inline static BOOL get_user_name( element_type* out_buffer, DWORD* pcb_buffer) { return ::GetUserNameW(out_buffer, pcb_buffer); } }; } namespace swish { namespace utils { namespace detail { /** * Get the current user's username. */ template inline typename T::return_type current_user() { // Calculate required size of output buffer DWORD len = 0; if (typename T::get_user_name(NULL, &len)) return typename T::return_type(); DWORD err = ::GetLastError(); if (err != ERROR_INSUFFICIENT_BUFFER) { BOOST_THROW_EXCEPTION( boost::system::system_error( err, boost::system::get_system_category())); } // Repeat call with a buffer of required size if (len > 0) { std::vector buffer(len); if (typename T::get_user_name(&buffer[0], &len)) { return typename T::return_type(&buffer[0], len - 1); } else { BOOST_THROW_EXCEPTION( boost::system::system_error( ::GetLastError(), boost::system::get_system_category())); } } return typename T::return_type(); } } // detail inline WideUserTraits::return_type current_user() { return detail::current_user(); } inline NarrowUserTraits::return_type current_user_a() { return detail::current_user(); } namespace detail { inline DWORD get_environment_variable( const char* key, char* buffer, DWORD size) { return ::GetEnvironmentVariableA(key, buffer, size); } inline DWORD get_environment_variable( const wchar_t* key, wchar_t* buffer, DWORD size) { return ::GetEnvironmentVariableW(key, buffer, size); } } /** * Fetch string value from an environment variable. * Returns empty string if the variable isn't present in the enviroment. */ template inline T environment_variable(const T& key) { DWORD len = detail::get_environment_variable(key.c_str(), NULL, 0); if (len == 0) return T(); std::vector buf(len); len = detail::get_environment_variable( key.c_str(), &buf[0], boost::numeric_cast(buf.size())); if (len == 0) BOOST_THROW_EXCEPTION( boost::system::system_error( ::GetLastError(), boost::system::get_system_category())); return T(buf.begin(), buf.begin() + len); } /** * Find home directory path. * * @throws std::exception if path can't be found. * @todo Try other means to find directory including NetUserGetInfo. */ template inline T home_directory() { // try SHGetKnowFolderPath T home = washer::shell::special_folder_path(CSIDL_PROFILE); if (!home.empty()) return home; // fall back to %HOME% const T::value_type home_key[] = {'H', 'O', 'M', 'E', '\0'}; home = environment_variable(T::string_type(home_key)); if (!home.empty()) return home; // fall back to %USERPROFILE% const T::value_type userprofile[] = {'U', 'S', 'E', 'R', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0'}; home = environment_variable(T::string_type(userprofile)); if (!home.empty()) return home; // fall back to %HOMEDRIVE%/%HOMEPATH% const T::value_type home_drive_key[] = {'H', 'O', 'M', 'E', 'D', 'R', 'I', 'V', 'E', '\0'}; const T::value_type home_path_key[] = {'H', 'O', 'M', 'E', 'P', 'A', 'T', 'H', '\0'}; T home_drive = environment_variable(T::string_type(home_drive_key)); T home_path = environment_variable(T::string_type(home_path_key)); home = home_drive / home_path; if (home.empty()) BOOST_THROW_EXCEPTION( std::exception("Can't find home directory")); return home; } namespace com { /** * Get the local Winstation Running Object Table. */ inline comet::com_ptr running_object_table() { comet::com_ptr rot; HRESULT hr = ::GetRunningObjectTable(0, rot.out()); assert(SUCCEEDED(hr)); assert(rot); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); return rot; } /** * Look up a CLSID in the registry using the ProgId. */ inline CLSID clsid_from_progid(const std::wstring& progid) { CLSID clsid; HRESULT hr = ::CLSIDFromProgID(progid.c_str(), &clsid); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); return clsid; } /** * Get the class object of a component by its CLSID. */ template inline comet::com_ptr class_object( const CLSID& clsid, DWORD dw_class_context=CLSCTX_ALL) { comet::com_ptr object; HRESULT hr = ::CoGetClassObject( clsid, dw_class_context, NULL, comet::uuidof(object.in()), reinterpret_cast(object.out())); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error(hr)); return object; } /** * Get the class object of a component by its ProgId. */ template inline comet::com_ptr class_object( const std::wstring& progid, DWORD dw_class_context=CLSCTX_ALL) { CLSID clsid = clsid_from_progid(progid); return class_object(clsid, dw_class_context); } } // namespace swish::utils::com }} // namespace swish::utils #pragma endregion ================================================ FILE: swish/versions/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . include(GetGitRevisionDescription) git_describe(SWISH_GIT_VERSION) set(GIT_VERSION_TEMPLATE "${CMAKE_CURRENT_SOURCE_DIR}/git_version.h.in") set(GIT_VERSION_GENERATED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/git_version.h") configure_file( ${GIT_VERSION_TEMPLATE} ${GIT_VERSION_GENERATED_OUTPUT} @ONLY) set_source_files_properties( ${GIT_VERSION_GENERATED_OUTPUT} PROPERTIES GENERATED TRUE) set(METADATA_TEMPLATE "${CMAKE_CURRENT_SOURCE_DIR}/metadata.h.in") set(METADATA_GENERATED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/swish/versions/metadata.h") configure_file( ${METADATA_TEMPLATE} ${METADATA_GENERATED_OUTPUT} @ONLY) set_source_files_properties( ${METADATA_GENERATED_OUTPUT} PROPERTIES GENERATED TRUE) set(SOURCES ${GIT_VERSION_GENERATED_OUTPUT} ${METADATA_GENERATED_OUTPUT} version.cpp version.hpp) # This target must NOT be called 'version'. That conflicts with the name of the # Windows library that we also need (via WinSparkle). If the names collide, # CMake gets confused which one we mean and chooses the target, not the system # library. add_library(versions ${SOURCES}) target_include_directories(versions PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") ================================================ FILE: swish/versions/git_version.h.in ================================================ const char git_version[] = "@SWISH_GIT_VERSION@"; ================================================ FILE: swish/versions/metadata.h.in ================================================ /* Copyright (C) 2009-2012 Vaclav Slavik Copyright (C) 2013, 2014, 2015 Alexander Lamaison Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Based on winsparkle-version.h. #ifndef SWISH_METADATA_H #define SWISH_METADATA_H // IMPORTANT: The version information in this file must be digestable by the // resource (RC) compiler. No C++ allowed. #define SWISH_MAJOR_VERSION @swish_VERSION_MAJOR@ #define SWISH_MINOR_VERSION @swish_VERSION_MINOR@ #define SWISH_BUGFIX_VERSION @swish_VERSION_PATCH@ #define SWISH_VERSION_STRING "@swish_VERSION@" #define SWISH_PROGRAM_NAME "@SWISH_FRIENDLY_NAME@" #define SWISH_COPYRIGHT "@SWISH_COPYRIGHT@" #define SWISH_DESCRIPTION "@SWISH_DESCRIPTION@" #endif ================================================ FILE: swish/versions/version.cpp ================================================ /** @file Swish version information. @if license Copyright (C) 2013 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . If you modify this Program, or any covered work, by linking or combining it with the OpenSSL project's OpenSSL library (or a modified version of that library), containing parts covered by the terms of the OpenSSL or SSLeay licenses, the licensors of this Program grant you additional permission to convey the resulting work. @endif */ #include "version.hpp" #include "git_version.h" // generated #include "swish/versions/metadata.h" using std::auto_ptr; using std::string; namespace swish { string snapshot_version() { return git_version; } std::string build_time() { return __TIME__; } std::string build_date() { return __DATE__; } /* class structured_version_impl { public: virtual ~structured_version_impl() {} virtual int major() const = 0; virtual int minor() const = 0; virtual int bugfix() const = 0; virtual std::string as_string() const = 0; }; */ structured_version::structured_version(const structured_version_impl& impl) : m_pimpl(impl.clone()) {} structured_version::structured_version(const structured_version& other) : m_pimpl(other.m_pimpl->clone()) {} structured_version& structured_version::operator=(structured_version other) { swap(*this, other); return *this; } int structured_version::major() const { return m_pimpl->major(); } int structured_version::minor() const { return m_pimpl->minor(); } int structured_version::bugfix() const { return m_pimpl->bugfix(); } string structured_version::as_string() const { return m_pimpl->as_string(); } structured_version release_version() { class swish_version : public structured_version_impl { public: virtual int major() const { return SWISH_MAJOR_VERSION; } virtual int minor() const { return SWISH_MINOR_VERSION; } virtual int bugfix() const { return SWISH_BUGFIX_VERSION; } virtual string as_string() const { return SWISH_VERSION_STRING; } virtual auto_ptr clone() const { return auto_ptr(new swish_version(*this)); } }; return structured_version(swish_version()); } } ================================================ FILE: swish/versions/version.hpp ================================================ /** @file C++ interface to Swish version information. @if license Copyright (C) 2013 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . If you modify this Program, or any covered work, by linking or combining it with the OpenSSL project's OpenSSL library (or a modified version of that library), containing parts covered by the terms of the OpenSSL or SSLeay licenses, the licensors of this Program grant you additional permission to convey the resulting work. @endif */ #ifndef SWISH_VERSION_HPP #define SWISH_VERSION_HPP #include // auto_ptr #include namespace swish { /** * Description of the version control snapshot from which the code was build. * * The description may be quite rough as there is no good way to describe * changes that occur in the working copy. * * Currently the description is the output of * `git describe --abbrev=4 --dirty --always` * and therefore looks similar to * `swish-0.7.2-1-g5227-dirty`. * This format should not be assumed. */ std::string snapshot_version(); /** * The time of the last build. * * Technically, the time the compilation unit implementing this function was * compiled. */ std::string build_time(); /** * The date of the last build. * * Technically, the date on which the compilation unit implementing this * function was compiled. */ std::string build_date(); class structured_version_impl { public: virtual ~structured_version_impl() {} virtual int major() const = 0; virtual int minor() const = 0; virtual int bugfix() const = 0; virtual std::string as_string() const = 0; virtual std::auto_ptr clone() const = 0; }; class structured_version { public: explicit structured_version(const structured_version_impl& impl); structured_version(const structured_version& other); structured_version& operator=(structured_version other); int major() const; int minor() const; int bugfix() const; std::string as_string() const; friend void swap(structured_version& l, structured_version& r) { swap(l.m_pimpl, r.m_pimpl); } private: std::auto_ptr m_pimpl; }; structured_version release_version(); } #endif ================================================ FILE: swish/windows_api.hpp ================================================ /** @file Reimplementation of some Windows API functions. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #pragma once #include // ::SHGetDesktopFolder, IL-* functions namespace swish { namespace windows_api { /** * Bind to the parent object of an absolute PIDL. * * This exists for compatability with Windows 9x which doesn't have this API * function. * * @returns A pointer to the requested interface of the parent object along * with a pointer to the item in the original PIDL relative to the * object. * * Based on the implementation from the Wine project subject to the LGPL: * http://source.winehq.org/source/dlls/shell32/pidl.c#L1282 * Copyright 1998 Juergen Schmied. */ inline HRESULT WINAPI SHBindToParent( PCIDLIST_ABSOLUTE pidl, REFIID riid, LPVOID* ppv, PCUITEMID_CHILD* ppidlLast) { IShellFolder* psfDesktop; HRESULT hr = E_FAIL; if (!ppv) return E_POINTER; *ppv = NULL; if (ppidlLast) *ppidlLast = NULL; if (!pidl) return E_INVALIDARG; hr = ::SHGetDesktopFolder(&psfDesktop); if (FAILED(hr)) return hr; if (::ILIsChild(pidl)) { /* we are on desktop level */ hr = psfDesktop->QueryInterface(riid, ppv); } else { PIDLIST_ABSOLUTE pidlParent = ::ILCloneFull(pidl); ::ILRemoveLastID(pidlParent); hr = psfDesktop->BindToObject(pidlParent, NULL, riid, ppv); ::ILFree(pidlParent); } psfDesktop->Release(); if (SUCCEEDED(hr) && ppidlLast) *ppidlLast = ::ILFindLastID(pidl); return hr; } }} // namespace swish::windows_api ================================================ FILE: test/CMakeLists.txt ================================================ # Copyright 2015, 2016 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . find_package(Boost 1.40 REQUIRED COMPONENTS unit_test_framework) add_subdirectory(common_boost) add_subdirectory(fixtures) option(MEMORY_LEAKS_ARE_FAILURES "Fail the test suite if a memory leak is detected" OFF) set(TEST_RUNNER_ARGUMENTS --result_code=yes --build_info=yes --log_level=test_suite) add_custom_target(BUILD_ALL_TESTS COMMENT "Building all tests") set(TEST_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fixtures") include(CMakeParseArguments) # swish_test_suite(SUBJECT test-target [VARIANT suite-variant] SOURCES ... # LIBRARIES ... [LABELS ...]) function(SWISH_TEST_SUITE) set(options) set(oneValueArgs SUBJECT VARIANT) set(multiValueArgs SOURCES LIBRARIES LABELS) cmake_parse_arguments(SWISH_TEST_SUITE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(SWISH_TEST_SUITE_VARIANT) set(_TEST_EXE_NAME "test-${SWISH_TEST_SUITE_SUBJECT}-${SWISH_TEST_SUITE_VARIANT}") else() set(_TEST_EXE_NAME "test-${SWISH_TEST_SUITE_SUBJECT}") endif() configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/../module.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/module.cpp @ONLY) add_executable(${_TEST_EXE_NAME} ${SWISH_TEST_SUITE_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/module.cpp) target_link_libraries(${_TEST_EXE_NAME} PRIVATE ${SWISH_TEST_SUITE_SUBJECT} test-common_boost ${SWISH_TEST_SUITE_LIBRARIES}) add_dependencies(BUILD_ALL_TESTS ${_TEST_EXE_NAME}) add_test( NAME ${_TEST_EXE_NAME} COMMAND ${_TEST_EXE_NAME} ${TEST_RUNNER_ARGUMENTS} WORKING_DIRECTORY "${TEST_DATA_DIR}") if(MEMORY_LEAKS_ARE_FAILURES) # Don't hide memory leak detection. The detector can't change the error # code so the test appears successful otherwise. set_tests_properties(${_TEST_EXE_NAME} PROPERTIES FAIL_REGULAR_EXPRESSION "Detected memory leaks") endif() # if(TEST_RUNNER_ENVIRONMENT) # set_tests_properties(${_TEST_EXE_NAME} PROPERTIES # ENVIRONMENT "${TEST_RUNNER_ENVIRONMENT}") # endif() if(SWISH_TEST_SUITE_LABELS) set_tests_properties( ${_TEST_EXE_NAME} PROPERTIES LABELS "${SWISH_TEST_SUITE_LABELS}") endif() endfunction() set(_FIXTURE_FILES test_zip_file.zip) set(_FIXTURE_SSHD_ETC_FILES fixture_dsakey fixture_dsakey.pub fixture_hostkey fixture_hostkey.pub fixture_rsakey fixture_rsakey.pub) set(_FIXTURE_FILES_DIR ${CMAKE_CURRENT_LIST_DIR}/common_boost) set(_PERMISSION_SCRIPT_DIR ${CMAKE_CURRENT_LIST_DIR}) function(SWISH_COPY_FIXTURE_FILES TEST_TARGET) foreach(FILE ${_FIXTURE_FILES}) add_custom_command( TARGET ${TEST_TARGET} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${_FIXTURE_FILES_DIR}/${FILE} $/${FILE} VERBATIM) endforeach() foreach(FILE ${_FIXTURE_SSHD_ETC_FILES}) add_custom_command( TARGET ${TEST_TARGET} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${_FIXTURE_FILES_DIR}/${FILE} $/sshd-etc/${FILE} VERBATIM) endforeach() if(CYGWIN_INSTALL_PATH) add_custom_command( TARGET ${TEST_TARGET} POST_BUILD COMMAND cd $/sshd-etc/ VERBATIM) add_custom_command( TARGET ${TEST_TARGET} POST_BUILD COMMAND set PATH=${CYGWIN_INSTALL_PATH}/bin$) add_custom_command( TARGET ${TEST_TARGET} POST_BUILD COMMAND bash ${_PERMISSION_SCRIPT_DIR}/fix_key_permissions.sh VERBATIM) endif() endfunction() add_subdirectory(connection) add_subdirectory(drop_target) add_subdirectory(ezel) add_subdirectory(forms) add_subdirectory(host_folder) add_subdirectory(nse) add_subdirectory(provider-integration) add_subdirectory(remote_folder) add_subdirectory(shell) add_subdirectory(shell_folder) add_subdirectory(shell_folder-dialogue) add_subdirectory(ssh) add_subdirectory(versions) # swish_declare_test_target(COMMAND_NAME [OPTIONAL_FLAGS...]) function(SWISH_DECLARE_TEST_TARGET COMMAND_NAME) set(options ALL) set(oneValueArgs) set(multiValueArgs) cmake_parse_arguments(SWISH_DECLARE_TEST_TARGET "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(SWISH_DECLARE_TEST_TARGET_ALL) set(SWISH_DECLARE_TEST_TARGET_ALL ALL) else() set(SWISH_DECLARE_TEST_TARGET_ALL) endif() # From http://stackoverflow.com/a/16163137/67013 if(CMAKE_CONFIGURATION_TYPES) add_custom_target(${COMMAND_NAME} ${SWISH_DECLARE_TEST_TARGET_ALL} COMMAND ${CMAKE_CTEST_COMMAND} --force-new-ctest-process --output-on-failure --build-config "$" ${SWISH_DECLARE_TEST_TARGET_UNPARSED_ARGUMENTS}) else() add_custom_target(${COMMAND_NAME} ${SWISH_DECLARE_TEST_TARGET_ALL} COMMAND ${CMAKE_CTEST_COMMAND} --force-new-ctest-process --output-on-failure ${SWISH_DECLARE_TEST_TARGET_UNPARSED_ARGUMENTS}) endif() add_dependencies(${COMMAND_NAME} BUILD_ALL_TESTS) endfunction() swish_declare_test_target(CHECK) swish_declare_test_target(CHECK_UNIT -L unit ALL) swish_declare_test_target(CHECK_INTEGRATION -L integration) swish_declare_test_target(CHECK_GUI -L gui) ================================================ FILE: test/common_boost/CMakeLists.txt ================================================ # Copyright (C) 2015, 2016 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES data_object_utils.cpp stream_utils.cpp ConsumerStub.hpp data_object_utils.hpp fixtures.hpp helpers.hpp MockConsumer.hpp MockProvider.hpp stream_utils.hpp SwishPidlFixture.hpp tree.hh tree.hpp) add_library(test-common_boost ${SOURCES}) hunter_add_package(Comet) hunter_add_package(Washer) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) target_link_libraries(test-common_boost PRIVATE shell shell_folder PUBLIC Comet::comet Washer::washer ${Boost_LIBRARIES}) ================================================ FILE: test/common_boost/ConsumerStub.hpp ================================================ /** @file Stub implementation of an ISftpConsumer. @if license Copyright (C) 2009, 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include "swish/provider/sftp_provider.hpp" #include "test/common_boost/helpers.hpp" #include // WASHER_COM_CATCH_AUTO_INTERFACE #include // simple_object #include // BOOST_THROW_EXCEPTION #include #include namespace test { /** * Very simple consumer that just handles authentication via pub-key. */ class CConsumerStub : public comet::simple_object { public: typedef ISftpConsumer interface_is; CConsumerStub( boost::filesystem::path privatekey, boost::filesystem::path publickey) : m_privateKey(privatekey), m_publicKey(publickey) {} // ISftpConsumer methods virtual boost::optional prompt_for_password() { return boost::optional(); } virtual boost::optional< std::pair> key_files() { return std::make_pair(m_privateKey, m_publicKey); } virtual boost::optional> challenge_response( const std::string& /*title*/, const std::string& /*instructions*/, const std::vector>& /*prompts*/) { BOOST_ERROR("Unexpected call to "__FUNCTION__); BOOST_THROW_EXCEPTION(std::exception("Not implemented")); } HRESULT OnConfirmOverwrite( BSTR /*bstrOldFile*/, BSTR /*bstrNewFile*/) { BOOST_ERROR("Unexpected call to "__FUNCTION__); return E_NOTIMPL; } HRESULT OnHostkeyMismatch( BSTR /*bstrHostName*/, BSTR /*bstrHostKey*/, BSTR /*bstrHostKeyType*/) { return S_FALSE; } HRESULT OnHostkeyUnknown( BSTR /*bstrHostName*/, BSTR /*bstrHostKey*/, BSTR /*bstrHostKeyType*/) { return S_FALSE; } private: boost::filesystem::path m_privateKey; boost::filesystem::path m_publicKey; }; } // namespace test ================================================ FILE: test/common_boost/MockConsumer.hpp ================================================ // Copyright 2010, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef TEST_COMMON_BOOST_MOCK_CONSUMER_HPP #define TEST_COMMON_BOOST_MOCK_CONSUMER_HPP #include "swish/provider/sftp_provider.hpp" #include // bstr_t #include // com_error #include #include // simple_object #include #include // BOOST_THROW_EXCEPTION #include // BOOST_FOREACH #include // BOOST_ERROR #include // assert #include namespace test { class MockConsumer : public comet::simple_object { public: typedef ISftpConsumer interface_is; /** * Possible behaviours of file overwrite confirmation handlers * OnConfirmOverwrite. */ enum ConfirmOverwriteBehaviour { AllowOverwrite, ///< Return S_OK PreventOverwrite, ///< Return E_ABORT }; /** * Possible behaviours of mock password request handler OnPasswordRequest. */ enum PasswordBehaviour { EmptyPassword, ///< Return an empty string CustomPassword, ///< Return the string set with SetPassword WrongPassword, ///< Return a very unlikely sequence of characters FailPassword, ///< Throw E_FAIL exception if password requested AbortPassword, ///< Return unitialised optional indicating abort. }; /** * Possible behaviours of mock keyboard-interactive request handler * OnKeyboardInteractiveRequest. */ enum KeyboardInteractiveBehaviour { EmptyResponse, ///< Return an empty BSTR (not NULL, "") CustomResponse, ///< Return the string set with SetPassword WrongResponse, ///< Return a very unlikely sequence of characters FailResponse, ///< Throw exception if kb-interaction requested AbortResponse, ///< Return E_ABORT (simulate user cancelled) }; /** * Possible behaviours of mock public-key file requests. */ enum PublicKeyBehaviour { EmptyKeys, ///< Return an empty BSTR (not NULL, "") CustomKeys, ///< Return the strings set with SetKeys WrongKeys, ///< Return the wrong, but existing, key files InvalidKeys, ///< Return key files that don't exist FailKeys, ///< Throw E_FAIL exceptionn if keys requested. AbortKeys, ///< Return unitialised optional indicating abort. }; MockConsumer() : m_password_behaviour(FailPassword), m_password_attempt_count(0), m_password_attempt_count_max(1), m_keyboard_interative_behaviour(FailResponse), m_pubkey_behaviour(FailKeys), m_ki_attempt_count(0), m_ki_attempt_count_max(1), m_confirm_overwrite_behaviour(PreventOverwrite), m_confirmed_overwrite(false) { } void set_password(const std::wstring& password) { m_password = password; } void set_password_behaviour(PasswordBehaviour behaviour) { m_password_behaviour = behaviour; } void set_password_max_attempts(int max) { m_password_attempt_count_max = max; } void set_keyboard_interactive_behaviour(KeyboardInteractiveBehaviour behaviour) { m_keyboard_interative_behaviour = behaviour; } void set_keyboard_interactive_max_attempts(int max) { m_ki_attempt_count_max = max; } void set_key_files(const std::string& private_key, const std::string& public_key) { m_private_key_file = private_key; m_public_key_file = public_key; } void set_pubkey_behaviour(PublicKeyBehaviour behaviour) { m_pubkey_behaviour = behaviour; } void set_confirm_overwrite_behaviour(ConfirmOverwriteBehaviour behaviour) { m_confirm_overwrite_behaviour = behaviour; } bool was_asked_to_confirm_overwrite() const { return m_confirmed_overwrite; } // ISftpConsumer methods virtual boost::optional prompt_for_password() { ++m_password_attempt_count; // Perform chosen test behaviour // The three password cases which should never succeed will try to send // their 'reply' up to m_nMaxPassword time to simulate a user repeatedly // trying the wrong password and then giving up. if (m_password_attempt_count > m_password_attempt_count_max) BOOST_THROW_EXCEPTION( comet::com_error("Too many attempts", E_FAIL)); switch (m_password_behaviour) { case CustomPassword: return m_password; case WrongPassword: return L"WrongPasswordXyayshdkhjhdk"; case EmptyPassword: // leave password blank return std::wstring(); case FailPassword: BOOST_THROW_EXCEPTION( comet::com_error("Mock fail behaviour", E_FAIL)); case AbortPassword: return boost::optional(); default: BOOST_FAIL( "Unreachable: Unrecognised OnPasswordRequest() behaviour"); return boost::optional(); } } virtual boost::optional< std::pair> key_files() { switch (m_pubkey_behaviour) { case CustomKeys: return std::make_pair(m_private_key_file, m_public_key_file); case WrongKeys: return std::make_pair(m_public_key_file, m_private_key_file); case InvalidKeys: return std::make_pair("HumptyDumpty", "SatOnAWall"); case EmptyKeys: return std::make_pair("", ""); case FailKeys: BOOST_THROW_EXCEPTION( comet::com_error("Mock fail behaviour", E_FAIL)); case AbortKeys: return boost::optional< std::pair>(); default: BOOST_FAIL("Unreachable: Unrecognised " __FUNCTION__ " behaviour"); return boost::optional< std::pair>(); } } virtual boost::optional> challenge_response(const std::string& /*title*/, const std::string& /*instructions*/, const std::vector>& prompts) { ++m_ki_attempt_count; typedef std::pair prompt_pair; BOOST_FOREACH (const prompt_pair& prompt, prompts) { BOOST_CHECK_GT(prompt.first.size(), 0U); } // Perform chosen test behaviour // The three response cases which should never succeed will try to send // their 'reply' up to m_nMaxKbdAttempts time to simulate a user // repeatedly trying the wrong password and then giving up. if (m_ki_attempt_count > m_ki_attempt_count_max) BOOST_THROW_EXCEPTION(std::exception("Too many kb-int attempts")); std::string response; switch (m_keyboard_interative_behaviour) { case CustomResponse: { response = boost::locale::conv::utf_to_utf(m_password); break; } case WrongResponse: response = "WrongPasswordXyayshdkhjhdk"; break; case EmptyResponse: // leave response empty break; case FailResponse: BOOST_THROW_EXCEPTION(std::exception("Mock fail behaviour")); case AbortResponse: return boost::optional>(); default: BOOST_FAIL("Unreachable: Unrecognised " "OnKeyboardInteractiveRequest() behaviour"); BOOST_THROW_EXCEPTION( std::exception("Unrecognised mock behaviour")); } std::vector responses; // Create responses. Return password as first response. Any other // prompts are responded to with an empty string. responses.push_back(response); while (responses.size() < prompts.size()) { responses.push_back(""); } return responses; } HRESULT OnConfirmOverwrite(BSTR /*bstrOldFile*/, BSTR /*bstrNewFile*/) { m_confirmed_overwrite = true; switch (m_confirm_overwrite_behaviour) { case AllowOverwrite: return S_OK; case PreventOverwrite: return E_ABORT; default: assert(!"Unreachable: Unrecognised " "OnConfirmOverwrite() behaviour"); return E_UNEXPECTED; } } HRESULT OnHostkeyMismatch(BSTR /*bstrHostName*/, BSTR /*bstrHostKey*/, BSTR /*bstrHostKeyType*/) { return S_FALSE; } HRESULT OnHostkeyUnknown(BSTR /*bstrHostName*/, BSTR /*bstrHostKey*/, BSTR /*bstrHostKeyType*/) { return S_FALSE; } private: PasswordBehaviour m_password_behaviour; int m_password_attempt_count; int m_password_attempt_count_max; std::wstring m_password; KeyboardInteractiveBehaviour m_keyboard_interative_behaviour; int m_ki_attempt_count; int m_ki_attempt_count_max; PublicKeyBehaviour m_pubkey_behaviour; std::string m_public_key_file; std::string m_private_key_file; ConfirmOverwriteBehaviour m_confirm_overwrite_behaviour; bool m_confirmed_overwrite; }; } // namespace test #endif ================================================ FILE: test/common_boost/MockProvider.hpp ================================================ /** @file Mock implementation of swish::provider::sftp_provider. @if license Copyright (C) 2010, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef TEST_COMMON_BOOST_MOCK_PROVIDER_HPP #define TEST_COMMON_BOOST_MOCK_PROVIDER_HPP #pragma once #include "test/common_boost/tree.hpp" // tree container for mocking filesystem #include "swish/provider/sftp_provider.hpp" // sftp_provider #include // bstr_t #include // datetime_t #include #include // path #include // BOOST_FOREACH #include // wformat #include #include // BOOST_THROW_EXCEPTION #include // equal_to, less #include #include #include // SHCreateMemStream namespace test { namespace detail { typedef tree Filesystem; typedef Filesystem::iterator FilesystemLocation; inline bool name_match( const std::wstring& name, const swish::provider::sftp_filesystem_item& sftp_item) { return name == sftp_item.filename(); } /** * Return an iterator to the node in the mock filesystem indicated by the * path given as a string. */ inline FilesystemLocation find_location_from_path( const Filesystem& filesystem, const ssh::filesystem::path& path) { // Start searching in root of 'filesystem' FilesystemLocation current_dir = filesystem.begin(); // Walk down list of tokens finding each item below the previous BOOST_FOREACH(ssh::filesystem::path segment, path.relative_path()) { std::wstring name = segment.wstring(); if (name == L".") continue; FilesystemLocation dir = find_if( filesystem.begin(current_dir), filesystem.end(current_dir), boost::bind(&name_match, name, _1)); if (dir == filesystem.end(current_dir)) { std::string message = str(boost::format("Mock file '%s' not found") % name); BOOST_THROW_EXCEPTION(std::exception(message.c_str())); } current_dir = dir; } if (current_dir == filesystem.end()) BOOST_THROW_EXCEPTION(std::exception("Unexpected lookup failure!")); return current_dir; } class mock_filesystem_file : public swish::provider::sftp_filesystem_item_interface { public: static swish::provider::sftp_filesystem_item create( const std::wstring& name, ULONG permissions, ULONGLONG size, comet::datetime_t date) { return swish::provider::sftp_filesystem_item( boost::shared_ptr( new mock_filesystem_file(name, permissions, size, date))); } BOOST_SCOPED_ENUM(type) type() const { return type::file; } ssh::filesystem::path filename() const { return m_name; } unsigned long permissions() const { return m_permissions; } boost::optional owner() const { return L"mockowner"; } unsigned long uid() const { return 42; } boost::optional group() const { return L"mockgroup"; } unsigned long gid() const { return 24; } boost::uint64_t size_in_bytes() const { return m_size; } comet::datetime_t last_accessed() const { return comet::datetime_t(); } comet::datetime_t last_modified() const { return m_date; } private: mock_filesystem_file( const std::wstring& name, ULONG permissions, ULONGLONG size, comet::datetime_t date) : m_name(name), m_permissions(permissions), m_size(size), m_date(date) {} std::wstring m_name; ULONG m_permissions; ULONGLONG m_size; comet::datetime_t m_date; }; class mock_filesystem_directory : public swish::provider::sftp_filesystem_item_interface { public: static swish::provider::sftp_filesystem_item create( const std::wstring& name) { return swish::provider::sftp_filesystem_item( boost::shared_ptr( new mock_filesystem_directory(name))); } BOOST_SCOPED_ENUM(type) type() const { return type::directory; } ssh::filesystem::path filename() const { return m_name; } unsigned long permissions() const { return 040777; } boost::optional owner() const { return L"mockowner"; } unsigned long uid() const { return 42; } boost::optional group() const { return L"mockgroup"; } unsigned long gid() const { return 24; } boost::uint64_t size_in_bytes() const { return 0U; } comet::datetime_t last_accessed() const { return comet::datetime_t(); } comet::datetime_t last_modified() const { return comet::datetime_t(1601, 10, 5, 13, 54, 22); } private: mock_filesystem_directory(const std::wstring& name) : m_name(name) {} std::wstring m_name; }; class mock_filesystem_link : public swish::provider::sftp_filesystem_item_interface { public: static swish::provider::sftp_filesystem_item create( const std::wstring& name) { return swish::provider::sftp_filesystem_item( boost::shared_ptr( new mock_filesystem_link(name))); } BOOST_SCOPED_ENUM(type) type() const { return type::link; } ssh::filesystem::path filename() const { return m_name; } unsigned long permissions() const { return 040777; } boost::optional owner() const { return L"mockowner"; } unsigned long uid() const { return 42; } boost::optional group() const { return L"mockgroup"; } unsigned long gid() const { return 24; } boost::uint64_t size_in_bytes() const { return 0U; } comet::datetime_t last_accessed() const { return comet::datetime_t(); } comet::datetime_t last_modified() const { return comet::datetime_t(1601, 10, 5, 13, 54, 22); } private: mock_filesystem_link(const std::wstring& name) : m_name(name) {} std::wstring m_name; }; inline std::wstring tag_filename( const std::wstring& filename, const ssh::filesystem::path& directory) { // UNOBVIOUS: converting the path to a string here, rather than passing // the path directly to the formatter, because Boost.Filesystem v3, // surprisingly, quotes all paths that are sent to an output stream. return str(boost::wformat(filename) % directory.filename().wstring()); } inline void make_item_in( Filesystem& filesystem, FilesystemLocation loc, const swish::provider::sftp_filesystem_item& item) { filesystem.append_child(loc, item); } inline void make_item_in( Filesystem& filesystem, const ssh::filesystem::path& path, const swish::provider::sftp_filesystem_item& item) { make_item_in( filesystem, find_location_from_path(filesystem, path), item); } /** * Generates a listing for the given directory and tags each filename with * the name of the parent folder. This allows us to detect a correct * listing later. */ inline void fill_mock_listing( Filesystem& filesystem, const ssh::filesystem::path& directory) { std::vector filenames; filenames.push_back(tag_filename(L"test%sfile", directory)); filenames.push_back(tag_filename(L"test%sFile", directory)); filenames.push_back(tag_filename(L"test%sfile.ext", directory)); filenames.push_back(tag_filename(L"test%sfile.txt", directory)); filenames.push_back(tag_filename(L"test%sfile with spaces", directory)); filenames.push_back( tag_filename(L"test%sfile with \"quotes\" and spaces", directory)); filenames.push_back(tag_filename(L"test%sfile.ext.txt", directory)); filenames.push_back(tag_filename(L"test%sfile..", directory)); filenames.push_back(tag_filename(L".test%shiddenfile", directory)); std::vector dates; dates.push_back(comet::datetime_t()); dates.push_back(comet::datetime_t::now()); dates.push_back(comet::datetime_t(1899, 7, 13, 17, 59, 12)); dates.push_back(comet::datetime_t(9999, 12, 31, 23, 59, 59)); dates.push_back(comet::datetime_t(2000, 2, 29, 12, 47, 1)); dates.push_back(comet::datetime_t(1978, 3, 3, 3, 00, 00)); dates.push_back(comet::datetime_t(1601, 1, 1, 0, 00, 00)); dates.push_back(comet::datetime_t(2007, 2, 28, 0, 0, 0)); dates.push_back(comet::datetime_t(1752, 9, 03, 7, 27, 8)); unsigned long cycle = 0; unsigned long size = 0; while (!filenames.empty()) { // Try to cycle through the permissions on each successive file // TODO: I have no idea if this works unsigned permissions = (cycle % 1) || ((cycle % 2) << 1) || ((cycle % 3) << 2); make_item_in( filesystem, directory, mock_filesystem_file::create( filenames.back(), permissions, size, dates.back())); dates.pop_back(); filenames.pop_back(); cycle++; size = (size + cycle) << 10; } // Add some dummy folders also std::vector folder_names; folder_names.push_back(tag_filename(L"Test%sfolder", directory)); folder_names.push_back(tag_filename(L"test%sfolder.ext", directory)); folder_names.push_back(tag_filename(L"test%sfolder.bmp", directory)); folder_names.push_back( tag_filename(L"test%sfolder with spaces", directory)); folder_names.push_back(tag_filename(L".test%shiddenfolder", directory)); while (!folder_names.empty()) { make_item_in( filesystem, directory, mock_filesystem_directory::create(folder_names.back())); folder_names.pop_back(); } // Last but not least, links std::vector link_names; link_names.push_back(tag_filename(L"link%sfolder", directory)); link_names.push_back(tag_filename(L"another link%sfolder", directory)); link_names.push_back(tag_filename(L"p%s", directory)); link_names.push_back(tag_filename(L".q%s", directory)); link_names.push_back(tag_filename(L"this_link_is_broken_%s", directory)); while (!link_names.empty()) { make_item_in( filesystem, directory, mock_filesystem_link::create(link_names.back())); link_names.pop_back(); } } struct comparator { bool operator()( const swish::provider::sftp_filesystem_item& left, const swish::provider::sftp_filesystem_item& right) { return left.filename() < right.filename(); } }; } class MockProvider : public swish::provider::sftp_provider { public: /** * Possible behaviours of listing returned by mock GetListing() method. */ typedef enum tagListingBehaviour { MockListing, ///< Return a dummy list of files and S_OK. EmptyListing, ///< Return an empty list and S_OK. SFalseNoListing, ///< Return a NULL listing and S_FALSE. AbortListing, ///< Return a NULL listing E_ABORT. FailListing ///< Return a NULL listing E_FAIL. } ListingBehaviour; /** * Possible behaviours of mock Rename() method. */ typedef enum tagRenameBehaviour { RenameOK, ///< Return S_OK - rename unconditionally succeeded. ConfirmOverwrite, ///< Call ISftpConsumer's OnConfirmOverwrite and ///< return its return value. AbortRename, ///< Return E_ABORT. FailRename ///< Return E_FAIL. } RenameBehaviour; MockProvider() : m_listing_behaviour(MockListing), m_rename_behaviour(RenameOK) { // Create filesystem root detail::FilesystemLocation root = m_filesystem.insert( m_filesystem.begin(), detail::mock_filesystem_directory::create(L"/")); // Create two subdirectories and fill them with an expected set of items // whose names are 'tagged' with the directory name detail::FilesystemLocation tmp = m_filesystem.append_child( root, detail::mock_filesystem_directory::create(L"tmp")); detail::FilesystemLocation swish = m_filesystem.append_child( tmp, detail::mock_filesystem_directory::create(L"swish")); detail::fill_mock_listing(m_filesystem, L"/tmp"); detail::fill_mock_listing(m_filesystem, L"/tmp/swish"); } ~MockProvider() { m_filesystem.clear(); } void set_listing_behaviour(ListingBehaviour behaviour) { m_listing_behaviour = behaviour; } void set_rename_behaviour(RenameBehaviour behaviour) { m_rename_behaviour = behaviour; } virtual swish::provider::directory_listing listing( const ssh::filesystem::path& directory) { std::vector files; switch (m_listing_behaviour) { case EmptyListing: break; case MockListing: { detail::FilesystemLocation dir = detail::find_location_from_path(m_filesystem, directory); // Copy directory out of tree and sort alphabetically files.insert( files.begin(), m_filesystem.begin(dir), m_filesystem.end(dir)); std::sort(files.begin(), files.end(), detail::comparator()); } break; case SFalseNoListing: BOOST_THROW_EXCEPTION(comet::com_error(S_FALSE)); case AbortListing: BOOST_THROW_EXCEPTION(comet::com_error(E_ABORT)); case FailListing: BOOST_THROW_EXCEPTION(comet::com_error(E_FAIL)); default: BOOST_THROW_EXCEPTION(comet::com_error( "Unreachable: Unrecognised mock behaviour", E_UNEXPECTED)); } return files; } virtual comet::com_ptr get_file( const ssh::filesystem::path& file_path, std::ios_base::openmode /*mode*/) { detail::find_location_from_path( m_filesystem, file_path); // test existence // Create IStream instance whose data is the file path return ::SHCreateMemStream( reinterpret_cast(file_path.wstring().c_str()), static_cast((file_path.wstring().size() + 1) * sizeof(wchar_t))); } virtual VARIANT_BOOL rename( ISftpConsumer* consumer, const ssh::filesystem::path& from_path, const ssh::filesystem::path& to_path) { detail::find_location_from_path( m_filesystem, from_path); // test existence switch (m_rename_behaviour) { case RenameOK: return VARIANT_FALSE; case ConfirmOverwrite: { HRESULT hr = consumer->OnConfirmOverwrite( comet::bstr_t(from_path).in(), comet::bstr_t(to_path).in()); if (SUCCEEDED(hr)) return VARIANT_TRUE; BOOST_THROW_EXCEPTION( comet::com_error_from_interface( consumer, hr)); } case AbortRename: BOOST_THROW_EXCEPTION(comet::com_error(E_ABORT)); case FailRename: BOOST_THROW_EXCEPTION(comet::com_error(E_FAIL)); default: BOOST_THROW_EXCEPTION(comet::com_error( "Unreachable: Unrecognised mock behaviour", E_UNEXPECTED)); } } virtual void remove_all(const ssh::filesystem::path& /*path*/) {}; virtual void create_new_directory(const ssh::filesystem::path& /*path*/) {}; virtual ssh::filesystem::path resolve_link( const ssh::filesystem::path& path) { std::wstring p(path.wstring()); // link names with 'broken' in their name we pretend to resolve to // a target that doesn't exist if (p.find(L"broken") != std::wstring::npos) return ssh::filesystem::path(L"/tmp/broken_link_target"); // link names with 'folder' in their name we pretend target a directory // (/tmp/testtmpfolder) and the others we target at a file // (/tmp/testfile) else if (p.find(L"folder") != std::wstring::npos) return ssh::filesystem::path(L"/tmp/Testtmpfolder"); else return ssh::filesystem::path(L"/tmp/testtmpfile"); }; virtual swish::provider::sftp_filesystem_item stat( const ssh::filesystem::path& path, bool follow_links) { ssh::filesystem::path target; if (follow_links) { target = resolve_link(path); } else { target = path; } detail::FilesystemLocation dir = detail::find_location_from_path(m_filesystem, target); return *dir; } private: detail::Filesystem m_filesystem; ListingBehaviour m_listing_behaviour; RenameBehaviour m_rename_behaviour; }; } // namespace test #endif ================================================ FILE: test/common_boost/SwishPidlFixture.hpp ================================================ // Copyright 2012, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef TEST_COMMON_SWISH_PIDL_FIXTURE_HPP #define TEST_COMMON_SWISH_PIDL_FIXTURE_HPP #include // create_host_itemid #include // create_remote_itemid #include // datetime_t #include // apidl_t, cpidl_t #include // pidl_from_parsing_name #include namespace test { class SwishPidlFixture { public: /** * Return a PIDL pretending to be the Swish HostFolder in Explorer. */ washer::shell::pidl::apidl_t fake_swish_pidl() { #include struct fake_swish_item_id { USHORT cb; WCHAR some_data[3]; }; struct fake_swish_item_template { fake_swish_item_id id; SHITEMID terminator; }; #include fake_swish_item_template item; std::memset(&item, 0, sizeof(item)); item.id.cb = sizeof(item.id); assert(item.terminator.cb == 0); // The full parsing name of the Swish pidl is // ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\ // ::{B816A83A-5022-11DC-9153-0090F5284F85} but we can't rely on the // second part existing because Swish might not be registered on this // machine. So we make a fake one. return washer::shell::pidl_from_parsing_name( L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}") + washer::shell::pidl::cpidl_t( reinterpret_cast(&item)); } /** * Return the PIDL of the Swish HostFolder in Explorer. * * Relies on Swish having been registered. */ washer::shell::pidl::apidl_t real_swish_pidl() { return washer::shell::pidl_from_parsing_name( L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\" L"::{B816A83A-5022-11DC-9153-0090F5284F85}"); } washer::shell::pidl::cpidl_t create_dummy_remote_itemid(const std::wstring& filename, bool is_folder) { return swish::remote_folder::create_remote_itemid( filename, is_folder, false, L"bobuser", L"bob's group", 1001, 65535, 040666, 18446744073709551615, comet::datetime_t(1970, 11, 1, 9, 15, 42, 6), comet::datetime_t((DATE)0)); } /** * Get an absolute PIDL that ends in a HOSTPIDL to root RemoteFolder on. */ washer::shell::pidl::apidl_t create_dummy_root_host_pidl() { return fake_swish_pidl() + swish::host_folder::create_host_itemid( L"test.example.com", L"user", L"/tmp", 22, L"Test PIDL"); } /** * Get an absolute PIDL that ends in a REMOTEPIDL to root RemoteFolder on. */ washer::shell::pidl::apidl_t create_dummy_root_pidl() { return create_dummy_root_host_pidl() + create_dummy_remote_itemid(L"swish", true); // Some (older) tests rely on the name being "swish" here } }; } #endif ================================================ FILE: test/common_boost/data_object_utils.cpp ================================================ /** @file Helper functions for tests that involve DataObjects. @if license Copyright (C) 2009, 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "data_object_utils.hpp" #include "swish/shell/shell.hpp" #include // bind_to_handler_object #include // com_error #include #include // BOOST_THROW_EXCEPTION #include // numeric_cast #include // system_error, get_system_category #include #include using swish::shell::pidl_from_path; using swish::shell::ui_object_of_items; using washer::shell::bind_to_handler_object; using boost::filesystem::path; using boost::shared_ptr; using boost::numeric_cast; using boost::system::get_system_category; using boost::system::system_error; using comet::com_error_from_interface; using comet::com_ptr; using std::wstring; using std::vector; namespace { /** * Return the path of the currently running executable. */ path get_module_path(HMODULE hmodule=NULL) { vector buffer(MAX_PATH); unsigned long len = ::GetModuleFileNameW( hmodule, &buffer[0], numeric_cast(buffer.size())); if (len == 0) BOOST_THROW_EXCEPTION( system_error(::GetLastError(), get_system_category())); return wstring(&buffer[0], len); } } namespace test { namespace data_object_utils { /** * Create a zip archive containing two files that we can use as a source * of 'virtual' namespace items. * * Virtual namespace items are not real files on disk and instead are * simulated by an IShellFolder implementation. This is how Swish * itself presents its 'files' to Explorer. The ZIP-file browser in * Windows 2000 and later does the same thing to give access to the * files inside a .zip. We're going to use one of these to test our * shell data object wrapper with virtual items. */ path create_test_zip_file(const path& in_directory) { path source = get_module_path().parent_path() / L"test_zip_file.zip"; path destination = in_directory / L"test_zip_file.zip"; copy_file(source, destination); return destination; } /** * Return a DataObject with the contents of a zip file. */ com_ptr data_object_for_zipfile(const path& zip_file) { shared_ptr zip_pidl = pidl_from_path(zip_file); com_ptr zip_folder = bind_to_handler_object(zip_pidl.get()); com_ptr enum_items; HRESULT hr = zip_folder->EnumObjects( NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, enum_items.out()); if (FAILED(hr)) BOOST_THROW_EXCEPTION(com_error_from_interface(zip_folder, hr)); enum_items->Reset(); vector > pidls; while (hr == S_OK) { PITEMID_CHILD pidl; hr = enum_items->Next(1, &pidl, NULL); if (hr == S_OK) { shared_ptr child_pidl(pidl, ::ILFree); pidls.push_back( shared_ptr( ::ILCombine(zip_pidl.get(), child_pidl.get()), ::ILFree)); } } return ui_object_of_items(pidls.begin(), pidls.end()); } }} // namespace test::data_object_utils ================================================ FILE: test/common_boost/data_object_utils.hpp ================================================ /** @file Helper functions for tests that involve DataObjects. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // path #include // com_ptr #include // IDataObject namespace test { namespace data_object_utils { boost::filesystem::path create_test_zip_file( const boost::filesystem::path& in_directory); comet::com_ptr data_object_for_zipfile( const boost::filesystem::path& zip_file); }} // namespace test::data_object_utils ================================================ FILE: test/common_boost/fixtures.hpp ================================================ // Copyright 2009, 2012, 2013, 2014, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #pragma once #include #include // For system_error #include // For WSAStartup/Cleanup #include // For CoInitialize/Uninitialize #include namespace test { /** * Fixture that initialises and uninitialises COM. */ class ComFixture { public: ComFixture() { HRESULT hr = ::CoInitialize(NULL); BOOST_WARN_MESSAGE(SUCCEEDED(hr), "::CoInitialize failed"); } virtual ~ComFixture() { ::CoUninitialize(); } }; /** * Fixture that initialises and uninitialises Winsock. */ class WinsockFixture { public: WinsockFixture() { WSADATA wsadata; int err = ::WSAStartup(MAKEWORD(2, 2), &wsadata); if (err) throw boost::system::system_error( err, boost::system::get_system_category()); } virtual ~WinsockFixture() { ::WSACleanup(); } }; } // namespace test ================================================ FILE: test/common_boost/helpers.hpp ================================================ /** @file Helper functions for Boost.Test, @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma once #include #include #include #include // predicate_result #include #include #include namespace std { inline std::ostream& operator<<( std::ostream& out, const std::wstring& wide_in) { out << swish::utils::WideStringToUtf8String(wide_in); return out; } inline std::ostream& operator<<( std::ostream& out, const wchar_t* wide_in) { out << std::wstring(wide_in); return out; } inline std::ostream& operator<<( std::ostream& out, const boost::filesystem::path& path) { out << path.string(); return out; } } namespace test { namespace detail { inline boost::test_tools::predicate_result s_ok(HRESULT hr) { if (hr == S_OK) { boost::test_tools::predicate_result res(true); res.message() << "COM status code was S_OK"; return res; } else { boost::test_tools::predicate_result res(false); res.message() << "COM status code was not S_OK: "; res.message() << comet::com_error(hr).s_str(); return res; } } template inline boost::test_tools::predicate_result s_ok_error_info( comet::com_ptr failure_source, HRESULT hr) { if (hr == S_OK) { boost::test_tools::predicate_result res(true); res.message() << "COM status code was S_OK"; return res; } else { boost::test_tools::predicate_result res(false); res.message() << "COM status code was not S_OK: "; res.message() << comet::com_error_from_interface(failure_source, hr).s_str(); return res; } } } } /** * COM HRESULT-specific assertions * @{ */ #define BOOST_REQUIRE_OK(hr) BOOST_REQUIRE(test::detail::s_ok( (hr) )) #define BOOST_CHECK_OK(hr) BOOST_CHECK(test::detail::s_ok( (hr) )) #define BOOST_WARN_OK(hr) BOOST_WARN(test::detail::s_ok( (hr) )) #define BOOST_REQUIRE_INTERFACE_OK(failure_source, hr) \ BOOST_REQUIRE(test::detail::s_ok_error_info( (failure_source), (hr) )) #define BOOST_CHECK_INTERFACE_OK(failure_source, hr) \ BOOST_CHECK(test::detail::s_ok_error_info( (failure_source), (hr) )) #define BOOST_WARN_INTERFACE_OK(failure_source, hr) \ BOOST_WARN(test::detail::s_ok_error_info( (failure_source), (hr) )) /* @} */ ================================================ FILE: test/common_boost/stream_utils.cpp ================================================ /** @file Helper functions for tests that involve IStreams. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "test/common_boost/helpers.hpp" // BOOST_REQUIRE_OK #include // numeric_cast #include // BOOST_CHECK, BOOST_REQUIRE using boost::numeric_cast; using comet::com_ptr; namespace test { namespace stream_utils { namespace { size_t verify_stream_read_( void* data, ULONG data_size, com_ptr stream) { ULONG total_bytes_read = 0; HRESULT hr = E_FAIL; do { ULONG bytes_requested = data_size - total_bytes_read; ULONG bytes_read = 0; hr = stream->Read( static_cast(data) + total_bytes_read, bytes_requested, &bytes_read); if (hr == S_OK) { // S_OK indicates a complete read so make sure this read // however many bytes were left from any previous (possibly // none) short reads BOOST_REQUIRE_EQUAL(bytes_read, bytes_requested); return total_bytes_read + bytes_read; } else if (hr == S_FALSE) { // S_FALSE indicated a 'short' read so make sure it really // is short BOOST_CHECK_LT(bytes_read, bytes_requested); total_bytes_read += bytes_read; break; // end of file } else { // not really requiring S_OK; S_FALSE is fine too BOOST_REQUIRE_OK(hr); } } while (hr == S_OK && (total_bytes_read < data_size)); // Trying to read more should succeed but return 0 bytes read char buf[10]; ULONG should_remain_zero = 0; BOOST_REQUIRE( SUCCEEDED(stream->Read(buf, sizeof(buf), &should_remain_zero))); BOOST_REQUIRE_EQUAL(should_remain_zero, 0U); return total_bytes_read; } } size_t verify_stream_read( void* data, size_t data_size, com_ptr stream) { return verify_stream_read_(data, numeric_cast(data_size), stream); } }} // namespace test::stream_utils ================================================ FILE: test/common_boost/stream_utils.hpp ================================================ /** @file Helper functions for tests that involve IStreams. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef SWISH_TEST_COMMON_BOOST_STREAM_UTILS #define SWISH_TEST_COMMON_BOOST_STREAM_UTILS #include // com_ptr #include // IStream namespace test { namespace stream_utils { size_t verify_stream_read( void* data, size_t data_size, comet::com_ptr stream); }} // namespace test::stream_utils #endif ================================================ FILE: test/common_boost/tree.hh ================================================ /* $Id: tree.hh,v 1.151 2008/05/07 15:46:14 peekas Exp $ STL-like templated tree class. Copyright (C) 2001-2006 Kasper Peeters . */ /** \mainpage tree.hh \author Kasper Peeters \version 2.62 \date 28-Aug-2008 \see http://www.aei.mpg.de/~peekas/tree/ \see http://www.aei.mpg.de/~peekas/tree/ChangeLog The tree.hh library for C++ provides an STL-like container class for n-ary trees, templated over the data stored at the nodes. Various types of iterators are provided (post-order, pre-order, and others). Where possible the access methods are compatible with the STL or alternative algorithms are available. */ /* The tree.hh code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 or 3. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** \todo - New-style move members are not completely finished yet. - It would be good to have an iterator which can iterate over all nodes below a given node. Something similar to the leaf iterator we have right now, but not restricted to the leaves. - If a range uses const iter_base& as end iterator, things will inevitably go wrong, because upcast from iter_base to a non-sibling_iter is incorrect. This upcast should be removed (and then all illegal uses as previously in 'equal' will be flagged by the compiler). This requires new copy constructors though. - There's a bug in replace(sibling_iterator, ...) when the ranges sit next to each other. Turned up in append_child(iter,iter) but has been avoided now. - "std::operator<" does not work correctly on our iterators, and for some reason a globally defined template operator< did not get picked up. Using a comparison class now, but this should be investigated. */ #ifndef tree_hh_ #define tree_hh_ #include #include #include #include #include #include #include // HP-style construct/destroy have gone from the standard, // so here is a copy. namespace kp { template void constructor(T1* p, T2& val) { new ((void *) p) T1(val); } template void constructor(T1* p) { new ((void *) p) T1; } template void destructor(T1* p) { p->~T1(); } }; /// A node in the tree, combining links to other nodes as well as the actual data. template class tree_node_ { // size: 5*4=20 bytes (on 32 bit arch), can be reduced by 8. public: tree_node_ *parent; tree_node_ *first_child, *last_child; tree_node_ *prev_sibling, *next_sibling; T data; }; // __attribute__((packed)); template > > class tree { protected: typedef tree_node_ tree_node; public: /// Value of the data stored at a node. typedef T value_type; class iterator_base; class pre_order_iterator; class post_order_iterator; class sibling_iterator; class leaf_iterator; tree(); tree(const T&); tree(const iterator_base&); tree(const tree&); ~tree(); void operator=(const tree&); /// Base class for iterators, only pointers stored, no traversal logic. #ifdef __SGI_STL_PORT class iterator_base : public stlport::bidirectional_iterator { #else class iterator_base { #endif public: typedef T value_type; typedef T* pointer; typedef T& reference; typedef size_t size_type; typedef ptrdiff_t difference_type; typedef std::bidirectional_iterator_tag iterator_category; iterator_base(); iterator_base(tree_node *); T& operator*() const; T* operator->() const; /// When called, the next increment/decrement skips children of this node. void skip_children(); /// Number of children of the node pointed to by the iterator. unsigned int number_of_children() const; sibling_iterator begin() const; sibling_iterator end() const; tree_node *node; protected: bool skip_current_children_; }; /// Depth-first iterator, first accessing the node, then its children. class pre_order_iterator : public iterator_base { public: pre_order_iterator(); pre_order_iterator(tree_node *); pre_order_iterator(const iterator_base&); pre_order_iterator(const sibling_iterator&); bool operator==(const pre_order_iterator&) const; bool operator!=(const pre_order_iterator&) const; pre_order_iterator& operator++(); pre_order_iterator& operator--(); pre_order_iterator operator++(int); pre_order_iterator operator--(int); pre_order_iterator& operator+=(unsigned int); pre_order_iterator& operator-=(unsigned int); }; /// Depth-first iterator, first accessing the children, then the node itself. class post_order_iterator : public iterator_base { public: post_order_iterator(); post_order_iterator(tree_node *); post_order_iterator(const iterator_base&); post_order_iterator(const sibling_iterator&); bool operator==(const post_order_iterator&) const; bool operator!=(const post_order_iterator&) const; post_order_iterator& operator++(); post_order_iterator& operator--(); post_order_iterator operator++(int); post_order_iterator operator--(int); post_order_iterator& operator+=(unsigned int); post_order_iterator& operator-=(unsigned int); /// Set iterator to the first child as deep as possible down the tree. void descend_all(); }; /// Breadth-first iterator, using a queue class breadth_first_queued_iterator : public iterator_base { public: breadth_first_queued_iterator(); breadth_first_queued_iterator(tree_node *); breadth_first_queued_iterator(const iterator_base&); bool operator==(const breadth_first_queued_iterator&) const; bool operator!=(const breadth_first_queued_iterator&) const; breadth_first_queued_iterator& operator++(); breadth_first_queued_iterator operator++(int); breadth_first_queued_iterator& operator+=(unsigned int); private: std::queue traversal_queue; }; /// The default iterator types throughout the tree class. typedef pre_order_iterator iterator; typedef breadth_first_queued_iterator breadth_first_iterator; /// Iterator which traverses only the nodes at a given depth from the root. class fixed_depth_iterator : public iterator_base { public: fixed_depth_iterator(); fixed_depth_iterator(tree_node *); fixed_depth_iterator(const iterator_base&); fixed_depth_iterator(const sibling_iterator&); fixed_depth_iterator(const fixed_depth_iterator&); bool operator==(const fixed_depth_iterator&) const; bool operator!=(const fixed_depth_iterator&) const; fixed_depth_iterator& operator++(); fixed_depth_iterator& operator--(); fixed_depth_iterator operator++(int); fixed_depth_iterator operator--(int); fixed_depth_iterator& operator+=(unsigned int); fixed_depth_iterator& operator-=(unsigned int); tree_node *top_node; }; /// Iterator which traverses only the nodes which are siblings of each other. class sibling_iterator : public iterator_base { public: sibling_iterator(); sibling_iterator(tree_node *); sibling_iterator(const sibling_iterator&); sibling_iterator(const iterator_base&); bool operator==(const sibling_iterator&) const; bool operator!=(const sibling_iterator&) const; sibling_iterator& operator++(); sibling_iterator& operator--(); sibling_iterator operator++(int); sibling_iterator operator--(int); sibling_iterator& operator+=(unsigned int); sibling_iterator& operator-=(unsigned int); tree_node *range_first() const; tree_node *range_last() const; tree_node *parent_; private: void set_parent_(); }; /// Iterator which traverses only the leaves. class leaf_iterator : public iterator_base { public: leaf_iterator(); leaf_iterator(tree_node *, tree_node *top=0); leaf_iterator(const sibling_iterator&); leaf_iterator(const iterator_base&); bool operator==(const leaf_iterator&) const; bool operator!=(const leaf_iterator&) const; leaf_iterator& operator++(); leaf_iterator& operator--(); leaf_iterator operator++(int); leaf_iterator operator--(int); leaf_iterator& operator+=(unsigned int); leaf_iterator& operator-=(unsigned int); private: tree_node *top_node; }; /// Return iterator to the beginning of the tree. inline pre_order_iterator begin() const; /// Return iterator to the end of the tree. inline pre_order_iterator end() const; /// Return post-order iterator to the beginning of the tree. post_order_iterator begin_post() const; /// Return post-order end iterator of the tree. post_order_iterator end_post() const; /// Return fixed-depth iterator to the first node at a given depth from the given iterator. fixed_depth_iterator begin_fixed(const iterator_base&, unsigned int) const; /// Return fixed-depth end iterator. fixed_depth_iterator end_fixed(const iterator_base&, unsigned int) const; /// Return breadth-first iterator to the first node at a given depth. breadth_first_queued_iterator begin_breadth_first() const; /// Return breadth-first end iterator. breadth_first_queued_iterator end_breadth_first() const; /// Return sibling iterator to the first child of given node. sibling_iterator begin(const iterator_base&) const; /// Return sibling end iterator for children of given node. sibling_iterator end(const iterator_base&) const; /// Return leaf iterator to the first leaf of the tree. leaf_iterator begin_leaf() const; /// Return leaf end iterator for entire tree. leaf_iterator end_leaf() const; /// Return leaf iterator to the first leaf of the subtree at the given node. leaf_iterator begin_leaf(const iterator_base& top) const; /// Return leaf end iterator for the subtree at the given node. leaf_iterator end_leaf(const iterator_base& top) const; /// Return iterator to the parent of a node. template static iter parent(iter); /// Return iterator to the previous sibling of a node. template iter previous_sibling(iter) const; /// Return iterator to the next sibling of a node. template iter next_sibling(iter) const; /// Return iterator to the next node at a given depth. template iter next_at_same_depth(iter) const; /// Erase all nodes of the tree. void clear(); /// Erase element at position pointed to by iterator, return incremented iterator. template iter erase(iter); /// Erase all children of the node pointed to by iterator. void erase_children(const iterator_base&); /// Insert empty node as last/first child of node pointed to by position. template iter append_child(iter position); template iter prepend_child(iter position); /// Insert node as last/first child of node pointed to by position. template iter append_child(iter position, const T& x); template iter prepend_child(iter position, const T& x); /// Append the node (plus its children) at other_position as last/first child of position. template iter append_child(iter position, iter other_position); template iter prepend_child(iter position, iter other_position); /// Append the nodes in the from-to range (plus their children) as last/first children of position. template iter append_children(iter position, sibling_iterator from, sibling_iterator to); template iter prepend_children(iter position, sibling_iterator from, sibling_iterator to); /// Short-hand to insert topmost node in otherwise empty tree. pre_order_iterator set_head(const T& x); /// Insert node as previous sibling of node pointed to by position. template iter insert(iter position, const T& x); /// Specialisation of previous member. sibling_iterator insert(sibling_iterator position, const T& x); /// Insert node (with children) pointed to by subtree as previous sibling of node pointed to by position. template iter insert_subtree(iter position, const iterator_base& subtree); /// Insert node as next sibling of node pointed to by position. template iter insert_after(iter position, const T& x); /// Insert node (with children) pointed to by subtree as next sibling of node pointed to by position. template iter insert_subtree_after(iter position, const iterator_base& subtree); /// Replace node at 'position' with other node (keeping same children); 'position' becomes invalid. template iter replace(iter position, const T& x); /// Replace node at 'position' with subtree starting at 'from' (do not erase subtree at 'from'); see above. template iter replace(iter position, const iterator_base& from); /// Replace string of siblings (plus their children) with copy of a new string (with children); see above sibling_iterator replace(sibling_iterator orig_begin, sibling_iterator orig_end, sibling_iterator new_begin, sibling_iterator new_end); /// Move all children of node at 'position' to be siblings, returns position. template iter flatten(iter position); /// Move nodes in range to be children of 'position'. template iter reparent(iter position, sibling_iterator begin, sibling_iterator end); /// Move all child nodes of 'from' to be children of 'position'. template iter reparent(iter position, iter from); /// Replace node with a new node, making the old node a child of the new node. template iter wrap(iter position, const T& x); /// Move 'source' node (plus its children) to become the next sibling of 'target'. template iter move_after(iter target, iter source); /// Move 'source' node (plus its children) to become the previous sibling of 'target'. template iter move_before(iter target, iter source); sibling_iterator move_before(sibling_iterator target, sibling_iterator source); /// Move 'source' node (plus its children) to become the node at 'target' (erasing the node at 'target'). template iter move_ontop(iter target, iter source); /// Merge with other tree, creating new branches and leaves only if they are not already present. void merge(sibling_iterator, sibling_iterator, sibling_iterator, sibling_iterator, bool duplicate_leaves=false); /// Sort (std::sort only moves values of nodes, this one moves children as well). void sort(sibling_iterator from, sibling_iterator to, bool deep=false); template void sort(sibling_iterator from, sibling_iterator to, StrictWeakOrdering comp, bool deep=false); /// Compare two ranges of nodes (compares nodes as well as tree structure). template bool equal(const iter& one, const iter& two, const iter& three) const; template bool equal(const iter& one, const iter& two, const iter& three, BinaryPredicate) const; template bool equal_subtree(const iter& one, const iter& two) const; template bool equal_subtree(const iter& one, const iter& two, BinaryPredicate) const; /// Extract a new tree formed by the range of siblings plus all their children. tree subtree(sibling_iterator from, sibling_iterator to) const; void subtree(tree&, sibling_iterator from, sibling_iterator to) const; /// Exchange the node (plus subtree) with its sibling node (do nothing if no sibling present). void swap(sibling_iterator it); /// Exchange two nodes (plus subtrees) void swap(iterator, iterator); /// Count the total number of nodes. size_t size() const; /// Count the total number of nodes below the indicated node (plus one). size_t size(const iterator_base&) const; /// Check if tree is empty. bool empty() const; /// Compute the depth to the root or to a fixed other iterator. static int depth(const iterator_base&); static int depth(const iterator_base&, const iterator_base&); /// Determine the maximal depth of the tree. An empty tree has max_depth=-1. int max_depth() const; /// Determine the maximal depth of the tree with top node at the given position. int max_depth(const iterator_base&) const; /// Count the number of children of node at position. static unsigned int number_of_children(const iterator_base&); /// Count the number of siblings (left and right) of node at iterator. Total nodes at this level is +1. unsigned int number_of_siblings(const iterator_base&) const; /// Determine whether node at position is in the subtrees with root in the range. bool is_in_subtree(const iterator_base& position, const iterator_base& begin, const iterator_base& end) const; /// Determine whether the iterator is an 'end' iterator and thus not actually pointing to a node. bool is_valid(const iterator_base&) const; /// Determine the index of a node in the range of siblings to which it belongs. unsigned int index(sibling_iterator it) const; /// Inverse of 'index': return the n-th child of the node at position. static sibling_iterator child(const iterator_base& position, unsigned int); /// Comparator class for iterators (compares pointer values; why doesn't this work automatically?) class iterator_base_less { public: bool operator()(const typename tree::iterator_base& one, const typename tree::iterator_base& two) const { return one.node < two.node; } }; tree_node *head, *feet; // head/feet are always dummy; if an iterator points to them it is invalid private: tree_node_allocator alloc_; void head_initialise_(); void copy_(const tree& other); /// Comparator class for two nodes of a tree (used for sorting and searching). template class compare_nodes { public: compare_nodes(StrictWeakOrdering comp) : comp_(comp) {}; bool operator()(const tree_node *a, const tree_node *b) { static StrictWeakOrdering comp; return comp(a->data, b->data); } private: StrictWeakOrdering comp_; }; }; //template //class iterator_base_less { // public: // bool operator()(const typename tree::iterator_base& one, // const typename tree::iterator_base& two) const // { // txtout << "operatorclass<" << one.node < two.node << std::endl; // return one.node < two.node; // } //}; // template // bool operator<(const typename tree::iterator& one, // const typename tree::iterator& two) // { // txtout << "operator< " << one.node < two.node << std::endl; // if(one.node < two.node) return true; // return false; // } // // template // bool operator==(const typename tree::iterator& one, // const typename tree::iterator& two) // { // txtout << "operator== " << one.node == two.node << std::endl; // if(one.node == two.node) return true; // return false; // } // // template // bool operator>(const typename tree::iterator_base& one, // const typename tree::iterator_base& two) // { // txtout << "operator> " << one.node < two.node << std::endl; // if(one.node > two.node) return true; // return false; // } // Tree template tree::tree() { head_initialise_(); } template tree::tree(const T& x) { head_initialise_(); set_head(x); } template tree::tree(const iterator_base& other) { head_initialise_(); set_head((*other)); replace(begin(), other); } template tree::~tree() { clear(); alloc_.deallocate(head,1); alloc_.deallocate(feet,1); } template void tree::head_initialise_() { head = alloc_.allocate(1,0); // MSVC does not have default second argument feet = alloc_.allocate(1,0); head->parent=0; head->first_child=0; head->last_child=0; head->prev_sibling=0; //head; head->next_sibling=feet; //head; feet->parent=0; feet->first_child=0; feet->last_child=0; feet->prev_sibling=head; feet->next_sibling=0; } template void tree::operator=(const tree& other) { copy_(other); } template tree::tree(const tree& other) { head_initialise_(); copy_(other); } template void tree::copy_(const tree& other) { clear(); pre_order_iterator it=other.begin(), to=begin(); while(it!=other.end()) { to=insert(to, (*it)); it.skip_children(); ++it; } to=begin(); it=other.begin(); while(it!=other.end()) { to=replace(to, it); to.skip_children(); it.skip_children(); ++to; ++it; } } template void tree::clear() { if(head) while(head->next_sibling!=feet) erase(pre_order_iterator(head->next_sibling)); } template void tree::erase_children(const iterator_base& it) { // std::cout << "erase_children " << it.node << std::endl; if(it.node==0) return; tree_node *cur=it.node->first_child; tree_node *prev=0; while(cur!=0) { prev=cur; cur=cur->next_sibling; erase_children(pre_order_iterator(prev)); kp::destructor(&prev->data); alloc_.deallocate(prev,1); } it.node->first_child=0; it.node->last_child=0; // std::cout << "exit" << std::endl; } template template iter tree::erase(iter it) { tree_node *cur=it.node; assert(cur!=head); iter ret=it; ret.skip_children(); ++ret; erase_children(it); if(cur->prev_sibling==0) { cur->parent->first_child=cur->next_sibling; } else { cur->prev_sibling->next_sibling=cur->next_sibling; } if(cur->next_sibling==0) { cur->parent->last_child=cur->prev_sibling; } else { cur->next_sibling->prev_sibling=cur->prev_sibling; } kp::destructor(&cur->data); alloc_.deallocate(cur,1); return ret; } template typename tree::pre_order_iterator tree::begin() const { return pre_order_iterator(head->next_sibling); } template typename tree::pre_order_iterator tree::end() const { return pre_order_iterator(feet); } template typename tree::breadth_first_queued_iterator tree::begin_breadth_first() const { return breadth_first_queued_iterator(head->next_sibling); } template typename tree::breadth_first_queued_iterator tree::end_breadth_first() const { return breadth_first_queued_iterator(); } template typename tree::post_order_iterator tree::begin_post() const { tree_node *tmp=head->next_sibling; if(tmp!=feet) { while(tmp->first_child) tmp=tmp->first_child; } return post_order_iterator(tmp); } template typename tree::post_order_iterator tree::end_post() const { return post_order_iterator(feet); } template typename tree::fixed_depth_iterator tree::begin_fixed(const iterator_base& pos, unsigned int dp) const { typename tree::fixed_depth_iterator ret; ret.top_node=pos.node; tree_node *tmp=pos.node; unsigned int curdepth=0; while(curdepthfirst_child==0) { if(tmp->next_sibling==0) { // try to walk up and then right again do { if(tmp==ret.top_node) throw std::range_error("tree: begin_fixed out of range"); tmp=tmp->parent; if(tmp==0) throw std::range_error("tree: begin_fixed out of range"); --curdepth; } while(tmp->next_sibling==0); } tmp=tmp->next_sibling; } tmp=tmp->first_child; ++curdepth; } ret.node=tmp; return ret; } template typename tree::fixed_depth_iterator tree::end_fixed(const iterator_base& pos, unsigned int dp) const { assert(1==0); // FIXME: not correct yet: use is_valid() as a temporary workaround tree_node *tmp=pos.node; unsigned int curdepth=1; while(curdepthfirst_child==0) { tmp=tmp->next_sibling; if(tmp==0) throw std::range_error("tree: end_fixed out of range"); } tmp=tmp->first_child; ++curdepth; } return tmp; } template typename tree::sibling_iterator tree::begin(const iterator_base& pos) const { assert(pos.node!=0); if(pos.node->first_child==0) { return end(pos); } return pos.node->first_child; } template typename tree::sibling_iterator tree::end(const iterator_base& pos) const { sibling_iterator ret(0); ret.parent_=pos.node; return ret; } template typename tree::leaf_iterator tree::begin_leaf() const { tree_node *tmp=head->next_sibling; if(tmp!=feet) { while(tmp->first_child) tmp=tmp->first_child; } return leaf_iterator(tmp); } template typename tree::leaf_iterator tree::end_leaf() const { return leaf_iterator(feet); } template typename tree::leaf_iterator tree::begin_leaf(const iterator_base& top) const { tree_node *tmp=top.node; while(tmp->first_child) tmp=tmp->first_child; return leaf_iterator(tmp, top.node); } template typename tree::leaf_iterator tree::end_leaf(const iterator_base& top) const { return leaf_iterator(top.node, top.node); } template template iter tree::parent(iter position) { assert(position.node!=0); return iter(position.node->parent); } template template iter tree::previous_sibling(iter position) const { assert(position.node!=0); iter ret(position); ret.node=position.node->prev_sibling; return ret; } template template iter tree::next_sibling(iter position) const { assert(position.node!=0); iter ret(position); ret.node=position.node->next_sibling; return ret; } template template iter tree::next_at_same_depth(iter position) const { // We make use of a temporary fixed_depth iterator to implement this. typename tree::fixed_depth_iterator tmp(position.node); ++tmp; return iter(tmp); // assert(position.node!=0); // iter ret(position); // // if(position.node->next_sibling) { // ret.node=position.node->next_sibling; // } // else { // int relative_depth=0; // upper: // do { // ret.node=ret.node->parent; // if(ret.node==0) return ret; // --relative_depth; // } while(ret.node->next_sibling==0); // lower: // ret.node=ret.node->next_sibling; // while(ret.node->first_child==0) { // if(ret.node->next_sibling==0) // goto upper; // ret.node=ret.node->next_sibling; // if(ret.node==0) return ret; // } // while(relative_depth<0 && ret.node->first_child!=0) { // ret.node=ret.node->first_child; // ++relative_depth; // } // if(relative_depth<0) { // if(ret.node->next_sibling==0) goto upper; // else goto lower; // } // } // return ret; } template template iter tree::append_child(iter position) { assert(position.node!=head); assert(position.node); tree_node *tmp=alloc_.allocate(1,0); kp::constructor(&tmp->data); tmp->first_child=0; tmp->last_child=0; tmp->parent=position.node; if(position.node->last_child!=0) { position.node->last_child->next_sibling=tmp; } else { position.node->first_child=tmp; } tmp->prev_sibling=position.node->last_child; position.node->last_child=tmp; tmp->next_sibling=0; return tmp; } template template iter tree::prepend_child(iter position) { assert(position.node!=head); assert(position.node); tree_node *tmp=alloc_.allocate(1,0); kp::constructor(&tmp->data); tmp->first_child=0; tmp->last_child=0; tmp->parent=position.node; if(position.node->first_child!=0) { position.node->first_child->prev_sibling=tmp; } else { position.node->last_child=tmp; } tmp->next_sibling=position.node->first_child; position.node->prev_child=tmp; tmp->prev_sibling=0; return tmp; } template template iter tree::append_child(iter position, const T& x) { // If your program fails here you probably used 'append_child' to add the top // node to an empty tree. From version 1.45 the top element should be added // using 'insert'. See the documentation for further information, and sorry about // the API change. assert(position.node!=head); assert(position.node); tree_node* tmp = alloc_.allocate(1,0); kp::constructor(&tmp->data, x); tmp->first_child=0; tmp->last_child=0; tmp->parent=position.node; if(position.node->last_child!=0) { position.node->last_child->next_sibling=tmp; } else { position.node->first_child=tmp; } tmp->prev_sibling=position.node->last_child; position.node->last_child=tmp; tmp->next_sibling=0; return tmp; } template template iter tree::prepend_child(iter position, const T& x) { assert(position.node!=head); assert(position.node); tree_node* tmp = alloc_.allocate(1,0); kp::constructor(&tmp->data, x); tmp->first_child=0; tmp->last_child=0; tmp->parent=position.node; if(position.node->first_child!=0) { position.node->first_child->prev_sibling=tmp; } else { position.node->last_child=tmp; } tmp->next_sibling=position.node->first_child; position.node->first_child=tmp; tmp->prev_sibling=0; return tmp; } template template iter tree::append_child(iter position, iter other) { assert(position.node!=head); assert(position.node); sibling_iterator aargh=append_child(position, value_type()); return replace(aargh, other); } template template iter tree::prepend_child(iter position, iter other) { assert(position.node!=head); assert(position.node); sibling_iterator aargh=prepend_child(position, value_type()); return replace(aargh, other); } template template iter tree::append_children(iter position, sibling_iterator from, sibling_iterator to) { assert(position.node!=head); assert(position.node); iter ret=from; while(from!=to) { insert_subtree(position.end(), from); ++from; } return ret; } template template iter tree::prepend_children(iter position, sibling_iterator from, sibling_iterator to) { assert(position.node!=head); assert(position.node); iter ret=from; while(from!=to) { insert_subtree(position.begin(), from); ++from; } return ret; } template typename tree::pre_order_iterator tree::set_head(const T& x) { assert(head->next_sibling==feet); return insert(iterator(feet), x); } template template iter tree::insert(iter position, const T& x) { if(position.node==0) { position.node=feet; // Backward compatibility: when calling insert on a null node, // insert before the feet. } tree_node* tmp = alloc_.allocate(1,0); kp::constructor(&tmp->data, x); tmp->first_child=0; tmp->last_child=0; tmp->parent=position.node->parent; tmp->next_sibling=position.node; tmp->prev_sibling=position.node->prev_sibling; position.node->prev_sibling=tmp; if(tmp->prev_sibling==0) { if(tmp->parent) // when inserting nodes at the head, there is no parent tmp->parent->first_child=tmp; } else tmp->prev_sibling->next_sibling=tmp; return tmp; } template typename tree::sibling_iterator tree::insert(sibling_iterator position, const T& x) { tree_node* tmp = alloc_.allocate(1,0); kp::constructor(&tmp->data, x); tmp->first_child=0; tmp->last_child=0; tmp->next_sibling=position.node; if(position.node==0) { // iterator points to end of a subtree tmp->parent=position.parent_; tmp->prev_sibling=position.range_last(); tmp->parent->last_child=tmp; } else { tmp->parent=position.node->parent; tmp->prev_sibling=position.node->prev_sibling; position.node->prev_sibling=tmp; } if(tmp->prev_sibling==0) { if(tmp->parent) // when inserting nodes at the head, there is no parent tmp->parent->first_child=tmp; } else tmp->prev_sibling->next_sibling=tmp; return tmp; } template template iter tree::insert_after(iter position, const T& x) { tree_node* tmp = alloc_.allocate(1,0); kp::constructor(&tmp->data, x); tmp->first_child=0; tmp->last_child=0; tmp->parent=position.node->parent; tmp->prev_sibling=position.node; tmp->next_sibling=position.node->next_sibling; position.node->next_sibling=tmp; if(tmp->next_sibling==0) { if(tmp->parent) // when inserting nodes at the head, there is no parent tmp->parent->last_child=tmp; } else { tmp->next_sibling->prev_sibling=tmp; } return tmp; } template template iter tree::insert_subtree(iter position, const iterator_base& subtree) { // insert dummy iter it=insert(position, value_type()); // replace dummy with subtree return replace(it, subtree); } template template iter tree::insert_subtree_after(iter position, const iterator_base& subtree) { // insert dummy iter it=insert_after(position, value_type()); // replace dummy with subtree return replace(it, subtree); } // template // template // iter tree::insert_subtree(sibling_iterator position, iter subtree) // { // // insert dummy // iter it(insert(position, value_type())); // // replace dummy with subtree // return replace(it, subtree); // } template template iter tree::replace(iter position, const T& x) { kp::destructor(&position.node->data); kp::constructor(&position.node->data, x); return position; } template template iter tree::replace(iter position, const iterator_base& from) { assert(position.node!=head); tree_node *current_from=from.node; tree_node *start_from=from.node; tree_node *current_to =position.node; // replace the node at position with head of the replacement tree at from // std::cout << "warning!" << position.node << std::endl; erase_children(position); // std::cout << "no warning!" << std::endl; tree_node* tmp = alloc_.allocate(1,0); kp::constructor(&tmp->data, (*from)); tmp->first_child=0; tmp->last_child=0; if(current_to->prev_sibling==0) { if(current_to->parent!=0) current_to->parent->first_child=tmp; } else { current_to->prev_sibling->next_sibling=tmp; } tmp->prev_sibling=current_to->prev_sibling; if(current_to->next_sibling==0) { if(current_to->parent!=0) current_to->parent->last_child=tmp; } else { current_to->next_sibling->prev_sibling=tmp; } tmp->next_sibling=current_to->next_sibling; tmp->parent=current_to->parent; kp::destructor(¤t_to->data); alloc_.deallocate(current_to,1); current_to=tmp; // only at this stage can we fix 'last' tree_node *last=from.node->next_sibling; pre_order_iterator toit=tmp; // copy all children do { assert(current_from!=0); if(current_from->first_child != 0) { current_from=current_from->first_child; toit=append_child(toit, current_from->data); } else { while(current_from->next_sibling==0 && current_from!=start_from) { current_from=current_from->parent; toit=parent(toit); assert(current_from!=0); } current_from=current_from->next_sibling; if(current_from!=last) { toit=append_child(parent(toit), current_from->data); } } } while(current_from!=last); return current_to; } template typename tree::sibling_iterator tree::replace( sibling_iterator orig_begin, sibling_iterator orig_end, sibling_iterator new_begin, sibling_iterator new_end) { tree_node *orig_first=orig_begin.node; tree_node *new_first=new_begin.node; tree_node *orig_last=orig_first; while((++orig_begin)!=orig_end) orig_last=orig_last->next_sibling; tree_node *new_last=new_first; while((++new_begin)!=new_end) new_last=new_last->next_sibling; // insert all siblings in new_first..new_last before orig_first bool first=true; pre_order_iterator ret; while(1==1) { pre_order_iterator tt=insert_subtree(pre_order_iterator(orig_first), pre_order_iterator(new_first)); if(first) { ret=tt; first=false; } if(new_first==new_last) break; new_first=new_first->next_sibling; } // erase old range of siblings bool last=false; tree_node *next=orig_first; while(1==1) { if(next==orig_last) last=true; next=next->next_sibling; erase((pre_order_iterator)orig_first); if(last) break; orig_first=next; } return ret; } template template iter tree::flatten(iter position) { if(position.node->first_child==0) return position; tree_node *tmp=position.node->first_child; while(tmp) { tmp->parent=position.node->parent; tmp=tmp->next_sibling; } if(position.node->next_sibling) { position.node->last_child->next_sibling=position.node->next_sibling; position.node->next_sibling->prev_sibling=position.node->last_child; } else { position.node->parent->last_child=position.node->last_child; } position.node->next_sibling=position.node->first_child; position.node->next_sibling->prev_sibling=position.node; position.node->first_child=0; position.node->last_child=0; return position; } template template iter tree::reparent(iter position, sibling_iterator begin, sibling_iterator end) { tree_node *first=begin.node; tree_node *last=first; assert(first!=position.node); if(begin==end) return begin; // determine last node while((++begin)!=end) { last=last->next_sibling; } // move subtree if(first->prev_sibling==0) { first->parent->first_child=last->next_sibling; } else { first->prev_sibling->next_sibling=last->next_sibling; } if(last->next_sibling==0) { last->parent->last_child=first->prev_sibling; } else { last->next_sibling->prev_sibling=first->prev_sibling; } if(position.node->first_child==0) { position.node->first_child=first; position.node->last_child=last; first->prev_sibling=0; } else { position.node->last_child->next_sibling=first; first->prev_sibling=position.node->last_child; position.node->last_child=last; } last->next_sibling=0; tree_node *pos=first; while(1==1) { pos->parent=position.node; if(pos==last) break; pos=pos->next_sibling; } return first; } template template iter tree::reparent(iter position, iter from) { if(from.node->first_child==0) return position; return reparent(position, from.node->first_child, end(from)); } template template iter tree::wrap(iter position, const T& x) { assert(position.node!=0); sibling_iterator fr=position, to=position; ++to; iter ret = insert(position, x); reparent(ret, fr, to); return ret; } template template iter tree::move_after(iter target, iter source) { tree_node *dst=target.node; tree_node *src=source.node; assert(dst); assert(src); if(dst==src) return source; if(dst->next_sibling) if(dst->next_sibling==src) // already in the right spot return source; // take src out of the tree if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; else src->parent->first_child=src->next_sibling; if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; else src->parent->last_child=src->prev_sibling; // connect it to the new point if(dst->next_sibling!=0) dst->next_sibling->prev_sibling=src; else dst->parent->last_child=src; src->next_sibling=dst->next_sibling; dst->next_sibling=src; src->prev_sibling=dst; src->parent=dst->parent; return src; } template template iter tree::move_before(iter target, iter source) { tree_node *dst=target.node; tree_node *src=source.node; assert(dst); assert(src); if(dst==src) return source; if(dst->prev_sibling) if(dst->prev_sibling==src) // already in the right spot return source; // take src out of the tree if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; else src->parent->first_child=src->next_sibling; if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; else src->parent->last_child=src->prev_sibling; // connect it to the new point if(dst->prev_sibling!=0) dst->prev_sibling->next_sibling=src; else dst->parent->first_child=src; src->prev_sibling=dst->prev_sibling; dst->prev_sibling=src; src->next_sibling=dst; src->parent=dst->parent; return src; } // specialisation for sibling_iterators template typename tree::sibling_iterator tree::move_before(sibling_iterator target, sibling_iterator source) { tree_node *dst=target.node; tree_node *src=source.node; tree_node *dst_prev_sibling; if(dst==0) { // must then be an end iterator dst_prev_sibling=target.parent_->last_child; assert(dst_prev_sibling); } else dst_prev_sibling=dst->prev_sibling; assert(src); if(dst==src) return source; if(dst_prev_sibling) if(dst_prev_sibling==src) // already in the right spot return source; // take src out of the tree if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; else src->parent->first_child=src->next_sibling; if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; else src->parent->last_child=src->prev_sibling; // connect it to the new point if(dst_prev_sibling!=0) dst_prev_sibling->next_sibling=src; else target.parent_->first_child=src; src->prev_sibling=dst_prev_sibling; if(dst) { dst->prev_sibling=src; src->parent=dst->parent; } src->next_sibling=dst; return src; } template template iter tree::move_ontop(iter target, iter source) { tree_node *dst=target.node; tree_node *src=source.node; assert(dst); assert(src); if(dst==src) return source; // remember connection points tree_node *b_prev_sibling=dst->prev_sibling; tree_node *b_next_sibling=dst->next_sibling; tree_node *b_parent=dst->parent; // remove target erase(target); // take src out of the tree if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; else src->parent->first_child=src->next_sibling; if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; else src->parent->last_child=src->prev_sibling; // connect it to the new point if(b_prev_sibling!=0) b_prev_sibling->next_sibling=src; else b_parent->first_child=src; if(b_next_sibling!=0) b_next_sibling->prev_sibling=src; else b_parent->last_child=src; src->prev_sibling=b_prev_sibling; src->next_sibling=b_next_sibling; src->parent=b_parent; return src; } template void tree::merge(sibling_iterator to1, sibling_iterator to2, sibling_iterator from1, sibling_iterator from2, bool duplicate_leaves) { sibling_iterator fnd; while(from1!=from2) { if((fnd=std::find(to1, to2, (*from1))) != to2) { // element found if(from1.begin()==from1.end()) { // full depth reached if(duplicate_leaves) append_child(parent(to1), (*from1)); } else { // descend further merge(fnd.begin(), fnd.end(), from1.begin(), from1.end(), duplicate_leaves); } } else { // element missing insert_subtree(to2, from1); } ++from1; } } template void tree::sort(sibling_iterator from, sibling_iterator to, bool deep) { std::less comp; sort(from, to, comp, deep); } template template void tree::sort(sibling_iterator from, sibling_iterator to, StrictWeakOrdering comp, bool deep) { if(from==to) return; // make list of sorted nodes // CHECK: if multiset stores equivalent nodes in the order in which they // are inserted, then this routine should be called 'stable_sort'. std::multiset > nodes(comp); sibling_iterator it=from, it2=to; while(it != to) { nodes.insert(it.node); ++it; } // reassemble --it2; // prev and next are the nodes before and after the sorted range tree_node *prev=from.node->prev_sibling; tree_node *next=it2.node->next_sibling; typename std::multiset >::iterator nit=nodes.begin(), eit=nodes.end(); if(prev==0) { if((*nit)->parent!=0) // to catch "sorting the head" situations, when there is no parent (*nit)->parent->first_child=(*nit); } else prev->next_sibling=(*nit); --eit; while(nit!=eit) { (*nit)->prev_sibling=prev; if(prev) prev->next_sibling=(*nit); prev=(*nit); ++nit; } // prev now points to the last-but-one node in the sorted range if(prev) prev->next_sibling=(*eit); // eit points to the last node in the sorted range. (*eit)->next_sibling=next; (*eit)->prev_sibling=prev; // missed in the loop above if(next==0) { if((*eit)->parent!=0) // to catch "sorting the head" situations, when there is no parent (*eit)->parent->last_child=(*eit); } else next->prev_sibling=(*eit); if(deep) { // sort the children of each node too sibling_iterator bcs(*nodes.begin()); sibling_iterator ecs(*eit); ++ecs; while(bcs!=ecs) { sort(begin(bcs), end(bcs), comp, deep); ++bcs; } } } template template bool tree::equal(const iter& one_, const iter& two, const iter& three_) const { std::equal_to comp; return equal(one_, two, three_, comp); } template template bool tree::equal_subtree(const iter& one_, const iter& two_) const { std::equal_to comp; return equal_subtree(one_, two_, comp); } template template bool tree::equal(const iter& one_, const iter& two, const iter& three_, BinaryPredicate fun) const { pre_order_iterator one(one_), three(three_); // if(one==two && is_valid(three) && three.number_of_children()!=0) // return false; while(one!=two && is_valid(three)) { if(!fun(*one,*three)) return false; if(one.number_of_children()!=three.number_of_children()) return false; ++one; ++three; } return true; } template template bool tree::equal_subtree(const iter& one_, const iter& two_, BinaryPredicate fun) const { pre_order_iterator one(one_), two(two_); if(!fun(*one,*two)) return false; if(number_of_children(one)!=number_of_children(two)) return false; return equal(begin(one),end(one),begin(two),fun); } template tree tree::subtree(sibling_iterator from, sibling_iterator to) const { tree tmp; tmp.set_head(value_type()); tmp.replace(tmp.begin(), tmp.end(), from, to); return tmp; } template void tree::subtree(tree& tmp, sibling_iterator from, sibling_iterator to) const { tmp.set_head(value_type()); tmp.replace(tmp.begin(), tmp.end(), from, to); } template size_t tree::size() const { size_t i=0; pre_order_iterator it=begin(), eit=end(); while(it!=eit) { ++i; ++it; } return i; } template size_t tree::size(const iterator_base& top) const { size_t i=0; pre_order_iterator it=top, eit=top; eit.skip_children(); ++eit; while(it!=eit) { ++i; ++it; } return i; } template bool tree::empty() const { pre_order_iterator it=begin(), eit=end(); return (it==eit); } template int tree::depth(const iterator_base& it) { tree_node* pos=it.node; assert(pos!=0); int ret=0; while(pos->parent!=0) { pos=pos->parent; ++ret; } return ret; } template int tree::depth(const iterator_base& it, const iterator_base& root) { tree_node* pos=it.node; assert(pos!=0); int ret=0; while(pos->parent!=0 && pos!=root.node) { pos=pos->parent; ++ret; } return ret; } template int tree::max_depth() const { int maxd=-1; for(tree_node *it = head->next_sibling; it!=feet; it=it->next_sibling) maxd=std::max(maxd, max_depth(it)); return maxd; } template int tree::max_depth(const iterator_base& pos) const { tree_node *tmp=pos.node; if(tmp==0 || tmp==head || tmp==feet) return -1; int curdepth=0, maxdepth=0; while(true) { // try to walk the bottom of the tree while(tmp->first_child==0) { if(tmp==pos.node) return maxdepth; if(tmp->next_sibling==0) { // try to walk up and then right again do { tmp=tmp->parent; if(tmp==0) return maxdepth; --curdepth; } while(tmp->next_sibling==0); } if(tmp==pos.node) return maxdepth; tmp=tmp->next_sibling; } tmp=tmp->first_child; ++curdepth; maxdepth=std::max(curdepth, maxdepth); } } template unsigned int tree::number_of_children(const iterator_base& it) { tree_node *pos=it.node->first_child; if(pos==0) return 0; unsigned int ret=1; // while(pos!=it.node->last_child) { // ++ret; // pos=pos->next_sibling; // } while((pos=pos->next_sibling)) ++ret; return ret; } template unsigned int tree::number_of_siblings(const iterator_base& it) const { tree_node *pos=it.node; unsigned int ret=0; // count forward while(pos->next_sibling && pos->next_sibling!=head && pos->next_sibling!=feet) { ++ret; pos=pos->next_sibling; } // count backward pos=it.node; while(pos->prev_sibling && pos->prev_sibling!=head && pos->prev_sibling!=feet) { ++ret; pos=pos->prev_sibling; } return ret; } template void tree::swap(sibling_iterator it) { tree_node *nxt=it.node->next_sibling; if(nxt) { if(it.node->prev_sibling) it.node->prev_sibling->next_sibling=nxt; else it.node->parent->first_child=nxt; nxt->prev_sibling=it.node->prev_sibling; tree_node *nxtnxt=nxt->next_sibling; if(nxtnxt) nxtnxt->prev_sibling=it.node; else it.node->parent->last_child=it.node; nxt->next_sibling=it.node; it.node->prev_sibling=nxt; it.node->next_sibling=nxtnxt; } } template void tree::swap(iterator one, iterator two) { // if one and two are adjacent siblings, use the sibling swap if(one.node->next_sibling==two.node) swap(one); else if(two.node->next_sibling==one.node) swap(two); else { tree_node *nxt1=one.node->next_sibling; tree_node *nxt2=two.node->next_sibling; tree_node *pre1=one.node->prev_sibling; tree_node *pre2=two.node->prev_sibling; tree_node *par1=one.node->parent; tree_node *par2=two.node->parent; // reconnect one.node->parent=par2; one.node->next_sibling=nxt2; if(nxt2) nxt2->prev_sibling=one.node; else par2->last_child=one.node; one.node->prev_sibling=pre2; if(pre2) pre2->next_sibling=one.node; else par2->first_child=one.node; two.node->parent=par1; two.node->next_sibling=nxt1; if(nxt1) nxt1->prev_sibling=two.node; else par1->last_child=two.node; two.node->prev_sibling=pre1; if(pre1) pre1->next_sibling=two.node; else par1->first_child=two.node; } } // template // tree::iterator tree::find_subtree( // sibling_iterator subfrom, sibling_iterator subto, iterator from, iterator to, // BinaryPredicate fun) const // { // assert(1==0); // this routine is not finished yet. // while(from!=to) { // if(fun(*subfrom, *from)) { // // } // } // return to; // } template bool tree::is_in_subtree(const iterator_base& it, const iterator_base& begin, const iterator_base& end) const { // FIXME: this should be optimised. pre_order_iterator tmp=begin; while(tmp!=end) { if(tmp==it) return true; ++tmp; } return false; } template bool tree::is_valid(const iterator_base& it) const { if(it.node==0 || it.node==feet || it.node==head) return false; else return true; } template unsigned int tree::index(sibling_iterator it) const { unsigned int ind=0; if(it.node->parent==0) { while(it.node->prev_sibling!=head) { it.node=it.node->prev_sibling; ++ind; } } else { while(it.node->prev_sibling!=0) { it.node=it.node->prev_sibling; ++ind; } } return ind; } template typename tree::sibling_iterator tree::child(const iterator_base& it, unsigned int num) { tree_node *tmp=it.node->first_child; while(num--) { assert(tmp!=0); tmp=tmp->next_sibling; } return tmp; } // Iterator base template tree::iterator_base::iterator_base() : node(0), skip_current_children_(false) { } template tree::iterator_base::iterator_base(tree_node *tn) : node(tn), skip_current_children_(false) { } template T& tree::iterator_base::operator*() const { return node->data; } template T* tree::iterator_base::operator->() const { return &(node->data); } template bool tree::post_order_iterator::operator!=(const post_order_iterator& other) const { if(other.node!=this->node) return true; else return false; } template bool tree::post_order_iterator::operator==(const post_order_iterator& other) const { if(other.node==this->node) return true; else return false; } template bool tree::pre_order_iterator::operator!=(const pre_order_iterator& other) const { if(other.node!=this->node) return true; else return false; } template bool tree::pre_order_iterator::operator==(const pre_order_iterator& other) const { if(other.node==this->node) return true; else return false; } template bool tree::sibling_iterator::operator!=(const sibling_iterator& other) const { if(other.node!=this->node) return true; else return false; } template bool tree::sibling_iterator::operator==(const sibling_iterator& other) const { if(other.node==this->node) return true; else return false; } template bool tree::leaf_iterator::operator!=(const leaf_iterator& other) const { if(other.node!=this->node) return true; else return false; } template bool tree::leaf_iterator::operator==(const leaf_iterator& other) const { if(other.node==this->node && other.top_node==this->top_node) return true; else return false; } template typename tree::sibling_iterator tree::iterator_base::begin() const { if(node->first_child==0) return end(); sibling_iterator ret(node->first_child); ret.parent_=this->node; return ret; } template typename tree::sibling_iterator tree::iterator_base::end() const { sibling_iterator ret(0); ret.parent_=node; return ret; } template void tree::iterator_base::skip_children() { skip_current_children_=true; } template unsigned int tree::iterator_base::number_of_children() const { tree_node *pos=node->first_child; if(pos==0) return 0; unsigned int ret=1; while(pos!=node->last_child) { ++ret; pos=pos->next_sibling; } return ret; } // Pre-order iterator template tree::pre_order_iterator::pre_order_iterator() : iterator_base(0) { } template tree::pre_order_iterator::pre_order_iterator(tree_node *tn) : iterator_base(tn) { } template tree::pre_order_iterator::pre_order_iterator(const iterator_base &other) : iterator_base(other.node) { } template tree::pre_order_iterator::pre_order_iterator(const sibling_iterator& other) : iterator_base(other.node) { if(this->node==0) { if(other.range_last()!=0) this->node=other.range_last(); else this->node=other.parent_; this->skip_children(); ++(*this); } } template typename tree::pre_order_iterator& tree::pre_order_iterator::operator++() { assert(this->node!=0); if(!this->skip_current_children_ && this->node->first_child != 0) { this->node=this->node->first_child; } else { this->skip_current_children_=false; while(this->node->next_sibling==0) { this->node=this->node->parent; if(this->node==0) return *this; } this->node=this->node->next_sibling; } return *this; } template typename tree::pre_order_iterator& tree::pre_order_iterator::operator--() { assert(this->node!=0); if(this->node->prev_sibling) { this->node=this->node->prev_sibling; while(this->node->last_child) this->node=this->node->last_child; } else { this->node=this->node->parent; if(this->node==0) return *this; } return *this; } template typename tree::pre_order_iterator tree::pre_order_iterator::operator++(int n) { pre_order_iterator copy = *this; ++(*this); return copy; } template typename tree::pre_order_iterator tree::pre_order_iterator::operator--(int n) { pre_order_iterator copy = *this; --(*this); return copy; } template typename tree::pre_order_iterator& tree::pre_order_iterator::operator+=(unsigned int num) { while(num>0) { ++(*this); --num; } return (*this); } template typename tree::pre_order_iterator& tree::pre_order_iterator::operator-=(unsigned int num) { while(num>0) { --(*this); --num; } return (*this); } // Post-order iterator template tree::post_order_iterator::post_order_iterator() : iterator_base(0) { } template tree::post_order_iterator::post_order_iterator(tree_node *tn) : iterator_base(tn) { } template tree::post_order_iterator::post_order_iterator(const iterator_base &other) : iterator_base(other.node) { } template tree::post_order_iterator::post_order_iterator(const sibling_iterator& other) : iterator_base(other.node) { if(this->node==0) { if(other.range_last()!=0) this->node=other.range_last(); else this->node=other.parent_; this->skip_children(); ++(*this); } } template typename tree::post_order_iterator& tree::post_order_iterator::operator++() { assert(this->node!=0); if(this->node->next_sibling==0) { this->node=this->node->parent; this->skip_current_children_=false; } else { this->node=this->node->next_sibling; if(this->skip_current_children_) { this->skip_current_children_=false; } else { while(this->node->first_child) this->node=this->node->first_child; } } return *this; } template typename tree::post_order_iterator& tree::post_order_iterator::operator--() { assert(this->node!=0); if(this->skip_current_children_ || this->node->last_child==0) { this->skip_current_children_=false; while(this->node->prev_sibling==0) this->node=this->node->parent; this->node=this->node->prev_sibling; } else { this->node=this->node->last_child; } return *this; } template typename tree::post_order_iterator tree::post_order_iterator::operator++(int) { post_order_iterator copy = *this; ++(*this); return copy; } template typename tree::post_order_iterator tree::post_order_iterator::operator--(int) { post_order_iterator copy = *this; --(*this); return copy; } template typename tree::post_order_iterator& tree::post_order_iterator::operator+=(unsigned int num) { while(num>0) { ++(*this); --num; } return (*this); } template typename tree::post_order_iterator& tree::post_order_iterator::operator-=(unsigned int num) { while(num>0) { --(*this); --num; } return (*this); } template void tree::post_order_iterator::descend_all() { assert(this->node!=0); while(this->node->first_child) this->node=this->node->first_child; } // Breadth-first iterator template tree::breadth_first_queued_iterator::breadth_first_queued_iterator() : iterator_base() { } template tree::breadth_first_queued_iterator::breadth_first_queued_iterator(tree_node *tn) : iterator_base(tn) { traversal_queue.push(tn); } template tree::breadth_first_queued_iterator::breadth_first_queued_iterator(const iterator_base& other) : iterator_base(other.node) { traversal_queue.push(other.node); } template bool tree::breadth_first_queued_iterator::operator!=(const breadth_first_queued_iterator& other) const { if(other.node!=this->node) return true; else return false; } template bool tree::breadth_first_queued_iterator::operator==(const breadth_first_queued_iterator& other) const { if(other.node==this->node) return true; else return false; } template typename tree::breadth_first_queued_iterator& tree::breadth_first_queued_iterator::operator++() { assert(this->node!=0); // Add child nodes and pop current node sibling_iterator sib=this->begin(); while(sib!=this->end()) { traversal_queue.push(sib.node); ++sib; } traversal_queue.pop(); if(traversal_queue.size()>0) this->node=traversal_queue.front(); else this->node=0; return (*this); } template typename tree::breadth_first_queued_iterator tree::breadth_first_queued_iterator::operator++(int n) { breadth_first_queued_iterator copy = *this; ++(*this); return copy; } template typename tree::breadth_first_queued_iterator& tree::breadth_first_queued_iterator::operator+=(unsigned int num) { while(num>0) { ++(*this); --num; } return (*this); } // Fixed depth iterator template tree::fixed_depth_iterator::fixed_depth_iterator() : iterator_base() { } template tree::fixed_depth_iterator::fixed_depth_iterator(tree_node *tn) : iterator_base(tn), top_node(0) { } template tree::fixed_depth_iterator::fixed_depth_iterator(const iterator_base& other) : iterator_base(other.node), top_node(0) { } template tree::fixed_depth_iterator::fixed_depth_iterator(const sibling_iterator& other) : iterator_base(other.node), top_node(0) { } template tree::fixed_depth_iterator::fixed_depth_iterator(const fixed_depth_iterator& other) : iterator_base(other.node), top_node(other.top_node) { } template bool tree::fixed_depth_iterator::operator==(const fixed_depth_iterator& other) const { if(other.node==this->node && other.top_node==top_node) return true; else return false; } template bool tree::fixed_depth_iterator::operator!=(const fixed_depth_iterator& other) const { if(other.node!=this->node || other.top_node!=top_node) return true; else return false; } template typename tree::fixed_depth_iterator& tree::fixed_depth_iterator::operator++() { assert(this->node!=0); if(this->node->next_sibling) { this->node=this->node->next_sibling; } else { int relative_depth=0; upper: do { if(this->node==this->top_node) { this->node=0; // FIXME: return a proper fixed_depth end iterator once implemented return *this; } this->node=this->node->parent; if(this->node==0) return *this; --relative_depth; } while(this->node->next_sibling==0); lower: this->node=this->node->next_sibling; while(this->node->first_child==0) { if(this->node->next_sibling==0) goto upper; this->node=this->node->next_sibling; if(this->node==0) return *this; } while(relative_depth<0 && this->node->first_child!=0) { this->node=this->node->first_child; ++relative_depth; } if(relative_depth<0) { if(this->node->next_sibling==0) goto upper; else goto lower; } } return *this; } template typename tree::fixed_depth_iterator& tree::fixed_depth_iterator::operator--() { assert(this->node!=0); if(this->node->prev_sibling) { this->node=this->node->prev_sibling; } else { int relative_depth=0; upper: do { if(this->node==this->top_node) { this->node=0; return *this; } this->node=this->node->parent; if(this->node==0) return *this; --relative_depth; } while(this->node->prev_sibling==0); lower: this->node=this->node->prev_sibling; while(this->node->last_child==0) { if(this->node->prev_sibling==0) goto upper; this->node=this->node->prev_sibling; if(this->node==0) return *this; } while(relative_depth<0 && this->node->last_child!=0) { this->node=this->node->last_child; ++relative_depth; } if(relative_depth<0) { if(this->node->prev_sibling==0) goto upper; else goto lower; } } return *this; // // // assert(this->node!=0); // if(this->node->prev_sibling!=0) { // this->node=this->node->prev_sibling; // assert(this->node!=0); // if(this->node->parent==0 && this->node->prev_sibling==0) // head element // this->node=0; // } // else { // tree_node *par=this->node->parent; // do { // par=par->prev_sibling; // if(par==0) { // FIXME: need to keep track of this! // this->node=0; // return *this; // } // } while(par->last_child==0); // this->node=par->last_child; // } // return *this; } template typename tree::fixed_depth_iterator tree::fixed_depth_iterator::operator++(int) { fixed_depth_iterator copy = *this; ++(*this); return copy; } template typename tree::fixed_depth_iterator tree::fixed_depth_iterator::operator--(int) { fixed_depth_iterator copy = *this; --(*this); return copy; } template typename tree::fixed_depth_iterator& tree::fixed_depth_iterator::operator-=(unsigned int num) { while(num>0) { --(*this); --(num); } return (*this); } template typename tree::fixed_depth_iterator& tree::fixed_depth_iterator::operator+=(unsigned int num) { while(num>0) { ++(*this); --(num); } return *this; } // Sibling iterator template tree::sibling_iterator::sibling_iterator() : iterator_base() { set_parent_(); } template tree::sibling_iterator::sibling_iterator(tree_node *tn) : iterator_base(tn) { set_parent_(); } template tree::sibling_iterator::sibling_iterator(const iterator_base& other) : iterator_base(other.node) { set_parent_(); } template tree::sibling_iterator::sibling_iterator(const sibling_iterator& other) : iterator_base(other), parent_(other.parent_) { } template void tree::sibling_iterator::set_parent_() { parent_=0; if(this->node==0) return; if(this->node->parent!=0) parent_=this->node->parent; } template typename tree::sibling_iterator& tree::sibling_iterator::operator++() { if(this->node) this->node=this->node->next_sibling; return *this; } template typename tree::sibling_iterator& tree::sibling_iterator::operator--() { if(this->node) this->node=this->node->prev_sibling; else { assert(parent_); this->node=parent_->last_child; } return *this; } template typename tree::sibling_iterator tree::sibling_iterator::operator++(int) { sibling_iterator copy = *this; ++(*this); return copy; } template typename tree::sibling_iterator tree::sibling_iterator::operator--(int) { sibling_iterator copy = *this; --(*this); return copy; } template typename tree::sibling_iterator& tree::sibling_iterator::operator+=(unsigned int num) { while(num>0) { ++(*this); --num; } return (*this); } template typename tree::sibling_iterator& tree::sibling_iterator::operator-=(unsigned int num) { while(num>0) { --(*this); --num; } return (*this); } template typename tree::tree_node *tree::sibling_iterator::range_first() const { tree_node *tmp=parent_->first_child; return tmp; } template typename tree::tree_node *tree::sibling_iterator::range_last() const { return parent_->last_child; } // Leaf iterator template tree::leaf_iterator::leaf_iterator() : iterator_base(0), top_node(0) { } template tree::leaf_iterator::leaf_iterator(tree_node *tn, tree_node *top) : iterator_base(tn), top_node(top) { } template tree::leaf_iterator::leaf_iterator(const iterator_base &other) : iterator_base(other.node), top_node(0) { } template tree::leaf_iterator::leaf_iterator(const sibling_iterator& other) : iterator_base(other.node), top_node(0) { if(this->node==0) { if(other.range_last()!=0) this->node=other.range_last(); else this->node=other.parent_; ++(*this); } } template typename tree::leaf_iterator& tree::leaf_iterator::operator++() { assert(this->node!=0); if(this->node->first_child!=0) { // current node is no longer leaf (children got added) while(this->node->first_child) this->node=this->node->first_child; } else { while(this->node->next_sibling==0) { if (this->node->parent==0) return *this; this->node=this->node->parent; if (top_node != 0 && this->node==top_node) return *this; } this->node=this->node->next_sibling; while(this->node->first_child) this->node=this->node->first_child; } return *this; } template typename tree::leaf_iterator& tree::leaf_iterator::operator--() { assert(this->node!=0); while (this->node->prev_sibling==0) { if (this->node->parent==0) return *this; this->node=this->node->parent; if (top_node !=0 && this->node==top_node) return *this; } this->node=this->node->prev_sibling; while(this->node->last_child) this->node=this->node->last_child; return *this; } template typename tree::leaf_iterator tree::leaf_iterator::operator++(int) { leaf_iterator copy = *this; ++(*this); return copy; } template typename tree::leaf_iterator tree::leaf_iterator::operator--(int) { leaf_iterator copy = *this; --(*this); return copy; } template typename tree::leaf_iterator& tree::leaf_iterator::operator+=(unsigned int num) { while(num>0) { ++(*this); --num; } return (*this); } template typename tree::leaf_iterator& tree::leaf_iterator::operator-=(unsigned int num) { while(num>0) { --(*this); --num; } return (*this); } #endif // Local variables: // default-tab-width: 3 // End: ================================================ FILE: test/common_boost/tree.hpp ================================================ /** @file Warnings wrapper for tree.hh n-ary tree container by Kasper Peeters. Copyright (C) 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef TEST_COMMON_BOOST_TREE_HPP #define TEST_COMMON_BOOST_TREE_HPP #pragma once #pragma warning (push) #pragma warning (disable: 4100) #include "tree.hh" #pragma warning (pop) #endif ================================================ FILE: test/connection/CMakeLists.txt ================================================ # Copyright (C) 2015, 2016 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(UNIT_TESTS connection_spec_test.cpp) set(INTEGRATION_TESTS authenticated_session_test.cpp connection_spec_create_session_test.cpp running_session_test.cpp session_manager_test.cpp session_pool_test.cpp) swish_test_suite( SUBJECT connection VARIANT unit SOURCES ${UNIT_TESTS} LIBRARIES ${Boost_LIBRARIES} LABELS unit) swish_test_suite( SUBJECT connection VARIANT integration SOURCES ${INTEGRATION_TESTS} LIBRARIES ${Boost_LIBRARIES} openssh_fixture LABELS integration) ================================================ FILE: test/connection/authenticated_session_test.cpp ================================================ // Copyright 2013, 2014, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "swish/connection/authenticated_session.hpp" // Test subject #include "test/common_boost/ConsumerStub.hpp" #include "test/fixtures/openssh_fixture.hpp" #include #include #include #include #include #include // this_thread #include #include using test::CConsumerStub; using test::fixtures::openssh_fixture; using swish::connection::authenticated_session; using boost::make_shared; using boost::move; using boost::shared_ptr; using boost::test_tools::predicate_result; using std::vector; using std::wstring; BOOST_FIXTURE_TEST_SUITE(authenticated_session_tests, openssh_fixture) namespace { predicate_result sftp_is_alive(authenticated_session& session) { try { session.get_sftp_filesystem().directory_iterator("/"); return true; } catch (...) { predicate_result res(false); res.message() << "SFTP not working; unable to access root directory"; return res; } } } /** * Test that connecting succeeds. */ BOOST_AUTO_TEST_CASE(connect) { authenticated_session session( whost(), port(), wuser(), new CConsumerStub(private_key_path(), public_key_path())); BOOST_CHECK(!session.is_dead()); BOOST_CHECK(sftp_is_alive(session)); } BOOST_AUTO_TEST_CASE(multiple_connections) { vector> sessions; for (int i = 0; i < 5; i++) { sessions.push_back(make_shared( whost(), port(), wuser(), new CConsumerStub(private_key_path(), public_key_path()))); } for (int i = 0; i < 5; i++) { BOOST_CHECK(!sessions.at(i)->is_dead()); BOOST_CHECK(sftp_is_alive(*sessions.at(i))); } } /** * Test that session reports its death. */ BOOST_AUTO_TEST_CASE(server_death) { authenticated_session session( whost(), port(), wuser(), new CConsumerStub(private_key_path(), public_key_path())); BOOST_CHECK(sftp_is_alive(session)); stop_server(); boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); BOOST_CHECK(session.is_dead()); BOOST_CHECK(!sftp_is_alive(session)); } /** * Test that session reports its death if server is restarted. */ BOOST_AUTO_TEST_CASE(server_restart) { authenticated_session session( whost(), port(), wuser(), new CConsumerStub(private_key_path(), public_key_path())); BOOST_CHECK(sftp_is_alive(session)); restart_server(); boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); BOOST_CHECK(session.is_dead()); BOOST_CHECK(!sftp_is_alive(session)); } namespace { authenticated_session move_create(const wstring& host, unsigned int port, const wstring& user, ISftpConsumer* consumer) { authenticated_session session(host, port, user, consumer); return move(session); } } BOOST_AUTO_TEST_CASE(move_contruct) { authenticated_session session = move_create(whost(), port(), wuser(), new CConsumerStub(private_key_path(), public_key_path())); BOOST_CHECK(!session.is_dead()); BOOST_CHECK(sftp_is_alive(session)); } BOOST_AUTO_TEST_CASE(move_assign) { authenticated_session session1( whost(), port(), wuser(), new CConsumerStub(private_key_path(), public_key_path())); authenticated_session session2( whost(), port(), wuser(), new CConsumerStub(private_key_path(), public_key_path())); session1 = move(session2); BOOST_CHECK(!session1.is_dead()); BOOST_CHECK(sftp_is_alive(session1)); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/connection/connection_spec_create_session_test.cpp ================================================ // Copyright 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "swish/connection/connection_spec.hpp" // Test subject #include "swish/connection/authenticated_session.hpp" #include "test/common_boost/ConsumerStub.hpp" #include "test/common_boost/helpers.hpp" #include "test/fixtures/openssh_fixture.hpp" #include // com_ptr #include #include #include using swish::connection::authenticated_session; using swish::connection::connection_spec; using test::fixtures::openssh_fixture; using test::CConsumerStub; using comet::com_ptr; using boost::test_tools::predicate_result; using std::exception; using std::map; namespace { class fixture : private openssh_fixture { public: connection_spec get_connection() { return connection_spec(whost(), wuser(), port()); } com_ptr consumer() { com_ptr consumer = new CConsumerStub(private_key_path(), public_key_path()); return consumer; } /** * Check that the given session responds sensibly to a request. */ predicate_result alive(authenticated_session& session) { try { session.get_sftp_filesystem().directory_iterator("/"); predicate_result res(true); res.message() << "Provider seems to be alive"; return res; } catch (const exception& e) { predicate_result res(false); res.message() << "Provider seems to be dead: " << e.what(); return res; } } }; } BOOST_FIXTURE_TEST_SUITE(connection_spec_session_create, fixture) BOOST_AUTO_TEST_CASE(create) { authenticated_session session(get_connection().create_session(consumer())); BOOST_CHECK(alive(session)); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/connection/connection_spec_test.cpp ================================================ // Copyright 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "swish/connection/connection_spec.hpp" // Test subject #include "test/common_boost/helpers.hpp" #include #include using swish::connection::connection_spec; using std::map; BOOST_AUTO_TEST_SUITE(connection_spec_comparison) BOOST_AUTO_TEST_CASE(self) { connection_spec s(L"A", L"b", 12); BOOST_CHECK(!(s < s)); } BOOST_AUTO_TEST_CASE(equal) { connection_spec s1(L"A", L"b", 12); connection_spec s2(L"A", L"b", 12); BOOST_CHECK(!(s1 < s2)); BOOST_CHECK(!(s2 < s1)); } BOOST_AUTO_TEST_CASE(less_host) { connection_spec s1(L"A", L"b", 12); connection_spec s2(L"B", L"b", 12); BOOST_CHECK(s1 < s2); BOOST_CHECK(!(s2 < s1)); } BOOST_AUTO_TEST_CASE(equal_host_less_user) { connection_spec s1(L"A", L"a", 12); connection_spec s2(L"A", L"b", 12); BOOST_CHECK(s1 < s2); BOOST_CHECK(!(s2 < s1)); } BOOST_AUTO_TEST_CASE(greater_host_less_user) { connection_spec s1(L"B", L"a", 12); connection_spec s2(L"A", L"b", 12); BOOST_CHECK(!(s1 < s2)); BOOST_CHECK(s2 < s1); } BOOST_AUTO_TEST_CASE(equal_host_equal_user_less_port) { connection_spec s1(L"A", L"b", 11); connection_spec s2(L"A", L"b", 12); BOOST_CHECK(s1 < s2); BOOST_CHECK(!(s2 < s1)); } BOOST_AUTO_TEST_CASE(equal_host_greater_user_less_port) { connection_spec s1(L"A", L"c", 11); connection_spec s2(L"A", L"b", 12); BOOST_CHECK(!(s1 < s2)); BOOST_CHECK(s2 < s1); } BOOST_AUTO_TEST_CASE(use_as_map_key_same) { connection_spec s1(L"A", L"b", 12); connection_spec s2(L"A", L"b", 12); map m; m[s1] = 3; m[s2] = 7; BOOST_CHECK_EQUAL(m.size(), 1); BOOST_CHECK_EQUAL(m[s1], 7); BOOST_CHECK_EQUAL(m[s2], 7); } BOOST_AUTO_TEST_CASE(use_as_map_key_different_user) { connection_spec s1(L"A", L"b", 12); connection_spec s2(L"A", L"a", 12); map m; m[s1] = 3; m[s2] = 7; BOOST_CHECK_EQUAL(m.size(), 2); BOOST_CHECK_EQUAL(m[s1], 3); BOOST_CHECK_EQUAL(m[s2], 7); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/connection/running_session_test.cpp ================================================ // Copyright 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "swish/connection/running_session.hpp" // Test subject #include "test/fixtures/openssh_fixture.hpp" #include #include #include #include #include // runtime_error #include #include using test::fixtures::openssh_fixture; using swish::connection::running_session; using boost::make_shared; using boost::move; using boost::shared_ptr; using std::runtime_error; using std::vector; using std::wstring; BOOST_FIXTURE_TEST_SUITE(running_session_tests, openssh_fixture) BOOST_AUTO_TEST_CASE(connecting_with_correct_host_and_port_succeeds) { running_session session(whost(), port()); BOOST_CHECK(!session.is_dead()); } BOOST_AUTO_TEST_CASE(connection_failure_throws_error) { BOOST_CHECK_THROW(running_session(L"nonsense.invalid", 65535), runtime_error); } BOOST_AUTO_TEST_CASE(multiple_connections_do_not_interfere) { vector> sessions; for (int i = 0; i < 5; i++) { sessions.push_back(make_shared(whost(), port())); } for (int i = 0; i < 5; i++) { BOOST_CHECK(!sessions.at(i)->is_dead()); } } namespace { running_session move_create(const wstring& host, unsigned int port) { running_session session(host, port); return move(session); } } BOOST_AUTO_TEST_CASE(session_survives_move_construction) { running_session session = move_create(whost(), port()); BOOST_CHECK(!session.is_dead()); } BOOST_AUTO_TEST_CASE(session_survives_move_assignment) { running_session session1(whost(), port()); running_session session2(whost(), port()); session1 = move(session2); BOOST_CHECK(!session1.is_dead()); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/connection/session_manager_test.cpp ================================================ // Copyright 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "swish/connection/session_manager.hpp" // Test subject #include "swish/connection/authenticated_session.hpp" #include "swish/connection/connection_spec.hpp" #include "test/common_boost/helpers.hpp" #include "test/fixtures/openssh_fixture.hpp" #include "test/common_boost/ConsumerStub.hpp" #include // com_ptr #include // move-aware vector #include #include #include #include #include #include #include using swish::connection::authenticated_session; using swish::connection::connection_spec; using swish::connection::session_manager; using swish::connection::session_reservation; using test::CConsumerStub; using test::fixtures::openssh_fixture; using comet::com_ptr; using boost::container::vector; using boost::move; using boost::mutex; using boost::shared_ptr; using boost::ref; using boost::test_tools::predicate_result; using boost::thread; using std::exception; using std::string; namespace { // private /** * Fixture that returns backend connections from the connection pool. */ class fixture : private openssh_fixture { public: connection_spec get_connection() { return connection_spec(whost(), wuser(), port()); } com_ptr consumer() { com_ptr consumer = new CConsumerStub(private_key_path(), public_key_path()); return consumer; } }; /** * Check that the given provider responds sensibly to a request. */ predicate_result alive(authenticated_session& session) { try { session.get_sftp_filesystem().directory_iterator("/"); predicate_result res(true); res.message() << "Session seems to be alive"; return res; } catch (const exception& e) { predicate_result res(false); res.message() << "Session seems to be dead: " << e.what(); return res; } } } BOOST_FIXTURE_TEST_SUITE(session_manager_tests, fixture) BOOST_AUTO_TEST_CASE(new_reservation_are_registered_with_session_manager) { connection_spec spec(get_connection()); BOOST_CHECK(!session_manager().has_session(spec)); session_reservation ticket = session_manager().reserve_session(spec, consumer(), "Testing"); BOOST_CHECK(session_manager().has_session(spec)); authenticated_session& session = ticket.session(); BOOST_CHECK(session_manager().has_session(spec)); BOOST_CHECK(alive(session)); } BOOST_AUTO_TEST_CASE(session_outlives_reservation) { connection_spec spec(get_connection()); BOOST_CHECK(!session_manager().has_session(spec)); session_manager().reserve_session(spec, consumer(), "Testing"); BOOST_CHECK(session_manager().has_session(spec)); } BOOST_AUTO_TEST_CASE(factory_reuses_existing_sessions) { connection_spec spec(get_connection()); session_reservation ticket1 = session_manager().reserve_session(spec, consumer(), "Testing1"); session_reservation ticket2 = session_manager().reserve_session(spec, consumer(), "Testing2"); BOOST_CHECK(&(ticket1.session()) == &(ticket2.session())); } namespace { class progress_callback : boost::noncopyable { public: progress_callback( vector tickets = vector()) : m_releasing_started(false), m_tickets(move(tickets)) { } template bool operator()(const Range& pending_tasks) { mutex::scoped_lock lock(m_mutex); m_notified_task_ranges.push_back(std::vector( boost::begin(pending_tasks), boost::end(pending_tasks))); if (!m_releasing_started) { thread(&progress_callback::release_tickets, this); m_releasing_started = true; } return true; } std::vector> notifications() { mutex::scoped_lock lock(m_mutex); return m_notified_task_ranges; } private: void release_tickets() { while (!m_tickets.empty()) { m_tickets.erase(m_tickets.end() - 1); } } mutex m_mutex; bool m_releasing_started; // Stores the tickets we need to simulate other task gradually // releasing their reservations on this vector m_tickets; // Stores each range of tasks we are notified of, in the // order we are notified of them std::vector> m_notified_task_ranges; }; } BOOST_AUTO_TEST_CASE(removing_session_really_removes_it) { connection_spec spec(get_connection()); session_manager().reserve_session(spec, consumer(), "Testing"); BOOST_CHECK(session_manager().has_session(spec)); progress_callback progress; session_manager().disconnect_session(spec, ref(progress)); BOOST_CHECK(!session_manager().has_session(spec)); // There should be no pending_task notifications because there were // no tasks with a reservation when we disconnected the session. // The only notification should be the empty task range indicating 'done' BOOST_REQUIRE_EQUAL(progress.notifications().size(), 1U); BOOST_CHECK_EQUAL(progress.notifications()[0].size(), 0U); } BOOST_AUTO_TEST_CASE(removing_session_with_pending_task) { connection_spec spec(get_connection()); vector tasks; tasks.push_back( session_manager().reserve_session(spec, consumer(), "Testing")); progress_callback progress(move(tasks)); session_manager().disconnect_session(spec, ref(progress)); BOOST_CHECK(!session_manager().has_session(spec)); // The progress should have been notified twice ... BOOST_REQUIRE_EQUAL(progress.notifications().size(), 2U); // ... first with one pending task BOOST_REQUIRE_EQUAL(progress.notifications()[0].size(), 1U); BOOST_CHECK_EQUAL(progress.notifications()[0][0], "Testing"); // ... then to say it's done BOOST_CHECK_EQUAL(progress.notifications()[1].size(), 0U); } BOOST_AUTO_TEST_CASE(removing_session_with_multiple_pending_tasks) { connection_spec spec(get_connection()); vector tasks; tasks.push_back( session_manager().reserve_session(spec, consumer(), "Testing")); tasks.push_back( session_manager().reserve_session(spec, consumer(), "Testing2")); tasks.push_back( session_manager().reserve_session(spec, consumer(), "Testing3")); progress_callback progress(move(tasks)); session_manager().disconnect_session(spec, ref(progress)); BOOST_CHECK(!session_manager().has_session(spec)); // The progress should have been notified four times ... BOOST_REQUIRE_EQUAL(progress.notifications().size(), 4U); // ... each time with one less task BOOST_REQUIRE_EQUAL(progress.notifications()[0].size(), 3U); BOOST_CHECK_EQUAL(progress.notifications()[0][0], "Testing"); BOOST_CHECK_EQUAL(progress.notifications()[0][1], "Testing2"); BOOST_CHECK_EQUAL(progress.notifications()[0][2], "Testing3"); BOOST_REQUIRE_EQUAL(progress.notifications()[1].size(), 2U); BOOST_CHECK_EQUAL(progress.notifications()[1][0], "Testing"); BOOST_CHECK_EQUAL(progress.notifications()[1][1], "Testing2"); BOOST_REQUIRE_EQUAL(progress.notifications()[2].size(), 1U); BOOST_CHECK_EQUAL(progress.notifications()[2][0], "Testing"); // ... until it's done BOOST_CHECK_EQUAL(progress.notifications()[3].size(), 0U); } BOOST_AUTO_TEST_CASE(removing_session_with_colliding_task_names) { connection_spec spec(get_connection()); vector tasks; tasks.push_back( session_manager().reserve_session(spec, consumer(), "Testing")); tasks.push_back( session_manager().reserve_session(spec, consumer(), "Testing")); progress_callback progress(move(tasks)); session_manager().disconnect_session(spec, ref(progress)); BOOST_CHECK(!session_manager().has_session(spec)); // The progress should have been notified thrice ... BOOST_REQUIRE_EQUAL(progress.notifications().size(), 3U); // ... each time with one less task BOOST_REQUIRE_EQUAL(progress.notifications()[0].size(), 2U); BOOST_CHECK_EQUAL(progress.notifications()[0][0], "Testing"); BOOST_CHECK_EQUAL(progress.notifications()[0][1], "Testing"); BOOST_REQUIRE_EQUAL(progress.notifications()[1].size(), 1U); BOOST_CHECK_EQUAL(progress.notifications()[1][0], "Testing"); // ... until it's done BOOST_CHECK_EQUAL(progress.notifications()[2].size(), 0U); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/connection/session_pool_test.cpp ================================================ // Copyright 2009, 2010, 2011, 2013, 2014, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "swish/connection/session_pool.hpp" // Test subject #include "swish/connection/connection_spec.hpp" #include "test/common_boost/ConsumerStub.hpp" #include "test/common_boost/helpers.hpp" #include "test/fixtures/openssh_fixture.hpp" #include // com_ptr #include // thread #include #include // shared_ptr #include // BOOST_FOREACH #include // diagnostic_information #include #include #include #include using swish::connection::authenticated_session; using swish::connection::connection_spec; using swish::connection::session_pool; using test::CConsumerStub; using test::fixtures::openssh_fixture; using comet::com_ptr; using comet::thread; using boost::exception_ptr; using boost::shared_ptr; using boost::test_tools::predicate_result; using std::exception; using std::vector; namespace { // private class fixture : public openssh_fixture { public: connection_spec get_connection() { return connection_spec(whost(), wuser(), port()); } com_ptr consumer() { com_ptr consumer = new CConsumerStub(private_key_path(), public_key_path()); return consumer; } /** * Check that the given session responds sensibly to a request. */ predicate_result alive(authenticated_session& session) { try { session.get_sftp_filesystem().directory_iterator("/"); predicate_result res(true); res.message() << "Provider seems to be alive"; return res; } catch (const exception& e) { predicate_result res(false); res.message() << "Provider seems to be dead: " << e.what(); return res; } } }; } BOOST_FIXTURE_TEST_SUITE(pool_tests, fixture) /** * Test the situation where the specified connection is not already in the * pool. * * Ensures a connection specification can create a session and that the * pool reports session status correctly. */ BOOST_AUTO_TEST_CASE(new_session) { connection_spec spec(get_connection()); BOOST_CHECK(!session_pool().has_session(spec)); authenticated_session& session = session_pool().pooled_session(spec, consumer()); BOOST_CHECK(session_pool().has_session(spec)); BOOST_CHECK(alive(session)); } /** * Test that creating a session does not affect the status of unrelated * connections. */ BOOST_AUTO_TEST_CASE(unrelated_unaffected_by_creation) { connection_spec unrelated_spec(L"Unrelated", L"Spec", 123); BOOST_CHECK(!session_pool().has_session(unrelated_spec)); authenticated_session& session = session_pool().pooled_session(get_connection(), consumer()); BOOST_CHECK(!session_pool().has_session(unrelated_spec)); } /** * Test that the pool reuses existing sessions. */ BOOST_AUTO_TEST_CASE(existing_session) { connection_spec spec(get_connection()); authenticated_session& first_session = session_pool().pooled_session(spec, consumer()); authenticated_session& second_session = session_pool().pooled_session(spec, consumer()); BOOST_CHECK(&second_session == &first_session); BOOST_CHECK(alive(second_session)); BOOST_CHECK(session_pool().has_session(spec)); } const int THREAD_COUNT = 30; template class use_session_thread : public thread { public: use_session_thread(T* fixture, exception_ptr& error) : thread(), m_fixture(fixture), m_error(error) { } private: DWORD thread_main() { try { { connection_spec spec(m_fixture->get_connection()); // This first call may or may not return running depending on // whether it is on the first thread scheduled, so we don't test // its value, just that it succeeds. session_pool().has_session(spec); authenticated_session& first_session = session_pool().pooled_session(spec, m_fixture->consumer()); // However, by this point it *must* be running if (!session_pool().has_session(spec)) BOOST_THROW_EXCEPTION( std::exception("Test failed: no session")); if (!m_fixture->alive(first_session)) BOOST_THROW_EXCEPTION( std::exception("Test failed: first session is dead")); authenticated_session& second_session = session_pool().pooled_session(spec, m_fixture->consumer()); if (!session_pool().has_session(spec)) BOOST_THROW_EXCEPTION( std::exception("Test failed: no session")); if (!m_fixture->alive(second_session)) BOOST_THROW_EXCEPTION( std::exception("Test failed: second session is dead")); if (&second_session != &first_session) BOOST_THROW_EXCEPTION( std::exception("Test failed: session was not reused")); } } catch (...) { // Boost.Test is not threadsafe so we can't report the error // directly when it happens. Instead pass it out via exception_ptr // to let the test find it. m_error = boost::current_exception(); } return 1; } exception_ptr& m_error; T* m_fixture; }; typedef use_session_thread test_thread; /** * Retrieve and prod a session from many threads. */ BOOST_AUTO_TEST_CASE(threaded) { vector> threads(THREAD_COUNT); vector errors(THREAD_COUNT); for (size_t i = 0; i < threads.size(); ++i) { threads[i] = shared_ptr(new test_thread(this, errors[i])); threads[i]->start(); } for (size_t i = 0; i < threads.size(); ++i) { threads[i]->wait(); } // Must process errors after finished waiting for all threads, otherwise // remaining threads will try to write to errors vector after it has been // destroyed for (size_t i = 0; i < errors.size(); ++i) { if (errors[i]) { boost::rethrow_exception(errors[i]); } } } BOOST_AUTO_TEST_CASE(remove_session) { connection_spec spec(get_connection()); authenticated_session& session = session_pool().pooled_session(spec, consumer()); session_pool().remove_session(spec); BOOST_CHECK(!session_pool().has_session(spec)); } /** * Test that sessions in the pool survive server restarts * (modulo re-authentication). * * By 'survive', we mean the pool is able to serve a usable session * with the same specification, not that the actual session instance has * to be the same (value-semantics and all that jazz). */ BOOST_AUTO_TEST_CASE(sessions_across_server_restart) { connection_spec spec(get_connection()); session_pool().pooled_session(spec, consumer()); BOOST_CHECK(session_pool().has_session(spec)); restart_server(); BOOST_CHECK(alive(session_pool().pooled_session(spec, consumer()))); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/drop_target/CMakeLists.txt ================================================ # Copyright (C) 2015, 2016 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(UNIT_TESTS rooted_source_test.cpp) set(INTEGRATION_TESTS drop_target_test.cpp) swish_test_suite( SUBJECT drop_target VARIANT unit SOURCES ${UNIT_TESTS} LIBRARIES shell ${Boost_LIBRARIES} local_sandbox_fixture LABELS unit) swish_test_suite( SUBJECT drop_target VARIANT integration SOURCES ${INTEGRATION_TESTS} LIBRARIES shell ${Boost_LIBRARIES} provider_fixture local_sandbox_fixture LABELS integration) ================================================ FILE: test/drop_target/drop_target_test.cpp ================================================ // Copyright 2009, 2010, 2012, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "swish/drop_target/DropTarget.hpp" // Test subject #include "swish/shell/shell.hpp" // data_object_for_files #include "test/common_boost/data_object_utils.hpp" // DataObjects on zip #include "test/common_boost/helpers.hpp" // BOOST_REQUIRE_OK #include "test/common_boost/fixtures.hpp" // ComFixture #include "test/fixtures/local_sandbox_fixture.hpp" #include "test/fixtures/provider_fixture.hpp" #include #include #include #include #include #include #include #include #include #include #include using swish::drop_target::CDropTarget; using swish::drop_target::DropActionCallback; using swish::drop_target::copy_data_to_provider; using swish::drop_target::Progress; using swish::shell::data_object_for_files; using test::ComFixture; using test::fixtures::provider_fixture; using test::fixtures::local_sandbox_fixture; using test::data_object_utils::create_test_zip_file; using test::data_object_utils::data_object_for_zipfile; using comet::com_ptr; using ssh::filesystem::ifstream; using ssh::filesystem::sftp_filesystem; using boost::filesystem::ofstream; using boost::filesystem::path; using boost::make_shared; using boost::shared_ptr; using boost::test_tools::predicate_result; using std::string; using std::wstring; using std::vector; using std::istreambuf_iterator; using washer::shell::pidl::apidl_t; namespace { // private const string TEST_DATA = "Lorem ipsum dolor sit amet.\nbob\r\nsally"; const string LARGER_TEST_DATA = ";sdkfna;sldjnksj fjnweneofiun weof woenf woeunr2938y4192n34kj1458c" "d;ofn3498tv 3405jnv 3498thv-948rc 34f 9485hv94htc rwr98thv3948h534h4"; /** * The test data which will be written and read from files to * check correct transmission. */ string test_data() { return TEST_DATA; } /** * Write some data to a collection of local files and return them in * a DataObject created by the shell. * * The files must all be in the same filesystem folder. */ template com_ptr create_multifile_data_object(It begin, It end) { std::for_each(begin, end, fill_file); return data_object_for_files(begin, end); } /** * Write some data to a local file and return it as a DataObject. */ com_ptr create_data_object(const path& local) { return create_multifile_data_object(&local, &local + 1); } /** * Fill a file with the test data. */ void fill_file(const path& file) { ofstream stream(file, std::ios_base::binary); stream.write(test_data().c_str(), test_data().size()); } /** * Create a new empty file at the given path. */ void create_empty_file(path name) { ofstream file(name, std::ios_base::out | std::ios_base::trunc); } class ProgressStub : public Progress { public: bool user_cancelled() { return false; } void line(DWORD, const wstring&) { } void line_path(DWORD, const wstring&) { } void update(ULONGLONG, ULONGLONG) { } void hide() { } void show() { } }; class CopyCallbackStub : public DropActionCallback { public: void site(com_ptr) { } std::auto_ptr progress() { return std::auto_ptr(new ProgressStub()); } bool can_overwrite(const ssh::filesystem::path&) { throw std::exception("unexpected request to confirm overwrite"); } void handle_last_exception() { } }; class ForbidOverwrite : public CopyCallbackStub { public: bool can_overwrite(const ssh::filesystem::path&) { return false; } }; class AllowOverwrite : public CopyCallbackStub { public: bool can_overwrite(const ssh::filesystem::path&) { return true; } }; class DropTargetFixture : public provider_fixture, public ComFixture, public local_sandbox_fixture { public: comet::com_ptr create_drop_target() { ssh::filesystem::path target_directory = sandbox() / L"drop-target"; create_directory(filesystem(), target_directory); return new CDropTarget(Provider(), directory_pidl(target_directory), make_shared()); } /** * Check if a file's contents is our test data. */ predicate_result file_contents_correct(const ssh::filesystem::path& file) { ifstream stream(filesystem(), file); string contents = string(istreambuf_iterator(stream), istreambuf_iterator()); if (contents.size() != test_data().size()) { predicate_result res(false); res.message() << "File contents differs in length from expected [" << contents.size() << " != " << test_data().size() << "] [" << contents << " != " << test_data() << "]"; return res; } else if (contents != test_data()) { for (size_t i = 0; i < contents.size(); ++i) { if (contents[i] != test_data()[i]) { predicate_result res(false); res.message() << "File contents differs at index " << i << " [" << contents[i] << " != " << test_data()[i] << "] [" << contents << " != " << test_data() << "]"; return res; } return true; } }; return true; } }; } #pragma region SFTP folder Drop Target tests BOOST_FIXTURE_TEST_SUITE(drop_target_tests, DropTargetFixture) /** * Create an instance. */ BOOST_AUTO_TEST_CASE(create) { com_ptr sp = create_drop_target(); BOOST_REQUIRE(sp); } #pragma region DataObject copy tests BOOST_FIXTURE_TEST_SUITE(drop_target_copy_tests, DropTargetFixture) /** * Copy single regular file. * * Test our ability to handle a DataObject produced by the shell for a * single, regular file (real file in the filesystem). */ BOOST_AUTO_TEST_CASE(copy_single) { path file = new_file_in_local_sandbox(); com_ptr spdo = create_data_object(file); ssh::filesystem::path destination = new_directory_in_sandbox(); shared_ptr cb(new CopyCallbackStub); copy_data_to_provider(spdo, Provider(), directory_pidl(destination), cb); ssh::filesystem::path expected = destination / file.filename().wstring(); BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_CHECK_EQUAL(file_size(filesystem(), expected), test_data().size()); BOOST_REQUIRE(file_contents_correct(expected)); } /** * Copy several regular files. * * Test our ability to handle a DataObject produced by the shell for * more than one regular file (real files in the filesystem). */ BOOST_AUTO_TEST_CASE(copy_many) { vector locals; locals.push_back(new_file_in_local_sandbox()); locals.push_back(new_file_in_local_sandbox()); locals.push_back(new_file_in_local_sandbox()); com_ptr spdo = create_multifile_data_object(locals.begin(), locals.end()); ssh::filesystem::path destination = new_directory_in_sandbox(); shared_ptr cb(new CopyCallbackStub); copy_data_to_provider(spdo, Provider(), directory_pidl(destination), cb); vector::const_iterator it; for (it = locals.begin(); it != locals.end(); ++it) { ssh::filesystem::path expected = destination / (*it).filename().wstring(); BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(file_contents_correct(expected)); } } /** * Recursively copy a folder hierarchy. * * Our test hierarchy look like this: * Sandbox - file0 * \ file1 * \ empty_folder * \ non_empty_folder - second_level_file * \ second_level_folder - third_level_file * * We could just make a DataObject by passing the sandbox dir to the shell * function but instead we pass the four items directly within it to * test how we handle a mix of recursive dirs and simple files. */ BOOST_AUTO_TEST_CASE(copy_recursively) { vector top_level; // Build top-level - these are the only items stored in the vector top_level.push_back(new_file_in_local_sandbox()); top_level.push_back(new_file_in_local_sandbox()); path empty_folder = local_sandbox() / L"empty"; path non_empty_folder = local_sandbox() / L"non-empty"; create_directory(empty_folder); create_directory(non_empty_folder); top_level.push_back(empty_folder); top_level.push_back(non_empty_folder); // Build lower levels path second_level_folder = non_empty_folder / L"second-level-folder"; create_directory(second_level_folder); path second_level_file = non_empty_folder / L"second-level-file"; create_empty_file(second_level_file); fill_file(second_level_file); path second_level_zip_file = create_test_zip_file(non_empty_folder); path third_level_file = second_level_folder / L"third-level-file"; create_empty_file(third_level_file); fill_file(third_level_file); com_ptr spdo = create_multifile_data_object(top_level.begin(), top_level.end()); ssh::filesystem::path destination = sandbox() / L"copy-destination"; create_directory(filesystem(), destination); shared_ptr cb(new CopyCallbackStub); copy_data_to_provider(spdo, Provider(), directory_pidl(destination), cb); ssh::filesystem::path expected; expected = destination / top_level[0].filename().wstring(); BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(file_contents_correct(expected)); expected = destination / top_level[0].filename().wstring(); BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(file_contents_correct(expected)); expected = destination / empty_folder.filename().wstring(); BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(is_empty(filesystem(), expected)); expected = destination / non_empty_folder.filename().wstring(); BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), expected)); expected = destination / non_empty_folder.filename().wstring() / second_level_file.filename().wstring(); BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(file_contents_correct(expected)); expected = destination / non_empty_folder.filename().wstring() / second_level_folder.filename().wstring(); BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(!is_empty(filesystem(), expected)); // The zip file must be copied as-is, not expanded expected = destination / non_empty_folder.filename().wstring() / second_level_zip_file.filename().wstring(); BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(is_regular_file(filesystem(), expected)); BOOST_REQUIRE_GT(file_size(filesystem(), expected), 800); expected = destination / non_empty_folder.filename().wstring() / second_level_folder.filename().wstring() / third_level_file.filename().wstring(); BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(file_contents_correct(expected)); } /** * Recursively copy a virtual hierarchy from a ZIP file. * * Our test hierarchy look like this: * Sandbox - file1.txt * \ file2.txt * \ empty_folder * \ non_empty_folder - second_level_file * \ second_level_folder - third_level_file * * We could just make a DataObject by passing the sandbox dir to the shell * function but instead we pass the four items directly within it to * test how we handle a mix of recursive dirs and simple files. */ BOOST_AUTO_TEST_CASE(copy_virtual_hierarchy_recursively) { com_ptr spdo = data_object_for_zipfile(create_test_zip_file(local_sandbox())); ssh::filesystem::path destination = sandbox() / L"copy-destination"; create_directory(filesystem(), destination); shared_ptr cb(new CopyCallbackStub); copy_data_to_provider(spdo, Provider(), directory_pidl(destination), cb); ssh::filesystem::path expected; expected = destination / L"file1.txt"; BOOST_REQUIRE(exists(filesystem(), expected)); expected = destination / L"file2.txt"; BOOST_REQUIRE(exists(filesystem(), expected)); expected = destination / L"empty"; BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(is_empty(filesystem(), expected)); expected = destination / L"non-empty"; BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), expected)); expected = destination / L"non-empty" / L"second-level-file"; BOOST_REQUIRE(exists(filesystem(), expected)); expected = destination / L"non-empty" / L"second-level-folder"; BOOST_REQUIRE(exists(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(!is_empty(filesystem(), expected)); expected = destination / L"non-empty" / L"second-level-folder" / L"third-level-file"; BOOST_REQUIRE(exists(filesystem(), expected)); } /** * Overwrite an existing file. * * Must ask the user to confirm. This test and the test after together * ensure that the user's response makes a difference to the outcome and * thereby proves that the user was asked. */ BOOST_AUTO_TEST_CASE(copy_overwrite_yes) { path file = new_file_in_local_sandbox(); com_ptr spdo = create_data_object(file); ssh::filesystem::path destination = sandbox() / L"copy-destination"; ssh::filesystem::path obstruction = destination / file.filename().wstring(); create_directory(filesystem(), destination); ssh::filesystem::ofstream(filesystem(), obstruction).close(); BOOST_CHECK(exists(filesystem(), obstruction)); BOOST_CHECK(!file_contents_correct(obstruction)); shared_ptr cb(new AllowOverwrite); copy_data_to_provider(spdo, Provider(), directory_pidl(destination), cb); BOOST_CHECK(exists(filesystem(), obstruction)); BOOST_CHECK(file_contents_correct(obstruction)); } /** * Deny permission to overwrite an existing file. */ BOOST_AUTO_TEST_CASE(copy_overwrite_no) { path file = new_file_in_local_sandbox(); com_ptr spdo = create_data_object(file); ssh::filesystem::path destination = sandbox() / L"copy-destination"; ssh::filesystem::path obstruction = destination / file.filename().wstring(); create_directory(filesystem(), destination); ssh::filesystem::ofstream(filesystem(), obstruction).close(); // empty BOOST_CHECK(exists(filesystem(), obstruction)); BOOST_CHECK(!file_contents_correct(obstruction)); shared_ptr cb(new ForbidOverwrite); copy_data_to_provider(spdo, Provider(), directory_pidl(destination), cb); BOOST_CHECK(exists(filesystem(), obstruction)); BOOST_CHECK_EQUAL(file_size(filesystem(), obstruction), 0); // still empty } /** * Overwrite a larger file. * * Tests that we truncate the large file before writing. Otherwise the * final * file would be corrupt. */ BOOST_AUTO_TEST_CASE(copy_overwrite_larger) { path target = new_file_in_local_sandbox(); com_ptr spdo = create_data_object(target); ssh::filesystem::path destination = sandbox() / L"copy-destination"; ssh::filesystem::path obstruction = destination / target.filename().wstring(); // make sure that the destination file already exists and is larger // that what we're about to copy to it create_directory(filesystem(), destination); ssh::filesystem::ofstream stream(filesystem(), obstruction); stream << LARGER_TEST_DATA; stream.close(); BOOST_REQUIRE(exists(filesystem(), obstruction)); BOOST_REQUIRE(!file_contents_correct(obstruction)); shared_ptr cb(new AllowOverwrite); copy_data_to_provider(spdo, Provider(), directory_pidl(destination), cb); BOOST_REQUIRE(exists(filesystem(), obstruction)); BOOST_REQUIRE(file_contents_correct(obstruction)); } BOOST_AUTO_TEST_SUITE_END() #pragma endregion #pragma region Drag - n - Drop behaviour tests BOOST_FIXTURE_TEST_SUITE(drop_target_dnd_tests, DropTargetFixture) /** * Drag enter. * Simulate the user dragging a file onto our folder with the left * mouse button. The 'shell' is requesting either a copy or a link at our * discretion. The folder drop target should respond S_OK and specify * that the effect of the operation is a copy. */ BOOST_AUTO_TEST_CASE(drag_enter) { path file = new_file_in_local_sandbox(); com_ptr spdo = create_data_object(file); com_ptr spdt = create_drop_target(); POINTL pt = {0, 0}; DWORD dwEffect = DROPEFFECT_COPY | DROPEFFECT_LINK; BOOST_REQUIRE_OK(spdt->DragEnter(spdo.in(), MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_COPY)); } /** * Drag enter. * Simulate the user dragging a file onto our folder but requesting an * effect, link, that we don't support. The folder drop target should * respond S_OK but set the effect to DROPEFFECT_NONE to indicate the drop * wasn't possible. */ BOOST_AUTO_TEST_CASE(drag_enter_bad_effect) { path file = new_file_in_local_sandbox(); com_ptr spdo = create_data_object(file); com_ptr spdt = create_drop_target(); POINTL pt = {0, 0}; DWORD dwEffect = DROPEFFECT_LINK; BOOST_REQUIRE_OK(spdt->DragEnter(spdo.in(), MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_NONE)); } /** * Drag over. * Simulate the situation where a user drags a file over our folder and * changes * which operation they want as they do so. In other words, on DragEnter * they * chose a link which we cannot perform but as they continue the drag * (DragOver) * they chang their request to a copy which we can do. * * The folder drop target should respond S_OK and specify that the effect of * the operation is a copy. */ BOOST_AUTO_TEST_CASE(drag_over) { path file = new_file_in_local_sandbox(); com_ptr spdo = create_data_object(file); com_ptr spdt = create_drop_target(); POINTL pt = {0, 0}; // Do enter with link which should be declined (DROPEFFECT_NONE) DWORD dwEffect = DROPEFFECT_LINK; BOOST_REQUIRE_OK(spdt->DragEnter(spdo.in(), MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_NONE)); // Change request to copy which should be accepted dwEffect = DROPEFFECT_COPY; BOOST_REQUIRE_OK(spdt->DragOver(MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_COPY)); } /** * Drag leave. * Simulate an aborted drag-drop loop where the user drags a file onto our * folder, moves it around, and then leaves without dropping. * * The folder drop target should respond S_OK and any subsequent * DragOver calls should be declined. */ BOOST_AUTO_TEST_CASE(drag_leave) { path file = new_file_in_local_sandbox(); com_ptr spdo = create_data_object(file); com_ptr spdt = create_drop_target(); POINTL pt = {0, 0}; // Do enter with copy which sould be accepted DWORD dwEffect = DROPEFFECT_COPY; BOOST_REQUIRE_OK(spdt->DragEnter(spdo.in(), MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_COPY)); // Continue drag BOOST_REQUIRE_OK(spdt->DragOver(MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_COPY)); // Finish drag without dropping BOOST_REQUIRE_OK(spdt->DragLeave()); // Decline any further queries until next DragEnter() BOOST_REQUIRE_OK(spdt->DragOver(MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_NONE)); } /** * Drag and drop. * Simulate a complete drag-drop loop where the user drags a file onto our * folder, moves it around, and then drops it. * * The folder drop target should copy the contents of the DataObject to the * remote end, respond S_OK and any subsequent DragOver calls should be * declined until a new drag-and-drop loop is started with DragEnter(). */ BOOST_AUTO_TEST_CASE(drop) { path file = new_file_in_local_sandbox(); com_ptr spdo = create_data_object(file); com_ptr spdt = create_drop_target(); POINTL pt = {0, 0}; // Do enter with copy which should be accepted DWORD dwEffect = DROPEFFECT_COPY; BOOST_REQUIRE_OK(spdt->DragEnter(spdo.in(), MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_COPY)); // Continue drag BOOST_REQUIRE_OK(spdt->DragOver(MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_COPY)); // Drop onto DropTarget BOOST_REQUIRE_OK(spdt->Drop(spdo.in(), MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_COPY)); // Decline any further queries until next DragEnter() BOOST_REQUIRE_OK(spdt->DragOver(MK_LBUTTON, pt, &dwEffect)); BOOST_REQUIRE_EQUAL(dwEffect, static_cast(DROPEFFECT_NONE)); ssh::filesystem::path expected = sandbox() / L"drop-target" / file.filename().wstring(); BOOST_CHECK(exists(filesystem(), expected)); BOOST_CHECK(file_contents_correct(expected)); } BOOST_AUTO_TEST_SUITE_END() #pragma endregion BOOST_AUTO_TEST_SUITE_END() #pragma endregion ================================================ FILE: test/drop_target/rooted_source_test.cpp ================================================ // Copyright 2012, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "swish/drop_target/RootedSource.hpp" // Test subject #include // wchar_t ostream #include #include // pidl_from_parsing_name #include // pidl_shell_item #include // apidl_t, cpidl_t #include // path #include // ofstream #include using swish::drop_target::RootedSource; using test::fixtures::local_sandbox_fixture; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::pidl_t; using washer::shell::pidl_from_parsing_name; using washer::shell::pidl_shell_item; using boost::filesystem::wofstream; using boost::filesystem::path; namespace { class RootedSourceFixture : public local_sandbox_fixture { public: path test_root() { return local_sandbox(); } path child_file() { return new_file_in_local_sandbox(); } path child_directory() { path directory = local_sandbox() / L"testdir"; create_directory(directory); return directory; } path grandchild_file() { path directory = local_sandbox() / L"testdir"; create_directory(directory); path file = directory / L"testfile.txt"; wofstream s(file); return file; } path greatgrandchild_file() { path directory1 = local_sandbox() / L"testdir1"; create_directory(directory1); path directory2 = directory1 / L"testdir2"; create_directory(directory2); path file = directory2 / L"testfile.txt"; wofstream s(file); return file; } }; inline bool operator==(const apidl_t& lhs, const apidl_t& rhs) { return pidl_shell_item(lhs).parsing_name() == pidl_shell_item(rhs).parsing_name(); } apidl_t to_pidl(const path& path) { return pidl_from_parsing_name(path.wstring()); } } BOOST_FIXTURE_TEST_SUITE(rooted_source_tests, RootedSourceFixture) /** * Test the source where the root is the source itself (no branch). */ BOOST_AUTO_TEST_CASE(root) { apidl_t root_pidl = to_pidl(test_root()); RootedSource source(root_pidl, cpidl_t()); BOOST_CHECK(source.pidl() == root_pidl); BOOST_CHECK(source.common_root() == root_pidl); BOOST_CHECK_EQUAL(source.relative_name(), L""); } /** * Test the source where the source is a file directly under the root. */ BOOST_AUTO_TEST_CASE(child) { path file = child_file(); apidl_t pidl = to_pidl(file); RootedSource source(pidl.parent(), pidl.last_item()); BOOST_CHECK(source.pidl() == pidl); BOOST_CHECK(source.common_root() == pidl.parent()); BOOST_CHECK_EQUAL(source.relative_name(), file.filename()); } /** * Test the source where the source is a directory directly under the root. */ BOOST_AUTO_TEST_CASE(child_dir) { path directory = child_directory(); apidl_t pidl = to_pidl(directory); RootedSource source(pidl.parent(), pidl.last_item()); BOOST_CHECK(source.pidl() == pidl); BOOST_CHECK(source.common_root() == pidl.parent()); BOOST_CHECK_EQUAL(source.relative_name(), directory.filename()); } /** * Test the source where the source is grandchild of the root. */ BOOST_AUTO_TEST_CASE(grandchild) { path file = grandchild_file(); apidl_t pidl = to_pidl(file); apidl_t root_pidl = pidl.parent().parent(); pidl_t branch = pidl.parent().last_item() + pidl.last_item(); RootedSource source(root_pidl, branch); BOOST_CHECK(source.pidl() == pidl); BOOST_CHECK(source.common_root() == root_pidl); path expected_relative_name = file.parent_path().filename(); expected_relative_name /= file.filename(); BOOST_CHECK_EQUAL(source.relative_name(), expected_relative_name.wstring()); } /** * Test the source where the source is grandchild of the root. */ BOOST_AUTO_TEST_CASE(greatgrandchild) { path file = greatgrandchild_file(); apidl_t pidl = to_pidl(file); apidl_t root_pidl = pidl.parent().parent().parent(); pidl_t branch = pidl.parent().parent().last_item() + pidl.parent().last_item() + pidl.last_item(); RootedSource source(root_pidl, branch); BOOST_CHECK(source.pidl() == pidl); BOOST_CHECK(source.common_root() == root_pidl); path expected_relative_name = file.parent_path().parent_path().filename(); expected_relative_name /= file.parent_path().filename(); expected_relative_name /= file.filename(); BOOST_CHECK_EQUAL(source.relative_name(), expected_relative_name.wstring()); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/ezel/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES form_test.cpp) swish_test_suite( SUBJECT ezel SOURCES ${SOURCES} LIBRARIES shell_folder ${Boost_LIBRARIES} LABELS gui) ================================================ FILE: test/ezel/form_test.cpp ================================================ /** @file Tests for forms. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // wstring output #include // test subject #include // button #include // checkbox #include // edit #include // label #include // bind #include // BOOST_FOREACH #include using ezel::form; using ezel::controls::button; using ezel::controls::checkbox; using ezel::controls::edit; using ezel::controls::label; using boost::bind; BOOST_AUTO_TEST_SUITE(form_tests) namespace { class form1 { public: form1() : m_form(L"my title", 30, 40, 30, 30) { m_form.on_activate().connect( boost::bind(&form1::test_creation_and_die, this)); m_form.show(); } void test_creation_and_die(/*bool by_mouse*/) { // BOOST_CHECK(!by_mouse); BOOST_CHECK_EQUAL(m_form.text(), L"my title"); m_form.end(); } form& get_form() { return m_form; } private: form m_form; }; class form2 { public: form2() : m_form(L"", 30, 40, 30, 30) { m_form.on_create().connect( boost::bind(&form2::test_creation_and_die, this)); m_form.show(); } bool test_creation_and_die() { BOOST_CHECK_EQUAL(m_form.text(), L""); m_form.end(); return true; } form& get_form() { return m_form; } private: form m_form; }; /** * Monitor text change event. */ class form3 { public: form3() : m_form(L"initial text", 30, 40, 30, 30), m_change_detected(false) { m_form.on_create().connect( boost::bind(&form3::test_and_die, this)); m_form.on_text_changed().connect( boost::bind(&form3::text_changed, this)); m_form.show(); } void text_changed() { m_change_detected = true; } bool test_and_die() { BOOST_CHECK_EQUAL(m_form.text(), L"initial text"); m_form.text(L"changed text"); BOOST_CHECK(m_change_detected); BOOST_CHECK_EQUAL(m_form.text(), L"changed text"); m_form.end(); return true; } private: form m_form; bool m_change_detected; }; } /** * Create a form and test some basic properties. Then destroy it and test * them again. */ BOOST_AUTO_TEST_CASE( create_form ) { form1 frm; BOOST_CHECK_EQUAL(frm.get_form().text(), L"my title"); } /** * Create a form with an empty title. */ BOOST_AUTO_TEST_CASE( create_form_no_title ) { form2 frm; BOOST_CHECK_EQUAL(frm.get_form().text(), L""); } /** * Test that we can react to changes in form properties. * In other words, test that events work for forms. */ BOOST_AUTO_TEST_CASE( create_form_change_title ) { form3 frm; } /** * Put a button on a form. */ BOOST_AUTO_TEST_CASE( form_with_button ) { form frm(L"my title", 30, 40, 100, 50); button hello(L"Hello", 0, 0, 30, 20); hello.on_click().connect(frm.killer()); frm.add_control(hello); frm.show(); BOOST_CHECK_EQUAL(frm.text(), L"my title"); BOOST_CHECK_EQUAL(hello.text(), L"Hello"); } namespace { void beep() { ::MessageBeep(0); } } /** * Put two buttons on a form. */ BOOST_AUTO_TEST_CASE( form_with_two_controls ) { form frm(L"Pick one", 30, 40, 200, 50); button hello(L"Oh noes!", 10, 10, 50, 20, true); hello.on_click().connect(frm.killer()); button parp(L"Parp", 70, 10, 50, 20); parp.on_click().connect(beep); frm.add_control(hello); frm.add_control(parp); frm.show(); } /** * Put two different controls on a form. */ BOOST_AUTO_TEST_CASE( form_with_different_controls ) { form frm(L"A button and a box went to tea", 30, 40, 200, 50); button hello(L"Hello", 10, 10, 30, 20, true); hello.on_click().connect(frm.killer()); frm.add_control(hello); edit text_box(L"Some text", 70, 10, 70, 14, edit::style::default); text_box.on_update().connect(beep); frm.add_control(text_box); frm.show(); } /** * Test that control template alignment is being done correctly. * * Change the control alignment by varying the title text by one character at * a time to cycle though the four alignment possibilities: * aligned, off-by-one, off-by-two, off-by-three (not necessarily in that * order). */ BOOST_AUTO_TEST_CASE( four_different_alignments ) { wchar_t* titles[] = { L"Hello", L"Helloo", L"Hellooo", L"Helloooo" }; BOOST_FOREACH(wchar_t* title, titles) { form frm(L"You'll see me four times", 30, 40, 200, 50); button hello(title, 10, 10, 60, 20); hello.on_click().connect(frm.killer()); label lab(L"press the button to exit", 70, 10, 50, 20); frm.add_control(hello); frm.add_control(lab); frm.show(); } } /** * Put a button on a form using inline temporary construction. * * The add_control method should copy the new button in such a way that * it works once the temporary is destroyed. */ BOOST_AUTO_TEST_CASE( form_with_button_inline_contructor ) { form frm(L"my title", 30, 40, 100, 50); button close(L"Close", 40, 25, 60, 20, true); close.on_click().connect(frm.killer()); frm.add_control(close); frm.add_control(button(L"I do nothing", 0, 0, 75, 20)); frm.show(); } /** * Link two controls. */ BOOST_AUTO_TEST_CASE( one_control_updates_another ) { form frm(L"Multipass", 30, 40, 220, 50); button close(L"Close", 10, 10, 30, 20); close.on_click().connect(frm.killer()); label lab(L"My old text", 160, 15, 50, 20); button change(L"Click me to change him", 50, 10, 100, 20, true); change.on_click().connect( bind(&label::text, boost::ref(lab), L"I got new!")); frm.add_control(change); frm.add_control(close); frm.add_control(lab); frm.show(); BOOST_CHECK_EQUAL(lab.text(), L"I got new!"); } /** * Chain two events (beep end end). */ BOOST_AUTO_TEST_CASE( chain_events ) { form frm(L"I should beep then die", 30, 40, 100, 50); button ping(L"Ping!", 0, 0, 100, 50); ping.on_click().connect(beep); ping.on_click().connect(frm.killer()); frm.add_control(ping); frm.show(); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/fix_key_permissions.sh ================================================ set -eu if [ "${BASH_VERSINFO[0]}" -ge 3 ]; then set -o pipefail fi IFS=$'\n\t' KEYFILE=fixture_hostkey chgrp -v 545 $KEYFILE chmod -v 600 $KEYFILE ================================================ FILE: test/fixtures/CMakeLists.txt ================================================ # Copyright (C) 2016 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . add_library(local_sandbox_fixture local_sandbox_fixture.cpp local_sandbox_fixture.hpp) target_link_libraries(local_sandbox_fixture ${Boost_LIBRARIES}) add_library(openssh_fixture openssh_fixture.cpp openssh_fixture.hpp) hunter_add_package(Boost.Process) hunter_add_package(Comet) hunter_add_package(Washer) find_package(Comet REQUIRED CONFIG) find_package(Washer REQUIRED CONFIG) target_link_libraries(openssh_fixture PUBLIC ${Boost_LIBRARIES} PRIVATE Boost::Process) add_library(session_fixture session_fixture.cpp session_fixture.hpp) target_link_libraries(session_fixture PUBLIC openssh_fixture ssh) add_library(sftp_fixture sftp_fixture.cpp sftp_fixture.hpp) target_link_libraries(sftp_fixture PUBLIC session_fixture) add_library(com_stream_fixture com_stream_fixture.cpp com_stream_fixture.hpp) target_link_libraries(com_stream_fixture PUBLIC sftp_fixture) add_library(provider_fixture provider_fixture.cpp provider_fixture.hpp) target_link_libraries(provider_fixture PUBLIC sftp_fixture Comet::comet Washer::washer) ================================================ FILE: test/fixtures/com_stream_fixture.cpp ================================================ // Copyright 2009, 2010, 2011, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "com_stream_fixture.hpp" #include "swish/connection/authenticated_session.hpp" #include #include // fstream #include // com_ptr #include // adapt_stream_pointer #include #include // shared_ptr using swish::connection::authenticated_session; using ssh::filesystem::fstream; using ssh::filesystem::path; using comet::adapt_stream_pointer; using comet::com_ptr; using boost::make_shared; using boost::shared_ptr; using std::ios_base; namespace test { namespace fixtures { com_stream_fixture::com_stream_fixture() : m_path(new_file_in_sandbox()) { } com_ptr com_stream_fixture::get_stream(ios_base::openmode flags) { // TODO: This should not create the stream directly but should use // SftpDirectory. This can happen when SftpDirectory is merged with // the provider project return adapt_stream_pointer(make_shared(boost::ref(filesystem()), test_file().wstring(), flags), test_file().wstring()); } path com_stream_fixture::test_file() { return m_path; } } } // namespace test::fixtures ================================================ FILE: test/fixtures/com_stream_fixture.hpp ================================================ // Copyright 2009, 2010, 2011, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SWISH_TEST_FIXTURES_COM_STREAM_FIXTURE_HPP #define SWISH_TEST_FIXTURES_COM_STREAM_FIXTURE_HPP #include "sftp_fixture.hpp" #include #include // com_ptr namespace test { namespace fixtures { /** * Extends the Sandbox fixture by allowing the creation of swish::provider * IStreams that pass through the OpenSSH server pointing to files in the * sandbox. */ class com_stream_fixture : virtual public sftp_fixture { public: /** * Initialise the test fixture with the path of a new, empty file * in the sandbox. */ com_stream_fixture(); /** * Create an IStream instance open on a temporary file in our sandbox. * By default the stream is open for reading and writing but different * flags can be passed to change this. */ comet::com_ptr get_stream( std::ios_base::openmode flags = std::ios_base::in | std::ios_base::out); ssh::filesystem::path test_file(); private: ssh::filesystem::path m_path; }; } } // namespace test::fixtures #endif ================================================ FILE: test/fixtures/fixture_dsakey ================================================ -----BEGIN DSA PRIVATE KEY----- MIIBvAIBAAKBgQCtiYdgpPvFtfi7Ba44DiB+1x8kojjT0nRvn2hU2aa4p4fXI8kd 6Hc57VQO/lLhR9eFpxjP7m+jGwF468Q6NU8xiC71ucep0OoXS7u8RcoIoWfLDtZi DDlahnZTW04mB5fFxo2y7dYl31vE4TPdSxhqpkvnIBIstMFh2M7Dl0w8/QIVAP95 u6dg1OW6gGsRgiircsy1A9tzAoGBAIzwc5FCnJnzAJm9Hjv0AFV5l/i/DQulZ9pu EILkNiHCfDR+lTJ8VxAR7J3pgjmvYzeeRvi519ez1YriktDt66kIknQOcHB8ghyg U+dff79SkDcpg8LnX5xb3cVMgABujA0sSpaW1wwm64RXdvmoQvWu6ympUT0l0dEd oYVkb4ytAoGAJ+CGwV/1S4j1GVwa6pSP0nj4V86GWXosTTBg7GT+rKWu8lrxIcr6 FzLWgFi/gHoMrgnKWGxO1yF7vkoYM5Yfo84oBYiH+MgpiBuOrZrgzacHsA66JJbU frESRFWZl2blIPr6Gyjj6cVGgMabK3yCiTRi0v7hwffpm0rKyKv7GooCFQCyaA6T tkJunHP+F0Xg/WAUV6tcqA== -----END DSA PRIVATE KEY----- ================================================ FILE: test/fixtures/fixture_dsakey.pub ================================================ ssh-dss AAAAB3NzaC1kc3MAAACBAK2Jh2Ck+8W1+LsFrjgOIH7XHySiONPSdG+faFTZprinh9cjyR3odzntVA7+UuFH14WnGM/ub6MbAXjrxDo1TzGILvW5x6nQ6hdLu7xFygihZ8sO1mIMOVqGdlNbTiYHl8XGjbLt1iXfW8ThM91LGGqmS+cgEiy0wWHYzsOXTDz9AAAAFQD/ebunYNTluoBrEYIoq3LMtQPbcwAAAIEAjPBzkUKcmfMAmb0eO/QAVXmX+L8NC6Vn2m4QguQ2IcJ8NH6VMnxXEBHsnemCOa9jN55G+LnX17PViuKS0O3rqQiSdA5wcHyCHKBT519/v1KQNymDwudfnFvdxUyAAG6MDSxKlpbXDCbrhFd2+ahC9a7rKalRPSXR0R2hhWRvjK0AAACAJ+CGwV/1S4j1GVwa6pSP0nj4V86GWXosTTBg7GT+rKWu8lrxIcr6FzLWgFi/gHoMrgnKWGxO1yF7vkoYM5Yfo84oBYiH+MgpiBuOrZrgzacHsA66JJbUfrESRFWZl2blIPr6Gyjj6cVGgMabK3yCiTRi0v7hwffpm0rKyKv7Goo= awl03@bounty ================================================ FILE: test/fixtures/fixture_hostkey ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEoQIBAAKCAQEArrr/JuJmaZligyfS8vcNur+mWR2ddDQtVdhHzdKUUoR6/Om6 cvxpe61H1YZO1xCpLUBXmkki4HoNtYOpPB2W4V+8U4BDeVBD5crypEOE1+7BAm99 fnEDxYIOZq2/jTP0yQmzCpWYS3COyFmkOL7sfX1wQMeW5zQT2WKcxC6FSWbhDqrB eNEGi687hJJoJ7YXgY/IdiYW5NcOuqRSWljjGS3dAJsHHWk4nJbhjEDXbPaeduMA wQU9i6ELfP3r+q6wdu0P4jWaoo3De1aYxnToV/ldXykpipON4NPamsb6Ph2qlJQK ypq7J4iQgkIIbCU1A31+4ExvcIVoxLQw/aTSbwIBIwKCAQAd9Cu9heWrs+UAinvv Iwmq/EhnDGQijJoOt1zEMrpXSekyq7mQDgN0SZdJLPeSlSRQ5nVq5/dZroYB3A5i E7N3F7nibcJskWq5rcMyGjQHwod8wqfMiGcL6mjeZu2jLXprm0NDpJ3DyicbCA2G EhnpoHmktIBE5FsslI/nHer2o6OA/kVWSEjak+pvI1pm22T8QOBBfY0yAX7B0ebk 8o4lB4cdLf3In7Q0ahpHNOwIPdRvQ2c4Tm/DcfUBkTW2ZYGUd45cFsyHqXZscNNy GX2Wcy/FLEvQ6zBFJsNLpxCYsUyBxfSDygn9dx9RQfiWFXjdRaRPpyRAr+BTXkLU yvabAoGBANt7sxfjvu/SLkRc7TnBoJ0h/AL7Mcuu9PJmOnis4boyF9ZxqbiRiS3J yK+EKxfC0S+xf5WJ5uf7dVGnOXHXKaRl4xH90iRtryNlvtILZwHw1DTqRFxv9jtz tTRrYMEHAnMKzadgDfV/lv4iJ6nwFzK76GQ7RQNZYiGTMEh3pUNjAoGBAMvNLGpz FxhpIh+fVvRjawKgGVP87T482WOUdsF18EEPFMe6D7DO5xpLuJi+C7QkvMI8WjvD /3RGvaSh9Wt7ikLZpeogiSJy121HsEqheTR5hTx2t72ClrjZvIhLbQMRu6PqGPu/ HOC2urEGGYm7O2vnftwpuG3zIVVLM2KstPCFAoGBAM7w+VEJ7opYdMQdGi8kRvqN wbmrAxCAY0ryrCijALbexgS0T5DDu9q28Gr49W4stpquq35dc0/BNBnJje7+EVHc aGFrqOCErHHU9b66Sy23LnsIxBykFAwrRHNAq66u1mx35nk9Tv1pq58nhHun21u4 fAa7ijZblwm2qd3tJsqBAoGAEXf8ficfPJtMEVbM8GBLADmbxV7Sga1xuBQKLdbo tR6MwKmMUPvKqnuE2eRnZzZZUnoznrkHRHsXkcS9Q7ohyzc6G2Hf3mGdb8RQ8HQ9 lsiWZESwqdf+SlvOVNND27EQFV01V2gnC/JnxgfWTaJVjOf07ky4CWycdQZyHmaT Ko8CgYB58jOyXMdo2ggOCG/HX2H92KPPpFUBFCX27fCue8BZLD5quIltpXupx5oj EyltgvPcmNDgvdSadkHvP5s6nykS+n5we+d9yIIJF/BfETWsXjR3ooip+trqiirw 0aHqUDFcYn9unm2wtrMYYViiDLRijNwLZ2sG0JIU4JHyseh+NA== -----END RSA PRIVATE KEY----- ================================================ FILE: test/fixtures/fixture_hostkey.pub ================================================ ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArrr/JuJmaZligyfS8vcNur+mWR2ddDQtVdhHzdKUUoR6/Om6cvxpe61H1YZO1xCpLUBXmkki4HoNtYOpPB2W4V+8U4BDeVBD5crypEOE1+7BAm99fnEDxYIOZq2/jTP0yQmzCpWYS3COyFmkOL7sfX1wQMeW5zQT2WKcxC6FSWbhDqrBeNEGi687hJJoJ7YXgY/IdiYW5NcOuqRSWljjGS3dAJsHHWk4nJbhjEDXbPaeduMAwQU9i6ELfP3r+q6wdu0P4jWaoo3De1aYxnToV/ldXykpipON4NPamsb6Ph2qlJQKypq7J4iQgkIIbCU1A31+4ExvcIVoxLQw/aTSbw== awl03@bounty ================================================ FILE: test/fixtures/fixture_rsakey ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEoQIBAAKCAQEAnak1T7zHJ+hVRFBDQ9pf1KVzmd5gaNc7y7NPmL13aOG3sYeJ evi1x1WM/R3tb8XnUnzZUX9GJN0MYovvZsw9bknG1mDP72LFbGp/gzPddGIKHBBp vceDaJ85sM/ME3XOtD7uuXQuNAuEHwEzSMMiSIEMcQS+lXIcMLr5xPLEkyNvqsO5 RqSjMTLHKHgY8gLWx7oQ1avokhwuDxF7P3Pqtj+rW2Te6vR0i1H6EyFPsBkzkgNX b33cus8M1CnTmYTSgJgmHO2LLcGpjQ5sL8T/PWIWHaSqTnkrFXEMysgoteXnAYIL jzyBaqq2WV4KA3TluGdAP2p8gC32QtKmIuis3QIBIwKCAQB1Hpyhoi2LXCIVfXPM AU6AtWvRY12PtdSl8uqr+nX2JATNBZlUCTaUE6qQJNxEZyDeMNvzZdxV5gkzQ2Fi TpQIyRddbH01fJKoTxzlHzbLfAeCj9mFqicahOkHAMN8K6Ddqxe89zhD60w0SgjW 91tLzZQ2sxE70RxBdPQOpbaZLxmUZSVxRgf5djotyZqB4CcGblKCEZYJ9ZemgCnF gEcSsqcn0Jxfu+aEJ4WinN2orWs+okfgsUu9G9Ozwcy9Ptq1LkIzcwwTIpL7TTDd LMvhql39a07SysepjFRHxjvXh8Gv+SsLvKQPJHheVv8XoG0dZd+9/Eden9rHKoVm vGPLAoGBANGDQtv5K/md/3sRGeJ6Ir3/Ao+WMe8C5onck+hW4y/2yQqm3ZLzyZon KdWRj2q4dnxFZyoyDgX0UHLpM4aSsMRjn4C6vcPLcYaZ9CGB5FWPGZrq+q6vuMGK V9/fo4ZNFkNK3wo4WCSgxC1Y8XUJc3klOvPVjsmVxZaeZnkukkAFAoGBAMCkqe/S hrKITzjZuyGN90a2Nq+3xMNGuc400Qvoi27D1OcSn7SJ/K3tVWbENOH3CAlkmlZT 46IM2SRRmM0bxF3aThEwnsD5yPqgz+tcweX+gK3nXnP5JZfYF1kArXk80/eYhNE9 PwnJNXDQMoxaM0/X6BVgQyt03/Q12lH9u0j5AoGAR9U7fp6Su/uoDO/rnhs/HJHy P9u5WULSsuyKe4uBF8JTjp+cbOXeuIJ0vkCI8WPQ2iZsg37gPI5Hd9rtGDJLPATm OsOuxslowG9MY0J6K/aMb6EFfbiXHckIL3/gS02hO6SkPgSwgZY0odVaGX+VThtk q18ppDNZr/vLXL+CmZsCgYEAlJxIlG80tZxaXw5dKIN1nPL2/JUUIZz1vFShQ7Nk P4EglP+9B52lqr5mc9kwHAe1vhpobns6kvP393IlawbKrsz6ZQg/8/PkLw5XQIli YPeH1pyKsTyKtvcn9DO5BcE1zaGLB9ApULEpOcUuTwPBLvcDfjRREuUhywT44Coi w0MCgYAX5yc7/Z3R6M30rGsrgb1Y2siHYsi2LCygUj7TDGQYpaZN4afPJOT5H/Nr 7x7dgZkbOR6PQFm00VgML0XxKih59t0dcQ+2qk1LX5JDKRF/1kER3np6dpceteDu cC+MEHB/KvijnviAtBZGvD0O7oZgvbkKHESu2igXpAnfXPZFvw== -----END RSA PRIVATE KEY----- ================================================ FILE: test/fixtures/fixture_rsakey.pub ================================================ ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAnak1T7zHJ+hVRFBDQ9pf1KVzmd5gaNc7y7NPmL13aOG3sYeJevi1x1WM/R3tb8XnUnzZUX9GJN0MYovvZsw9bknG1mDP72LFbGp/gzPddGIKHBBpvceDaJ85sM/ME3XOtD7uuXQuNAuEHwEzSMMiSIEMcQS+lXIcMLr5xPLEkyNvqsO5RqSjMTLHKHgY8gLWx7oQ1avokhwuDxF7P3Pqtj+rW2Te6vR0i1H6EyFPsBkzkgNXb33cus8M1CnTmYTSgJgmHO2LLcGpjQ5sL8T/PWIWHaSqTnkrFXEMysgoteXnAYILjzyBaqq2WV4KA3TluGdAP2p8gC32QtKmIuis3Q== awl03@bounty ================================================ FILE: test/fixtures/fixture_wrong_dsakey ================================================ -----BEGIN DSA PRIVATE KEY----- MIIBuwIBAAKBgQCE1v/lL1VvjlJMyG7q0wAgl2tqVMzy5h1RVOtDS8bTlXLJg7ks T63wTmXlp2HedgKkfHCu7AKsjPyg1CTrvRBa8BFEvMoUDARonMwql34aiKVMy/t0 /ehnmCQV+ZMFpsVFnphJpZuXLTW1F3pnEbSNud5sACjbWb51uly5AUynuwIVAOhj rbNOaAtC1oYki8CVwpkQ8rHhAoGAYSepXRF3GJSjseYgJ2bCgcJS0L9agcvKAf+F dc+ZDJOchhnZC/hGHsjAfg62KowwKuOYsbcR3S4LJxiERcmRabww+kUIL1E8bLaQ RbOygNsHU8LyBdSx3WqC2WEOpVkTAjYDWTkbN+qkb53IBoI0GwFt5P9GHvQcAGkj GJQAWWYCgYAt7vxpDC5Xs6GxbaUupfIP95ZTMx2LqqFjqfT/81nypIHVyIlCnWMi a0mWGe4qXmHSyk6ZYnsk7Ll6WxdwUrFhd75qERyXlRK2x/v/Q3h9IOwChpHdSFx/ Tq1Zl9vMx3tmS1H0YF9tUdN7g8S5XTUSvYA+0Lzxs/9zOU5fa55+pAIVAKV45RLf hg2GNXvO68Q4tt3F6kSP -----END DSA PRIVATE KEY----- ================================================ FILE: test/fixtures/fixture_wrong_dsakey.pub ================================================ ssh-dss AAAAB3NzaC1kc3MAAACBAITW/+UvVW+OUkzIburTACCXa2pUzPLmHVFU60NLxtOVcsmDuSxPrfBOZeWnYd52AqR8cK7sAqyM/KDUJOu9EFrwEUS8yhQMBGiczCqXfhqIpUzL+3T96GeYJBX5kwWmxUWemEmlm5ctNbUXemcRtI253mwAKNtZvnW6XLkBTKe7AAAAFQDoY62zTmgLQtaGJIvAlcKZEPKx4QAAAIBhJ6ldEXcYlKOx5iAnZsKBwlLQv1qBy8oB/4V1z5kMk5yGGdkL+EYeyMB+DrYqjDAq45ixtxHdLgsnGIRFyZFpvDD6RQgvUTxstpBFs7KA2wdTwvIF1LHdaoLZYQ6lWRMCNgNZORs36qRvncgGgjQbAW3k/0Ye9BwAaSMYlABZZgAAAIAt7vxpDC5Xs6GxbaUupfIP95ZTMx2LqqFjqfT/81nypIHVyIlCnWMia0mWGe4qXmHSyk6ZYnsk7Ll6WxdwUrFhd75qERyXlRK2x/v/Q3h9IOwChpHdSFx/Tq1Zl9vMx3tmS1H0YF9tUdN7g8S5XTUSvYA+0Lzxs/9zOU5fa55+pA== awl03@bounty ================================================ FILE: test/fixtures/local_sandbox_fixture.cpp ================================================ // Copyright 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "local_sandbox_fixture.hpp" #include #include #include // random_generator #include // to_string using boost::filesystem::path; using boost::filesystem::ofstream; using boost::filesystem::temp_directory_path; using boost::uuids::random_generator; namespace test { namespace fixtures { local_sandbox_fixture::local_sandbox_fixture() : m_sandbox(temp_directory_path() / to_string(random_generator()())) { create_directory(m_sandbox); } local_sandbox_fixture::~local_sandbox_fixture() { try { remove_all(m_sandbox); } catch (...) { } } path local_sandbox_fixture::local_sandbox() { return m_sandbox; } path local_sandbox_fixture::new_file_in_local_sandbox(const path& name) { path p = local_sandbox() / name; ofstream file(p); return p; } path local_sandbox_fixture::new_file_in_local_sandbox() { random_generator generator; path filename = to_string(generator()); return new_file_in_local_sandbox(filename); } path local_sandbox_fixture::new_directory_in_local_sandbox() { random_generator generator; path directory_name = to_string(generator()); path directory = local_sandbox() / directory_name; create_directory(directory); return directory; } path local_sandbox_fixture::new_directory_in_local_sandbox(const path& name) { path p = local_sandbox() / name; create_directory(p); return p; } } } ================================================ FILE: test/fixtures/local_sandbox_fixture.hpp ================================================ // Copyright 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SWISH_TEST_FIXTURES_LOCAL_SANDBOX_FIXTURE_HPP #define SWISH_TEST_FIXTURES_LOCAL_SANDBOX_FIXTURE_HPP #include namespace test { namespace fixtures { /** * Fixture that creates and destroys a sandbox directory. */ class local_sandbox_fixture { public: local_sandbox_fixture(); virtual ~local_sandbox_fixture(); boost::filesystem::path local_sandbox(); boost::filesystem::path new_file_in_local_sandbox(); boost::filesystem::path new_file_in_local_sandbox(const boost::filesystem::path& name); boost::filesystem::path new_directory_in_local_sandbox(); boost::filesystem::path new_directory_in_local_sandbox(const boost::filesystem::path& name); private: boost::filesystem::path m_sandbox; }; } } #endif ================================================ FILE: test/fixtures/openssh_fixture.cpp ================================================ // Copyright 2009, 2010, 2011, 2012, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "openssh_fixture.hpp" #include "swish/connection/session_pool.hpp" #include #include #include #include #include #include #include #include #include #include // find_executable_in_path #include #include #include #include #include // this_thread #include #include #include #include #include #include using boost::assign::list_of; using boost::filesystem::path; using boost::io::quoted; using boost::lexical_cast; using boost::locale::conv::to_utf; using boost::locale::generator; using boost::locale::util::get_system_locale; using boost::optional; using boost::process::behavior::pipe; using boost::process::child; using boost::process::context; using boost::process::environment; using boost::process::find_executable_in_path; using boost::process::pistream; using boost::process::self; using boost::process::stderr_id; using boost::process::stdout_id; using std::locale; using std::map; using std::ostream_iterator; using std::ostringstream; using std::runtime_error; using std::string; using std::vector; using std::wstring; namespace { // private const string SSHD_PRIVATE_KEY_FILE = "fixture_dsakey"; const string SSHD_PUBLIC_KEY_FILE = "fixture_dsakey.pub"; const string SSHD_WRONG_PRIVATE_KEY_FILE = "fixture_wrong_dsakey"; const string SSHD_WRONG_PUBLIC_KEY_FILE = "fixture_wrong_dsakey.pub"; template string error_message_from_stderr(const string& command, const ArgSequence& arguments, child& process) { pistream command_stderr(process.get_handle(stderr_id)); ostringstream message; message << quoted(command) << " "; copy(arguments.begin(), arguments.end(), ostream_iterator(message, " ")); message << " failed: "; message << command_stderr.rdbuf() << std::flush; return message.str(); } template Out single_value_from_executable(const path& executable, const ArgSequence& arguments) { context ctx; ctx.env = self::get_environment(); ctx.streams[stdout_id] = pipe(); ctx.streams[stderr_id] = pipe(); child process = create_child(executable.string(), arguments, ctx); pistream command_stdout(process.get_handle(stdout_id)); Out out; command_stdout >> out; int status = process.wait(); // TODO: Check if process exited, with WIFEXITED - may need to upgrade // Boost.Process to the 2012 version to get mitigate.hpp, which handles this // portably. if (status == 0) { return out; } else { BOOST_THROW_EXCEPTION(runtime_error(error_message_from_stderr( executable.string(), arguments, process))); } } template Out single_value_from_command(const string& command, const ArgSequence& arguments) { path command_executable = find_executable_in_path(command); return single_value_from_executable(command_executable, arguments); } template Out single_value_from_docker_command(const ArgSequence& arguments) { return single_value_from_command("docker", arguments); } template Out single_value_from_docker_machine_command(const ArgSequence& arguments) { return single_value_from_command("docker-machine", arguments); } template void run_docker_command(const ArgSequence& arguments) { single_value_from_docker_command(arguments); } optional docker_machine_name() { const string docker_machine_name_variable = "DOCKER_MACHINE_NAME"; boost::process::environment environment = self::get_environment(); if (environment.count(docker_machine_name_variable) == 1) { return environment[docker_machine_name_variable]; } else { return optional(); } } } extern "C" { extern void ERR_free_strings(); extern void ERR_remove_state(unsigned long); extern void EVP_cleanup(); extern void CRYPTO_cleanup_all_ex_data(); extern void ENGINE_cleanup(); extern void CONF_modules_unload(int); extern void CONF_modules_free(); extern void RAND_cleanup(); } namespace { struct global_fixture { global_fixture() { // Ensure the docker image has been built vector build_command = (list_of(string("build")), "-t", "swish/openssh_server", "ssh_server"); run_docker_command(build_command); } ~global_fixture() { // We call this here as a bit of a hack to stop memory-leak // detection incorrectly detecting OpenSSL global data as a // leak swish::connection::session_pool().destroy(); ::RAND_cleanup(); ::ENGINE_cleanup(); ::CONF_modules_unload(1); ::CONF_modules_free(); ::EVP_cleanup(); ::ERR_free_strings(); ::ERR_remove_state(0); ::CRYPTO_cleanup_all_ex_data(); } }; } BOOST_GLOBAL_FIXTURE(global_fixture); namespace test { namespace fixtures { openssh_fixture::openssh_fixture() { vector docker_command = (list_of(string("run")), "--detach", "-P", "swish/openssh_server"); m_container_id = single_value_from_docker_command(docker_command); m_host = ask_docker_for_host(); m_port = ask_docker_for_port(); } openssh_fixture::~openssh_fixture() { try { stop_server(); } catch (...) { } } void openssh_fixture::stop_server() { vector stop_command = (list_of(string("stop")), m_container_id); run_docker_command(stop_command); } void openssh_fixture::restart_server() { stop_server(); vector docker_command = (list_of(string("run")), "--detach", "-p", lexical_cast(m_port) + ":22", "swish/openssh_server"); m_container_id = single_value_from_docker_command(docker_command); // We make sure we bind to the same port in the new docker container. Do we // have to do anything to ensure we get the same IP? Presumably not unless // docker-machine changed machine in the middle of restarting. assert(ask_docker_for_host() == m_host); assert(ask_docker_for_port() == m_port); } string openssh_fixture::host() const { return m_host; } wstring openssh_fixture::whost() const { generator gen; locale loc = gen(get_system_locale()); return to_utf(m_host, loc); } string openssh_fixture::ask_docker_for_host() const { optional active_docker_machine = docker_machine_name(); if (active_docker_machine) { // This can be flaky when tests run in parallel (see // https://github.com/docker/machine/issues/2612), so we retry a few // times with exponential backoff if it fails int attempt_no = 0; boost::posix_time::milliseconds wait_time(100); while (true) { ++attempt_no; try { vector machine_ip_command = (list_of(string("ip")), "default"); return single_value_from_docker_machine_command( machine_ip_command); } catch (const runtime_error&) { if (attempt_no > 5) { throw; } else { wait_time *= 2; } } boost::this_thread::sleep(wait_time); } } else { vector inspect_host_command = (list_of(string("inspect")), "--format", "{{ .NetworkSettings.IPAddress }}", m_container_id); return single_value_from_docker_command(inspect_host_command); } } string openssh_fixture::user() const { return "swish"; } wstring openssh_fixture::wuser() const { return L"swish"; } int openssh_fixture::port() const { return m_port; } string openssh_fixture::password() const { return "my test password"; } wstring openssh_fixture::wpassword() const { return L"my test password"; } int openssh_fixture::ask_docker_for_port() const { vector inspect_host_command = (list_of(string("inspect")), "--format", "{{ index (index (index .NetworkSettings.Ports \"22/tcp\") " "0) \"HostPort\" }}", m_container_id); return single_value_from_docker_command(inspect_host_command); } /** * The private half of a key-pair that is expected to authenticate successfully * with the fixture server. */ path openssh_fixture::private_key_path() const { return SSHD_PRIVATE_KEY_FILE; } /** * The public half of a key-pair that is expected to authenticate successfully * with the fixture server. */ path openssh_fixture::public_key_path() const { return SSHD_PUBLIC_KEY_FILE; } /** * The private half of a key-pair that is expected to fail to authenticate * with the fixture server. * * This must be in the same format as the successful key-pair so that the key * mismatches rather than format mismatches are the cause of authentication * failure regardless of which combination of keys is passed. */ path openssh_fixture::wrong_private_key_path() const { return SSHD_WRONG_PRIVATE_KEY_FILE; } /** * The public half of a key-pair that is expected to fail to authenticate * with the fixture server. * * This must be in the same format as the successful key-pair so that the * key mismatches rather than format mismatches are the cause of * authentication * failure regardless of which combination of keys is passed. */ path openssh_fixture::wrong_public_key_path() const { return SSHD_WRONG_PUBLIC_KEY_FILE; } } } // namespace test::fixtures ================================================ FILE: test/fixtures/openssh_fixture.hpp ================================================ // Copyright 2009, 2010, 2012, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SWISH_TEST_FIXTURES_OPENSSH_FIXTURE_HPP #define SWISH_TEST_FIXTURES_OPENSSH_FIXTURE_HPP #include // path #include namespace test { namespace fixtures { /** * Fixture that starts and stops an OpenSSH server. */ class openssh_fixture { public: openssh_fixture(); virtual ~openssh_fixture(); void stop_server(); void restart_server(); std::string host() const; std::string user() const; std::wstring whost() const; std::wstring wuser() const; int port() const; std::string password() const; std::wstring wpassword() const; boost::filesystem::path private_key_path() const; boost::filesystem::path public_key_path() const; boost::filesystem::path wrong_private_key_path() const; boost::filesystem::path wrong_public_key_path() const; private: std::string m_container_id; std::string m_host; int m_port; std::string ask_docker_for_host() const; int ask_docker_for_port() const; }; } } // namespace test::fixtures #endif ================================================ FILE: test/fixtures/provider_fixture.cpp ================================================ // Copyright 2009, 2010, 2011, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "provider_fixture.hpp" #include "test/common_boost/MockConsumer.hpp" #include "swish/connection/connection_spec.hpp" #include "swish/connection/session_manager.hpp" #include "swish/provider/Provider.hpp" // CProvider #include "swish/host_folder/host_pidl.hpp" // create_host_itemid #include "swish/shell_folder/SftpDataObject.h" // CSftpDataObject #include "swish/shell_folder/SftpDirectory.h" // CSftpDirectory #include // com_ptr #include // PIDL array wrapper #include // desktop_folder #include // numeric_cast #include #include using swish::connection::connection_spec; using swish::connection::session_manager; using swish::connection::session_reservation; using swish::host_folder::create_host_itemid; using swish::provider::CProvider; using swish::provider::sftp_provider; using comet::com_ptr; using namespace washer::shell::pidl; using washer::shell::desktop_folder; using boost::numeric_cast; using boost::shared_ptr; using std::vector; using ssh::filesystem::path; namespace comet { template <> struct comtype { static const IID& uuid() { return IID_IDataObject; } typedef ::IUnknown base; }; } namespace test { namespace fixtures { shared_ptr provider_fixture::Provider() { session_reservation ticket(session_manager().reserve_session( connection_spec(whost(), wuser(), port()), Consumer(), "Running tests")); return boost::shared_ptr(new CProvider(ticket)); } com_ptr provider_fixture::Consumer() { com_ptr consumer = new test::MockConsumer(); consumer->set_pubkey_behaviour(test::MockConsumer::CustomKeys); consumer->set_key_files(private_key_path().string(), public_key_path().string()); return consumer; } apidl_t provider_fixture::directory_pidl(const path& directory) { return real_swish_pidl() + create_host_itemid(whost(), wuser(), directory, port()); } apidl_t provider_fixture::sandbox_pidl() { return directory_pidl(sandbox()); } vector provider_fixture::pidls_in_sandbox() { CSftpDirectory dir(sandbox_pidl(), Provider()); com_ptr pidl_enum = dir.GetEnum( SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN); vector pidls; cpidl_t pidl; while (pidl_enum->Next(1, pidl.out(), NULL) == S_OK) { pidls.push_back(pidl); } return pidls; } com_ptr provider_fixture::data_object_from_sandbox() { vector pidls = pidls_in_sandbox(); pidl_array array(pidls.begin(), pidls.end()); BOOST_REQUIRE_EQUAL(array.size(), 2U); com_ptr data_object = new CSftpDataObject(numeric_cast(array.size()), array.as_array(), sandbox_pidl().get(), Provider()); BOOST_REQUIRE(data_object); return data_object; } } } // namespace test::fixture ================================================ FILE: test/fixtures/provider_fixture.hpp ================================================ // Copyright 2009, 2010, 2011, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SWISH_TEST_FIXTURES_PROVIDER_FIXTURE_HPP #define SWISH_TEST_FIXTURES_PROVIDER_FIXTURE_HPP #include "test/common_boost/MockConsumer.hpp" #include "test/common_boost/SwishPidlFixture.hpp" #include "test/fixtures/sftp_fixture.hpp" #include "swish/provider/sftp_provider.hpp" #include #include // apidl_t, cpidl_t #include // com_ptr #include // path #include #include #include #include // IDataObject namespace test { namespace fixtures { /** Fixture for tests that need a backend data provider. */ class provider_fixture : virtual public sftp_fixture, public test::SwishPidlFixture // for fake_swish_pidl { public: /** * Get an sftp_provider connected to the fixture SSH server. */ boost::shared_ptr Provider(); /** * Get a dummy consumer to use in calls to provider. */ comet::com_ptr Consumer(); /** * Return an absolute PIDL to a remote directory. * * We cheat by returning a PIDL to a HostFolder item with the * shortcut path set to the remote directory. */ washer::shell::pidl::apidl_t directory_pidl(const ssh::filesystem::path& directory); /** * Return an absolute PIDL to the sandbox on the remote end. * * This is, of course, the local sandbox but the PIDL points to * it via Swish rather than via the local filesystem. */ washer::shell::pidl::apidl_t sandbox_pidl(); /** * Return pidls for all the items in the sandbox directory. */ std::vector pidls_in_sandbox(); /** * Make a DataObject to all the items in the sandbox, via the SFTP * connection. */ comet::com_ptr data_object_from_sandbox(); }; } } // namespace test::fixture #endif ================================================ FILE: test/fixtures/session_fixture.cpp ================================================ // Copyright 2010, 2011, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "session_fixture.hpp" #include // system_error #include // BOOST_THROW_EXCEPTION #include #include // basic_ostringstream #include // logic_error #include using ssh::session; using boost::system::system_error; using std::auto_ptr; using std::locale; using std::string; using std::ostringstream; using std::logic_error; namespace test { namespace fixtures { namespace { /** * Locale-independent port number to port string conversion. */ string port_to_string(long port) { ostringstream stream; stream.imbue(locale::classic()); // force locale-independence stream << port; if (!stream) BOOST_THROW_EXCEPTION( logic_error("Unable to convert port number to string")); return stream.str(); } void open_socket_to_host(boost::asio::io_service& io, boost::asio::ip::tcp::socket& socket, const string& host_name, int port) { using boost::asio::ip::tcp; tcp::resolver resolver(io); typedef tcp::resolver::query Lookup; Lookup query(host_name, port_to_string(port)); tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); tcp::resolver::iterator end; boost::system::error_code error = boost::asio::error::host_not_found; while (error && endpoint_iterator != end) { socket.close(); socket.connect(*endpoint_iterator++, error); } if (error) BOOST_THROW_EXCEPTION(system_error(error)); } } session_fixture::session_fixture() : m_io(0), m_socket(m_io), m_session(session(open_socket(host(), port()).native())) { } session& session_fixture::test_session() { return m_session; } auto_ptr session_fixture::connect_additional_socket() { auto_ptr socket( new boost::asio::ip::tcp::socket(m_io)); open_socket_to_host(m_io, *socket, host(), port()); return socket; } boost::asio::ip::tcp::socket& session_fixture::open_socket(const string& host_name, int port) { open_socket_to_host(m_io, m_socket, host_name, port); return m_socket; } } } // namespace test::fixtures ================================================ FILE: test/fixtures/session_fixture.hpp ================================================ // Copyright 2010, 2011, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SWISH_TEST_FIXTURES_SESSION_FIXTURE_HPP #define SWISH_TEST_FIXTURES_SESSION_FIXTURE_HPP #include "openssh_fixture.hpp" #include #include // Boost sockets #include namespace test { namespace fixtures { /** * Fixture serving ssh::session objects connected to a running server. */ class session_fixture : virtual public openssh_fixture { public: session_fixture(); ssh::session& test_session(); std::auto_ptr connect_additional_socket(); private: boost::asio::ip::tcp::socket& open_socket(const std::string& host_name, int port); boost::asio::io_service m_io; ///< Boost IO system boost::asio::ip::tcp::socket m_socket; ssh::session m_session; }; } } // namespace test::fixtures #endif ================================================ FILE: test/fixtures/sftp_fixture.cpp ================================================ // Copyright 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "sftp_fixture.hpp" #include #include #include #include #include // random_generator #include // to_string #include using ssh::filesystem::directory_iterator; using ssh::filesystem::ofstream; using ssh::filesystem::path; using ssh::filesystem::sftp_file; using ssh::filesystem::sftp_filesystem; using ssh::session; using boost::bind; using boost::uuids::random_generator; using test::fixtures::session_fixture; using std::string; namespace { bool filename_matches(const string& filename, const sftp_file& remote_file) { return filename == remote_file.path().filename(); } } namespace test { namespace fixtures { sftp_fixture::sftp_fixture() : m_filesystem(authenticate_and_create_sftp()) { } sftp_filesystem& sftp_fixture::filesystem() { return m_filesystem; } path sftp_fixture::sandbox() const { return "sandbox"; } path sftp_fixture::absolute_sandbox() const { return "/home/swish/sandbox"; } sftp_file sftp_fixture::find_file_in_sandbox(const string& filename) { directory_iterator it = filesystem().directory_iterator(sandbox()); directory_iterator pos = find_if(it, filesystem().directory_iterator(), bind(filename_matches, filename, _1)); BOOST_REQUIRE(pos != filesystem().directory_iterator()); return *pos; } path sftp_fixture::new_file_in_sandbox() { random_generator generator; path filename = to_string(generator()); return new_file_in_sandbox(filename); } path sftp_fixture::new_file_in_sandbox(const path& filename) { path file = sandbox() / filename; ofstream(filesystem(), file).close(); return file; } path sftp_fixture::new_file_in_sandbox_containing_data(const string& data) { path p = new_file_in_sandbox(); ofstream s(filesystem(), p); s.write(data.data(), data.size()); return p; } path sftp_fixture::new_file_in_sandbox_containing_data(const path& name, const string& data) { path p = new_file_in_sandbox(name); ofstream s(filesystem(), p); s.write(data.data(), data.size()); return p; } path sftp_fixture::new_directory_in_sandbox() { random_generator generator; path directory_name = to_string(generator()); path directory = sandbox() / directory_name; create_directory(filesystem(), directory); return directory; } path sftp_fixture::new_directory_in_sandbox(const path& name) { path directory = sandbox() / name; create_directory(filesystem(), directory); return directory; } void sftp_fixture::create_symlink(const path& link, const path& target) { // Passing arguments in the wrong order to work around OpenSSH bug ::ssh::filesystem::create_symlink(filesystem(), target, link); } sftp_filesystem sftp_fixture::authenticate_and_create_sftp() { session& s = test_session(); s.authenticate_by_key_files(user(), public_key_path(), private_key_path(), ""); return s.connect_to_filesystem(); } } } ================================================ FILE: test/fixtures/sftp_fixture.hpp ================================================ // Copyright 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SWISH_TEST_FIXTURES_SFTP_FIXTURE_HPP #define SWISH_TEST_FIXTURES_SFTP_FIXTURE_HPP #include "session_fixture.hpp" #include #include namespace test { namespace fixtures { class sftp_fixture : virtual public session_fixture { public: sftp_fixture(); ssh::filesystem::path sandbox() const; ssh::filesystem::path absolute_sandbox() const; ssh::filesystem::sftp_filesystem& filesystem(); ssh::filesystem::sftp_file find_file_in_sandbox(const std::string& filename); ssh::filesystem::path new_file_in_sandbox(); ssh::filesystem::path new_file_in_sandbox(const ssh::filesystem::path& filename); ssh::filesystem::path new_file_in_sandbox_containing_data(const std::string& data); ssh::filesystem::path new_file_in_sandbox_containing_data(const ssh::filesystem::path& name, const std::string& data); ssh::filesystem::path new_directory_in_sandbox(); ssh::filesystem::path new_directory_in_sandbox(const ssh::filesystem::path& name); void create_symlink(const ssh::filesystem::path& link, const ssh::filesystem::path& target); private: ssh::filesystem::sftp_filesystem authenticate_and_create_sftp(); ssh::filesystem::sftp_filesystem m_filesystem; }; } } #endif ================================================ FILE: test/fixtures/ssh_server/Dockerfile ================================================ # Copyright 2016 Alexander Lamaison # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . FROM debian:jessie RUN apt-get update \ && apt-get install -y openssh-server \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* RUN mkdir /var/run/sshd # Chmodding because, when building on Windows, files are copied in with # -rwxr-xr-x permissions. # # Copying to a temp location, then moving because chmodding the copied file has # no effect (Docker AUFS-related bug maybe?) COPY ssh_host_rsa_key /tmp/etc/ssh/ssh_host_rsa_key RUN mv /tmp/etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key RUN chmod 600 /etc/ssh/ssh_host_rsa_key RUN adduser --disabled-password --gecos 'Test user for Swish integration tests' swish RUN echo 'swish:my test password' | chpasswd RUN sed -i 's/ChallengeResponseAuthentication no/ChallengeResponseAuthentication yes/' /etc/ssh/sshd_config # SSH login fix. Otherwise user is kicked off after login RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd USER swish RUN mkdir -p /home/swish/.ssh RUN mkdir -p /home/swish/sandbox COPY authorized_keys /tmp/swish/.ssh/authorized_keys RUN cp /tmp/swish/.ssh/authorized_keys /home/swish/.ssh/authorized_keys RUN chmod 600 /home/swish/.ssh/authorized_keys USER root EXPOSE 22 # # -e gives logs via 'docker logs' CMD ["/usr/sbin/sshd", "-D", "-e"] ================================================ FILE: test/fixtures/ssh_server/authorized_keys ================================================ ssh-dss AAAAB3NzaC1kc3MAAACBAK2Jh2Ck+8W1+LsFrjgOIH7XHySiONPSdG+faFTZprinh9cjyR3odzntVA7+UuFH14WnGM/ub6MbAXjrxDo1TzGILvW5x6nQ6hdLu7xFygihZ8sO1mIMOVqGdlNbTiYHl8XGjbLt1iXfW8ThM91LGGqmS+cgEiy0wWHYzsOXTDz9AAAAFQD/ebunYNTluoBrEYIoq3LMtQPbcwAAAIEAjPBzkUKcmfMAmb0eO/QAVXmX+L8NC6Vn2m4QguQ2IcJ8NH6VMnxXEBHsnemCOa9jN55G+LnX17PViuKS0O3rqQiSdA5wcHyCHKBT519/v1KQNymDwudfnFvdxUyAAG6MDSxKlpbXDCbrhFd2+ahC9a7rKalRPSXR0R2hhWRvjK0AAACAJ+CGwV/1S4j1GVwa6pSP0nj4V86GWXosTTBg7GT+rKWu8lrxIcr6FzLWgFi/gHoMrgnKWGxO1yF7vkoYM5Yfo84oBYiH+MgpiBuOrZrgzacHsA66JJbUfrESRFWZl2blIPr6Gyjj6cVGgMabK3yCiTRi0v7hwffpm0rKyKv7Goo= awl03@bounty ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAnak1T7zHJ+hVRFBDQ9pf1KVzmd5gaNc7y7NPmL13aOG3sYeJevi1x1WM/R3tb8XnUnzZUX9GJN0MYovvZsw9bknG1mDP72LFbGp/gzPddGIKHBBpvceDaJ85sM/ME3XOtD7uuXQuNAuEHwEzSMMiSIEMcQS+lXIcMLr5xPLEkyNvqsO5RqSjMTLHKHgY8gLWx7oQ1avokhwuDxF7P3Pqtj+rW2Te6vR0i1H6EyFPsBkzkgNXb33cus8M1CnTmYTSgJgmHO2LLcGpjQ5sL8T/PWIWHaSqTnkrFXEMysgoteXnAYILjzyBaqq2WV4KA3TluGdAP2p8gC32QtKmIuis3Q== awl03@bounty ================================================ FILE: test/fixtures/ssh_server/ssh_host_rsa_key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEoQIBAAKCAQEArrr/JuJmaZligyfS8vcNur+mWR2ddDQtVdhHzdKUUoR6/Om6 cvxpe61H1YZO1xCpLUBXmkki4HoNtYOpPB2W4V+8U4BDeVBD5crypEOE1+7BAm99 fnEDxYIOZq2/jTP0yQmzCpWYS3COyFmkOL7sfX1wQMeW5zQT2WKcxC6FSWbhDqrB eNEGi687hJJoJ7YXgY/IdiYW5NcOuqRSWljjGS3dAJsHHWk4nJbhjEDXbPaeduMA wQU9i6ELfP3r+q6wdu0P4jWaoo3De1aYxnToV/ldXykpipON4NPamsb6Ph2qlJQK ypq7J4iQgkIIbCU1A31+4ExvcIVoxLQw/aTSbwIBIwKCAQAd9Cu9heWrs+UAinvv Iwmq/EhnDGQijJoOt1zEMrpXSekyq7mQDgN0SZdJLPeSlSRQ5nVq5/dZroYB3A5i E7N3F7nibcJskWq5rcMyGjQHwod8wqfMiGcL6mjeZu2jLXprm0NDpJ3DyicbCA2G EhnpoHmktIBE5FsslI/nHer2o6OA/kVWSEjak+pvI1pm22T8QOBBfY0yAX7B0ebk 8o4lB4cdLf3In7Q0ahpHNOwIPdRvQ2c4Tm/DcfUBkTW2ZYGUd45cFsyHqXZscNNy GX2Wcy/FLEvQ6zBFJsNLpxCYsUyBxfSDygn9dx9RQfiWFXjdRaRPpyRAr+BTXkLU yvabAoGBANt7sxfjvu/SLkRc7TnBoJ0h/AL7Mcuu9PJmOnis4boyF9ZxqbiRiS3J yK+EKxfC0S+xf5WJ5uf7dVGnOXHXKaRl4xH90iRtryNlvtILZwHw1DTqRFxv9jtz tTRrYMEHAnMKzadgDfV/lv4iJ6nwFzK76GQ7RQNZYiGTMEh3pUNjAoGBAMvNLGpz FxhpIh+fVvRjawKgGVP87T482WOUdsF18EEPFMe6D7DO5xpLuJi+C7QkvMI8WjvD /3RGvaSh9Wt7ikLZpeogiSJy121HsEqheTR5hTx2t72ClrjZvIhLbQMRu6PqGPu/ HOC2urEGGYm7O2vnftwpuG3zIVVLM2KstPCFAoGBAM7w+VEJ7opYdMQdGi8kRvqN wbmrAxCAY0ryrCijALbexgS0T5DDu9q28Gr49W4stpquq35dc0/BNBnJje7+EVHc aGFrqOCErHHU9b66Sy23LnsIxBykFAwrRHNAq66u1mx35nk9Tv1pq58nhHun21u4 fAa7ijZblwm2qd3tJsqBAoGAEXf8ficfPJtMEVbM8GBLADmbxV7Sga1xuBQKLdbo tR6MwKmMUPvKqnuE2eRnZzZZUnoznrkHRHsXkcS9Q7ohyzc6G2Hf3mGdb8RQ8HQ9 lsiWZESwqdf+SlvOVNND27EQFV01V2gnC/JnxgfWTaJVjOf07ky4CWycdQZyHmaT Ko8CgYB58jOyXMdo2ggOCG/HX2H92KPPpFUBFCX27fCue8BZLD5quIltpXupx5oj EyltgvPcmNDgvdSadkHvP5s6nykS+n5we+d9yIIJF/BfETWsXjR3ooip+trqiirw 0aHqUDFcYn9unm2wtrMYYViiDLRijNwLZ2sG0JIU4JHyseh+NA== -----END RSA PRIVATE KEY----- ================================================ FILE: test/fixtures/test_known_hosts ================================================ host1.example.com,192.168.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZnix8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nCqtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbodUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEldwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4QFeigFf3QY7rGUgBEm/wMgxggdvLUCQ== test@swish unrecognisedkey.example.com,192.168.2.1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/qJfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1E7E= host2.example.com,10.0.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezsHRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEYho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaMVMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yDFZYbAkXz8QjhGL/qTywA7Afglyt5/w== host3.example.com,192.168.1.1 ssh-dss AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt402tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgsrs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYDUvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WGI3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAakCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/IQcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w== test@swish ================================================ FILE: test/fixtures/test_known_hosts_hashed ================================================ # There are two entries for each host, first the hashed IP address, second # the hashed host. If you regenerate this file from test_known_hosts # using ssh-keygen -H and the erasure tests start failing, ssh-keygen probably # reordered them so that the host has is first. Just swap each pair of lines # to fix this. |1|zgCaq7/YEx5K8JidOuvMUgLen5M=|2E8Pgkzd8Izo5yUCTkdUHJYxmOE= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZnix8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nCqtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbodUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEldwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4QFeigFf3QY7rGUgBEm/wMgxggdvLUCQ== |1|t3DdA7tkFZOFbi/s0eZ7J5+EFoo=|pZVcJk4rGYIZTdmM56xF75BUBIA= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZnix8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nCqtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbodUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEldwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4QFeigFf3QY7rGUgBEm/wMgxggdvLUCQ== |1|omREKWLfSo4emtLIvwB/TQrLwAk=|banp/Ra/d10UwM5WwLtzMUeKd7M= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/qJfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1E7E= |1|TdmIYPmnBFnDMkYhDtKjnoIyW8M=|tsCyyCykyxVRnt2NejdekCgEqFo= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/qJfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1E7E= |1|QoctyHBi5b2zysdvpnPn/nh7jXI=|UhxbMpvA8OkmFMJBm5ym3Enfurc= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezsHRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEYho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaMVMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yDFZYbAkXz8QjhGL/qTywA7Afglyt5/w== |1|sf6lAuYow3JBSdiZQ99BP4Qnbqc=|LeWkRRPlTMoztEws6an/K6vHwNY= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezsHRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEYho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaMVMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yDFZYbAkXz8QjhGL/qTywA7Afglyt5/w== |1|MNSfXoAF6FmLI6GUb1hOh+rzd40=|eM/07xIcp1edKt3h4kUsnzyjKjY= ssh-dss AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt402tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgsrs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYDUvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WGI3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAakCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/IQcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w== |1|oS2oUoU87z0tnrn6xoZlietQCyo=|k1m5Wo5oSXL5ddO0LgnMtXwg5FY= ssh-dss AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt402tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgsrs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYDUvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WGI3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAakCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/IQcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w== ================================================ FILE: test/fixtures/test_known_hosts_out ================================================ 192.168.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZnix8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nCqtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbodUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEldwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4QFeigFf3QY7rGUgBEm/wMgxggdvLUCQ== test@swish host1.example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZnix8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nCqtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbodUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEldwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4QFeigFf3QY7rGUgBEm/wMgxggdvLUCQ== test@swish 192.168.2.1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/qJfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1E7E= unrecognisedkey.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/qJfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1E7E= 10.0.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezsHRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEYho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaMVMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yDFZYbAkXz8QjhGL/qTywA7Afglyt5/w== host2.example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezsHRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEYho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaMVMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yDFZYbAkXz8QjhGL/qTywA7Afglyt5/w== 192.168.1.1 ssh-dss AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt402tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgsrs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYDUvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WGI3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAakCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/IQcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w== test@swish host3.example.com ssh-dss AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt402tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgsrs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYDUvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WGI3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAakCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/IQcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w== test@swish ================================================ FILE: test/forms/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES add_host_test.cpp password_test.cpp) swish_test_suite( SUBJECT forms SOURCES ${SOURCES} LIBRARIES ${Boost_LIBRARIES} LABELS gui) ================================================ FILE: test/forms/add_host_test.cpp ================================================ /** @file Exercise new host dialogue box. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #pragma comment(linker, \ "\"/manifestdependency:type='Win32' "\ "name='Microsoft.Windows.Common-Controls' "\ "version='6.0.0.0' "\ "processorArchitecture='*' "\ "publicKeyToken='6595b64144ccf1df' "\ "language='*'\"") #include "swish/forms/add_host.hpp" // test subject #include BOOST_AUTO_TEST_SUITE(add_host_tests) namespace { /** * Sends a button click to the Cancel button of the dialog programmatically. * * @todo This relies on internal knowledge that the cancel is the 17th control * and the dialog template applies an offset of 100. Not cool! */ DWORD WINAPI click_cancel_thread(LPVOID /*thread_param*/) { ::Sleep(2000); HWND hwnd = GetForegroundWindow(); // Send Cancel button click message to dialog box //WPARAM wParam = MAKEWPARAM(IDCANCEL, BN_CLICKED); //pThis->m_dlg.SendMessage(WM_COMMAND, wParam); // Alternatively post left mouse button up/down directly to Cancel button ::SendMessage( ::GetDlgItem(hwnd, 117), WM_LBUTTONDOWN, MK_LBUTTON, NULL); ::SendMessage(::GetDlgItem(hwnd, 117), WM_LBUTTONUP, NULL, NULL); return 0; } } BOOST_AUTO_TEST_CASE( show ) { DWORD thread_id; HANDLE thread = ::CreateThread( NULL, 0, click_cancel_thread, NULL, 0, &thread_id); try { swish::forms::add_host(NULL); } catch (const std::exception& e) { BOOST_REQUIRE_EQUAL(e.what(), "user cancelled form"); } ::CloseHandle(thread); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/forms/password_test.cpp ================================================ /** @file Exercise password dialogue box. @if license Copyright (C) 2010, 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/forms/password.hpp" // test subject #include #include BOOST_AUTO_TEST_SUITE(password_tests) /** * Sends a button click to the Cancel button of the dialog programmatically. * * @todo This relies on internal knowledge that the cancel is the 4th control * and the dialog template applies an offset of 100. Not cool! */ DWORD WINAPI click_cancel_thread(LPVOID /*thread_param*/) { ::Sleep(1700); HWND hwnd = GetForegroundWindow(); // Post left mouse button up/down directly to Cancel button ::SendMessage( ::GetDlgItem(hwnd, 103), WM_LBUTTONDOWN, MK_LBUTTON, NULL); ::SendMessage(::GetDlgItem(hwnd, 103), WM_LBUTTONUP, NULL, NULL); return 0; } BOOST_AUTO_TEST_CASE( show ) { DWORD thread_id; HANDLE thread = ::CreateThread( NULL, 0, click_cancel_thread, NULL, 0, &thread_id); std::wstring password; BOOST_CHECK( !swish::forms::password_prompt( NULL, L"Oi! Gimme a password", password)); BOOST_CHECK(password.empty()); ::CloseHandle(thread); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/host_folder/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES columns_test.cpp host_management_test.cpp host_pidl_test.cpp properties_test.cpp view_callback_test.cpp) swish_test_suite( SUBJECT host_folder SOURCES ${SOURCES} LIBRARIES ${Boost_LIBRARIES} LABELS unit) ================================================ FILE: test/host_folder/columns_test.cpp ================================================ /** @file Exercise host-folder columns. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // test subject, Column #include // create_host_itemid #include // wide-string output #include #include using swish::host_folder::Column; using swish::host_folder::create_host_itemid; using washer::shell::pidl::cpidl_t; using std::wstring; BOOST_AUTO_TEST_SUITE(column_tests) namespace { cpidl_t gimme_pidl() { return create_host_itemid( L"myhost", L"bobuser", L"/home/bobuser", 25, L"My Label"); } wstring header(size_t index) { Column col(index); return col.header(); } wstring detail(size_t index) { Column col(index); return col.detail(gimme_pidl()); } } BOOST_AUTO_TEST_CASE( label ) { BOOST_CHECK_EQUAL(header(0), L"Name"); BOOST_CHECK_EQUAL(detail(0), L"My Label"); } BOOST_AUTO_TEST_CASE( host ) { BOOST_CHECK_EQUAL(header(1), L"Host"); BOOST_CHECK_EQUAL(detail(1), L"myhost"); } BOOST_AUTO_TEST_CASE( user ) { BOOST_CHECK_EQUAL(header(2), L"Username"); BOOST_CHECK_EQUAL(detail(2), L"bobuser"); } BOOST_AUTO_TEST_CASE( port ) { BOOST_CHECK_EQUAL(header(3), L"Port"); BOOST_CHECK_EQUAL(detail(3), L"25"); } BOOST_AUTO_TEST_CASE( path ) { BOOST_CHECK_EQUAL(header(4), L"Remote path"); BOOST_CHECK_EQUAL(detail(4), L"/home/bobuser"); } BOOST_AUTO_TEST_CASE( type ) { BOOST_CHECK_EQUAL(header(5), L"Type"); BOOST_CHECK_EQUAL(detail(5), L"Network Drive"); } /** * Get one header too far. */ BOOST_AUTO_TEST_CASE( out_of_bounds ) { BOOST_CHECK_THROW(header(6), std::exception); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/host_folder/host_management_test.cpp ================================================ /** @file Exercise host management registry manipulation. @if license Copyright (C) 2012, 2015 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // test subject #include // host_itemid_view, // find_host_itemid #include // wide-string output #include // cpidl_t #include #include #include using swish::host_folder::host_management::AddConnectionToRegistry; using swish::host_folder::host_management::FindConnectionInRegistry; using swish::host_folder::host_management::LoadConnectionsFromRegistry; using swish::host_folder::host_management::RemoveConnectionFromRegistry; using swish::host_folder::host_management::RenameConnectionInRegistry; using swish::host_folder::host_itemid_view; using swish::host_folder::find_host_itemid; using washer::shell::pidl::cpidl_t; using comet::regkey; using std::exception; using std::wstring; namespace { const wstring TEST_CONNECTION_NAME = L"T"; // It doesn't matter what this name is, just as long as it's different const wstring OTHER_TEST_CONNECTION_NAME = L"T2"; struct cleanup_fixture { ~cleanup_fixture() { regkey connections = regkey(HKEY_CURRENT_USER).open(L"Software\\Swish\\Connections"); connections.delete_subkey_nothrow(TEST_CONNECTION_NAME); connections.delete_subkey_nothrow(OTHER_TEST_CONNECTION_NAME); } }; regkey test_connection_key() { return regkey(HKEY_CURRENT_USER).open( L"Software\\Swish\\Connections\\" + TEST_CONNECTION_NAME); } regkey other_test_connection_key() { return regkey(HKEY_CURRENT_USER).open( L"Software\\Swish\\Connections\\" + OTHER_TEST_CONNECTION_NAME); } } BOOST_FIXTURE_TEST_SUITE(host_management_tests, cleanup_fixture) BOOST_AUTO_TEST_CASE( add_minimal ) { AddConnectionToRegistry(TEST_CONNECTION_NAME, L"h", 1U, L"u", L"/"); regkey new_connection = test_connection_key(); BOOST_CHECK_EQUAL(new_connection[L"Host"].str(), L"h"); BOOST_CHECK_EQUAL(new_connection[L"User"].str(), L"u"); BOOST_CHECK_EQUAL(new_connection[L"Port"].dword(), 1U); BOOST_CHECK_EQUAL(new_connection[L"Path"].str(), L"/"); } BOOST_AUTO_TEST_CASE( add ) { wstring hostname = L"a.nice.really.beautiful.long.loooooooooooooooooooooooooooooo" L"ooooooong.host.name.example"; wstring username = L"dsflkm dfsdoifmo opim[i\"moimoimoimoim[ipom]0k3\"9k42p3m4l23 4k 23;" L"krjn1;oi[9j[c09j38j4kj2 3k4 ;2o3iun4[029j3[9mre4;cj ;l3i45r c"; wstring path = L"/krjn1;oi[9j[c09j38j4kj2 3k4 ;2o3iun4[029j3[9mre4;cj ;l3i45r c" L"dsflkm dfsdoifmo opim[i\"moimoimoimoim[ipom]0k3\"9k42p3m4l23 4k 23;"; AddConnectionToRegistry( TEST_CONNECTION_NAME, hostname, 65535U, username, path); regkey new_connection = test_connection_key(); BOOST_CHECK_EQUAL(new_connection[L"Host"].str(), hostname); BOOST_CHECK_EQUAL(new_connection[L"User"].str(), username); BOOST_CHECK_EQUAL(new_connection[L"Port"].dword(), 65535U); BOOST_CHECK_EQUAL(new_connection[L"Path"].str(), path); } BOOST_AUTO_TEST_CASE( load ) { AddConnectionToRegistry(TEST_CONNECTION_NAME, L"h", 1U, L"u", L"/"); BOOST_CHECK_GE(LoadConnectionsFromRegistry().size(), 1); } BOOST_AUTO_TEST_CASE( remove ) { AddConnectionToRegistry(TEST_CONNECTION_NAME, L"h", 1U, L"u", L"/"); RemoveConnectionFromRegistry(TEST_CONNECTION_NAME); BOOST_CHECK_THROW(test_connection_key(), exception); } BOOST_AUTO_TEST_CASE( rename ) { AddConnectionToRegistry(TEST_CONNECTION_NAME, L"h", 1U, L"u", L"/"); RenameConnectionInRegistry(TEST_CONNECTION_NAME, OTHER_TEST_CONNECTION_NAME); regkey renamed_connection = other_test_connection_key(); BOOST_CHECK_EQUAL(renamed_connection[L"Host"].str(), L"h"); BOOST_CHECK_EQUAL(renamed_connection[L"User"].str(), L"u"); BOOST_CHECK_EQUAL(renamed_connection[L"Port"].dword(), 1U); BOOST_CHECK_EQUAL(renamed_connection[L"Path"].str(), L"/"); BOOST_CHECK_THROW(test_connection_key(), exception); } BOOST_AUTO_TEST_CASE( find ) { AddConnectionToRegistry(TEST_CONNECTION_NAME, L"h", 1U, L"u", L"/"); cpidl_t connection = *FindConnectionInRegistry(TEST_CONNECTION_NAME); host_itemid_view connection_view = host_itemid_view(connection); BOOST_CHECK_EQUAL(connection_view.host(), L"h"); BOOST_CHECK_EQUAL(connection_view.user(), L"u"); BOOST_CHECK_EQUAL(connection_view.port(), 1U); BOOST_CHECK_EQUAL(connection_view.path(), L"/"); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/host_folder/host_pidl_test.cpp ================================================ /** @file Exercise host PIDL. @if license Copyright (C) 2011, 2016 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // test subject #include "test/common_boost/helpers.hpp" // wide-char comparison #include // fake_swish_pidl #include // apidl_t, cpidl_t #include // pidl_from_parsing_name #include #include #include // runtime_error #include using swish::host_folder::create_host_itemid; using swish::host_folder::host_itemid_view; using swish::host_folder::find_host_itemid; using swish::host_folder::url_from_host_itemid; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::pidl_t; using washer::shell::pidl_from_parsing_name; using comet::com_ptr; using std::exception; using std::runtime_error; BOOST_FIXTURE_TEST_SUITE(host_pidl_tests, test::SwishPidlFixture) BOOST_AUTO_TEST_CASE(create) { cpidl_t item = create_host_itemid(L"host.example.com", L"bobuser", L"/home/directory", 65535, L"My Label"); BOOST_REQUIRE(host_itemid_view(item).valid()); BOOST_CHECK_EQUAL(host_itemid_view(item).host(), L"host.example.com"); BOOST_CHECK_EQUAL(host_itemid_view(item).user(), L"bobuser"); BOOST_CHECK_EQUAL(host_itemid_view(item).path(), L"/home/directory"); BOOST_CHECK_EQUAL(host_itemid_view(item).label(), L"My Label"); BOOST_CHECK_EQUAL(host_itemid_view(item).port(), 65535); // repeat BOOST_CHECK_EQUAL(host_itemid_view(item).host(), L"host.example.com"); } BOOST_AUTO_TEST_CASE(create_from_raw) { cpidl_t managed_pidl = create_host_itemid(L"host.example.com", L"bobuser", L"/home/directory", 65535, L"My Label"); PCITEMID_CHILD item = managed_pidl.get(); BOOST_REQUIRE(host_itemid_view(item).valid()); BOOST_CHECK_EQUAL(host_itemid_view(item).host(), L"host.example.com"); BOOST_CHECK_EQUAL(host_itemid_view(item).user(), L"bobuser"); BOOST_CHECK_EQUAL(host_itemid_view(item).path(), L"/home/directory"); BOOST_CHECK_EQUAL(host_itemid_view(item).label(), L"My Label"); BOOST_CHECK_EQUAL(host_itemid_view(item).port(), 65535); // repeat BOOST_CHECK_EQUAL(host_itemid_view(item).host(), L"host.example.com"); } BOOST_AUTO_TEST_CASE(create_default_arg) { cpidl_t item = create_host_itemid(L"host.example.com", L"bobuser", L"/home/directory", 65535); BOOST_REQUIRE(host_itemid_view(item).valid()); BOOST_CHECK_EQUAL(host_itemid_view(item).host(), L"host.example.com"); BOOST_CHECK_EQUAL(host_itemid_view(item).user(), L"bobuser"); BOOST_CHECK_EQUAL(host_itemid_view(item).path(), L"/home/directory"); BOOST_CHECK_EQUAL(host_itemid_view(item).label(), L""); BOOST_CHECK_EQUAL(host_itemid_view(item).port(), 65535); // repeat BOOST_CHECK_EQUAL(host_itemid_view(item).host(), L"host.example.com"); } BOOST_AUTO_TEST_CASE(invalid_host_item) { apidl_t pidl = washer::shell::special_folder_pidl(CSIDL_DRIVES); BOOST_CHECK(!host_itemid_view(pidl).valid()); BOOST_CHECK_THROW(host_itemid_view(pidl).host(), exception); BOOST_CHECK_THROW(host_itemid_view(pidl).user(), exception); BOOST_CHECK_THROW(host_itemid_view(pidl).path(), exception); BOOST_CHECK_THROW(host_itemid_view(pidl).label(), exception); BOOST_CHECK_THROW(host_itemid_view(pidl).port(), exception); // repeat BOOST_CHECK_THROW(host_itemid_view(pidl).host(), exception); } BOOST_AUTO_TEST_CASE(find_host_item_in_pidl) { apidl_t pidl = fake_swish_pidl(); cpidl_t item = create_host_itemid(L"host.example.com", L"bobuser", L"/", 65535); pidl += item; pidl_t result = *find_host_itemid(pidl); BOOST_REQUIRE(host_itemid_view(result).valid()); BOOST_CHECK_EQUAL(host_itemid_view(result).host(), L"host.example.com"); BOOST_CHECK_EQUAL(host_itemid_view(result).user(), L"bobuser"); BOOST_CHECK_EQUAL(host_itemid_view(result).path(), L"/"); BOOST_CHECK_EQUAL(host_itemid_view(result).label(), L""); BOOST_CHECK_EQUAL(host_itemid_view(result).port(), 65535); } BOOST_AUTO_TEST_CASE(fail_to_find_host_item_in_pidl) { apidl_t pidl = fake_swish_pidl(); BOOST_CHECK_THROW(find_host_itemid(pidl), runtime_error); } BOOST_AUTO_TEST_CASE(hostitem_to_url) { cpidl_t item = create_host_itemid(L"host.example.com", L"bobuser", L"/p", 65535); BOOST_CHECK_EQUAL(url_from_host_itemid(item, false), L"sftp://bobuser@host.example.com:65535//p"); } BOOST_AUTO_TEST_CASE(hostitem_to_url_default_port) { cpidl_t item = create_host_itemid(L"host.example.com", L"bobuser", L"/p", 22); BOOST_CHECK_EQUAL(url_from_host_itemid(item, false), L"sftp://bobuser@host.example.com//p"); } BOOST_AUTO_TEST_CASE(hostitem_to_url_canonical) { cpidl_t item = create_host_itemid(L"host.example.com", L"bobuser", L"/p", 65535); BOOST_CHECK_EQUAL(url_from_host_itemid(item, true), L"sftp://bobuser@host.example.com:65535//p"); } BOOST_AUTO_TEST_CASE(hostitem_to_url_default_port_canonical) { cpidl_t item = create_host_itemid(L"host.example.com", L"bobuser", L"/p", 22); BOOST_CHECK_EQUAL(url_from_host_itemid(item, true), L"sftp://bobuser@host.example.com:22//p"); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/host_folder/properties_test.cpp ================================================ /** @file Exercise host-folder properties. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // test subject #include // create_host_itemid #include // property_key #include #include #include // PKEY_ * using swish::host_folder::compare_pidls_by_property; using swish::host_folder::create_host_itemid; using swish::host_folder::property_from_pidl; using washer::shell::pidl::cpidl_t; using washer::shell::property_key; using comet::variant_t; using std::string; BOOST_AUTO_TEST_SUITE(properties_tests) namespace { cpidl_t gimme_pidl() { return create_host_itemid( L"myhost", L"bobuser", L"/home/bobuser", 25, L"My Label"); } } BOOST_AUTO_TEST_CASE( prop_label ) { string prop = property_from_pidl(gimme_pidl(), PKEY_ItemNameDisplay); BOOST_CHECK_EQUAL(prop, "My Label"); } BOOST_AUTO_TEST_CASE( prop_host ) { string prop = property_from_pidl(gimme_pidl(), PKEY_ComputerName); BOOST_CHECK_EQUAL(prop, "myhost"); } BOOST_AUTO_TEST_CASE( prop_user ) { string prop = property_from_pidl( gimme_pidl(), swish::host_folder::PKEY_SwishHostUser); BOOST_CHECK_EQUAL(prop, "bobuser"); } BOOST_AUTO_TEST_CASE( prop_port ) { string prop = property_from_pidl( gimme_pidl(), swish::host_folder::PKEY_SwishHostPort); BOOST_CHECK_EQUAL(prop, "25"); } BOOST_AUTO_TEST_CASE( prop_path ) { string prop = property_from_pidl(gimme_pidl(), PKEY_ItemPathDisplay); BOOST_CHECK_EQUAL(prop, "/home/bobuser"); } BOOST_AUTO_TEST_CASE( prop_type ) { string prop = property_from_pidl(gimme_pidl(), PKEY_ItemType); BOOST_CHECK_EQUAL(prop, "Network Drive"); } namespace { cpidl_t comp_pidl() { return create_host_itemid( L"myhost", L"boxuser", L"/home/aobuser", 24, L"Your Label"); // == > < < > } int compare(const property_key& key) { return compare_pidls_by_property(gimme_pidl(), comp_pidl(), key); } } BOOST_AUTO_TEST_CASE( comp_label ) { int res = compare(PKEY_ItemNameDisplay); BOOST_CHECK_LT(res, 0); } BOOST_AUTO_TEST_CASE( comp_host ) { int res = compare(PKEY_ComputerName); BOOST_CHECK_EQUAL(res, 0); } BOOST_AUTO_TEST_CASE( comp_user ) { int res = compare(swish::host_folder::PKEY_SwishHostUser); BOOST_CHECK_LT(res, 0); } BOOST_AUTO_TEST_CASE( comp_port ) { int res = compare(swish::host_folder::PKEY_SwishHostPort); BOOST_CHECK_GT(res, 0); } BOOST_AUTO_TEST_CASE( comp_path ) { int res = compare(PKEY_ItemPathDisplay); BOOST_CHECK_GT(res, 0); } BOOST_AUTO_TEST_CASE( comp_type ) { int res = compare(PKEY_ItemType); BOOST_CHECK_EQUAL(res, 0); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/host_folder/view_callback_test.cpp ================================================ /** @file Tests for object that manages relationship with Explorer window. @if license Copyright (C) 2013, 2016 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . If you modify this Program, or any covered work, by linking or combining it with the OpenSSL project's OpenSSL library (or a modified version of that library), containing parts covered by the terms of the OpenSSL or SSLeay licenses, the licensors of this Program grant you additional permission to convey the resulting work. @endif */ #include // test subject #include // BOOST_CHECK_OK #include // fake_swish_pidl #include #include #include #include #include #include #include // com_ptr #include // BOOST_FOREACH #include #include using test::SwishPidlFixture; using swish::host_folder::CViewCallback; using namespace washer::gui::menu; using comet::com_ptr; using std::wstring; BOOST_FIXTURE_TEST_SUITE(view_callback_tests, SwishPidlFixture) BOOST_AUTO_TEST_CASE(basic_init) { com_ptr cb = new CViewCallback(fake_swish_pidl()); // TODO: Use real message-only window BOOST_CHECK_INTERFACE_OK(cb, cb->MessageSFVCB(SFVM_WINDOWCREATED, NULL, NULL)); } namespace { sub_menu_item_description dummy_menu(wstring title, UINT id) { menu empty_menu; sub_menu_item_description menu(string_button_description(title), empty_menu); menu.id(id); return menu; } sub_menu_item_description dummy_tools_menu() { // Purposely not called "Tools" to test that code doesn't rely on it return dummy_menu(L"Bert", FCIDM_MENU_TOOLS); } sub_menu_item_description dummy_help_menu() { // Purposely not called "Help" to test that code doesn't rely on it return dummy_menu(L"Sally", FCIDM_MENU_HELP); } sub_menu_item_description dummy_file_menu() { // Purposely not called "File" to test that code doesn't rely on it return dummy_menu(L"Bob", FCIDM_MENU_FILE); } class counting_visitor { public: typedef size_t result_type; size_t operator()(const sub_menu_item& item) { return item.menu().size(); } template size_t operator()(const ItemType&) { return 0; } }; } BOOST_AUTO_TEST_CASE(merge_menu) { com_ptr cb = new CViewCallback(fake_swish_pidl()); HMENU raw_menu = ::CreateMenu(); // Bitten here by the Most Vexing Parse menu_bar bar((menu_handle::adopt_handle(raw_menu))); bar.insert(dummy_tools_menu()); bar.insert(dummy_file_menu()); bar.insert(dummy_help_menu()); size_t size_before = bar.size(); UINT first_id_before = 42; QCMINFO q = {raw_menu, 7, first_id_before, 999, NULL}; BOOST_CHECK_INTERFACE_OK( cb, cb->MessageSFVCB(SFVM_MERGEMENU, NULL, reinterpret_cast(&q))); // Merge should not have inserted anything into the menu bar BOOST_CHECK_EQUAL(bar.size(), size_before); // But should definitely have inserted something in one of its submenus size_t count = 0; BOOST_FOREACH (const menu_bar::iterator::value_type& item, bar) { count += item.accept(counting_visitor()); } BOOST_CHECK_GT(count, 0U); // The QCMINFO should have been updated to reflect the added items BOOST_CHECK_GT(q.idCmdFirst, first_id_before); } BOOST_AUTO_TEST_CASE(merge_menu_no_tools) { com_ptr cb = new CViewCallback(fake_swish_pidl()); HMENU raw_menu = ::CreateMenu(); // Bitten here by the Most Vexing Parse menu_bar bar((menu_handle::adopt_handle(raw_menu))); bar.insert(dummy_file_menu()); bar.insert(dummy_help_menu()); QCMINFO q = {raw_menu, 7, 42, 999, NULL}; BOOST_CHECK_INTERFACE_OK( cb, cb->MessageSFVCB(SFVM_MERGEMENU, NULL, reinterpret_cast(&q))); } BOOST_AUTO_TEST_CASE(merge_menu_no_help) { com_ptr cb = new CViewCallback(fake_swish_pidl()); HMENU raw_menu = ::CreateMenu(); // Bitten here by the Most Vexing Parse menu_bar bar((menu_handle::adopt_handle(raw_menu))); bar.insert(dummy_file_menu()); bar.insert(dummy_tools_menu()); QCMINFO q = {raw_menu, 7, 42, 999, NULL}; BOOST_CHECK_INTERFACE_OK( cb, cb->MessageSFVCB(SFVM_MERGEMENU, NULL, reinterpret_cast(&q))); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/module.cpp.in ================================================ /** @file Test suite runner. @if license Copyright (C) 2013, 2014 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #define BOOST_TEST_MODULE Swish @SWISH_TEST_SUITE_SUBJECT@ tests #include ================================================ FILE: test/nse/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES default_context_menu_callback_tests.cpp explorer_command_tests.cpp) swish_test_suite( SUBJECT nse SOURCES ${SOURCES} LIBRARIES ${Boost_LIBRARIES} LABELS unit) ================================================ FILE: test/nse/default_context_menu_callback_tests.cpp ================================================ /** @file Unit tests for defcm callback implementation. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/nse/default_context_menu_callback.hpp" // test subject #include "test/common_boost/helpers.hpp" #include #include // com_error #include // com_ptr #include #include using swish::nse::default_context_menu_callback; using comet::com_error; using comet::com_ptr; using std::string; using std::vector; using std::wstring; BOOST_AUTO_TEST_SUITE(default_context_menu_callback_tests) BOOST_AUTO_TEST_CASE( create ) { default_context_menu_callback(); } BOOST_AUTO_TEST_CASE( unhandled_message ) { default_context_menu_callback callback; HRESULT hr = callback(NULL, NULL, (UINT)-1, 6, 7); BOOST_CHECK_EQUAL(hr, E_NOTIMPL); } namespace { class verb_callback : public default_context_menu_callback { virtual void verb(HWND, com_ptr, UINT, wstring& verb_out) { verb_out = L"test"; } virtual void verb(HWND, com_ptr, UINT, string& verb_out) { verb_out = "another test"; } }; } BOOST_AUTO_TEST_CASE( verbw ) { verb_callback callback; vector buffer(5, L'Z'); HRESULT hr = callback( NULL, NULL, DFM_GETVERBW, MAKELONG(6, buffer.size()), (LPARAM)(&buffer[0])); BOOST_REQUIRE_OK(hr); wchar_t* expected = L"test"; BOOST_CHECK_EQUAL_COLLECTIONS( buffer.begin(), buffer.end(), expected, expected + 5); } BOOST_AUTO_TEST_CASE( verbw_buffer_too_small ) { verb_callback callback; vector buffer(4, L'Z'); HRESULT hr = callback( NULL, NULL, DFM_GETVERBW, MAKELONG(6, buffer.size()), (LPARAM)(&buffer[0])); BOOST_CHECK(FAILED(hr)); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/nse/explorer_command_tests.cpp ================================================ /* Copyright (C) 2010, 2011, 2013, 2015 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "swish/nse/explorer_command.hpp" // test subject #include "swish/nse/Command.hpp" // Command #include "test/common_boost/helpers.hpp" #include #include // com_error #include // com_ptr #include // uuid_t #include // shared_ptr #include using swish::nse::CExplorerCommandProvider; using swish::nse::CExplorerCommand; using swish::nse::Command; using swish::nse::command_site; using comet::com_error; using comet::com_ptr; using comet::uuidof; using comet::uuid_t; using boost::shared_ptr; using std::wstring; namespace { struct TestCommand : public Command { TestCommand( const wstring& title, const uuid_t& guid, const wstring& tool_tip=wstring(), const wstring& icon_descriptor=wstring()) : Command(title, guid, tool_tip, icon_descriptor) {} BOOST_SCOPED_ENUM(state) state(com_ptr, bool) const { return state::enabled; } void operator()( com_ptr, const command_site&, com_ptr) const {} // noop }; const uuid_t DUMMY_GUID_1("002F9D5D-DB85-4224-9097-B1D06E681252"); const uuid_t DUMMY_GUID_2("3BDC0E76-2D94-43c3-AC33-ED629C24AA70"); struct DummyCommand1 : public TestCommand { DummyCommand1() : TestCommand( L"command_1", DUMMY_GUID_1, L"tool-tip-1") {} }; struct DummyCommand2 : public TestCommand { DummyCommand2() : TestCommand( L"command_2", DUMMY_GUID_2, L"tool-tip-2") {} }; CExplorerCommandProvider::ordered_commands dummy_commands() { CExplorerCommandProvider::ordered_commands commands; commands.push_back(new CExplorerCommand()); commands.push_back(new CExplorerCommand()); return commands; } } BOOST_AUTO_TEST_SUITE(explorer_command_tests) BOOST_AUTO_TEST_CASE( create_empty_provider ) { com_ptr commands = new CExplorerCommandProvider( CExplorerCommandProvider::ordered_commands()); BOOST_REQUIRE(commands); // Test GetCommands com_ptr enum_commands; BOOST_REQUIRE_OK( commands->GetCommands( NULL, uuidof(enum_commands.in()), reinterpret_cast(enum_commands.out()))); com_ptr command; BOOST_REQUIRE_EQUAL(enum_commands->Next(1, command.out(), NULL), S_FALSE); // Test GetCommand BOOST_REQUIRE_EQUAL( commands->GetCommand( GUID_NULL, uuidof(command.in()), reinterpret_cast(command.out())), E_FAIL); } BOOST_AUTO_TEST_CASE( commands ) { com_ptr commands = new CExplorerCommandProvider( dummy_commands()); BOOST_REQUIRE(commands); // Test GetCommands com_ptr enum_commands; BOOST_REQUIRE_OK( commands->GetCommands( NULL, uuidof(enum_commands.in()), reinterpret_cast(enum_commands.out()))); com_ptr command; uuid_t guid; BOOST_REQUIRE_OK(enum_commands->Next(1, command.out(), NULL)); BOOST_REQUIRE_OK(command->GetCanonicalName(guid.out())); BOOST_REQUIRE_EQUAL(guid, DUMMY_GUID_1); BOOST_REQUIRE_OK(enum_commands->Next(1, command.out(), NULL)); BOOST_REQUIRE_OK(command->GetCanonicalName(guid.out())); BOOST_REQUIRE_EQUAL(guid, DUMMY_GUID_2); BOOST_REQUIRE_EQUAL(enum_commands->Next(1, command.out(), NULL), S_FALSE); // Test GetCommand BOOST_REQUIRE_OK( commands->GetCommand( DUMMY_GUID_2, uuidof(command.in()), reinterpret_cast(command.out()))); BOOST_REQUIRE_OK(command->GetCanonicalName(guid.out())); BOOST_REQUIRE_EQUAL(guid, DUMMY_GUID_2); BOOST_REQUIRE_OK( commands->GetCommand( DUMMY_GUID_1, uuidof(command.in()), reinterpret_cast(command.out()))); BOOST_REQUIRE_OK(command->GetCanonicalName(guid.out())); BOOST_REQUIRE_EQUAL(guid, DUMMY_GUID_1); BOOST_REQUIRE_EQUAL( commands->GetCommand( GUID_NULL, uuidof(command.in()), reinterpret_cast(command.out())), E_FAIL); } namespace { const GUID TEST_GUID = { 0x1621a875, 0x1252, 0x4bde, { 0xb7, 0x69, 0x70, 0xa9, 0x5f, 0x49, 0x7c, 0x5f } }; struct HostCommand : public Command { HostCommand() : Command(L"title", TEST_GUID, L"tool-tip") {} BOOST_SCOPED_ENUM(state) state(com_ptr, bool) const { return state::enabled; } void operator()( com_ptr, const command_site&, com_ptr) const { throw com_error(E_ABORT); } }; com_ptr host_command() { com_ptr command = new CExplorerCommand(); BOOST_REQUIRE(command); return command; } } /** * GetTitle returns the string given in the constructor. */ BOOST_AUTO_TEST_CASE( title ) { com_ptr command = host_command(); wchar_t* ret_val; BOOST_REQUIRE_OK(command->GetTitle(NULL, &ret_val)); shared_ptr title(ret_val, ::CoTaskMemFree); BOOST_REQUIRE_EQUAL(title.get(), L"title"); } /** * GetIcon returns the expected empty string as it wasn't set in the * constructor. */ BOOST_AUTO_TEST_CASE( icon ) { com_ptr command = host_command(); wchar_t* ret_val; BOOST_REQUIRE_OK(command->GetIcon(NULL, &ret_val)); shared_ptr icon(ret_val, ::CoTaskMemFree); BOOST_REQUIRE_EQUAL(icon.get(), L""); } /** * GetToolTip returns the string given in the constructor. */ BOOST_AUTO_TEST_CASE( tool_tip ) { com_ptr command = host_command(); wchar_t* ret_val; BOOST_REQUIRE_OK(command->GetToolTip(NULL, &ret_val)); shared_ptr tip(ret_val, ::CoTaskMemFree); BOOST_REQUIRE_EQUAL(tip.get(), L"tool-tip"); } /** * GetCanonicalName returns the test GUID given in the constructor. */ BOOST_AUTO_TEST_CASE( guid ) { com_ptr command = host_command(); GUID guid; BOOST_REQUIRE_OK(command->GetCanonicalName(&guid)); BOOST_REQUIRE_EQUAL(uuid_t(guid), uuid_t(TEST_GUID)); } /** * GetFlags returns ECF_DEFAULT (0). */ BOOST_AUTO_TEST_CASE( flags ) { com_ptr command = host_command(); EXPCMDFLAGS flags; BOOST_REQUIRE_OK(command->GetFlags(&flags)); BOOST_REQUIRE_EQUAL(flags, 0U); } /** * GetState returns ECS_ENABLED (0). */ BOOST_AUTO_TEST_CASE( state ) { com_ptr command = host_command(); EXPCMDSTATE flags; BOOST_REQUIRE_OK(command->GetState(NULL, false, &flags)); BOOST_REQUIRE_EQUAL(flags, 0U); } /** * Invoke returns error that matches exception thrown by throwing_function * passed to constructor. */ BOOST_AUTO_TEST_CASE( invoke ) { com_ptr command = host_command(); BOOST_REQUIRE_EQUAL(command->Invoke(NULL, NULL), E_ABORT); } namespace { const GUID TEST_GUID2 = { 0xae4792b2, 0x3b35, 0x4c07, { 0x9a, 0x96, 0x2f, 0x33, 0xc5, 0x56, 0xdb, 0x4a } }; struct CommandNeedingSite : public Command { CommandNeedingSite() : Command(L"title", TEST_GUID2, L"tool-tip") {} BOOST_SCOPED_ENUM(state) state(com_ptr, bool) const { return state::enabled; } void operator()( com_ptr, const command_site&, com_ptr) const { throw com_error(E_ABORT); } void set_site(com_ptr ole_site) {} }; } /** * A CExplorerCommand must support IObjectWithSite. */ BOOST_AUTO_TEST_CASE( support_ole_site ) { com_ptr command = new CExplorerCommand(); com_ptr object_with_site = try_cast(command); BOOST_REQUIRE(object_with_site); BOOST_REQUIRE_OK(object_with_site->SetSite(NULL)); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/provider-integration/CMakeLists.txt ================================================ # Copyright 2015, 2016 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES auth_test.cpp provider_test.cpp stream_test.cpp stream_create_test.cpp stream_read_test.cpp stream_write_test.cpp) swish_test_suite( SUBJECT provider VARIANT integration SOURCES ${SOURCES} LIBRARIES ${Boost_LIBRARIES} openssh_fixture provider_fixture sftp_fixture com_stream_fixture LABELS integration) ================================================ FILE: test/provider-integration/auth_test.cpp ================================================ // Copyright 2008, 2009, 2010, 2012, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "test/fixtures/openssh_fixture.hpp" #include "test/common_boost/helpers.hpp" #include "test/common_boost/MockConsumer.hpp" #include "swish/connection/authenticated_session.hpp" #include "swish/connection/connection_spec.hpp" #include // com_error #include // com_ptr #include #include #include using test::fixtures::openssh_fixture; using test::MockConsumer; using swish::connection::authenticated_session; using swish::connection::connection_spec; using comet::com_error; using comet::com_ptr; using boost::system::system_error; using boost::test_tools::predicate_result; using std::exception; namespace { /** * Check that the given session responds sensibly to a request. */ predicate_result alive(authenticated_session& session) { try { session.get_sftp_filesystem().directory_iterator("/"); predicate_result res(true); res.message() << "Session seems to be alive"; return res; } catch (const exception& e) { predicate_result res(false); res.message() << "Session seems to be dead: " << e.what(); return res; } } bool is_e_abort(com_error e) { return e.hr() == E_ABORT; } class fixture : public openssh_fixture { public: swish::connection::connection_spec as_connection_spec() const { return swish::connection::connection_spec(whost(), wuser(), port()); } }; } BOOST_FIXTURE_TEST_SUITE(network_auth_tests, fixture) // This test needs kb-int to be disabled on the server, otherwise it will be // requested first and either succeed, which means password-auth doesn't get // tested, or fail, which aborts the whole process. /* BOOST_AUTO_TEST_CASE(SimplePasswordAuthentication) { // Choose mock behaviours to force only simple password authentication com_ptr consumer = new MockConsumer(); consumer->set_password_behaviour(MockConsumer::CustomPassword); consumer->set_keyboard_interactive_behaviour(MockConsumer::AbortResponse); consumer->set_pubkey_behaviour(MockConsumer::AbortKeys); consumer->set_password(wpassword()); // Fails if keyboard-int supported on the server as that gets preference // and replies with user-aborted authenticated_session session = as_connection_spec().create_session(consumer); BOOST_CHECK(alive(session)); } */ BOOST_AUTO_TEST_CASE(KeyboardInteractiveAuthentication) { // Choose mock behaviours to force only kbd-interactive authentication com_ptr consumer = new MockConsumer(); consumer->set_password_behaviour(MockConsumer::FailPassword); consumer->set_pubkey_behaviour(MockConsumer::AbortKeys); consumer->set_keyboard_interactive_behaviour(MockConsumer::CustomResponse); consumer->set_password(wpassword()); // This may fail if the server (which we can't control) doesn't allow // ki-auth authenticated_session session = as_connection_spec().create_session(consumer); BOOST_CHECK(alive(session)); } BOOST_AUTO_TEST_CASE(WrongPasswordOrResponse) { com_ptr consumer = new MockConsumer(); consumer->set_pubkey_behaviour(MockConsumer::AbortKeys); // We don't know which of password or kb-int (or both) is set up on the // server so we have to prime both to return the wrong password else // we may get E_ABORT for the kb-interactive response consumer->set_keyboard_interactive_behaviour(MockConsumer::WrongResponse); consumer->set_password_behaviour(MockConsumer::WrongPassword); // FIXME: Any exception will do. We don't have fine enough control over the // mock to test this properly. BOOST_CHECK_THROW(as_connection_spec().create_session(consumer), exception); } BOOST_AUTO_TEST_CASE(UserAborted) { com_ptr consumer = new MockConsumer(); consumer->set_keyboard_interactive_behaviour(MockConsumer::AbortResponse); consumer->set_password_behaviour(MockConsumer::AbortPassword); consumer->set_pubkey_behaviour(MockConsumer::AbortKeys); BOOST_CHECK_EXCEPTION(as_connection_spec().create_session(consumer), com_error, is_e_abort); } /** * Test to see that we can connect successfully after an aborted attempt. */ BOOST_AUTO_TEST_CASE(ReconnectAfterAbort) { // Choose mock behaviours to simulate a user cancelling authentication com_ptr consumer = new MockConsumer(); consumer->set_pubkey_behaviour(MockConsumer::AbortKeys); consumer->set_password_behaviour(MockConsumer::AbortPassword); consumer->set_keyboard_interactive_behaviour(MockConsumer::AbortResponse); BOOST_CHECK_EXCEPTION(as_connection_spec().create_session(consumer), com_error, is_e_abort); // Change mock behaviours so that authentication succeeds consumer->set_password_max_attempts(2); consumer->set_keyboard_interactive_max_attempts(2); consumer->set_password_behaviour(MockConsumer::CustomPassword); consumer->set_keyboard_interactive_behaviour(MockConsumer::CustomResponse); consumer->set_password(wpassword()); authenticated_session session = as_connection_spec().create_session(consumer); BOOST_CHECK(alive(session)); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/provider-integration/stream_create_test.cpp ================================================ // Copyright 2009, 2011, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "test/fixtures/com_stream_fixture.hpp" #include // com_error #include using test::fixtures::com_stream_fixture; using comet::com_error; BOOST_FIXTURE_TEST_SUITE(stream_create, com_stream_fixture) /** * Open a stream to a file that doesn't already exist. * The file should be created as only the `std::ios_base::out` flag is set. */ BOOST_AUTO_TEST_CASE(new_file) { // Delete sandbox file before creating stream remove(filesystem(), test_file()); BOOST_REQUIRE(!exists(filesystem(), test_file())); get_stream(std::ios_base::out); BOOST_REQUIRE(exists(filesystem(), test_file())); } /** * Open a stream for reading to a file that doesn't already exist. * This should fail and the file should not be created as the * `std::ios_base::out` flag isn't set which would cause the file to * be created. */ BOOST_AUTO_TEST_CASE(non_existent_file_fail) { // Delete sandbox file before creating stream remove(filesystem(), test_file()); BOOST_REQUIRE(!exists(filesystem(), test_file())); BOOST_REQUIRE_THROW(get_stream(std::ios_base::in), std::exception); BOOST_REQUIRE(!exists(filesystem(), test_file())); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/provider-integration/stream_read_test.cpp ================================================ // Copyright 2009, 2011, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "test/fixtures/com_stream_fixture.hpp" #include "test/common_boost/helpers.hpp" #include "test/common_boost/stream_utils.hpp" // verify_stream_read #include #include #include // com_ptr #include #include // numeric_cast #include #include // BOOST_THROW_EXCEPTION #include #include #include using test::fixtures::com_stream_fixture; using test::stream_utils::verify_stream_read; using comet::com_ptr; using ssh::filesystem::ofstream; using ssh::filesystem::perms; using boost::numeric_cast; using boost::shared_ptr; using std::exception; using std::string; using std::vector; namespace { // private const string TEST_DATA = "Humpty dumpty\nsat on the wall.\n\rHumpty ..."; /** * Fixture for tests that need to read data from an existing file. */ class stream_read_fixture : public com_stream_fixture { public: /** * Put test data into a file in our sandbox. */ stream_read_fixture() : com_stream_fixture() { ofstream file(filesystem(), test_file(), std::ios::binary); file << expected_data() << std::flush; } /** * Create an IStream instance open for reading on a temporary file * in our sandbox. The file contained the same data that * ExpectedData() returns. */ com_ptr get_read_stream() { return get_stream(std::ios_base::in); } /** * Return the data we expect to be able to read using the IStream. */ string expected_data() { return TEST_DATA; } }; } BOOST_FIXTURE_TEST_SUITE(stream_read, stream_read_fixture) /** * Simply get a stream. */ BOOST_AUTO_TEST_CASE(get) { com_ptr stream = get_read_stream(); BOOST_REQUIRE(stream); } /** * Get a read stream to a read-only file. * This tests that we aren't inadvertently asking for more permissions than * we need. */ BOOST_AUTO_TEST_CASE(get_readonly) { permissions(filesystem(), test_file(), perms::owner_read); com_ptr stream = get_read_stream(); BOOST_REQUIRE(stream); } /** * Try to get a stream to a non-readable file. * Test how we deal with opening failures. */ BOOST_AUTO_TEST_CASE(throws_exception_opening_unreadable_file) { permissions(filesystem(), test_file(), perms::none); BOOST_REQUIRE_THROW(get_read_stream(), exception); } /** * Read a sequence of characters. */ BOOST_AUTO_TEST_CASE(read_a_string) { com_ptr stream = get_read_stream(); string expected = expected_data(); vector buf(expected.size()); size_t bytes_read = verify_stream_read(&buf[0], numeric_cast(buf.size()), stream); BOOST_CHECK_EQUAL(bytes_read, expected.size()); // Test that the bytes we read match BOOST_REQUIRE_EQUAL_COLLECTIONS(buf.begin(), buf.end(), expected.begin(), expected.end()); } /** * Read a sequence of characters from a read-only file. */ BOOST_AUTO_TEST_CASE(read_a_string_readonly) { permissions(filesystem(), test_file(), perms::owner_read); com_ptr stream = get_read_stream(); string expected = expected_data(); vector buf(expected.size()); size_t bytes_read = verify_stream_read(&buf[0], numeric_cast(buf.size()), stream); BOOST_CHECK_EQUAL(bytes_read, expected.size()); // Test that the bytes we read match BOOST_REQUIRE_EQUAL_COLLECTIONS(buf.begin(), buf.end(), expected.begin(), expected.end()); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/provider-integration/stream_test.cpp ================================================ // Copyright 2011, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "test/common_boost/MockConsumer.hpp" // MockConsumer #include "test/common_boost/fixtures.hpp" // WinsockFixture #include "test/common_boost/helpers.hpp" #include "test/common_boost/stream_utils.hpp" // verify_stream_read #include "test/fixtures/openssh_fixture.hpp" #include "swish/connection/connection_spec.hpp" #include "swish/connection/session_manager.hpp" // session_reservation #include "swish/provider/sftp_provider.hpp" // sftp_provider, ISftpConsumer #include "swish/provider/Provider.hpp" // CProvider #include // com_ptr #include // wpath #include // numeric_cast #include #include #include // generate #include // rand #include // auto_ptr #include #include using test::MockConsumer; using test::WinsockFixture; using test::fixtures::openssh_fixture; using test::stream_utils::verify_stream_read; using swish::connection::session_manager; using swish::connection::session_reservation; using swish::connection::connection_spec; using swish::provider::sftp_provider; using swish::provider::CProvider; using comet::com_ptr; using boost::filesystem::wpath; using boost::numeric_cast; using boost::shared_ptr; using std::auto_ptr; using std::string; using std::generate; using std::rand; using std::vector; using std::wstring; namespace { class fixture : public WinsockFixture, public openssh_fixture { public: fixture() : m_provider(new CProvider(reserve_session(new MockConsumer()))) { } swish::connection::connection_spec as_connection_spec() const { return swish::connection::connection_spec(whost(), wuser(), port()); } shared_ptr provider() const { return m_provider; } com_ptr get_stream(const wstring& path, std::ios_base::openmode open_mode) { return provider()->get_file(path, open_mode); } session_reservation reserve_session(com_ptr consumer) { consumer->set_pubkey_behaviour(MockConsumer::AbortKeys); consumer->set_keyboard_interactive_behaviour( MockConsumer::CustomResponse); consumer->set_password_behaviour(MockConsumer::CustomPassword); consumer->set_password(wpassword()); return session_manager().reserve_session(as_connection_spec(), consumer, "Running tests"); } private: shared_ptr m_provider; }; } BOOST_FIXTURE_TEST_SUITE(remote_stream_tests, fixture) /** * Simply get a stream. */ BOOST_AUTO_TEST_CASE(get) { com_ptr stream = get_stream(L"/var/log/lastlog", std::ios_base::in); BOOST_REQUIRE(stream); } BOOST_AUTO_TEST_CASE(stat) { com_ptr stream = get_stream(L"/var/log/lastlog", std::ios_base::in); STATSTG stat = STATSTG(); HRESULT hr = stream->Stat(&stat, STATFLAG_DEFAULT); BOOST_REQUIRE_OK(hr); BOOST_CHECK(stat.pwcsName); BOOST_CHECK_EQUAL(L"lastlog", stat.pwcsName); BOOST_CHECK_EQUAL(STGTY_STREAM, (STGTY)stat.type); BOOST_CHECK_GT(stat.cbSize.QuadPart, 0U); FILETIME ft; BOOST_REQUIRE_OK(::CoFileTimeNow(&ft)); BOOST_CHECK_EQUAL(::CompareFileTime(&ft, &(stat.mtime)), 1); BOOST_CHECK_EQUAL(::CompareFileTime(&ft, &(stat.atime)), 1); BOOST_CHECK_EQUAL(::CompareFileTime(&ft, &(stat.ctime)), 1); BOOST_CHECK_EQUAL(stat.grfMode, 0U); BOOST_CHECK_EQUAL(stat.grfLocksSupported, 0U); BOOST_CHECK(stat.clsid == GUID_NULL); BOOST_CHECK_EQUAL(stat.grfStateBits, 0U); BOOST_CHECK_EQUAL(stat.reserved, 0U); } BOOST_AUTO_TEST_CASE(stat_exclude_name) { com_ptr stream = get_stream(L"/var/log/lastlog", std::ios_base::in); STATSTG stat = STATSTG(); HRESULT hr = stream->Stat(&stat, STATFLAG_NONAME); BOOST_REQUIRE_OK(hr); BOOST_CHECK(!stat.pwcsName); BOOST_CHECK_EQUAL(STGTY_STREAM, (STGTY)stat.type); BOOST_CHECK_GT(stat.cbSize.QuadPart, 0U); FILETIME ft; BOOST_REQUIRE_OK(::CoFileTimeNow(&ft)); BOOST_CHECK_EQUAL(::CompareFileTime(&ft, &(stat.mtime)), 1); BOOST_CHECK_EQUAL(::CompareFileTime(&ft, &(stat.atime)), 1); BOOST_CHECK_EQUAL(::CompareFileTime(&ft, &(stat.ctime)), 1); BOOST_CHECK_EQUAL(stat.grfMode, 0U); BOOST_CHECK_EQUAL(stat.grfLocksSupported, 0U); BOOST_CHECK(stat.clsid == GUID_NULL); BOOST_CHECK_EQUAL(stat.grfStateBits, 0U); BOOST_CHECK_EQUAL(stat.reserved, 0U); } BOOST_AUTO_TEST_CASE(read_file_small_buffer) { com_ptr stream = get_stream(L"/proc/cpuinfo", std::ios_base::in); string file_contents_read; ULONG bytes_read = 0; char buf[1]; HRESULT hr; do { hr = stream->Read(buf, ARRAYSIZE(buf), &bytes_read); file_contents_read.append(buf, bytes_read); } while (hr == S_OK && bytes_read == ARRAYSIZE(buf)); BOOST_CHECK_GT(file_contents_read.size(), 100U); BOOST_CHECK_EQUAL("processor", file_contents_read.substr(0, 9)); } BOOST_AUTO_TEST_CASE(read_file_medium_buffer) { com_ptr stream = get_stream(L"/proc/cpuinfo", std::ios_base::in); string file_contents_read; ULONG bytes_read = 0; char buf[4096]; HRESULT hr; do { hr = stream->Read(buf, ARRAYSIZE(buf), &bytes_read); file_contents_read.append(buf, bytes_read); } while (hr == S_OK && bytes_read == ARRAYSIZE(buf)); BOOST_CHECK_GT(file_contents_read.size(), 100U); BOOST_CHECK_EQUAL("processor", file_contents_read.substr(0, 9)); } /** * This highlights problems caused by short reads. * /dev/random produces data very slowly so the stream should block while * waiting for more data to become available. * libssh2 seems to get this wrong between 1.2.8 and 1.3.0 */ // FIXME: This probably works but, since we changed to using a buffered stream, // takes much too long to find out. The reason is that the buffered stream // tries to fill its buffer before returning the small number of characters // we requested. The buffer is much bigger than that number so the test // runs and runs /* BOOST_AUTO_TEST_CASE( read_small_buffer_from_slow_blocking_device ) { com_ptr stream = get_stream(L"/dev/random", std::ios_base::in); vector buffer(15, 'x'); size_t bytes_read = verify_stream_read(&buffer[0], buffer.size(), stream); BOOST_CHECK_EQUAL(bytes_read, buffer.size()); } */ /** * This tests a scenario that should *never* block. * /dev/zero immediately produces an endless stream of zeroes so the stream * should just keep reading until the buffer is full. If it blocks, something * has gone wrong somewhere. */ BOOST_AUTO_TEST_CASE(read_large_buffer) { com_ptr stream = get_stream(L"/dev/zero", std::ios_base::in); // using int to get legible output when collection comparison fails vector buffer(20000, 74); size_t size = buffer.size() * sizeof(buffer[0]); size_t bytes_read = verify_stream_read(&buffer[0], size, stream); BOOST_CHECK_EQUAL(bytes_read, size); vector expected(20000, 0); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), expected.begin(), expected.end()); } namespace { vector random_buffer(size_t buffer_size) { vector buffer(buffer_size); generate(buffer.begin(), buffer.end(), rand); return buffer; } } BOOST_AUTO_TEST_CASE(roundtrip) { com_ptr stream = get_stream( L"test_file", // trunc causes file creation (which in suppressed) std::ios_base::in | std::ios_base::out | std::ios_base::trunc); // using int to get legible output when collection comparison fails vector source_data = random_buffer(6543210); ULONG size_in_bytes = numeric_cast(source_data.size() * sizeof(source_data[0])); ULONG bytes_written = 0; HRESULT hr = stream->Write(&source_data[0], size_in_bytes, &bytes_written); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(bytes_written, size_in_bytes); LARGE_INTEGER dlibMove = {0}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_SET, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 0UL); vector buffer(source_data.size(), 33); size_t bytes_read = verify_stream_read(&buffer[0], size_in_bytes, stream); BOOST_CHECK_EQUAL(bytes_read, size_in_bytes); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), source_data.begin(), source_data.end()); } BOOST_AUTO_TEST_CASE(read_empty_file) { com_ptr stream = get_stream(L"/dev/null", std::ios_base::in); vector buffer(6543210, 'x'); size_t bytes_read = verify_stream_read(&buffer[0], buffer.size(), stream); BOOST_CHECK_EQUAL(bytes_read, 0U); char expected[] = {'x', 'x', 'x', 'x'}; BOOST_CHECK_EQUAL_COLLECTIONS(&buffer[0], &buffer[0] + 4, expected, expected + 4); } BOOST_AUTO_TEST_CASE(seek_noop) { com_ptr stream = get_stream(L"/var/log/lastlog", std::ios_base::in); HRESULT hr; // Move by 0 relative to current position { LARGE_INTEGER dlibMove = {0}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_CUR, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 0UL); } // Move by 0 but don't provide return slot { LARGE_INTEGER dlibMove = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_CUR, NULL); BOOST_REQUIRE_OK(hr); } } BOOST_AUTO_TEST_CASE(seek_relative) { com_ptr stream = get_stream(L"/var/log/lastlog", std::ios_base::in); HRESULT hr; // Move by 7 relative to current position: absolute pos 7 { LARGE_INTEGER dlibMove = {7}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_CUR, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 7UL); } // Move by 7 relative to current position: absolute pos 14 { LARGE_INTEGER dlibMove = {7}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_CUR, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 14UL); } // Move by -5 relative to current position: absolute pos 9 { LARGE_INTEGER dlibMove; dlibMove.QuadPart = -5; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_CUR, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 9UL); } } BOOST_AUTO_TEST_CASE(seek_relative_fail) { com_ptr stream = get_stream(L"/var/log/lastlog", std::ios_base::in); HRESULT hr; // Move by 7 relative to current position: absolute pos 7 { LARGE_INTEGER dlibMove = {7}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_CUR, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 7UL); } // Move by -9 relative to current position: absolute pos -2 { LARGE_INTEGER dlibMove; dlibMove.QuadPart = -9; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_CUR, &dlibNewPos); BOOST_CHECK_EQUAL(hr, STG_E_INVALIDFUNCTION); } } BOOST_AUTO_TEST_CASE(seek_absolute) { com_ptr stream = get_stream(L"/var/log/lastlog", std::ios_base::in); HRESULT hr; // Move to absolute position 7 { LARGE_INTEGER dlibMove = {7}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_SET, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 7UL); } // Move to absolute position 14 { LARGE_INTEGER dlibMove = {14}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_SET, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 14UL); } // Move to beginning of file: absolute position 0 { LARGE_INTEGER dlibMove = {0}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_SET, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 0UL); } } BOOST_AUTO_TEST_CASE(seek_absolute_fail) { com_ptr stream = get_stream(L"/var/log/lastlog", std::ios_base::in); HRESULT hr; // Move to absolute position -3 { LARGE_INTEGER dlibMove; dlibMove.QuadPart = -3; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_SET, &dlibNewPos); BOOST_CHECK_EQUAL(hr, STG_E_INVALIDFUNCTION); } } BOOST_AUTO_TEST_CASE(seek_get_current_position) { com_ptr stream = get_stream(L"/var/log/lastlog", std::ios_base::in); HRESULT hr; // Move to absolute position 7 { LARGE_INTEGER dlibMove = {7}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_SET, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 7UL); } // Move by 0 relative to current pos which should return current pos { LARGE_INTEGER dlibMove = {0}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_CUR, &dlibNewPos); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, 7UL); } } BOOST_AUTO_TEST_CASE(seek_relative_to_end) { com_ptr stream = get_stream(L"/var/log/lastlog", std::ios_base::in); HRESULT hr; ULONGLONG uSize; // Move to end of file: absolute position 0 from end { LARGE_INTEGER dlibMove = {0}; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_END, &dlibNewPos); BOOST_REQUIRE_OK(hr); // Should be a fairly large number BOOST_CHECK_GT(dlibNewPos.QuadPart, 100UL); uSize = dlibNewPos.QuadPart; } // Move to absolute position 7 from end of file { LARGE_INTEGER dlibMove; dlibMove.QuadPart = -7; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_END, &dlibNewPos); BOOST_REQUIRE_OK(hr); // Should be a fairly large number BOOST_CHECK_GT(dlibNewPos.QuadPart, 100UL); // Should be size of file minus 7 BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, uSize - 7); } // Move 50 past end of the file: this should still succeed { LARGE_INTEGER dlibMove; dlibMove.QuadPart = 50; ULARGE_INTEGER dlibNewPos = {0}; hr = stream->Seek(dlibMove, STREAM_SEEK_END, &dlibNewPos); BOOST_REQUIRE_OK(hr); // Should be a fairly large number BOOST_CHECK_GT(dlibNewPos.QuadPart, 100UL); // Should be size of file plus 50 BOOST_CHECK_EQUAL(dlibNewPos.QuadPart, uSize + 50); } } BOOST_AUTO_TEST_SUITE_END() /* void testStatExact() { CComPtr pStream = _CreateConnectInit("/boot/grub/default"); STATSTG stat; ::ZeroMemory(&stat, sizeof stat); HRESULT hr = stream->Stat(&stat, STATFLAG_DEFAULT); BOOST_REQUIRE_OK(hr); BOOST_CHECK(stat.pwcsName); BOOST_CHECK_EQUAL(CString(L"default"), CString(stat.pwcsName)); BOOST_CHECK_EQUAL(STGTY_STREAM, (STGTY)stat.type); BOOST_CHECK_EQUAL((ULONGLONG)197, stat.cbSize.QuadPart); BOOST_CHECK_EQUAL((DWORD)0, stat.grfMode); BOOST_CHECK_EQUAL((DWORD)0, stat.grfLocksSupported); //BOOST_CHECK_EQUAL(GUID_NULL, stat.clsid); BOOST_CHECK_EQUAL((DWORD)0, stat.grfStateBits); BOOST_CHECK_EQUAL((DWORD)0, stat.reserved); } void testReadFileExact() { CComPtr pStream = _CreateConnectInit("/boot/grub/default"); HRESULT hr; CStringA strFile; ULONG cbRead = 0; char buf[4096]; do { hr = stream->Read(buf, ARRAYSIZE(buf), &cbRead); strFile.Append(buf, cbRead); } while (hr == S_OK && cbRead == ARRAYSIZE(buf)); BOOST_CHECK_EQUAL(197, strFile.GetLength()); BOOST_CHECK_EQUAL(CStringA(szTestFile), strFile); } */ ================================================ FILE: test/provider-integration/stream_write_test.cpp ================================================ // Copyright 2009, 2011, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "test/fixtures/com_stream_fixture.hpp" #include "test/common_boost/helpers.hpp" #include "test/common_boost/stream_utils.hpp" // verify_stream_read #include #include // com_error #include // com_ptr #include // numeric_cast #include #include #include // BOOST_THROW_EXCEPTION #include // generate #include // rand #include #include using test::fixtures::com_stream_fixture; using test::stream_utils::verify_stream_read; using ssh::filesystem::perms; using comet::com_error; using comet::com_ptr; using boost::numeric_cast; using boost::shared_ptr; using std::exception; using std::generate; using std::rand; using std::string; using std::vector; BOOST_FIXTURE_TEST_SUITE(stream_write, com_stream_fixture) /** * Simply get a stream. */ BOOST_AUTO_TEST_CASE(get) { com_ptr stream = get_stream(); BOOST_REQUIRE(stream); } /** * Try to get a writable stream to a read-only file. * Test how we deal with opening failures. */ BOOST_AUTO_TEST_CASE(get_readonly) { permissions(filesystem(), test_file(), perms::owner_read); BOOST_REQUIRE_THROW(get_stream(), exception); } /** * Write one byte to stream, read it back and check that it is the same. */ BOOST_AUTO_TEST_CASE(write_one_byte) { com_ptr stream = get_stream(); // Write the character 'M' to the file char in[1] = {'M'}; ULONG cbWritten = 0; BOOST_REQUIRE_OK(stream->Write(in, sizeof(in), &cbWritten)); BOOST_REQUIRE_EQUAL(cbWritten, sizeof(in)); // Reset seek pointer to beginning and read back LARGE_INTEGER move = {0}; BOOST_REQUIRE_OK(stream->Seek(move, STREAM_SEEK_SET, NULL)); char out[1]; verify_stream_read(out, sizeof(out), stream); BOOST_REQUIRE_EQUAL('M', out[0]); } /** * Write a sequence of characters. */ BOOST_AUTO_TEST_CASE(write_a_string) { com_ptr stream = get_stream(); string in = "Lorem ipsum dolor sit amet. "; ULONG cbWritten = 0; BOOST_REQUIRE_OK( stream->Write(&in[0], numeric_cast(in.size()), &cbWritten)); BOOST_REQUIRE_EQUAL(cbWritten, in.size()); // Reset seek pointer to beginning and read back LARGE_INTEGER move = {0}; BOOST_REQUIRE_OK(stream->Seek(move, STREAM_SEEK_SET, NULL)); vector out(in.size()); verify_stream_read(&out[0], numeric_cast(out.size()), stream); BOOST_REQUIRE_EQUAL_COLLECTIONS(out.begin(), out.end(), in.begin(), in.end()); } namespace { vector random_buffer(size_t buffer_size) { vector buffer(buffer_size); generate(buffer.begin(), buffer.end(), rand); return buffer; } } /** * Write a large buffer. */ BOOST_AUTO_TEST_CASE(write_large) { com_ptr stream = get_stream(); vector in = random_buffer(1000000); ULONG cbWritten = 0; ULONG size_in_bytes = numeric_cast(in.size() * sizeof(int)); BOOST_REQUIRE_OK(stream->Write(&in[0], size_in_bytes, &cbWritten)); BOOST_REQUIRE_EQUAL(cbWritten, size_in_bytes); // Reset seek pointer to beginning and read back LARGE_INTEGER move = {0}; BOOST_REQUIRE_OK(stream->Seek(move, STREAM_SEEK_SET, NULL)); vector out(in.size()); verify_stream_read(&out[0], size_in_bytes, stream); BOOST_REQUIRE_EQUAL_COLLECTIONS(out.begin(), out.end(), in.begin(), in.end()); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/remote_folder/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(UNIT_TESTS columns_test.cpp properties_test.cpp remote_pidl_test.cpp swish_pidl_test.cpp) set(INTEGRATION_TESTS remote_commands_test.cpp) swish_test_suite( SUBJECT remote_folder VARIANT unit SOURCES ${UNIT_TESTS} LIBRARIES ${Boost_LIBRARIES} LABELS unit) swish_test_suite( SUBJECT remote_folder VARIANT integration SOURCES ${INTEGRATION_TESTS} LIBRARIES ${Boost_LIBRARIES} provider_fixture LABELS integration) ================================================ FILE: test/remote_folder/columns_test.cpp ================================================ /** @file Exercise remote-folder columns. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // test subject, Column #include // create_remote_itemid #include // wide-string output #include // format_date_time #include // datetime_t #include #include using swish::remote_folder::Column; using swish::remote_folder::create_remote_itemid; using washer::shell::format_date_time; using washer::shell::pidl::cpidl_t; using comet::datetime_t; using std::wstring; BOOST_AUTO_TEST_SUITE(column_tests) namespace { cpidl_t gimme_pidl() { return create_remote_itemid( L"some filename.txt", false, false, L"bobowner", L"mygroup", 578, 1001, 0100666, 1024, datetime_t(2010, 1, 1, 12, 30, 17, 42), datetime_t(2010, 1, 1, 0, 0, 5, 7)); } wstring header(size_t index) { Column col(index); return col.header(); } wstring detail(size_t index) { Column col(index); return col.detail(gimme_pidl()); } } BOOST_AUTO_TEST_CASE( label ) { BOOST_CHECK_EQUAL(header(0), L"Name"); BOOST_CHECK_EQUAL(detail(0), L"some filename.txt"); } BOOST_AUTO_TEST_CASE( size ) { BOOST_CHECK_EQUAL(header(1), L"Size"); BOOST_CHECK_EQUAL(detail(1), L"1 KB"); } BOOST_AUTO_TEST_CASE( type ) { BOOST_CHECK_EQUAL(header(2), L"Type"); BOOST_CHECK_EQUAL(detail(2), L"Text Document"); } BOOST_AUTO_TEST_CASE( modified ) { BOOST_CHECK_EQUAL(header(3), L"Date Modified"); BOOST_CHECK_EQUAL( detail(3), format_date_time(datetime_t(2010, 1, 1, 12, 30, 17, 42))); } BOOST_AUTO_TEST_CASE( accessed ) { BOOST_CHECK_EQUAL(header(4), L"Date Accessed"); BOOST_CHECK_EQUAL( detail(4), format_date_time(datetime_t(2010, 1, 1, 0, 0, 5, 7))); } BOOST_AUTO_TEST_CASE( perms ) { BOOST_CHECK_EQUAL(header(5), L"Permissions"); BOOST_CHECK_EQUAL(detail(5), L"-rw-rw-rw-"); } BOOST_AUTO_TEST_CASE( owner ) { BOOST_CHECK_EQUAL(header(6), L"Owner"); BOOST_CHECK_EQUAL(detail(6), L"bobowner"); } BOOST_AUTO_TEST_CASE( group ) { BOOST_CHECK_EQUAL(header(7), L"Group"); BOOST_CHECK_EQUAL(detail(7), L"mygroup"); } BOOST_AUTO_TEST_CASE( ownerid ) { BOOST_CHECK_EQUAL(header(8), L"Owner ID"); BOOST_CHECK_EQUAL(detail(8), L"578"); } BOOST_AUTO_TEST_CASE( groupid ) { BOOST_CHECK_EQUAL(header(9), L"Group ID"); BOOST_CHECK_EQUAL(detail(9), L"1001"); } /** * Get one header too far. */ BOOST_AUTO_TEST_CASE( out_of_bounds ) { BOOST_CHECK_THROW(header(10), std::exception); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/remote_folder/properties_test.cpp ================================================ /** @file Exercise remote-folder properties. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // test subject #include // create_remote_itemid #include // property_key #include // datetime_t #include #include #include // PKEY_ * using swish::remote_folder::compare_pidls_by_property; using swish::remote_folder::create_remote_itemid; using swish::remote_folder::property_from_pidl; using washer::shell::pidl::cpidl_t; using washer::shell::property_key; using comet::variant_t; using comet::datetime_t; using std::string; BOOST_AUTO_TEST_SUITE(properties_tests) namespace { cpidl_t gimme_pidl() { return create_remote_itemid( L"some filename.txt", false, false, L"bobowner", L"mygroup", 578, 1001, 0100666, 1024, datetime_t(2010, 1, 1, 12, 30, 17, 42), datetime_t(2010, 1, 1, 0, 0, 5, 7)); } } BOOST_AUTO_TEST_CASE( prop_label ) { string prop = property_from_pidl(gimme_pidl(), PKEY_ItemNameDisplay); BOOST_CHECK_EQUAL(prop, "some filename.txt"); } BOOST_AUTO_TEST_CASE( prop_owner ) { string prop = property_from_pidl(gimme_pidl(), PKEY_FileOwner); BOOST_CHECK_EQUAL(prop, "bobowner"); } BOOST_AUTO_TEST_CASE( prop_group ) { string prop = property_from_pidl( gimme_pidl(), swish::remote_folder::PKEY_Group); BOOST_CHECK_EQUAL(prop, "mygroup"); } BOOST_AUTO_TEST_CASE( prop_ownerid ) { int prop = property_from_pidl( gimme_pidl(), swish::remote_folder::PKEY_OwnerId); BOOST_CHECK_EQUAL(prop, 578); } BOOST_AUTO_TEST_CASE( prop_groupid ) { int prop = property_from_pidl( gimme_pidl(), swish::remote_folder::PKEY_GroupId); BOOST_CHECK_EQUAL(prop, 1001); } BOOST_AUTO_TEST_CASE( prop_perms ) { string prop = property_from_pidl( gimme_pidl(), swish::remote_folder::PKEY_Permissions); BOOST_CHECK_EQUAL(prop, "-rw-rw-rw-"); } BOOST_AUTO_TEST_CASE( prop_size ) { ULONGLONG prop = property_from_pidl(gimme_pidl(), PKEY_Size).as_ulonglong(); BOOST_CHECK_EQUAL(prop, 1024U); } BOOST_AUTO_TEST_CASE( prop_modified ) { datetime_t prop = property_from_pidl(gimme_pidl(), PKEY_DateModified); BOOST_CHECK_EQUAL(prop, datetime_t(2010, 1, 1, 12, 30, 17, 42)); } BOOST_AUTO_TEST_CASE( prop_accessed ) { datetime_t prop = property_from_pidl(gimme_pidl(), PKEY_DateAccessed); BOOST_CHECK_EQUAL(prop, datetime_t(2010, 1, 1, 0, 0, 5, 7)); } BOOST_AUTO_TEST_CASE( prop_type ) { string prop = property_from_pidl(gimme_pidl(), PKEY_ItemTypeText); BOOST_CHECK_EQUAL(prop, "Text Document"); } namespace { cpidl_t comp_pidl() { return create_remote_itemid( L"sane filename.txt", false, false, L"booowner", L"mygroup",0, 1001, // < > == < == 0100666, 1023, // == < datetime_t(2010, 1, 1, 12, 30, 17, 43), // > datetime_t(2010, 1, 1, 0, 0, 5, 7)); // == } int compare(const property_key& key) { return compare_pidls_by_property(gimme_pidl(), comp_pidl(), key); } } BOOST_AUTO_TEST_CASE( comp_label ) { int res = compare(PKEY_ItemNameDisplay); BOOST_CHECK_GT(res, 0); } BOOST_AUTO_TEST_CASE( comp_owner ) { int res = compare(PKEY_FileOwner); BOOST_CHECK_LT(res, 0); } BOOST_AUTO_TEST_CASE( comp_group ) { int res = compare(swish::remote_folder::PKEY_Group); BOOST_CHECK_EQUAL(res, 0); } BOOST_AUTO_TEST_CASE( comp_ownerid ) { int res = compare(swish::remote_folder::PKEY_OwnerId); BOOST_CHECK_GT(res, 0); } BOOST_AUTO_TEST_CASE( comp_groupid ) { int res = compare(swish::remote_folder::PKEY_GroupId); BOOST_CHECK_EQUAL(res, 0); } BOOST_AUTO_TEST_CASE( comp_perms ) { int res = compare(swish::remote_folder::PKEY_Permissions); BOOST_CHECK_EQUAL(res, 0); } BOOST_AUTO_TEST_CASE( comp_size ) { int res = compare(PKEY_Size); BOOST_CHECK_GT(res, 0); } BOOST_AUTO_TEST_CASE( comp_modified ) { int res = compare(PKEY_DateModified); BOOST_CHECK_LT(res, 0); } BOOST_AUTO_TEST_CASE( comp_accessed ) { int res = compare(PKEY_DateAccessed); BOOST_CHECK_EQUAL(res, 0); } BOOST_AUTO_TEST_CASE( comp_type ) { int res = compare(PKEY_ItemTypeText); BOOST_CHECK_EQUAL(res, 0); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/remote_folder/remote_commands_test.cpp ================================================ // Copyright 2011, 2012, 2013, 2015, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "swish/remote_folder/commands/commands.hpp" // test subject #include "swish/remote_folder/commands/NewFolder.hpp" // test subject #include "test/common_boost/helpers.hpp" // BOOST_REQUIRE_OK #include "test/fixtures/provider_fixture.hpp" #include #include #include // bind; #include #include using swish::nse::IEnumUICommand; using swish::nse::IUICommand; using swish::nse::command_site; using swish::remote_folder::commands::NewFolder; using swish::remote_folder::commands::remote_folder_task_pane_tasks; using test::fixtures::provider_fixture; using comet::com_ptr; using ssh::filesystem::directory_iterator; using ssh::filesystem::path; using boost::bind; using std::distance; using std::wstring; namespace { // private const wstring NEW_FOLDER = L"New folder"; class NewFolderCommandFixture : public provider_fixture { public: NewFolder new_folder_command() { return NewFolder(sandbox_pidl(), bind(&NewFolderCommandFixture::Provider, this), bind(&NewFolderCommandFixture::Consumer, this)); } }; } template <> struct comet::comtype { static const IID& uuid() throw() { return IID_IObjectWithSite; } typedef ::IUnknown base; }; #pragma region NewFolder tests BOOST_FIXTURE_TEST_SUITE(new_folder_tests, NewFolderCommandFixture) /** * Test NewFolder command has correct properties that don't involve executing * the command. */ BOOST_AUTO_TEST_CASE(non_execution_properties) { NewFolder command = new_folder_command(); BOOST_CHECK(!command.guid().is_null()); BOOST_CHECK(!command.title(NULL).empty()); BOOST_CHECK(!command.tool_tip(NULL).empty()); BOOST_CHECK(!command.icon_descriptor(NULL).empty()); BOOST_CHECK_EQUAL(command.state(NULL, true), NewFolder::state::enabled); } /** * Test in empty directory that (inevitably) has no collisions. */ BOOST_AUTO_TEST_CASE(no_collision_empty) { path expected = sandbox() / NEW_FOLDER; NewFolder command = new_folder_command(); command(NULL, command_site(), NULL); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_CHECK_EQUAL(distance(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator()), 1); } /** * Test in a directory that isn't empty but which doesn't have any collisions. */ BOOST_AUTO_TEST_CASE(no_collision) { new_file_in_sandbox(); path expected = sandbox() / NEW_FOLDER; NewFolder command = new_folder_command(); command(NULL, command_site(), NULL); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_CHECK_EQUAL(distance(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator()), 2); } /** * Test in a directory that has existing "New folder". Should create * "New folder (2)" instead. */ BOOST_AUTO_TEST_CASE(basic_collision) { path collision = sandbox() / NEW_FOLDER; path expected = sandbox() / (NEW_FOLDER + L" (2)"); BOOST_REQUIRE(create_directory(filesystem(), collision)); NewFolder command = new_folder_command(); command(NULL, command_site(), NULL); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), collision)); BOOST_CHECK_EQUAL(distance(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator()), 2); } /** * Test in a directory that has existing "New folder (2)" but not "New folder". * We want to make sure that this doesn't prevent "New folder" being created. */ BOOST_AUTO_TEST_CASE(non_interfering_collision) { path collision = sandbox() / (NEW_FOLDER + L" (2)"); path expected = sandbox() / NEW_FOLDER; BOOST_REQUIRE(create_directory(filesystem(), collision)); NewFolder command = new_folder_command(); command(NULL, command_site(), NULL); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), collision)); BOOST_CHECK_EQUAL(distance(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator()), 2); } /** * Test in a directory that has existing "New folder" and "New folder (2)". * Should create "New folder (3)" instead. */ BOOST_AUTO_TEST_CASE(multiple_collision) { path collision1 = sandbox() / NEW_FOLDER; path collision2 = sandbox() / (NEW_FOLDER + L" (2)"); path expected = sandbox() / (NEW_FOLDER + L" (3)"); BOOST_REQUIRE(create_directory(filesystem(), collision1)); BOOST_REQUIRE(create_directory(filesystem(), collision2)); NewFolder command = new_folder_command(); command(NULL, command_site(), NULL); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), collision1)); BOOST_REQUIRE(is_directory(filesystem(), collision2)); BOOST_CHECK_EQUAL(distance(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator()), 3); } /** * Test in a directory that has existing "New folder" and "New folder (3)" * but not "New folder (2). Should create "New folder (2)" in the gap. */ BOOST_AUTO_TEST_CASE(non_contiguous_collision1) { path collision1 = sandbox() / NEW_FOLDER; path collision2 = sandbox() / (NEW_FOLDER + L" (3)"); path expected = sandbox() / (NEW_FOLDER + L" (2)"); BOOST_REQUIRE(create_directory(filesystem(), collision1)); BOOST_REQUIRE(create_directory(filesystem(), collision2)); NewFolder command = new_folder_command(); command(NULL, command_site(), NULL); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), collision1)); BOOST_REQUIRE(is_directory(filesystem(), collision2)); BOOST_CHECK_EQUAL(distance(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator()), 3); } /** * Test in a directory that has existing "New folder", "New folder (2)" and * "New Folder (4)" but not "New folder (3)". Should create "New folder (3)" in * the gap. */ BOOST_AUTO_TEST_CASE(non_contiguous_collision2) { path collision1 = sandbox() / NEW_FOLDER; path collision2 = sandbox() / (NEW_FOLDER + L" (2)"); path collision3 = sandbox() / (NEW_FOLDER + L" (4)"); path expected = sandbox() / (NEW_FOLDER + L" (3)"); BOOST_REQUIRE(create_directory(filesystem(), collision1)); BOOST_REQUIRE(create_directory(filesystem(), collision2)); BOOST_REQUIRE(create_directory(filesystem(), collision3)); NewFolder command = new_folder_command(); command(NULL, command_site(), NULL); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), collision1)); BOOST_REQUIRE(is_directory(filesystem(), collision2)); BOOST_REQUIRE(is_directory(filesystem(), collision3)); BOOST_CHECK_EQUAL(distance(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator()), 4); } /** * Test in a directory that has existing "New folder", "New folder (2)" and * "New folder (3) " (note the trailing space). Should create "New folder (3)" * as it doesn't collide. */ BOOST_AUTO_TEST_CASE(collision_suffix_mismatch) { path collision1 = sandbox() / NEW_FOLDER; path collision2 = sandbox() / (NEW_FOLDER + L" (2)"); path collision3 = sandbox() / (NEW_FOLDER + L" (3) "); path expected = sandbox() / (NEW_FOLDER + L" (3)"); BOOST_REQUIRE(create_directory(filesystem(), collision1)); BOOST_REQUIRE(create_directory(filesystem(), collision2)); BOOST_REQUIRE(create_directory(filesystem(), collision3)); NewFolder command = new_folder_command(); command(NULL, command_site(), NULL); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), collision1)); BOOST_REQUIRE(is_directory(filesystem(), collision2)); BOOST_REQUIRE(is_directory(filesystem(), collision3)); BOOST_CHECK_EQUAL(distance(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator()), 4); } /** * Test in a directory that has existing "New folder", "New folder (2)" and * " New folder (3)" (note the leading space). Should create "New folder (3)" * as it doesn't collide. */ BOOST_AUTO_TEST_CASE(collision_prefix_mismatch) { path collision1 = sandbox() / NEW_FOLDER; path collision2 = sandbox() / (NEW_FOLDER + L" (2)"); path collision3 = sandbox() / (L" " + NEW_FOLDER + L" (3) "); path expected = sandbox() / (NEW_FOLDER + L" (3)"); BOOST_REQUIRE(create_directory(filesystem(), collision1)); BOOST_REQUIRE(create_directory(filesystem(), collision2)); BOOST_REQUIRE(create_directory(filesystem(), collision3)); NewFolder command = new_folder_command(); command(NULL, command_site(), NULL); BOOST_REQUIRE(is_directory(filesystem(), expected)); BOOST_REQUIRE(is_directory(filesystem(), collision1)); BOOST_REQUIRE(is_directory(filesystem(), collision2)); BOOST_REQUIRE(is_directory(filesystem(), collision3)); BOOST_CHECK_EQUAL(distance(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator()), 4); } BOOST_AUTO_TEST_SUITE_END() #pragma endregion #pragma region Task pane tests BOOST_FIXTURE_TEST_SUITE(new_folder_task_pane_tests, provider_fixture) /** * Test that task pane items can have their OLE site set. */ BOOST_AUTO_TEST_CASE(task_pane_old_site) { std::pair, com_ptr> panes = remote_folder_task_pane_tasks( sandbox_pidl(), NULL, bind(&NewFolderCommandFixture::Provider, this), bind(&NewFolderCommandFixture::Consumer, this)); BOOST_REQUIRE(panes.first); com_ptr new_folder; ULONG fetched = 0; HRESULT hr = panes.first->Next(1, new_folder.out(), &fetched); BOOST_REQUIRE_OK(hr); com_ptr object = try_cast(new_folder); hr = object->SetSite(NULL); BOOST_REQUIRE_OK(hr); } BOOST_AUTO_TEST_SUITE_END() #pragma endregion ================================================ FILE: test/remote_folder/remote_pidl_test.cpp ================================================ /** @file Exercise remote PIDL. @if license Copyright (C) 2011 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // test subject #include "test/common_boost/helpers.hpp" // wide-char comparison #include // datetime_t #include // apidl_t, cpidl_t #include // pidl_from_parsing_name #include #include #include // runtime_error #include using swish::remote_folder::create_remote_itemid; using swish::remote_folder::path_from_remote_pidl; using swish::remote_folder::remote_itemid_view; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::pidl::pidl_t; using washer::shell::pidl_from_parsing_name; using comet::com_ptr; using comet::datetime_t; using std::exception; using std::runtime_error; using std::wstring; namespace { /** * Return the PIDL to the Swish HostFolder in Explorer. */ apidl_t swish_pidl() { return pidl_from_parsing_name( L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\" L"::{B816A83A-5022-11DC-9153-0090F5284F85}"); } cpidl_t test_remote_itemid(const wstring& filename, bool is_folder) { return create_remote_itemid( filename, is_folder, false, L"bobuser", L"bob's group", 1001, 65535, 040666, 18446744073709551615, datetime_t(1970, 11, 1, 9, 15, 42, 6), datetime_t((DATE)0)); } } BOOST_AUTO_TEST_SUITE( remote_pidl_tests ) BOOST_AUTO_TEST_CASE( create_for_file ) { cpidl_t item = test_remote_itemid(L"testfile.txt", false); BOOST_REQUIRE(remote_itemid_view(item).valid()); BOOST_CHECK_EQUAL(remote_itemid_view(item).filename(), L"testfile.txt"); BOOST_CHECK(!remote_itemid_view(item).is_folder()); BOOST_CHECK(!remote_itemid_view(item).is_link()); BOOST_CHECK_EQUAL(remote_itemid_view(item).owner(), L"bobuser"); BOOST_CHECK_EQUAL(remote_itemid_view(item).group(), L"bob's group"); BOOST_CHECK_EQUAL(remote_itemid_view(item).owner_id(), 1001U); BOOST_CHECK_EQUAL(remote_itemid_view(item).group_id(), 65535U); BOOST_CHECK_EQUAL(remote_itemid_view(item).permissions(), 040666U); BOOST_CHECK_EQUAL(remote_itemid_view(item).size(), 18446744073709551615U); BOOST_CHECK_EQUAL( remote_itemid_view(item).date_modified(), datetime_t(1970, 11, 1, 9, 15, 42, 6)); BOOST_CHECK_EQUAL( remote_itemid_view(item).date_accessed(), datetime_t()); // repeat BOOST_CHECK_EQUAL(remote_itemid_view(item).filename(), L"testfile.txt"); } BOOST_AUTO_TEST_CASE( create_for_file_from_raw ) { cpidl_t managed_pidl = test_remote_itemid(L"testfile.txt", false); PCITEMID_CHILD item = managed_pidl.get(); BOOST_REQUIRE(remote_itemid_view(item).valid()); BOOST_CHECK_EQUAL(remote_itemid_view(item).filename(), L"testfile.txt"); BOOST_CHECK(!remote_itemid_view(item).is_folder()); BOOST_CHECK(!remote_itemid_view(item).is_link()); BOOST_CHECK_EQUAL(remote_itemid_view(item).owner(), L"bobuser"); BOOST_CHECK_EQUAL(remote_itemid_view(item).group(), L"bob's group"); BOOST_CHECK_EQUAL(remote_itemid_view(item).owner_id(), 1001U); BOOST_CHECK_EQUAL(remote_itemid_view(item).group_id(), 65535U); BOOST_CHECK_EQUAL(remote_itemid_view(item).permissions(), 040666U); BOOST_CHECK_EQUAL(remote_itemid_view(item).size(), 18446744073709551615U); BOOST_CHECK_EQUAL( remote_itemid_view(item).date_modified(), datetime_t(1970, 11, 1, 9, 15, 42, 6)); BOOST_CHECK_EQUAL( remote_itemid_view(item).date_accessed(), datetime_t()); // repeat BOOST_CHECK_EQUAL(remote_itemid_view(item).filename(), L"testfile.txt"); } BOOST_AUTO_TEST_CASE( create_for_folder ) { cpidl_t item = test_remote_itemid(L"testfolder.txt", true); BOOST_REQUIRE(remote_itemid_view(item).valid()); BOOST_CHECK_EQUAL(remote_itemid_view(item).filename(), L"testfolder.txt"); BOOST_CHECK(remote_itemid_view(item).is_folder()); BOOST_CHECK(!remote_itemid_view(item).is_link()); BOOST_CHECK_EQUAL(remote_itemid_view(item).owner(), L"bobuser"); BOOST_CHECK_EQUAL(remote_itemid_view(item).group(), L"bob's group"); BOOST_CHECK_EQUAL(remote_itemid_view(item).owner_id(), 1001U); BOOST_CHECK_EQUAL(remote_itemid_view(item).group_id(), 65535U); BOOST_CHECK_EQUAL(remote_itemid_view(item).permissions(), 040666U); BOOST_CHECK_EQUAL(remote_itemid_view(item).size(), 18446744073709551615U); BOOST_CHECK_EQUAL( remote_itemid_view(item).date_modified(), datetime_t(1970, 11, 1, 9, 15, 42, 6)); BOOST_CHECK_EQUAL( remote_itemid_view(item).date_accessed(), datetime_t()); // repeat BOOST_CHECK_EQUAL(remote_itemid_view(item).filename(), L"testfolder.txt"); } BOOST_AUTO_TEST_CASE( invalid_remote_item ) { apidl_t pidl = washer::shell::special_folder_pidl(CSIDL_DRIVES); BOOST_CHECK(!remote_itemid_view(pidl).valid()); BOOST_CHECK_THROW(remote_itemid_view(pidl).filename(), exception); BOOST_CHECK_THROW(remote_itemid_view(pidl).is_folder(), exception); BOOST_CHECK_THROW(remote_itemid_view(pidl).is_link(), exception); BOOST_CHECK_THROW(remote_itemid_view(pidl).owner(), exception); BOOST_CHECK_THROW(remote_itemid_view(pidl).group(), exception); BOOST_CHECK_THROW(remote_itemid_view(pidl).owner_id(), exception); BOOST_CHECK_THROW(remote_itemid_view(pidl).group_id(), exception); BOOST_CHECK_THROW(remote_itemid_view(pidl).permissions(), exception); BOOST_CHECK_THROW(remote_itemid_view(pidl).size(), exception); BOOST_CHECK_THROW(remote_itemid_view(pidl).date_modified(), exception); BOOST_CHECK_THROW(remote_itemid_view(pidl).date_accessed(), exception); // repeat BOOST_CHECK_THROW(remote_itemid_view(pidl).filename(), exception); } BOOST_AUTO_TEST_CASE( build_path_from_remote_pidl ) { pidl_t pidl = test_remote_itemid(L"foo", true) + test_remote_itemid(L"bar", false) + test_remote_itemid(L"biscuit.txt", false); BOOST_CHECK_EQUAL( path_from_remote_pidl(pidl), "foo/bar/biscuit.txt"); } BOOST_AUTO_TEST_CASE( build_path_from_remote_pidl_renders_expected_string ) { // The path may compare equal to the expected string without rendering // itself as the same string. For example, the slashes might be backslashes // instead of forward slashes. This causes problems down the line. pidl_t pidl = test_remote_itemid(L"foo", true) + test_remote_itemid(L"bar", false) + test_remote_itemid(L"biscuit.txt", false); BOOST_CHECK_EQUAL( path_from_remote_pidl(pidl).string(), "foo/bar/biscuit.txt"); } BOOST_AUTO_TEST_CASE( build_path_from_remote_pidl_single ) { cpidl_t pidl = test_remote_itemid(L"foo", true); BOOST_CHECK_EQUAL(path_from_remote_pidl(pidl), L"foo"); } BOOST_AUTO_TEST_CASE( build_path_from_remote_pidl_root ) { cpidl_t pidl = test_remote_itemid(L"/", true); BOOST_CHECK_EQUAL(path_from_remote_pidl(pidl), L"/"); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/remote_folder/swish_pidl_test.cpp ================================================ /** @file Exercise code that operates over complete Swish PIDLs. @if license Copyright (C) 2011, 2016 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include // test subject #include // create_host_itemid #include "test/common_boost/helpers.hpp" // wide-char comparison #include "test/common_boost/SwishPidlFixture.hpp" #include // apidl_t #include using swish::host_folder::create_host_itemid; using swish::remote_folder::absolute_path_from_swish_pidl; using washer::shell::pidl::apidl_t; BOOST_FIXTURE_TEST_SUITE(swish_pidl_tests, test::SwishPidlFixture) /** * Test that a Swish PIDL ending in just a host itemid results in the * correct path. * * @TODO: test with remote itemids as well. */ BOOST_AUTO_TEST_CASE(pidl_to_absolute_path_host_item_only) { apidl_t pidl = fake_swish_pidl() + create_host_itemid(L"host.example.com", L"bobuser", L"/p/q", 22); BOOST_CHECK_EQUAL(absolute_path_from_swish_pidl(pidl), L"/p/q"); } /** * Test path extraction for a Swish PIDL containing a remote item id. */ BOOST_AUTO_TEST_CASE(pidl_to_absolute_path) { apidl_t pidl = fake_swish_pidl() + create_host_itemid(L"host.example.com", L"bobuser", L"/p/q", 22); pidl += create_dummy_remote_itemid(L"foo", false); BOOST_CHECK_EQUAL(absolute_path_from_swish_pidl(pidl), L"/p/q/foo"); } /** * Test path extraction for a Swish PIDL containing a remote item id. */ BOOST_AUTO_TEST_CASE(pidl_to_absolute_path_renders_expected_string) { // The path may compare equal to the expected string without rendering // itself as the same string. For example, the slashes might be backslashes // instead of forward slashes. This causes problems down the line. apidl_t pidl = fake_swish_pidl() + create_host_itemid(L"host.example.com", L"bobuser", L"/p/q", 22); pidl += create_dummy_remote_itemid(L"foo", false); BOOST_CHECK_EQUAL(absolute_path_from_swish_pidl(pidl).string(), "/p/q/foo"); } /** * Test path extraction for a Swish PIDL containing two remote item ids. */ BOOST_AUTO_TEST_CASE(pidl_to_absolute_path_multiple_remote_items) { apidl_t pidl = fake_swish_pidl() + create_host_itemid(L"host.example.com", L"bobuser", L"/p/q", 22); pidl += create_dummy_remote_itemid(L"foo", true); pidl += create_dummy_remote_itemid(L".bob", false); BOOST_CHECK_EQUAL(absolute_path_from_swish_pidl(pidl), L"/p/q/foo/.bob"); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/shell/CMakeLists.txt ================================================ # Copyright 2015, 2016 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES shell_test.cpp) swish_test_suite( SUBJECT shell SOURCES ${SOURCES} LIBRARIES ${Boost_LIBRARIES} local_sandbox_fixture LABELS unit) swish_copy_fixture_files(test-shell) ================================================ FILE: test/shell/shell_test.cpp ================================================ /** @file Unit tests for the shell utility functions. @if license Copyright (C) 2009, 2011, 2012, 2015 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/shell/shell.hpp" // Test subject // use PidlFormat to inspect DataObjects produces by the Windows Shell #include "swish/shell_folder/data_object/ShellDataObject.hpp" #include "test/common_boost/fixtures.hpp" #include "test/common_boost/helpers.hpp" #include "test/fixtures/local_sandbox_fixture.hpp" #include // apidl_t #include #include #include #include // BOOST_THROW_EXCEPTION #include #include #include // transform using swish::shell::ui_object_of_item; using swish::shell::ui_object_of_items; using swish::shell::path_from_pidl; using swish::shell::pidl_from_path; using swish::shell::data_object_for_files; using swish::shell::data_object_for_file; using swish::shell::data_object_for_directory; using swish::shell_folder::data_object::PidlFormat; using washer::shell::pidl::apidl_t; using test::ComFixture; using test::fixtures::local_sandbox_fixture; using boost::filesystem::path; using boost::test_tools::predicate_result; using boost::shared_ptr; using std::vector; using std::wstring; namespace { // private /** * Check that a PIDL and a filesystem path refer to the same item. */ predicate_result pidl_path_equivalence(apidl_t pidl, path path) { vector name(MAX_PATH); ::SHGetPathFromIDListW(pidl.get(), &name[0]); if (!equivalent(path, &name[0])) { predicate_result res(false); res.message() << "Different items [" << wstring(&name[0]) << " != " << path.string() << "]"; return res; } return true; } class ShellFunctionFixture : public ComFixture, public local_sandbox_fixture { public: }; } // // There are three types of shell function being tested here: those // that require real filesystem (non-virtual paths), those to do with // DataObjects specifically and those that are generic with respect to // both the above (they work on generic objects and take PIDLs instead // of paths). // Perhaps these three types should be separated out. // #pragma region Shell utility function tests BOOST_FIXTURE_TEST_SUITE(shell_utility_tests, ShellFunctionFixture) /** * Convert a PIDL to a path. The path should match the source from which the * PIDL was created. * * Tests path_from_pidl(). */ BOOST_AUTO_TEST_CASE(convert_pidl_to_path) { path source = new_file_in_local_sandbox(); shared_ptr pidl( ::ILCreateFromPathW(source.wstring().c_str()), ::ILFree); path path_from_conversion = path_from_pidl(pidl.get()); BOOST_REQUIRE(equivalent(path_from_conversion, source)); } /** * Make a PIDL from a path. We should be able to convert the PIDL back to * a path that refers to the same item as the original path. * * Tests pidl_from_path(). */ BOOST_AUTO_TEST_CASE(convert_path_to_pidl) { path source = new_file_in_local_sandbox(); shared_ptr pidl = pidl_from_path(source); vector buffer(MAX_PATH); BOOST_REQUIRE(::SHGetPathFromIDListW(pidl.get(), &buffer[0])); BOOST_REQUIRE(equivalent(wstring(&buffer[0]), source)); } /** * Ask the shell for a DataObject 'on' a given file. This means that the * shell should create a DataObject holding a PIDL list format * (CFSTR_SHELLIDLIST) with two items in it: * - an absolute PIDL to the given file's parent folder * - the file's single-item (child) PIDL relative to the parent folder * * Tests data_object_for_file(). */ BOOST_AUTO_TEST_CASE(single_item_dataobject) { path source = new_file_in_local_sandbox(); PidlFormat format(data_object_for_file(source)); BOOST_REQUIRE_EQUAL(format.pidl_count(), 1U); BOOST_REQUIRE( pidl_path_equivalence(format.parent_folder(), local_sandbox())); BOOST_REQUIRE(pidl_path_equivalence(format.file(0), source)); } /** * Ask the shell for a DataObject 'on' two items in the same folder. * This means that the shell should create a DataObject holding a PIDL list * format (CFSTR_SHELLIDLIST) with three items in it: * - an absolute PIDL to the given files' parent folder * - the first file's single-item (child) PIDL relative to the parent folder * - the second file's single-item (child) PIDL relative to the parent folder * * Tests data_object_for_files(). */ BOOST_AUTO_TEST_CASE(multi_item_dataobject) { vector sources; sources.push_back(new_file_in_local_sandbox()); sources.push_back(new_file_in_local_sandbox()); PidlFormat format(data_object_for_files(sources.begin(), sources.end())); BOOST_REQUIRE_EQUAL(format.pidl_count(), 2U); BOOST_REQUIRE( pidl_path_equivalence(format.parent_folder(), local_sandbox())); BOOST_REQUIRE(pidl_path_equivalence(format.file(0), sources[0])); BOOST_REQUIRE(pidl_path_equivalence(format.file(1), sources[1])); } /** * Ask for an associated object of a given file. In this case we ask for a * DataObject because then we can subject it to the same tests as the * data_object_for_file test above. * * Tests ui_object_of_item(). */ BOOST_AUTO_TEST_CASE(single_item_ui_object) { path source = new_file_in_local_sandbox(); PidlFormat format( ui_object_of_item(pidl_from_path(source).get())); BOOST_REQUIRE_EQUAL(format.pidl_count(), 1U); BOOST_REQUIRE( pidl_path_equivalence(format.parent_folder(), local_sandbox())); BOOST_REQUIRE(pidl_path_equivalence(format.file(0), source)); } /** * Ask for an associated object of two files in the same folder. In this case * we ask for a DataObject because then we can subject it to the same tests * as the data_object_for_files test above. * * Tests ui_object_of_items(). */ BOOST_AUTO_TEST_CASE(multi_item_ui_object) { vector sources; sources.push_back(new_file_in_local_sandbox()); sources.push_back(new_file_in_local_sandbox()); vector> pidls; transform(sources.begin(), sources.end(), back_inserter(pidls), pidl_from_path); PidlFormat format( ui_object_of_items(pidls.begin(), pidls.end())); BOOST_REQUIRE_EQUAL(format.pidl_count(), 2U); BOOST_REQUIRE( pidl_path_equivalence(format.parent_folder(), local_sandbox())); BOOST_REQUIRE(pidl_path_equivalence(format.file(0), sources[0])); BOOST_REQUIRE(pidl_path_equivalence(format.file(1), sources[1])); } BOOST_AUTO_TEST_SUITE_END() #pragma endregion ================================================ FILE: test/shell_folder/CMakeLists.txt ================================================ # Copyright (C) 2015, 2016 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(UNIT_TESTS data_object_test.cpp exercise_data_object.h file_group_descriptor_test.cpp global_lock_test.cpp sftp_data_object_nasty_old_test.cpp sftp_directory_test.cpp shell_data_object_test.cpp utils_test.cpp) set(INTEGRATION_TESTS atl.cpp exercise_data_object.h remote_folder_test.cpp sftp_data_object_test.cpp) swish_test_suite( SUBJECT shell_folder VARIANT unit SOURCES ${UNIT_TESTS} LIBRARIES ${Boost_LIBRARIES} local_sandbox_fixture LABELS unit) swish_test_suite( SUBJECT shell_folder VARIANT integration SOURCES ${INTEGRATION_TESTS} LIBRARIES ${Boost_LIBRARIES} provider_fixture LABELS integration) swish_copy_fixture_files(test-shell_folder-integration) ================================================ FILE: test/shell_folder/atl.cpp ================================================ /** @file ATL Module required for ATL support. @if licence Copyright (C) 2009, 2015 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/atl.hpp" namespace test { namespace swish { namespace shell_folder { using ATL::CAtlModule; /** * ATL module needed to use ATL-based objects. */ class CModule : public CAtlModule { public : virtual HRESULT AddCommonRGSReplacements(IRegistrarBase*) throw() { return S_OK; } }; }}} // namespace test::swish::com_dll test::swish::shell_folder::CModule _AtlModule; ///< Global module instance ================================================ FILE: test/shell_folder/data_object_test.cpp ================================================ /** @file Testing DataObject implementation. @if license Copyright (C) 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "test/common_boost/fixtures.hpp" #include "test/common_boost/helpers.hpp" #include "test/common_boost/SwishPidlFixture.hpp" #include "exercise_data_object.h" #include "swish/host_folder/host_pidl.hpp" // create_host_itemid #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view #include "swish/shell_folder/DataObject.h" #include #include // bstr_t #include // com_ptr #include #include using swish::host_folder::create_host_itemid; using swish::remote_folder::remote_itemid_view; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using comet::bstr_t; using comet::com_ptr; namespace comet { template<> struct comtype { static const IID& uuid() { return IID_IDataObject; } typedef ::IUnknown base; }; } namespace test { namespace { /** * Test enumerator for the presence of CFSTR_SHELLIDLIST but the absence of * CFSTR_FILEDESCRIPTOR and CFSTR_FILECONTENTS. * * Format-limited version of _testEnumerator() in DataObjectTests.h. */ void _testCDataObjectEnumerator(com_ptr pEnum) { CLIPFORMAT cfShellIdList = static_cast( ::RegisterClipboardFormat(CFSTR_SHELLIDLIST)); CLIPFORMAT cfDescriptor = static_cast( ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR)); CLIPFORMAT cfContents = static_cast( ::RegisterClipboardFormat(CFSTR_FILECONTENTS)); bool fFoundShellIdList = false; bool fFoundDescriptor = false; bool fFoundContents = false; HRESULT hr; do { FORMATETC fetc; hr = pEnum->Next(1, &fetc, NULL); if (hr == S_OK) { if (fetc.cfFormat == cfShellIdList) fFoundShellIdList = true; else if (fetc.cfFormat == cfDescriptor) fFoundDescriptor = true; else if (fetc.cfFormat == cfContents) fFoundContents = true; } } while (hr == S_OK); // Test CFSTR_SHELLIDLIST (PIDL array) format present BOOST_CHECK(fFoundShellIdList); // Test CFSTR_FILEDESCRIPTOR (FILEGROUPDESCRIPTOR) format absent BOOST_CHECK(!fFoundDescriptor); // Test CFSTR_FILECONTENTS (IStream) format absent BOOST_CHECK(!fFoundContents); } /** * Test the GetData() enumerator for the presence of CFSTR_SHELLIDLIST * but the absence of CFSTR_FILEDESCRIPTOR and CFSTR_FILECONTENTS. * * Test the SetData() enumerator for the presence of all three formats. * * Format-limited version of _testBothEnumerators() in DataObjectTests.h. */ void _testBothCDataObjectEnumerators(com_ptr data_object) { HRESULT hr; // Test enumerator of GetData() formats com_ptr spEnumGet; hr = data_object->EnumFormatEtc(DATADIR_GET, spEnumGet.out()); BOOST_REQUIRE_OK(hr); _testCDataObjectEnumerator(spEnumGet); // Test enumerator of SetData() formats com_ptr spEnumSet; hr = data_object->EnumFormatEtc(DATADIR_SET, spEnumSet.out()); BOOST_REQUIRE_OK(hr); _testCDataObjectEnumerator(spEnumSet); } /** * Test QueryGetData() enumerator for the presence of CFSTR_SHELLIDLIST * but the absence of CFSTR_FILEDESCRIPTOR and CFSTR_FILECONTENTS. * * Format-limited version of _testQueryFormats() in DataObjectTests.h. */ void _testCDataObjectQueryFormats(com_ptr data_object) { // Test CFSTR_SHELLIDLIST (PIDL array) format succeeds CFormatEtc fetcShellIdList(CFSTR_SHELLIDLIST); BOOST_REQUIRE_OK(data_object->QueryGetData(&fetcShellIdList)); // Test CFSTR_FILEDESCRIPTOR (FILEGROUPDESCRIPTOR) format fails CFormatEtc fetcDescriptor(CFSTR_FILEDESCRIPTOR); BOOST_CHECK(data_object->QueryGetData(&fetcDescriptor) == S_FALSE); // Test CFSTR_FILECONTENTS (IStream) format fails CFormatEtc fetcContents(CFSTR_FILECONTENTS); BOOST_CHECK(data_object->QueryGetData(&fetcContents) == S_FALSE); } class TestFixture : public SwishPidlFixture, public ComFixture { }; } /** * Tests for our generic shell DataObject wrapper. This class only creates * CFSTR_SHELLIDLIST formats (and some misc private shell ones) on its own. * However, it will store other format when they are set using SetData() and * will return them in GetData() and well as acknowleging their presence * in QueryGetData() and in the IEnumFORMATETC. * * Creation of other formats is left to the CSftpDataObject subclass. * * These tests verify this behaviour. */ BOOST_FIXTURE_TEST_SUITE(data_object_tests, TestFixture) BOOST_AUTO_TEST_CASE( Create ) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl = create_dummy_remote_itemid(L"testswishfile.ext", false); PCITEMID_CHILD pidl_array[] = { pidl.get() }; com_ptr data_object = new CDataObject(1, pidl_array, root.get()); // Test CFSTR_SHELLIDLIST (PIDL array) format cpidl_t root_child = root.last_item(); remote_itemid_view folder(root_child); _testShellPIDLFolder(data_object, folder.filename()); _testShellPIDL(data_object, remote_itemid_view(pidl).filename(), 0); // Test CFSTR_FILEDESCRIPTOR (FILEGROUPDESCRIPTOR) format // CDataObject should not produce a CFSTR_FILEDESCRIPTOR format BOOST_CHECK_THROW( _testFileDescriptor(data_object, L"testswishfile.ext", 0), std::exception); // Test CFSTR_FILECONTENTS (IStream) format // CDataObject should not produce a CFSTR_FILECONTENTS format BOOST_CHECK_THROW( _testStreamContents(data_object, L"/tmp/swish/testswishfile.ext", 0), std::exception); } BOOST_AUTO_TEST_CASE( CreateMulti ) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl1 = create_dummy_remote_itemid(L"testswishfile.ext", false); cpidl_t pidl2 = create_dummy_remote_itemid(L"testswishfile.txt", false); cpidl_t pidl3 = create_dummy_remote_itemid(L"testswishFile", false); PCITEMID_CHILD aPidl[3]; aPidl[0] = pidl1.get(); aPidl[1] = pidl2.get(); aPidl[2] = pidl3.get(); com_ptr data_object = new CDataObject(3, aPidl, root.get()); // Test CFSTR_SHELLIDLIST (PIDL array) format cpidl_t root_child = root.last_item(); remote_itemid_view folder(root_child); _testShellPIDLFolder(data_object, folder.filename()); _testShellPIDL(data_object, remote_itemid_view(pidl1).filename(), 0); _testShellPIDL(data_object, remote_itemid_view(pidl2).filename(), 1); _testShellPIDL(data_object, remote_itemid_view(pidl3).filename(), 2); } /** * Test that QueryGetData fails for all our formats when created with * empty PIDL list. */ BOOST_AUTO_TEST_CASE( QueryFormatsEmpty ) { com_ptr data_object = new CDataObject(0, NULL, NULL); // Test that QueryGetData() responds negatively for all our formats _testQueryFormats(data_object, true); } /** * Test that none of our expected formats are in the enumerator when * created with empty PIDL list. */ BOOST_AUTO_TEST_CASE( EnumFormatsEmpty ) { com_ptr data_object = new CDataObject(0, NULL, NULL); // Test that enumerators of both GetData() and SetData() // formats fail to enumerate any of our formats _testBothEnumerators(data_object, true); } /** * Test that QueryGetData responds successfully for all our formats. */ BOOST_AUTO_TEST_CASE( QueryFormats ) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl = create_dummy_remote_itemid(L"testswishfile.ext", false); PCITEMID_CHILD pidl_array[] = { pidl.get() }; com_ptr data_object = new CDataObject(1, pidl_array, root.get()); // Perform query tests _testCDataObjectQueryFormats(data_object); } /** * Test that all our expected formats are in the enumeration. */ BOOST_AUTO_TEST_CASE( EnumFormats ) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl = create_dummy_remote_itemid(L"testswishfile.ext", false); PCITEMID_CHILD pidl_array[] = { pidl.get() }; com_ptr data_object = new CDataObject(1, pidl_array, root.get()); // Test enumerators of both GetData() and SetData() formats _testBothCDataObjectEnumerators(data_object); } /** * Test that QueryGetData responds successfully for all our formats when * initialised with multiple PIDLs. */ BOOST_AUTO_TEST_CASE( QueryFormatsMulti ) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl1 = create_dummy_remote_itemid(L"testswishfile.ext", false); cpidl_t pidl2 = create_dummy_remote_itemid(L"testswishfile.txt", false); cpidl_t pidl3 = create_dummy_remote_itemid(L"testswishFile", false); PCITEMID_CHILD aPidl[3]; aPidl[0] = pidl1.get(); aPidl[1] = pidl2.get(); aPidl[2] = pidl3.get(); com_ptr data_object = new CDataObject(3, aPidl, root.get()); // Perform query tests _testCDataObjectQueryFormats(data_object); } /** * Test that all our expected formats are in the enumeration when * initialised with multiple PIDLs. */ BOOST_AUTO_TEST_CASE( EnumFormatsMulti ) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl1 = create_dummy_remote_itemid(L"testswishfile.ext", false); cpidl_t pidl2 = create_dummy_remote_itemid(L"testswishfile.txt", false); cpidl_t pidl3 = create_dummy_remote_itemid(L"testswishFile", false); PCITEMID_CHILD aPidl[3]; aPidl[0] = pidl1.get(); aPidl[1] = pidl2.get(); aPidl[2] = pidl3.get(); com_ptr data_object = new CDataObject(3, aPidl, root.get()); // Test enumerators of both GetData() and SetData() formats _testBothCDataObjectEnumerators(data_object); } } // namespace test BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/shell_folder/exercise_data_object.h ================================================ /** @file Miscellaneous tests for the Swish DataObject. @if license Copyright (C) 2012 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #ifndef TEST_SHELL_FOLDER_EXERCISE_DATA_OBJECT_HPP #define TEST_SHELL_FOLDER_EXERCISE_DATA_OBJECT_HPP #include "swish/host_folder/host_pidl.hpp" // host_itemid_view #include "swish/remote_folder/remote_pidl.hpp" // path_from_remote_pidl // remote_itemid_view #include "swish/shell_folder/DataObject.h" #include // com_error_from_interface #include // com_ptr #include // numeric_cast #include // BOOST_THROW_EXCEPTION #include // replace #include #include /** * Test that Shell PIDL from DataObject holds the expected number of PIDLs. */ static void _testShellPIDLCount( comet::com_ptr data_object, UINT nExpected) { FORMATETC fetc = { (CLIPFORMAT)::RegisterClipboardFormat(CFSTR_SHELLIDLIST), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM stg; HRESULT hr = data_object->GetData(&fetc, &stg); BOOST_REQUIRE_OK(hr); BOOST_CHECK(stg.hGlobal); CIDA *pida = (CIDA *)::GlobalLock(stg.hGlobal); BOOST_CHECK(pida); UINT nActual = pida->cidl; BOOST_CHECK_EQUAL(nExpected, nActual); ::GlobalUnlock(stg.hGlobal); pida = NULL; ::ReleaseStgMedium(&stg); } #define GetPIDLFolder(pida) \ (PCIDLIST_ABSOLUTE)(((LPBYTE)pida)+(pida)->aoffset[0]) #define GetPIDLItem(pida, i) \ (PCIDLIST_RELATIVE)(((LPBYTE)pida)+(pida)->aoffset[i+1]) /** * Test that Shell PIDL from DataObject represent the expected file. */ static void _testShellPIDL( comet::com_ptr data_object, std::wstring expected, UINT iFile) { FORMATETC fetc = { (CLIPFORMAT)::RegisterClipboardFormat(CFSTR_SHELLIDLIST), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM stg; HRESULT hr = data_object->GetData(&fetc, &stg); BOOST_REQUIRE_OK(hr); BOOST_CHECK(stg.hGlobal); CIDA *pida = (CIDA *)::GlobalLock(stg.hGlobal); BOOST_CHECK(pida); BOOST_CHECK_EQUAL( expected, swish::remote_folder::path_from_remote_pidl( GetPIDLItem(pida, iFile)).wstring().c_str()); ::GlobalUnlock(stg.hGlobal); pida = NULL; ::ReleaseStgMedium(&stg); } /** * Test that Shell PIDL from DataObject represents the common root folder. * * The PIDL may be a RemoteItemId, in which case @p expected should be the * name of the directory (e.g "tmp"), but it may also be an HostItemId in which * case the path (e.g. "/tmp") that is expected to be found in that item * should be passed. */ static void _testShellPIDLFolder( comet::com_ptr data_object, std::wstring expected) { FORMATETC fetc = { (CLIPFORMAT)::RegisterClipboardFormat(CFSTR_SHELLIDLIST), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM stg; HRESULT hr = data_object->GetData(&fetc, &stg); BOOST_REQUIRE_OK(hr); BOOST_CHECK(stg.hGlobal); CIDA *pida = (CIDA *)::GlobalLock(stg.hGlobal); BOOST_CHECK(pida); // Test folder PIDL which may be a RemoteItemId or a HostItemId washer::shell::pidl::cpidl_t actual = ::ILFindLastID(GetPIDLFolder(pida)); if (swish::remote_folder::remote_itemid_view(actual).valid()) { BOOST_CHECK_EQUAL( expected, swish::remote_folder::remote_itemid_view(actual).filename()); } else if (swish::host_folder::host_itemid_view(actual).valid()) { swish::host_folder::host_itemid_view itemid(actual); std::wstring actual_path = itemid.path().wstring(); BOOST_CHECK_EQUAL(expected, actual_path); } else { BOOST_FAIL("Invalid PIDL"); } ::GlobalUnlock(stg.hGlobal); pida = NULL; ::ReleaseStgMedium(&stg); } /** * Test that the the FILEGROUPDESCRIPTOR and ith FILEDESCRIPTOR * match expected values. * File descriptors should use Windows path separators so we replace * forward slashes with back slashes in expected string. */ static void _testFileDescriptor( comet::com_ptr data_object, std::wstring expected, UINT iFile) { std::replace(expected.begin(), expected.end(), L'/', L'\\'); FORMATETC fetc = { (CLIPFORMAT)::RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM stg = STGMEDIUM(); stg.tymed = fetc.tymed; HRESULT hr = data_object->GetData(&fetc, &stg); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error_from_interface(data_object, hr)); BOOST_CHECK(stg.hGlobal); FILEGROUPDESCRIPTOR *fgd = (FILEGROUPDESCRIPTOR *)::GlobalLock(stg.hGlobal); BOOST_CHECK(fgd); BOOST_CHECK(iFile < fgd->cItems); BOOST_CHECK(fgd->fgd); std::wstring actual(fgd->fgd[iFile].cFileName); BOOST_CHECK_EQUAL(expected, actual); ::GlobalUnlock(stg.hGlobal); fgd = NULL; ::ReleaseStgMedium(&stg); } /** * Test that the contents of the DummyStream matches what is expected. */ static void _testStreamContents( comet::com_ptr data_object, std::wstring expected, UINT iFile) { FORMATETC fetc = { (CLIPFORMAT)::RegisterClipboardFormat(CFSTR_FILECONTENTS), NULL, DVASPECT_CONTENT, iFile, TYMED_ISTREAM }; STGMEDIUM stg; HRESULT hr = data_object->GetData(&fetc, &stg); if (FAILED(hr)) BOOST_THROW_EXCEPTION(comet::com_error_from_interface(data_object, hr)); BOOST_CHECK(stg.pstm); std::vector buffer(MAX_PATH); ULONG cbRead = 0; hr = stg.pstm->Read( &buffer[0], boost::numeric_cast(buffer.size()), &cbRead); BOOST_REQUIRE_OK(hr); std::wstring actual(&buffer[0]); BOOST_CHECK_EQUAL(expected, actual); ::ReleaseStgMedium(&stg); } /** * Test for success (or failure) when querying the presence of * our expected formats. */ static void _testQueryFormats( comet::com_ptr data_object, bool fFailTest=false) { HRESULT hr; // Test CFSTR_SHELLIDLIST (PIDL array) format if (!fFailTest) // Vista includes this format even for empty PIDL array { CFormatEtc fetcShellIdList(CFSTR_SHELLIDLIST); hr = data_object->QueryGetData(&fetcShellIdList); BOOST_REQUIRE_OK(hr); } // Test CFSTR_FILEDESCRIPTOR (FILEGROUPDESCRIPTOR) format CFormatEtc fetcDescriptor(CFSTR_FILEDESCRIPTOR); hr = data_object->QueryGetData(&fetcDescriptor); BOOST_CHECK(hr == ((fFailTest) ? S_FALSE : S_OK)); // Test CFSTR_FILECONTENTS (IStream) // Since Windows 7 (or maybe Vista) we must get TYMED_ISTREAM right here. // Previously if you prodded with a TYMED_ISTREAM but checked with // TYMED_GLOBAL it still worked. Not any more CFormatEtc fetcContents(CFSTR_FILECONTENTS, TYMED_ISTREAM); hr = data_object->QueryGetData(&fetcContents); BOOST_CHECK(hr == ((fFailTest) ? S_FALSE : S_OK)); } /** * Test enumerator for the presence (or absence) of our expected formats. */ static void _testEnumerator( comet::com_ptr pEnum, bool fFailTest=false) { CLIPFORMAT cfShellIdList = static_cast( ::RegisterClipboardFormat(CFSTR_SHELLIDLIST)); CLIPFORMAT cfDescriptor = static_cast( ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR)); CLIPFORMAT cfContents = static_cast( ::RegisterClipboardFormat(CFSTR_FILECONTENTS)); bool fFoundShellIdList = false; bool fFoundDescriptor = false; bool fFoundContents = false; HRESULT hr; do { FORMATETC fetc; hr = pEnum->Next(1, &fetc, NULL); if (hr == S_OK) { if (fetc.cfFormat == cfShellIdList) fFoundShellIdList = true; else if (fetc.cfFormat == cfDescriptor) fFoundDescriptor = true; else if (fetc.cfFormat == cfContents) fFoundContents = true; } } while (hr == S_OK); // Test CFSTR_SHELLIDLIST (PIDL array) format if (!fFailTest) // Vista includes this format even for empty PIDL array BOOST_CHECK(fFoundShellIdList); // Test CFSTR_FILEDESCRIPTOR (FILEGROUPDESCRIPTOR) format BOOST_CHECK((fFailTest) ? !fFoundDescriptor : fFoundDescriptor); // Test CFSTR_FILECONTENTS (IStream) BOOST_CHECK((fFailTest) ? !fFoundContents : fFoundContents); } /** * Perform our enumerator tests for both SetData() and GetData() enums. */ static void _testBothEnumerators( comet::com_ptr data_object, bool fFailTest=false) { HRESULT hr; // Test enumerator of GetData() formats comet::com_ptr enum_get; hr = data_object->EnumFormatEtc(DATADIR_GET, enum_get.out()); BOOST_REQUIRE_OK(hr); _testEnumerator(enum_get, fFailTest); // Test enumerator of SetData() formats comet::com_ptr enum_set; hr = data_object->EnumFormatEtc(DATADIR_SET, enum_set.out()); BOOST_REQUIRE_OK(hr); _testEnumerator(enum_set, fFailTest); } #endif ================================================ FILE: test/shell_folder/file_group_descriptor_test.cpp ================================================ /** @file Unit tests for classes derived from basic_pidl. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/shell_folder/data_object/FileGroupDescriptor.hpp" // subject #include #include #include // shared_ptr #include // memset, memcpy using namespace swish::shell_folder::data_object; using boost::shared_ptr; namespace { class FgdFixture { public: static const size_t TEST_ALLOC_SIZE = sizeof(FILEGROUPDESCRIPTOR) + sizeof(FILEDESCRIPTOR); /** * Allocate a fake FILEGROUPDESCRIPTOR with space for two * FILEDESCRIPTORS. */ FgdFixture() : m_hglobal( ::GlobalAlloc(GMEM_MOVEABLE, TEST_ALLOC_SIZE), ::GlobalFree) { BOOST_REQUIRE(m_hglobal.get()); FILEGROUPDESCRIPTOR* fgd = static_cast( ::GlobalLock(m_hglobal.get())); BOOST_REQUIRE(fgd); fgd->cItems = 2; FILEDESCRIPTOR fd1; std::memset(&fd1, 0, sizeof(fd1)); wcscpy_s(fd1.cFileName, L"test\\item\\path"); FILEDESCRIPTOR fd2; std::memset(&fd2, 0, sizeof(fd2)); wcscpy_s(fd2.cFileName, L"test\\item\\bob"); fgd->fgd[0] = fd1; fgd->fgd[1] = fd2; BOOST_REQUIRE(::GlobalUnlock(fgd)); } /** * Return pointer to chunk of memory intialised with fake * FILEGROUPDESCRIPTOR. */ HGLOBAL get() const { return m_hglobal.get(); } private: shared_ptr m_hglobal; // HGLOBAL }; } #pragma region FileGroupDescriptor tests BOOST_FIXTURE_TEST_SUITE(file_group_descriptor_tests, FgdFixture) /** * Constructor doesn't throw. */ BOOST_AUTO_TEST_CASE( create ) { FileGroupDescriptor fgd(get()); } /** * Counting contained descriptors gives the expected value of 2. */ BOOST_AUTO_TEST_CASE( size ) { FileGroupDescriptor fgd(get()); size_t size = fgd.size(); BOOST_REQUIRE_EQUAL(size, 2U); } /** * Accessing descriptors renders the expected data. */ BOOST_AUTO_TEST_CASE( access ) { FileGroupDescriptor fgd(get()); BOOST_CHECK_EQUAL(fgd[0].path(), L"test\\item\\path"); BOOST_CHECK_EQUAL(fgd[1].path(), L"test\\item\\bob"); BOOST_CHECK_EQUAL(fgd[0].path(), L"test\\item\\path"); } /** * Accessing an out-of-bounds descriptor throws an exception. */ BOOST_AUTO_TEST_CASE( bounds_error ) { FileGroupDescriptor fgd(get()); BOOST_REQUIRE_THROW(fgd[2], std::out_of_range); } /** * The lifetime of a descriptor outlives that of its parent group. */ BOOST_AUTO_TEST_CASE( descriptor_lifetime ) { FileGroupDescriptor fgd(get()); Descriptor d = fgd[0]; { FileGroupDescriptor scoped_fgd(get()); d = scoped_fgd[1]; } BOOST_CHECK_EQUAL(d.path(), L"test\\item\\bob"); } /** * Changing a descriptor outside the FileGroupDescriptor should leave the * copy in the FGD unchanged. This checks that descriptors point at copies * not references to the original memory. */ BOOST_AUTO_TEST_CASE( descriptor_independence ) { FileGroupDescriptor fgd(get()); Descriptor d = fgd[1]; d.path(L"replaced/path"); BOOST_CHECK_EQUAL(d.path(), L"replaced\\path"); BOOST_CHECK_EQUAL(fgd[1].path(), L"test\\item\\bob"); } /** * Changing a descriptor in the FileGroupDescriptor directly should change * the value returned in subsequent accesses. This checks that the FGD * [] accessor returns the descriptors by reference. */ BOOST_AUTO_TEST_CASE( descriptor_access_byref ) { FileGroupDescriptor fgd(get()); fgd[1].path(L"replaced/path"); Descriptor d = fgd[1]; BOOST_CHECK_EQUAL(d.path(), L"replaced\\path"); BOOST_CHECK_EQUAL(fgd[1].path(), L"replaced\\path"); } /** * A copy of an FGD should give the expected data from its accessors. * This checks that the copied FGD has access to sensible data but does * *not* check that it points to the same copy of the data as the original. */ BOOST_AUTO_TEST_CASE( copy_construct ) { FileGroupDescriptor fgd_orig(get()); FileGroupDescriptor fgd = fgd_orig; BOOST_CHECK_EQUAL(fgd[0].path(), L"test\\item\\path"); BOOST_CHECK_EQUAL(fgd[1].path(), L"test\\item\\bob"); BOOST_REQUIRE_EQUAL(fgd.size(), 2U); } /** * A copy of an FGD should point to the same memory as the original. * Therefore, changes to one should affect the other. */ BOOST_AUTO_TEST_CASE( copies_are_linked ) { FileGroupDescriptor fgd_orig(get()); FileGroupDescriptor fgd = fgd_orig; fgd[1].path(L"replaced/path"); BOOST_CHECK_EQUAL(fgd_orig[1].path(), L"replaced\\path"); } /** * Descriptor field are initialised to zero. */ BOOST_AUTO_TEST_CASE( descriptor_zero_init ) { Descriptor d; const FILEDESCRIPTOR fd = d.get(); FILEDESCRIPTOR fd_zero; std::memset(&fd_zero, 0, sizeof(fd_zero)); BOOST_REQUIRE(!std::memcmp(&fd, &fd_zero, sizeof(fd))); } BOOST_AUTO_TEST_SUITE_END() #pragma endregion ================================================ FILE: test/shell_folder/global_lock_test.cpp ================================================ /** @file Unit tests for the locked HGLOBAL wrapper. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/atl.hpp" #include "swish/shell_folder/data_object/GlobalLocker.hpp" // Test subject #include "test/common_boost/fixtures.hpp" #include #include // shared_ptr #include // system_error #include using boost::shared_ptr; using boost::system::system_error; using std::string; namespace { // private typedef swish::shell_folder::data_object::GlobalLocker GlobalStringLock; /** * Put the test string into global memory and return a smart pointer to it. */ shared_ptr global_test_data(const string& data) { shared_ptr global( ::GlobalAlloc(GMEM_MOVEABLE, data.size()+1), ::GlobalFree); char* buf = static_cast(::GlobalLock(global.get())); ::CopyMemory(buf, data.c_str(), data.size()); buf[data.size()] = '\0'; ::GlobalUnlock(buf); return global; } } BOOST_AUTO_TEST_SUITE( global_lock_test ) /** * Get locked data and check that it isn't unexpectedly different. */ BOOST_AUTO_TEST_CASE( lock ) { shared_ptr global = global_test_data("lorem ipsum"); GlobalStringLock lock(global.get()); char* data = lock.get(); BOOST_REQUIRE_EQUAL(data, "lorem ipsum"); } /** * Create on an invalid HGLOBAL. * This should fail and throw an exception. */ BOOST_AUTO_TEST_CASE( lock_fail ) { BOOST_REQUIRE_THROW(GlobalStringLock lock(NULL), system_error); } /** * Copy a GlobalLock using the copy contructor. * The pointers returned from get() should be identical by *address*. */ BOOST_AUTO_TEST_CASE( lock_copy ) { shared_ptr global = global_test_data("lorem ipsum"); GlobalStringLock lock(global.get()); void* data1 = lock.get(); GlobalStringLock lock_copy(lock); void* data2 = lock_copy.get(); BOOST_REQUIRE_EQUAL(data1, data2); // Compare address, not strings } /** * Copy a GlobalLock using copy assignment. * The pointers returned from get() should be identical by *address*. */ BOOST_AUTO_TEST_CASE( lock_copy_assign ) { // Create first lock on global data shared_ptr global1 = global_test_data("lorem ipsum"); GlobalStringLock lock1(global1.get()); // Create a lock on *other* global data shared_ptr global2 = global_test_data("dolor sit amet"); GlobalStringLock lock2(global2.get()); // Assign second lock to first which should point both locks to second data lock1 = lock2; char* data1 = lock1.get(); char* data2 = lock2.get(); // Compare addresses and make sure it points to the *second* string BOOST_REQUIRE_EQUAL(static_cast(data1), static_cast(data2)); BOOST_REQUIRE_EQUAL(data1, "dolor sit amet"); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/shell_folder/remote_folder_test.cpp ================================================ // Copyright 2011, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "swish/shell_folder/RemoteFolder.h" // test subject #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view #include "test/common_boost/helpers.hpp" // BOOST_REQUIRE_OK #include "test/common_boost/fixtures.hpp" // ComFixture #include "test/fixtures/provider_fixture.hpp" #include // apidl_t #include // strret_to_string #include // datetime_t #include // enum_iterator #include // com_error #include // com_ptr #include // bind #include // lexical_cast #include #include // find_if using test::fixtures::provider_fixture; using swish::remote_folder::remote_itemid_view; using swish::utils::Utf8StringToWideString; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using washer::shell::strret_to_string; using comet::com_error; using comet::com_ptr; using comet::datetime_t; using comet::enum_iterator; using ssh::filesystem::path; using boost::lexical_cast; using boost::test_tools::predicate_result; using std::find_if; using std::wstring; namespace comet { template <> struct comtype { static const IID& uuid() throw() { return IID_IEnumIDList; } typedef IUnknown base; }; template <> struct enumerated_type_of { typedef PITEMID_CHILD is; }; /** * Copy-policy for use by enumerators of child PIDLs. */ template <> struct impl::type_policy { static void init(PITEMID_CHILD& t, const cpidl_t& s) { s.copy_to(t); } static void clear(PITEMID_CHILD& t) { ::ILFree(t); } }; } namespace { // private class RemoteFolderFixture : public provider_fixture, public test::ComFixture { private: com_ptr m_folder; public: RemoteFolderFixture() : m_folder(CRemoteFolder::Create( sandbox_pidl().get(), boost::bind(&RemoteFolderFixture::consumer_factory, this, _1))) { } com_ptr folder() const { return m_folder; } comet::com_ptr consumer_factory(HWND) { return Consumer(); } }; void test_enum(com_ptr pidls, SHCONTF flags) { PITEMID_CHILD pidl; ULONG fetched; HRESULT hr = pidls->Next(1, &pidl, &fetched); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(fetched, 1U); do { remote_itemid_view itemid(pidl); // Check REMOTEPIDLness BOOST_REQUIRE(itemid.valid()); // Check filename BOOST_CHECK_GT(itemid.filename().size(), 0U); if (!(flags & SHCONTF_INCLUDEHIDDEN)) BOOST_CHECK_NE(itemid.filename(), L"."); // Check folderness if (!(flags & SHCONTF_FOLDERS)) BOOST_CHECK(!itemid.is_folder()); if (!(flags & SHCONTF_NONFOLDERS)) BOOST_CHECK(itemid.is_folder()); // Check group and owner exist BOOST_CHECK_GT(itemid.owner().size(), 0U); BOOST_CHECK_GT(itemid.group().size(), 0U); // Check date validity BOOST_CHECK(itemid.date_modified().good()); hr = pidls->Next(1, &pidl, &fetched); } while (hr == S_OK); BOOST_CHECK_EQUAL(hr, S_FALSE); BOOST_CHECK_EQUAL(fetched, 0U); } void test_enum(ATL::CComPtr pidls, SHCONTF flags) { test_enum(com_ptr(pidls.p), flags); } } BOOST_FIXTURE_TEST_SUITE(remote_folder_tests, RemoteFolderFixture) /** * When a remote directory is empty, the remote folder's enumerator must * be empty. */ BOOST_AUTO_TEST_CASE(enum_empty) { SHCONTF flags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN; com_ptr listing; HRESULT hr = folder()->EnumObjects(NULL, flags, listing.out()); BOOST_REQUIRE_OK(hr); ULONG fetched = 1; PITEMID_CHILD pidl; BOOST_CHECK_EQUAL(listing->Next(1, &pidl, &fetched), S_FALSE); BOOST_CHECK_EQUAL(fetched, 0U); } /** * Requesting everything should return folder and dotted files as well. */ BOOST_AUTO_TEST_CASE(enum_everything) { path file1 = new_file_in_sandbox(); path file2 = new_file_in_sandbox(); path folder1 = sandbox() / L"folder1"; create_directory(filesystem(), folder1); path folder2 = sandbox() / L"folder2"; create_directory(filesystem(), folder2); SHCONTF flags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN; com_ptr listing; HRESULT hr = folder()->EnumObjects(NULL, flags, listing.out()); BOOST_REQUIRE_OK(hr); test_enum(listing, flags); } namespace { bool pidl_matches_filename(PCUITEMID_CHILD remote_pidl, wstring name) { remote_itemid_view item(remote_pidl); return item.filename() == name; } cpidl_t pidl_for_file(com_ptr folder, wstring name) { SHCONTF flags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN; com_ptr listing; HRESULT hr = folder->EnumObjects(NULL, flags, listing.out()); BOOST_REQUIRE_OK(hr); enum_iterator pos = std::find_if( enum_iterator(listing), enum_iterator(), bind(pidl_matches_filename, _1, name)); BOOST_REQUIRE_MESSAGE(pos != enum_iterator(), "PIDL not found"); return *pos; } predicate_result display_name_matches(com_ptr folder, SHGDNF flags, const path& filename, const wstring& expected_display_name) { cpidl_t pidl = pidl_for_file(folder, filename.wstring()); STRRET strret; HRESULT hr = folder->GetDisplayNameOf(pidl.get(), flags, &strret); BOOST_REQUIRE_OK(hr); wstring display_name = strret_to_string(strret); if (display_name != expected_display_name) { predicate_result res(false); res.message() << L"Display name for '" << filename << L"' unexpected: [" << display_name << L" != " << expected_display_name << L"]"; return res; } return true; } } /** * Request the display name for a file. * * This is the name of the file in a form suitable displaying to the user * anywhere in Windows and therefore may need disambiguation information * included. For example 'filename on host' rather than just 'filename'. * * The result may or may not include the extension depending on the user's * settings, so we accept either as a successful result. * * Currently we don't support disambiguation information in Swish. * * This name does not have to be parseable. */ BOOST_AUTO_TEST_CASE(display_name_file) { path file = new_file_in_sandbox(L"testfile.txt"); SHGDNF flags = SHGDN_NORMAL; wstring expected_with = L"testfile.txt"; wstring expected_without = L"testfile"; BOOST_CHECK( display_name_matches(folder(), flags, file.filename(), expected_with) || display_name_matches(folder(), flags, file.filename(), expected_without)); } /** * Request the display name for a Unix 'hidden' file. * * On Unix files are considered to be hidden if they start with a full-stop. * We adhere to this convention and should not treat an initial dot dot as part * of the extension. * * The result may or may not include the extension depending on the user's * settings, so we accept either as a successful result. */ BOOST_AUTO_TEST_CASE(display_name_hidden_file) { path file1 = new_file_in_sandbox(L".hidden"); path file2 = new_file_in_sandbox(L".testfile.txt"); SHGDNF flags = SHGDN_NORMAL; wstring expected1 = L".hidden"; wstring expected2_with = L".testfile.txt"; wstring expected2_without = L".testfile"; BOOST_CHECK( display_name_matches(folder(), flags, file1.filename(), expected1)); BOOST_CHECK(display_name_matches(folder(), flags, file2.filename(), expected2_with) || display_name_matches(folder(), flags, file2.filename(), expected2_without)); } /** * Request the editing name for a file as though it were being edited elsewhere * than within its parent folder view. * I'm not sure how this situation would work but I don't think it matters for * us so we just return the usual editing name. */ BOOST_AUTO_TEST_CASE(editing_name_file) { path file = new_file_in_sandbox(L"testfile.txt"); SHGDNF flags = SHGDN_NORMAL | SHGDN_FOREDITING; wstring expected = L"testfile.txt"; BOOST_CHECK( display_name_matches(folder(), flags, file.filename(), expected)); } /** * Request the name for a file as though it were shown in the address bar * somewhere that isn't necessarily the parent folder. */ BOOST_AUTO_TEST_CASE(address_bar_name_file) { BOOST_WARN_MESSAGE(false, "skipping - testing full address bar requires " "registration and knowledge of the parent host folder"); return; // Leaving code here in case we find a way round this path file = new_file_in_sandbox(L"testfile.txt"); SHGDNF flags = SHGDN_NORMAL | SHGDN_FORADDRESSBAR; wstring expected = L"sftp://" + wuser() + L"@" + whost() + L":" + lexical_cast(port()) + L"/" + file.wstring(); BOOST_CHECK( display_name_matches(folder(), flags, file.filename(), expected)); } /** * Check the display name for a file as it should be shown in a listing * of its containing folder. * In particular, this doesn't need disambiguation information that relates * to the folder it is in as this name is only used within the parent folder. * * The result may or may not include the extension depending on the user's * settings, so we accept either as a successful result. * * This name does not have to be parseable. */ BOOST_AUTO_TEST_CASE(in_folder_display_name_file) { path file = new_file_in_sandbox(L"testfile.txt"); SHGDNF flags = SHGDN_INFOLDER; wstring expected_with = L"testfile.txt"; wstring expected_without = L"testfile"; BOOST_CHECK( display_name_matches(folder(), flags, file.filename(), expected_with) || display_name_matches(folder(), flags, file.filename(), expected_without)); } /** * Check the display name for a file of unregistered type as it should be * shown in a listing of its containing folder. * In particular, this doesn't need disambiguation information that relates * to the folder it is in as this name is only used within the parent folder. * * This test differs from in_folder_display_name_file in that the file * extension is of an unregistered type. These should always show the * extension. * * This name does not have to be parseable. */ BOOST_AUTO_TEST_CASE(in_folder_display_name_unknown_file) { // May fail if .xyz is actually a registered type path file = new_file_in_sandbox(L"testfile.xyz"); SHGDNF flags = SHGDN_INFOLDER; wstring expected = L"testfile.xyz"; BOOST_CHECK( display_name_matches(folder(), flags, file.filename(), expected)); } /** * Check the parsing name of a file relative to its containing folder. * In other words, return the name of the file in such a form that it can * be uniquely identified given that we know the folder it is in. * Effectively, this means return the filename with its extension but any * decorative text that isn't part of its real name should be removed. * * Our files over SFTP don't have any decorative text but we do have to deal * with the extension. * * The FORPARSING flag forces the file extension to be included, regardless * of any user setting. */ BOOST_AUTO_TEST_CASE(in_folder_parsing_name_file) { path file = new_file_in_sandbox(L"testfile.txt"); SHGDNF flags = SHGDN_INFOLDER | SHGDN_FORPARSING; wstring expected = L"testfile.txt"; BOOST_CHECK( display_name_matches(folder(), flags, file.filename(), expected)); } /** * Request the editing name for a file as though it were being renamed in-place. * Normally in Windows this is different from the in-folder parsing name in that * it wouldn't include the extension but we tweak this slightly so that * renaming a file shows the extension even if that isn't the default user * setting. */ BOOST_AUTO_TEST_CASE(in_folder_editing_name_file) { path file = new_file_in_sandbox(L"testfile.txt"); SHGDNF flags = SHGDN_INFOLDER | SHGDN_FOREDITING; wstring expected = L"testfile.txt"; BOOST_CHECK( display_name_matches(folder(), flags, file.filename(), expected)); } // NORMAL + FORPARSING = ABSOLUTE // // ... or so it would seem /** * Request the absolute name of a file as shown in the address bar. * * This should be a 'pretty' version of the name rather than the * truly parseable version that includes GUIDs etc. */ BOOST_AUTO_TEST_CASE(absolute_address_bar_parsing_name_file) { BOOST_WARN_MESSAGE(false, "skipping - testing absolute parsing name requires " "registration and knowledge of the parent"); return; // Leaving code here in case we find a way round this path file = new_file_in_sandbox(L"testfile.txt"); SHGDNF flags = SHGDN_NORMAL | SHGDN_FORADDRESSBAR | SHGDN_FORPARSING; wstring expected = L"Computer\\Swish\\sftp://" + wuser() + L"@" + whost() + L":" + lexical_cast(port()) + L"/" + file.wstring(); BOOST_CHECK( display_name_matches(folder(), flags, file.filename(), expected)); } /** * Request the absolute parsing name for a file. * * It must be possible to pass this to the @b desktop folder's ParseDisplayName * and get back a pidl for this item. */ BOOST_AUTO_TEST_CASE(absolute_parsing_name_file) { BOOST_WARN_MESSAGE(false, "skipping - testing absolute parsing name requires " "registration and knowledge of the parent"); return; // Leaving code here in case we find a way round this path file = new_file_in_sandbox(L"testfile.txt"); SHGDNF flags = SHGDN_NORMAL | SHGDN_FORPARSING; wstring expected = L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\" L"::{B816A83A-5022-11DC-9153-0090F5284F85}\\sftp://" + wuser() + L"@" + whost() + L":" + lexical_cast(port()) + L"/" + file.wstring(); BOOST_CHECK( display_name_matches(folder(), flags, file.filename(), expected)); } /** * Request the display name for a folder. * * This is the name of the file in a form suitable displaying to the user * anywhere in Windows and therefore may need disambiguation information * included. For example 'folder on host' rather than just 'folder'. * * Currently we don't support disambiguation information in Swish. * * This name does not have to be parseable. */ BOOST_AUTO_TEST_CASE(display_name_folder) { path directory = sandbox() / L"testfolder"; create_directory(filesystem(), directory); SHGDNF flags = SHGDN_NORMAL; wstring expected = L"testfolder"; BOOST_CHECK( display_name_matches(folder(), flags, directory.filename(), expected)); } /** * Request the display name for a folder within its parent folder view. * * This name does not have to be parseable. */ BOOST_AUTO_TEST_CASE(in_folder_name_folder) { path directory = sandbox() / L"testfolder"; create_directory(filesystem(), directory); SHGDNF flags = SHGDN_INFOLDER; wstring expected = L"testfolder"; BOOST_CHECK( display_name_matches(folder(), flags, directory.filename(), expected)); } /** * Request the display name for a folder that looks like it has an extension. * * Dots in a folder don't really indicate an extension so we should return * the whole thing. */ BOOST_AUTO_TEST_CASE(display_name_folder_with_extension) { path directory = sandbox() / L"testfolder.txt"; create_directory(filesystem(), directory); SHGDNF flags = SHGDN_NORMAL; wstring expected = L"testfolder.txt"; BOOST_CHECK( display_name_matches(folder(), flags, directory.filename(), expected)); } /** * Request the display name for a folder that looks like it has an extension * in a form for use within its parent folder view. * * Dots in a folder don't really indicate an extension so we should return * the whole thing. */ BOOST_AUTO_TEST_CASE(in_folder_name_folder_with_extension) { path directory = sandbox() / L"testfolder.txt"; create_directory(filesystem(), directory); SHGDNF flags = SHGDN_INFOLDER; wstring expected = L"testfolder.txt"; BOOST_CHECK( display_name_matches(folder(), flags, directory.filename(), expected)); } /** * Request the display name for a Unix 'hidden' directory. * * On Unix files are considered to be hidden if they start with a full-stop. * Although we shouldn't treat any part of a folder name as an extension, we * test the initial-dot case here specially just to make sure. */ BOOST_AUTO_TEST_CASE(display_name_hidden_folder) { path dir1 = sandbox() / L".hidden"; create_directory(filesystem(), dir1); path dir2 = sandbox() / L".testfolder.txt"; create_directory(filesystem(), dir2); SHGDNF flags = SHGDN_NORMAL; wstring expected1 = L".hidden"; wstring expected2 = L".testfolder.txt"; BOOST_CHECK( display_name_matches(folder(), flags, dir1.filename(), expected1)); BOOST_CHECK( display_name_matches(folder(), flags, dir2.filename(), expected2)); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/shell_folder/sftp_data_object_nasty_old_test.cpp ================================================ /** @file Testing DataObject implementation. @if license Copyright (C) 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "test/common_boost/fixtures.hpp" #include "test/common_boost/helpers.hpp" #include "test/common_boost/MockProvider.hpp" #include "test/common_boost/SwishPidlFixture.hpp" #include "exercise_data_object.h" #include "swish/host_folder/host_pidl.hpp" // create_host_itemid #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view #include "swish/shell_folder/SftpDataObject.h" #include // cpidl_t, apidl_t #include // bstr_t #include // com_ptr #include #include using swish::host_folder::create_host_itemid; using swish::provider::sftp_provider; using swish::remote_folder::remote_itemid_view; using boost::shared_ptr; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using comet::bstr_t; using comet::com_ptr; using std::string; using std::vector; using std::wstring; namespace comet { template <> struct comtype { static const IID& uuid() { return IID_IDataObject; } typedef ::IUnknown base; }; } namespace test { namespace { class TestFixture : public ComFixture, public SwishPidlFixture { public: TestFixture() { // Create mock object coclass instances m_pProvider = shared_ptr(new MockProvider()); } protected: shared_ptr m_pProvider; }; } // HACK: // A lot of these tests rely on SwishPidlFixture creating a host PIDL with // path `/tmp` and a remote root PIDL with path `swish`. BOOST_FIXTURE_TEST_SUITE(sftp_data_object_nasty_old_tests, TestFixture) BOOST_AUTO_TEST_CASE(Create) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl = create_dummy_remote_itemid(L"testswishfile.ext", false); PCUITEMID_CHILD pidl_array[] = {pidl.get()}; com_ptr data_object = new CSftpDataObject(1, pidl_array, root.get(), m_pProvider); // Test CFSTR_SHELLIDLIST (PIDL array) format cpidl_t root_child = root.last_item(); remote_itemid_view folder(root_child); _testShellPIDLFolder(data_object, folder.filename()); _testShellPIDL(data_object, remote_itemid_view(pidl).filename(), 0); // Test CFSTR_FILEDESCRIPTOR (FILEGROUPDESCRIPTOR) format _testFileDescriptor(data_object, L"testswishfile.ext", 0); // Test CFSTR_FILECONTENTS (IStream) format _testStreamContents(data_object, L"/tmp/swish/testswishfile.ext", 0); } BOOST_AUTO_TEST_CASE(CreateMulti) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl1 = create_dummy_remote_itemid(L"testswishfile.ext", false); cpidl_t pidl2 = create_dummy_remote_itemid(L"testswishfile.txt", false); cpidl_t pidl3 = create_dummy_remote_itemid(L"testswishFile", false); PCITEMID_CHILD aPidl[3]; aPidl[0] = pidl1.get(); aPidl[1] = pidl2.get(); aPidl[2] = pidl3.get(); com_ptr data_object = new CSftpDataObject(3, aPidl, root.get(), m_pProvider); // Test CFSTR_SHELLIDLIST (PIDL array) format cpidl_t root_child = root.last_item(); remote_itemid_view folder(root_child); _testShellPIDLFolder(data_object, folder.filename()); _testShellPIDL(data_object, remote_itemid_view(pidl1).filename(), 0); _testShellPIDL(data_object, remote_itemid_view(pidl2).filename(), 1); _testShellPIDL(data_object, remote_itemid_view(pidl3).filename(), 2); // Test CFSTR_FILEDESCRIPTOR (FILEGROUPDESCRIPTOR) format _testFileDescriptor(data_object, L"testswishfile.ext", 0); _testFileDescriptor(data_object, L"testswishfile.txt", 1); _testFileDescriptor(data_object, L"testswishFile", 2); // Test CFSTR_FILECONTENTS (IStream) format _testStreamContents(data_object, L"/tmp/swish/testswishfile.ext", 0); _testStreamContents(data_object, L"/tmp/swish/testswishfile.txt", 1); _testStreamContents(data_object, L"/tmp/swish/testswishFile", 2); } /** * Test that QueryGetData fails for all our formats when created with * empty PIDL list. */ BOOST_AUTO_TEST_CASE(QueryFormatsEmpty) { com_ptr data_object = new CSftpDataObject(0, NULL, NULL, m_pProvider); // Perform query tests _testQueryFormats(data_object, true); } /** * Test that none of our expected formats are in the enumerator when * created with empty PIDL list. */ BOOST_AUTO_TEST_CASE(EnumFormatsEmpty) { com_ptr data_object = new CSftpDataObject(0, NULL, NULL, m_pProvider); // Test enumerators of both GetData() and SetData() formats _testBothEnumerators(data_object, true); } /** * Test that QueryGetData responds successfully for all our formats. */ BOOST_AUTO_TEST_CASE(QueryFormats) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl = create_dummy_remote_itemid(L"testswishfile.ext", false); PCUITEMID_CHILD pidl_array[] = {pidl.get()}; com_ptr data_object = new CSftpDataObject(1, pidl_array, root.get(), m_pProvider); // Perform query tests _testQueryFormats(data_object); } /** * Test that all our expected formats are in the enumeration. */ BOOST_AUTO_TEST_CASE(EnumFormats) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl = create_dummy_remote_itemid(L"testswishfile.ext", false); PCUITEMID_CHILD pidl_array[] = {pidl.get()}; com_ptr data_object = new CSftpDataObject(1, pidl_array, root.get(), m_pProvider); // Test enumerators of both GetData() and SetData() formats _testBothEnumerators(data_object); } /** * Test that QueryGetData responds successfully for all our formats when * initialised with multiple PIDLs. */ BOOST_AUTO_TEST_CASE(QueryFormatsMulti) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl1 = create_dummy_remote_itemid(L"testswishfile.ext", false); cpidl_t pidl2 = create_dummy_remote_itemid(L"testswishfile.txt", false); cpidl_t pidl3 = create_dummy_remote_itemid(L"testswishFile", false); PCITEMID_CHILD aPidl[3]; aPidl[0] = pidl1.get(); aPidl[1] = pidl2.get(); aPidl[2] = pidl3.get(); com_ptr data_object = new CSftpDataObject(3, aPidl, root.get(), m_pProvider); // Perform query tests _testQueryFormats(data_object); } /** * Test that all our expected formats are in the enumeration when * initialised with multiple PIDLs. */ BOOST_AUTO_TEST_CASE(EnumFormatsMulti) { apidl_t root = create_dummy_root_pidl(); cpidl_t pidl1 = create_dummy_remote_itemid(L"testswishfile.ext", false); cpidl_t pidl2 = create_dummy_remote_itemid(L"testswishfile.txt", false); cpidl_t pidl3 = create_dummy_remote_itemid(L"testswishFile", false); PCITEMID_CHILD aPidl[3]; aPidl[0] = pidl1.get(); aPidl[1] = pidl2.get(); aPidl[2] = pidl3.get(); com_ptr data_object = new CSftpDataObject(3, aPidl, root.get(), m_pProvider); // Test enumerators of both GetData() and SetData() formats _testBothEnumerators(data_object); } BOOST_AUTO_TEST_CASE(FullDirectoryTree) { // This has to start at / rather than /tmp apidl_t root = fake_swish_pidl() + create_host_itemid(L"test.example.com", L"user", L"/", 22, L"Test PIDL"); cpidl_t pidl = create_dummy_remote_itemid(L"tmp", true); PCUITEMID_CHILD pidl_array[] = {pidl.get()}; com_ptr data_object = new CSftpDataObject(1, pidl_array, root.get(), m_pProvider); // Test CFSTR_SHELLIDLIST (PIDL array) format. _testShellPIDLFolder(data_object, L"/"); _testShellPIDLCount(data_object, 1); _testShellPIDL(data_object, L"tmp", 0); // Build list of paths in entire expected hierarchy // HACK: // These paths depend on the paths generated in the MockProvider. Any // slight change there kills this test vector testfiles; testfiles.push_back(L"tmp"); testfiles.push_back(L"tmp/.qtmp"); testfiles.push_back(L"tmp/.testtmphiddenfile"); testfiles.push_back(L"tmp/.testtmphiddenfolder"); testfiles.push_back(L"tmp/Testtmpfolder"); testfiles.push_back(L"tmp/another linktmpfolder"); testfiles.push_back(L"tmp/linktmpfolder"); testfiles.push_back(L"tmp/ptmp"); testfiles.push_back(L"tmp/swish"); testfiles.push_back(L"tmp/swish/.qswish"); testfiles.push_back(L"tmp/swish/.testswishhiddenfile"); testfiles.push_back(L"tmp/swish/.testswishhiddenfolder"); testfiles.push_back(L"tmp/swish/Testswishfolder"); testfiles.push_back(L"tmp/swish/another linkswishfolder"); testfiles.push_back(L"tmp/swish/linkswishfolder"); testfiles.push_back(L"tmp/swish/pswish"); testfiles.push_back(L"tmp/swish/testswishFile"); testfiles.push_back(L"tmp/swish/testswishfile"); testfiles.push_back(L"tmp/swish/testswishfile with \"quotes\" and spaces"); testfiles.push_back(L"tmp/swish/testswishfile with spaces"); testfiles.push_back(L"tmp/swish/testswishfile.."); testfiles.push_back(L"tmp/swish/testswishfile.ext"); testfiles.push_back(L"tmp/swish/testswishfile.ext.txt"); testfiles.push_back(L"tmp/swish/testswishfile.txt"); testfiles.push_back(L"tmp/swish/testswishfolder with spaces"); testfiles.push_back(L"tmp/swish/testswishfolder.bmp"); testfiles.push_back(L"tmp/swish/testswishfolder.ext"); testfiles.push_back(L"tmp/swish/this_link_is_broken_swish"); testfiles.push_back(L"tmp/testtmpFile"); testfiles.push_back(L"tmp/testtmpfile"); testfiles.push_back(L"tmp/testtmpfile with \"quotes\" and spaces"); testfiles.push_back(L"tmp/testtmpfile with spaces"); testfiles.push_back(L"tmp/testtmpfile.."); testfiles.push_back(L"tmp/testtmpfile.ext"); testfiles.push_back(L"tmp/testtmpfile.ext.txt"); testfiles.push_back(L"tmp/testtmpfile.txt"); testfiles.push_back(L"tmp/testtmpfolder with spaces"); testfiles.push_back(L"tmp/testtmpfolder.bmp"); testfiles.push_back(L"tmp/testtmpfolder.ext"); testfiles.push_back(L"tmp/this_link_is_broken_tmp"); // Test CFSTR_FILEDESCRIPTOR (FILEGROUPDESCRIPTOR) format. The // descriptor should include every item in the entire hierarchy // generated by CMockSftpProvider. for (UINT i = 0; i < testfiles.size(); ++i) { _testFileDescriptor(data_object, testfiles[i], i); } // Test CFSTR_FILECONTENTS (IStream) format. The dummy streams should // contain the absolute path to the file as a string for (UINT i = 0; i < testfiles.size(); ++i) { _testStreamContents(data_object, wstring(L"/") + testfiles[i], i); } } BOOST_AUTO_TEST_SUITE_END() } // namespace test ================================================ FILE: test/shell_folder/sftp_data_object_test.cpp ================================================ // Copyright 2009, 2010, 2011, 2012, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . /** * @file * * Unlike the tests in drop_target_test.cpp, these tests do not exercise * the CDropTarget component alone. Nor do they exercise it directly. * Instead the simulate the calls the shell itself would make to drag * a file making use of the whole Shell Namespace Folder hierarchy. */ #include "swish/shell_folder/SftpDataObject.h" // test subject #include "swish/shell_folder/data_object/FileGroupDescriptor.hpp" // accessor #include "swish/shell_folder/data_object/ShellDataObject.hpp" // accessor #include "swish/shell_folder/data_object/StorageMedium.hpp" // accessor #include "test/common_boost/helpers.hpp" // BOOST_REQUIRE_OK #include "test/fixtures/provider_fixture.hpp" #include // apidl_t, cpidl_t #include // pidl_shell_item #include // com_ptr #include #include #pragma warning(push) #pragma warning(disable : 4244) // conversion from uint64_t to uint32_t #include // from_time_t #pragma warning(pop) #include // ptime stringerising #include // numeric_cast #include #include #include // BOOST_THROW_EXCEPTION #include #include using namespace swish::shell_folder::data_object; using test::fixtures::provider_fixture; using namespace washer::shell::pidl; using washer::shell::pidl_shell_item; using comet::com_ptr; using ssh::filesystem::ifstream; using ssh::filesystem::ofstream; using ssh::filesystem::path; using ssh::filesystem::perms; using ssh::filesystem::sftp_filesystem; using boost::numeric_cast; using boost::system::system_error; using boost::system::get_system_category; using boost::posix_time::from_time_t; using boost::test_tools::predicate_result; using std::string; using std::wstring; using std::vector; using std::istreambuf_iterator; namespace comet { template <> struct comtype<::IDataObject> { static const ::IID& uuid() throw() { return ::IID_IDataObject; } typedef ::IUnknown base; }; } namespace { // private class DataObjectFixture : public provider_fixture { public: vector make_test_files(bool readonly = false) { vector files; files.push_back(new_file_in_sandbox("second")); files.push_back(new_file_in_sandbox("first")); { ofstream s(filesystem(), files.at(0)); s << "blah"; } { ofstream s(filesystem(), files.at(1)); s << "more stuff"; } if (readonly) { permissions(filesystem(), files.at(0), perms::owner_read); permissions(filesystem(), files.at(1), perms::owner_read); } return files; } }; /** * Check that two PIDLS refer to the same item. */ predicate_result pidl_equivalence(const apidl_t& pidl1, const apidl_t& pidl2) { wstring name1 = pidl_shell_item(pidl1).parsing_name(); wstring name2 = pidl_shell_item(pidl2).parsing_name(); if (name1 != name2) { predicate_result res(false); res.message() << "PIDLs resolve to different items [" << name1 << " != " << name2 << "]"; return res; } return true; } /** * Check that two PIDLS have the same representation. */ predicate_result pidl_equality(const apidl_t& pidl1, const apidl_t& pidl2) { if (!::ILIsEqual(pidl1.get(), pidl2.get())) { predicate_result res(false); res.message() << "PIDLs have different representations"; return res; } return true; } /** * Check that the contents of a file and a stream are the same */ predicate_result file_stream_equivalence(sftp_filesystem& filesystem, const path& file, const com_ptr& stream) { // Read in from file ifstream in_stream(filesystem, file); string file_contents = string(istreambuf_iterator(in_stream), istreambuf_iterator()); // Read in from stream ULARGE_INTEGER stream_size; LARGE_INTEGER zero = {0}; BOOST_REQUIRE_OK(stream->Seek(zero, STREAM_SEEK_END, &stream_size)); BOOST_REQUIRE_OK(stream->Seek(zero, STREAM_SEEK_SET, NULL)); vector buf(stream_size.LowPart); string stream_contents; if (buf.size()) { ULONG cbRead = 0; BOOST_REQUIRE_OK( stream->Read(&buf[0], numeric_cast(buf.size()), &cbRead)); stream_contents = string(&buf[0], cbRead); } if (file_contents != stream_contents) { predicate_result res(false); res.message() << "File and IStream contents do not match [" << file_contents << " != " << stream_contents << "]"; return res; } return true; } const CLIPFORMAT CF_FILEDESCRIPTORW = static_cast(::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW)); const CLIPFORMAT CF_FILECONTENTS = static_cast(::RegisterClipboardFormat(CFSTR_FILECONTENTS)); } #pragma region SftpDataObject tests BOOST_FIXTURE_TEST_SUITE(sftp_data_object_tests, DataObjectFixture) BOOST_AUTO_TEST_CASE(create) { com_ptr data_object = new CSftpDataObject(0, NULL, sandbox_pidl().get(), Provider()); BOOST_REQUIRE(data_object); } /** * Ask for the SHELLIDLIST format. * This should hold the PIDLs that the DataObject was originally initialised * with. */ BOOST_AUTO_TEST_CASE(pidls) { make_test_files(); vector pidls = pidls_in_sandbox(); com_ptr data_object = data_object_from_sandbox(); PidlFormat format(data_object); BOOST_REQUIRE_EQUAL(format.pidl_count(), pidls.size()); BOOST_REQUIRE(pidl_equality(format.file(0), sandbox_pidl() + pidls[0])); BOOST_REQUIRE(pidl_equality(format.file(1), sandbox_pidl() + pidls[1])); BOOST_REQUIRE(pidl_equality(format.parent_folder(), sandbox_pidl())); } /** * Ask for the HDROP format. * This should fail as the SftpDataObject can't render such a format. */ BOOST_AUTO_TEST_CASE(hdrop) { make_test_files(); com_ptr data_object = data_object_from_sandbox(); FORMATETC fetc = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; StorageMedium medium; HRESULT hr = data_object->GetData(&fetc, medium.out()); BOOST_REQUIRE(FAILED(hr)); } void do_filedescriptor_test(const com_ptr& data_object, sftp_filesystem& filesystem, const vector& files) { FORMATETC fetc = {CF_FILEDESCRIPTORW, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; StorageMedium medium; HRESULT hr = data_object->GetData(&fetc, medium.out()); BOOST_REQUIRE_OK(hr); FileGroupDescriptor fgd(medium.get().hGlobal); BOOST_REQUIRE_EQUAL(fgd.size(), files.size()); for (size_t i = 0; i < files.size(); ++i) { BOOST_REQUIRE_EQUAL(fgd[i].path(), files[i].filename()); BOOST_REQUIRE_EQUAL(fgd[i].file_size(), file_size(filesystem, files[i])); BOOST_REQUIRE_EQUAL(fgd[i].last_write_time(), from_time_t(last_write_time(filesystem, files[i]))); } } void do_filecontents_test(const com_ptr& data_object, sftp_filesystem& filesystem, const vector& files, size_t index) { FORMATETC fetc = {CF_FILECONTENTS, NULL, DVASPECT_CONTENT, numeric_cast(index), TYMED_ISTREAM}; StorageMedium medium; HRESULT hr = data_object->GetData(&fetc, medium.out()); BOOST_REQUIRE_OK(hr); com_ptr stream = medium.get().pstm; BOOST_REQUIRE(file_stream_equivalence(filesystem, files.at(index), stream)); } /** * Ask for the FILEDESCRIPTOR format. * This should provide streams to the test files via the SSH connection. */ BOOST_AUTO_TEST_CASE(file_descriptor) { vector files = make_test_files(); com_ptr data_object = data_object_from_sandbox(); do_filedescriptor_test(data_object, filesystem(), files); for (size_t i = 0; i < files.size(); ++i) do_filecontents_test(data_object, filesystem(), files, i); } /** * Ask for the FILEDESCRIPTOR format. * This should provide streams to the test files via the SSH connection. */ BOOST_AUTO_TEST_CASE(file_descriptor_readonly) { vector files = make_test_files(true); com_ptr data_object = data_object_from_sandbox(); do_filedescriptor_test(data_object, filesystem(), files); for (size_t i = 0; i < files.size(); ++i) do_filecontents_test(data_object, filesystem(), files, i); } BOOST_AUTO_TEST_SUITE_END() #pragma endregion ================================================ FILE: test/shell_folder/sftp_directory_test.cpp ================================================ /** @file Unit tests for the SftpDirector class. @if license Copyright (C) 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/shell_folder/SftpDirectory.h" // test subject #include "swish/atl.hpp" // Common ATL setup #include "swish/host_folder/host_pidl.hpp" // create_host_itemid #include "swish/remote_folder/remote_pidl.hpp" // remote_itemid_view, // create_remote_itemid #include "test/common_boost/helpers.hpp" // BOOST_REQUIRE_OK #include "test/common_boost/MockConsumer.hpp" // MockConsumer #include "test/common_boost/MockProvider.hpp" // MockProvider #include // apidl_t, cpidl_t #include // datetime_t #include // enum_iterator #include // com_error #include // com_ptr #include #include #include #include using test::MockProvider; using test::MockConsumer; using swish::host_folder::create_host_itemid; using swish::remote_folder::create_remote_itemid; using swish::remote_folder::remote_itemid_view; using washer::shell::pidl::apidl_t; using washer::shell::pidl::cpidl_t; using comet::com_error; using comet::com_ptr; using comet::datetime_t; using comet::enum_iterator; using boost::make_shared; using boost::shared_ptr; using std::vector; using std::wstring; namespace comet { template<> struct comtype { static const IID& uuid() throw() { return IID_IEnumIDList; } typedef IUnknown base; }; template<> struct enumerated_type_of { typedef PITEMID_CHILD is; }; /** * Copy-policy for use by enumerators of child PIDLs. */ template<> struct impl::type_policy { static void init(PITEMID_CHILD& t, const cpidl_t& s) { s.copy_to(t); } static void clear(PITEMID_CHILD& t) { ::ILFree(t); } }; } namespace { // private apidl_t test_pidl() { return apidl_t() + create_host_itemid( L"testhost", L"testuser", L"/tmp", 22); } class SftpDirectoryFixture { private: shared_ptr m_provider; com_ptr m_consumer; public: SftpDirectoryFixture() : m_provider(make_shared()), m_consumer(new MockConsumer()) {} CSftpDirectory directory() { return directory(test_pidl()); } CSftpDirectory directory(const apidl_t& pidl) { return CSftpDirectory(pidl, provider()); } shared_ptr provider() { return m_provider; } com_ptr consumer() { return m_consumer; } }; void test_enum(com_ptr pidls, SHCONTF flags) { PITEMID_CHILD pidl; ULONG fetched; HRESULT hr = pidls->Next(1, &pidl, &fetched); BOOST_REQUIRE_OK(hr); BOOST_CHECK_EQUAL(fetched, 1U); do { remote_itemid_view itemid(pidl); // Check filename BOOST_CHECK_GT(itemid.filename().size(), 0U); if (!(flags & SHCONTF_INCLUDEHIDDEN)) BOOST_CHECK_NE(itemid.filename(), L"."); // Check folderness if (!(flags & SHCONTF_FOLDERS)) BOOST_CHECK(!itemid.is_folder()); if (!(flags & SHCONTF_NONFOLDERS)) BOOST_CHECK(itemid.is_folder()); // Check group and owner exist BOOST_CHECK_GT(itemid.owner().size(), 0U); BOOST_CHECK_GT(itemid.group().size(), 0U); // Check date validity BOOST_CHECK(itemid.date_modified().good()); hr = pidls->Next(1, &pidl, &fetched); } while (hr == S_OK); BOOST_CHECK_EQUAL(hr, S_FALSE); BOOST_CHECK_EQUAL(fetched, 0U); } void test_enum(ATL::CComPtr pidls, SHCONTF flags) { test_enum(com_ptr(pidls.p), flags); } cpidl_t create_test_pidl(const wstring& filename) { return create_remote_itemid( filename, false, false, L"", L"", 0, 0, 040666, 42, datetime_t(), datetime_t()); } void standard_checks(remote_itemid_view itemid) { // Check filename is sensible BOOST_CHECK_GT(itemid.filename().size(), 0U); // Check group and owner exist BOOST_CHECK_GT(itemid.owner().size(), 0U); BOOST_CHECK_GT(itemid.group().size(), 0U); // Check date validity BOOST_CHECK(itemid.date_modified().good()); } template void expected_filenames( com_ptr listing, const wchar_t* (&expected)[size]) { vector sorted_expected(expected, expected + size); std::sort(sorted_expected.begin(), sorted_expected.end()); vector actual; enum_iterator e(listing); for (; e != enum_iterator(); ++e) { actual.push_back(remote_itemid_view(*e).filename()); } std::sort(actual.begin(), actual.end()); BOOST_CHECK_EQUAL_COLLECTIONS( actual.begin(), actual.end(), sorted_expected.begin(), sorted_expected.end()); } } #pragma region SftpDirectory tests BOOST_FIXTURE_TEST_SUITE(sftp_directory_tests, SftpDirectoryFixture) /** * When a provider returns no files, the SftpDirectory mustn't either. */ BOOST_AUTO_TEST_CASE( empty ) { SHCONTF flags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN; provider()->set_listing_behaviour(MockProvider::EmptyListing); com_ptr listing = directory().GetEnum(flags); ULONG fetched = 1; PITEMID_CHILD pidl; BOOST_CHECK_EQUAL(listing->Next(1, &pidl, &fetched), S_FALSE); BOOST_CHECK_EQUAL(fetched, 0U); } /** * Requesting everything should return folder and dotted files as well. */ BOOST_AUTO_TEST_CASE( everything ) { SHCONTF flags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN; enum_iterator e(directory().GetEnum(flags)); for (; e != enum_iterator(); ++e) { standard_checks(remote_itemid_view(*e)); } } /** * Check that link are correctly PIDLed. */ BOOST_AUTO_TEST_CASE( links ) { SHCONTF flags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN; // Keep list of what is a link to test against vector link_names; link_names.push_back(L"linktmpfolder"); link_names.push_back(L"another linktmpfolder"); link_names.push_back(L"ptmp"); link_names.push_back(L".qtmp"); link_names.push_back(L"this_link_is_broken_tmp"); enum_iterator e(directory().GetEnum(flags)); for (; e != enum_iterator(); ++e) { remote_itemid_view itemid(*e); if (std::find( link_names.begin(), link_names.end(), itemid.filename()) != link_names.end()) { BOOST_CHECK_MESSAGE( itemid.is_link(), itemid.filename() + L" is not recognised as a link"); } else { BOOST_CHECK_MESSAGE( !itemid.is_link(), itemid.filename() + L" is incorrectly recognised as a link"); } } } /** * Requesting just folders must only return folders but must return links * that target folders. */ BOOST_AUTO_TEST_CASE( only_folder ) { SHCONTF flags = SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN; enum_iterator e(directory().GetEnum(flags)); for (; e != enum_iterator(); ++e) { remote_itemid_view itemid(*e); BOOST_CHECK(itemid.is_folder()); standard_checks(itemid); } const wchar_t* expected[] = { L"Testtmpfolder", L"testtmpfolder.ext", L"testtmpfolder.bmp", L"testtmpfolder with spaces", L".testtmphiddenfolder", L"linktmpfolder", L"another linktmpfolder", L"swish"}; expected_filenames(directory().GetEnum(flags), expected); } /** * Requesting just files must only return files. */ BOOST_AUTO_TEST_CASE( only_files ) { SHCONTF flags = SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN; enum_iterator e(directory().GetEnum(flags)); for (; e != enum_iterator(); ++e) { remote_itemid_view itemid(*e); BOOST_CHECK(!itemid.is_folder()); standard_checks(itemid); } const wchar_t* expected[] = { L"testtmpfile", L"testtmpFile", L"testtmpfile.ext", L"testtmpfile.txt", L"testtmpfile with spaces", L"testtmpfile with \"quotes\" and spaces", L"testtmpfile.ext.txt", L"testtmpfile..", L".testtmphiddenfile", L"ptmp", L".qtmp", L"this_link_is_broken_tmp"}; // broken link is considered a file expected_filenames(directory().GetEnum(flags), expected); } /** * If hidden items aren't requested, they mustn't be included. */ BOOST_AUTO_TEST_CASE( no_hidden ) { SHCONTF flags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS; const wchar_t* expected[] = { L"Testtmpfolder", L"testtmpfolder.ext", L"testtmpfolder.bmp", L"testtmpfolder with spaces", L"linktmpfolder", L"another linktmpfolder", L"swish", L"testtmpfile", L"testtmpFile", L"testtmpfile.ext", L"testtmpfile.txt", L"testtmpfile with spaces", L"testtmpfile with \"quotes\" and spaces", L"testtmpfile.ext.txt", L"testtmpfile..", L"ptmp", L"this_link_is_broken_tmp"}; expected_filenames(directory().GetEnum(flags), expected); } /** * If hidden items aren't requested, they mustn't be included even when only * folders are requested. */ BOOST_AUTO_TEST_CASE( no_hidden_only_folders ) { SHCONTF flags = SHCONTF_FOLDERS; const wchar_t* expected[] = { L"Testtmpfolder", L"testtmpfolder.ext", L"testtmpfolder.bmp", L"testtmpfolder with spaces", L"linktmpfolder", L"another linktmpfolder", L"swish"}; expected_filenames(directory().GetEnum(flags), expected); } /** * If hidden items aren't requested, they mustn't be included even when only * files are requested. */ BOOST_AUTO_TEST_CASE( no_hidden_only_files ) { SHCONTF flags = SHCONTF_NONFOLDERS; const wchar_t* expected[] = { L"testtmpfile", L"testtmpFile", L"testtmpfile.ext", L"testtmpfile.txt", L"testtmpfile with spaces", L"testtmpfile with \"quotes\" and spaces", L"testtmpfile.ext.txt", L"testtmpfile..", L"ptmp", L"this_link_is_broken_tmp"}; expected_filenames(directory().GetEnum(flags), expected); } /** * Rename a file where to provider doesn't request confirmation (i.e. acts * as though the new name doesn't already exist. Check that it reports * that nothing was overwritten. */ BOOST_AUTO_TEST_CASE( rename ) { provider()->set_rename_behaviour(MockProvider::RenameOK); // PIDL of old file. Would normally come from GetEnum() cpidl_t pidl = create_test_pidl(L"testtmpfile"); BOOST_CHECK_EQUAL( directory().Rename(pidl, L"renamed to", consumer()), false); } /** * Rename a file where there are multiple segments to the path. */ BOOST_AUTO_TEST_CASE( rename_in_subfolder ) { provider()->set_rename_behaviour(MockProvider::RenameOK); // PIDL of old file. Would normally come from GetEnum() cpidl_t pidl = create_test_pidl(L"testswishfile"); BOOST_CHECK_EQUAL( directory( apidl_t() + create_host_itemid( L"testhost", L"testuser", L"/tmp/swish", 22)).Rename( pidl, L"renamed to", consumer()), false); } /** * Rename a file but make the provider request confirmation and the consumer * grant permission. Check that it reports that the file was overwritten. */ BOOST_AUTO_TEST_CASE( rename_with_confirmation_granted ) { provider()->set_rename_behaviour(MockProvider::ConfirmOverwrite); consumer()->set_confirm_overwrite_behaviour(MockConsumer::AllowOverwrite); cpidl_t pidl = create_test_pidl(L"testtmpfile"); BOOST_CHECK_EQUAL( directory().Rename(pidl, L"renamed to", consumer()), true); BOOST_CHECK(consumer()->was_asked_to_confirm_overwrite()); } namespace { bool is_com_abort(const com_error& error) { return error.hr() == E_ABORT; } bool is_com_fail(const com_error& error) { return error.hr() == E_FAIL; } } /** * Rename a file but make the provider request confirmation but the consumer * denies permission. Check that it reports that nothing was overwritten. */ BOOST_AUTO_TEST_CASE( rename_with_confirmation_denied ) { provider()->set_rename_behaviour(MockProvider::ConfirmOverwrite); consumer()->set_confirm_overwrite_behaviour(MockConsumer::PreventOverwrite); cpidl_t pidl = create_test_pidl(L"testtmpfile"); BOOST_CHECK_EXCEPTION( directory().Rename(pidl, L"renamed to", consumer()), com_error, is_com_abort); BOOST_CHECK(consumer()->was_asked_to_confirm_overwrite()); } /** * Handle error case where we tried to rename a file but the provider aborted. */ BOOST_AUTO_TEST_CASE( rename_provider_aborts ) { provider()->set_rename_behaviour(MockProvider::AbortRename); cpidl_t pidl = create_test_pidl(L"testtmpfile"); BOOST_CHECK_EXCEPTION( directory().Rename(pidl, L"renamed to", consumer()), com_error, is_com_abort); BOOST_CHECK(!consumer()->was_asked_to_confirm_overwrite()); } /** * Handle error case where we tried to rename a file but the provider failed. */ BOOST_AUTO_TEST_CASE( rename_provider_fail ) { provider()->set_rename_behaviour(MockProvider::FailRename); cpidl_t pidl = create_test_pidl(L"testtmpfile"); BOOST_CHECK_EXCEPTION( directory().Rename(pidl, L"renamed to", consumer()), com_error, is_com_fail); BOOST_CHECK(!consumer()->was_asked_to_confirm_overwrite()); } BOOST_AUTO_TEST_SUITE_END() #pragma endregion ================================================ FILE: test/shell_folder/shell_data_object_test.cpp ================================================ /** @file Unit tests for the ShellDataObject wrapper. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/shell_folder/data_object/ShellDataObject.hpp" // Test subject #include "swish/shell_folder/data_object/StorageMedium.hpp" // Test subject #include "swish/shell/shell.hpp" // data_object_for_* #include "test/fixtures/local_sandbox_fixture.hpp" #include "test/common_boost/fixtures.hpp" #include "test/common_boost/helpers.hpp" #include "test/common_boost/data_object_utils.hpp" // DataObject zip stuff #include #include #include #include #include using swish::shell_folder::data_object::ShellDataObject; // test subject using swish::shell_folder::data_object::PidlFormat; // test subject using swish::shell_folder::data_object::StorageMedium; // test subject using swish::shell::data_object_for_file; using swish::shell::data_object_for_directory; using washer::shell::pidl::apidl_t; using test::ComFixture; using test::data_object_utils::create_test_zip_file; using test::data_object_utils::data_object_for_zipfile; using test::fixtures::local_sandbox_fixture; using boost::filesystem::path; using boost::test_tools::predicate_result; using std::vector; using std::wstring; namespace { // private /** * Check that a PIDL and a filesystem path refer to the same item. */ predicate_result pidl_path_equivalence(apidl_t pidl, path path) { vector name(MAX_PATH); ::SHGetPathFromIDListW(pidl.get(), &name[0]); if (!equivalent(path, &name[0])) { predicate_result res(false); res.message() << "Different items [" << wstring(&name[0]) << " != " << path.string() << "]"; return res; } return true; } class DataObjectFixture : public ComFixture, public local_sandbox_fixture { }; } #pragma region StorageMedium tests BOOST_AUTO_TEST_SUITE(storage_medium_tests) /** * Create and destroy an instance of the StorageMedium helper object. * Check a few members to see if they are initialsed properly. */ BOOST_AUTO_TEST_CASE(storage_medium_lifecycle) { { StorageMedium medium; BOOST_REQUIRE(medium.empty()); BOOST_REQUIRE_EQUAL(medium.get().hGlobal, static_cast(NULL)); BOOST_REQUIRE_EQUAL(medium.get().pstm, static_cast(NULL)); BOOST_REQUIRE_EQUAL(medium.get().pUnkForRelease, static_cast(NULL)); } } BOOST_AUTO_TEST_SUITE_END() #pragma endregion #pragma region ShellDataObject tests BOOST_FIXTURE_TEST_SUITE(shell_data_object_tests, DataObjectFixture) /** * Detecting the CF_HDROP format for a filesystem item. * * The shell data object should always have this format for items that are * backed by a real filesystem (i.e. not virtual). This is a test of whether * we can recognise that or not. */ BOOST_AUTO_TEST_CASE(cf_hdrop_format) { path file = new_file_in_local_sandbox(); ShellDataObject data_object(data_object_for_file(file).get()); BOOST_REQUIRE(data_object.has_hdrop_format()); } /** * Detecting the CF_HDROP format for virtual items. * * A data object should not have this format for virtual items as they have no * filesystem path. This is a test of whether we can recognise that or not. */ BOOST_AUTO_TEST_CASE(cf_hdrop_format_virtual) { path zip_file = create_test_zip_file(local_sandbox()); ShellDataObject data_object(data_object_for_zipfile(zip_file).get()); BOOST_REQUIRE(!data_object.has_hdrop_format()); } /** * Detecting the CFSTR_SHELLIDLIST format for a filesystem item. * * The shell data object should always have this format. This is a test * of whether we can recognise that or not. * * @todo Unset the format and test a negative result. */ BOOST_AUTO_TEST_CASE(cfstr_shellidlist_format) { path file = new_file_in_local_sandbox(); ShellDataObject data_object(data_object_for_file(file).get()); BOOST_REQUIRE(data_object.has_pidl_format()); } /** * Detecting the CFSTR_SHELLIDLIST format for virtual items. * * The shell data object should always have this format. This is a test * of whether we can recognise that or not. * * @todo Unset the format and test a negative result. */ BOOST_AUTO_TEST_CASE(cfstr_shellidlist_format_virtual) { path zip_file = create_test_zip_file(local_sandbox()); ShellDataObject data_object(data_object_for_zipfile(zip_file).get()); BOOST_REQUIRE(data_object.has_pidl_format()); } /** * Detecting the CFSTR_FILEDESCRIPTOR format for virtual items. * * This format is expected for data objects holding virtual items. This is a * test of whether we can recognise that or not. */ BOOST_AUTO_TEST_CASE(cf_file_group_descriptor_format_virtual) { path zip_file = create_test_zip_file(local_sandbox()); ShellDataObject data_object(data_object_for_zipfile(zip_file).get()); BOOST_REQUIRE(data_object.has_file_group_descriptor_format()); } BOOST_AUTO_TEST_SUITE_END() #pragma endregion #pragma region PidlFormat tests BOOST_FIXTURE_TEST_SUITE(pidl_format_tests, DataObjectFixture) /** * Get a PIDL from a shell data object. * * Create the DataObject with one item, the test file in the sandbox. Get * the item from the data object as a PIDL and check that it can be resolved * back the to filename from which the data object was created. */ BOOST_AUTO_TEST_CASE(cfstr_shellidlist_item) { path file = new_file_in_local_sandbox(); PidlFormat format(data_object_for_file(file)); BOOST_REQUIRE_EQUAL(format.pidl_count(), 1U); apidl_t pidl = format.file(0); BOOST_REQUIRE(pidl_path_equivalence(pidl, file)); } /** * Get a PIDL's parent from a shell data object. * * Create the DataObject with one item, the test file in the sandbox. Get * the parent folder of this item (the sandbox) from the data object as a * PIDL and check that it can be resolved back the sandbox's path. */ BOOST_AUTO_TEST_CASE(cfstr_shellidlist_parent) { path file = new_file_in_local_sandbox(); PidlFormat format(data_object_for_file(file)); BOOST_REQUIRE_EQUAL(format.pidl_count(), 1U); apidl_t folder_pidl = format.parent_folder(); BOOST_REQUIRE(pidl_path_equivalence(folder_pidl, file.parent_path())); } /** * Try to get a non-existent PIDL from the data object. * * Create the DataObject with one item. Attempt to get the @b second item * from the data object. As there is no second item this should fail by * throwing an range_error exception. */ BOOST_AUTO_TEST_CASE(cfstr_shellidlist_item_fail) { path file = new_file_in_local_sandbox(); PidlFormat format(data_object_for_file(file)); BOOST_REQUIRE_EQUAL(format.pidl_count(), 1U); BOOST_REQUIRE_THROW(format.file(1), std::range_error) } /** * Get PIDLs from a shell data object with more than one. * * Create the DataObject with three items, test files in the sandbox. Get * the items from the data object as PIDLs and check that they can be resolved * back the to the filenames from which the data object was created. */ BOOST_AUTO_TEST_CASE(cfstr_shellidlist_multiple_items) { vector files; files.push_back(new_file_in_local_sandbox()); files.push_back(new_file_in_local_sandbox()); files.push_back(new_file_in_local_sandbox()); sort(files.begin(), files.end()); PidlFormat format(data_object_for_directory(local_sandbox())); BOOST_REQUIRE_EQUAL(format.pidl_count(), 3U); apidl_t pidl1 = format.file(0); apidl_t pidl2 = format.file(1); apidl_t pidl3 = format.file(2); BOOST_REQUIRE(pidl_path_equivalence(pidl1, files[0])); BOOST_REQUIRE(pidl_path_equivalence(pidl2, files[1])); BOOST_REQUIRE(pidl_path_equivalence(pidl3, files[2])); BOOST_REQUIRE_THROW(format.file(4), std::range_error) } /** * The format should respond sensibly even if created with a NULL pointer. * * The behaviour should act as though this NULL pointer were an empty * DataObject; this is the meaning the shell gives it - for instance when * nothing is selected in a folder a NULL pointer is returned as the * DataObject. */ BOOST_AUTO_TEST_CASE(null_dataobject) { PidlFormat format(NULL); BOOST_REQUIRE_EQUAL(format.pidl_count(), 0U); BOOST_REQUIRE_THROW(format.file(0), std::range_error); BOOST_REQUIRE_THROW(format.relative_file(0), std::range_error); BOOST_REQUIRE_THROW(format.parent_folder(), std::logic_error); } BOOST_AUTO_TEST_SUITE_END() #pragma endregion ================================================ FILE: test/shell_folder/utils_test.cpp ================================================ /** @file Tests common utils. @if license Copyright (C) 2009, 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #include "swish/utils.hpp" // Test subject #include "test/common_boost/helpers.hpp" #include // path #include #include using boost::filesystem::path; using std::string; using std::wstring; BOOST_AUTO_TEST_SUITE(SwishUtils) /** * Narrow a wide string. */ BOOST_AUTO_TEST_CASE( narrowing_string ) { wstring wide = L"This was a wide-char string"; string narrow = "This was a wide-char string"; string out = swish::utils::WideStringToUtf8String(wide); BOOST_REQUIRE_EQUAL(out, narrow); } /** * Narrowing an empty string produces an empty string. */ BOOST_AUTO_TEST_CASE( narrowing_empty_string ) { wstring wide = L""; string narrow = ""; string out = swish::utils::WideStringToUtf8String(wide); BOOST_REQUIRE_EQUAL(out, narrow); } /** * Widening a narrow string. */ BOOST_AUTO_TEST_CASE( widening_string ) { wstring wide = L"This was a wide-char string"; string narrow = "This was a wide-char string"; wstring out = swish::utils::Utf8StringToWideString(narrow); BOOST_REQUIRE_EQUAL(out, wide); } /** * Widening an empty string produces an empty string. */ BOOST_AUTO_TEST_CASE( widening_empty_string ) { wstring wide = L""; string narrow = ""; wstring out = swish::utils::Utf8StringToWideString(narrow); BOOST_REQUIRE_EQUAL(out, wide); } /** * Test getting current user's username. */ BOOST_AUTO_TEST_CASE( get_current_user ) { wstring name = swish::utils::current_user(); BOOST_REQUIRE_GE(name.size(), wstring(L"a").size()); } /** * Test getting current user's username (ANSI version). */ BOOST_AUTO_TEST_CASE( get_current_user_a ) { string name = swish::utils::current_user_a(); BOOST_REQUIRE_GE(name.size(), string("a").size()); } /** * Test getting current user's home directory (ANSI). */ BOOST_AUTO_TEST_CASE( get_homedir ) { path home = swish::utils::home_directory(); BOOST_CHECK(!home.empty()); BOOST_CHECK(is_directory(home)); } /** * Test getting current user's home directory (Unicode). */ BOOST_AUTO_TEST_CASE( get_homedir_w ) { path home = swish::utils::home_directory(); BOOST_CHECK(!home.empty()); BOOST_CHECK(is_directory(home)); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/shell_folder-com_dll/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES CppUnitExtensions.h HostFolder_test.cpp main.cpp Module.cpp pidl.cpp RemoteFolder_test.cpp pidl.hpp) swish_test_suite( SUBJECT shell_folder-com_dll SOURCES ${SOURCES} LIBRARIES ${Boost_LIBRARIES}) swish_copy_fixture_files(test-shell_folder-com_dll) ================================================ FILE: test/shell_folder-com_dll/CppUnitExtensions.h ================================================ /** @file Swish-specific extensions to the CppUnit facilities */ #pragma once #pragma warning(push) #pragma warning(disable:4512) // assignment operator could not be generated #include #pragma warning(pop) #include "swish/atl.hpp" // Common ATL setup #include // CString CPPUNIT_NS_BEGIN /** * Provides CPPUNIT_ASSERT_EQUAL capability for CStrings. * * @example * CString x = "bob" * CString y = "sally" * CPPUNIT_ASSERT_EQUAL( x, y ) */ template <> struct assertion_traits { static bool equal(ATL::CString x, ATL::CString y) { return x == y; } static std::string toString(ATL::CString x) { ATL::CStringA y(x); std::string s(y); return s; } }; #define STRMESSAGE_CASE(hr) case hr: strMessage = #hr; break; inline std::string GetErrorFromHResult(HRESULT hResult) { std::string strMessage; switch (hResult) { STRMESSAGE_CASE(S_OK); STRMESSAGE_CASE(S_FALSE); STRMESSAGE_CASE(E_ABORT); STRMESSAGE_CASE(E_ACCESSDENIED); STRMESSAGE_CASE(E_UNEXPECTED); STRMESSAGE_CASE(E_NOTIMPL); STRMESSAGE_CASE(E_OUTOFMEMORY); STRMESSAGE_CASE(E_INVALIDARG); STRMESSAGE_CASE(E_NOINTERFACE); STRMESSAGE_CASE(E_POINTER); STRMESSAGE_CASE(E_HANDLE); STRMESSAGE_CASE(E_FAIL); STRMESSAGE_CASE(E_PENDING); default: strMessage = ""; } char *pszMessage = NULL; if (::FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, hResult, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&pszMessage), 0, NULL) && (pszMessage != NULL)) { strMessage += ": "; strMessage += pszMessage; ::LocalFree(reinterpret_cast(pszMessage)); } return strMessage; } /** * COM HRESULT-specific assertions * @{ */ #define CPPUNIT_ASSERT_OK(hresult) \ do { \ HRESULT hrCopy; \ hrCopy = hresult; \ std::string str("COM return code was "); \ str += CPPUNIT_NS::GetErrorFromHResult(hrCopy); \ CPPUNIT_ASSERT_MESSAGE(str, hrCopy == S_OK); \ } while(0); #define CPPUNIT_ASSERT_SUCCEEDED(hresult) CPPUNIT_ASSERT(SUCCEEDED(hresult)) #define CPPUNIT_ASSERT_FAILED(hresult) CPPUNIT_ASSERT(FAILED(hresult)) /* @} */ /** * Convenience macros * @{ */ template void assertZero( const T& actual, SourceLine sourceLine, const std::string &message ) { CPPUNIT_NS::assertEquals(static_cast(0), actual, sourceLine, message); } #define CPPUNIT_ASSERT_ZERO(actual) \ do { \ CPPUNIT_NS::assertZero((actual), CPPUNIT_SOURCELINE(), \ ""#actual " != 0"); \ } while(0); /* @} */ CPPUNIT_NS_END ================================================ FILE: test/shell_folder-com_dll/HostFolder_test.cpp ================================================ /** @file Testing CHostFolder via the external COM interfaces. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "pidl.hpp" // Custom PIDL functions #include "swish/shell_folder/Swish.h" // for HostFolder UUID #include "swish/atl.hpp" // Common ATL setup #include // For IHTMLDOMTextNode2 namespace test { namespace swish { namespace com_dll { using pidl::MakeHostPidl; using ATL::CComPtr; using ATL::CComQIPtr; using ATL::CString; class CHostFolderPreInitialize_test : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE( CHostFolderPreInitialize_test ); CPPUNIT_TEST( testQueryInterface ); CPPUNIT_TEST( testGetCLSID ); CPPUNIT_TEST( testInitialize ); CPPUNIT_TEST_SUITE_END(); public: CHostFolderPreInitialize_test() : m_pFolder(NULL) { HRESULT hr; // Start up COM hr = ::CoInitialize(NULL); CPPUNIT_ASSERT_OK(hr); // One-off tests // Store HostFolder CLSID CLSID CLSID_CHostFolder; hr = ::CLSIDFromProgID(OLESTR("Swish.HostFolder"), &CLSID_CHostFolder); CPPUNIT_ASSERT_OK(hr); // Check that CLSID was correctly constructed from ProgID LPOLESTR pszUuid = NULL; hr = ::StringFromCLSID( CLSID_CHostFolder, &pszUuid ); CString strExpectedUuid = L"{b816a83a-5022-11dc-9153-0090f5284f85}"; CString strActualUuid = pszUuid; ::CoTaskMemFree(pszUuid); CPPUNIT_ASSERT_EQUAL( strExpectedUuid.MakeLower(), strActualUuid.MakeLower()); // Shut down COM ::CoUninitialize(); } void setUp() { HRESULT hr; // Start up COM hr = ::CoInitialize(NULL); CPPUNIT_ASSERT_OK(hr); // Create instance of folder using CLSID hr = m_spFolder.CoCreateInstance(__uuidof(CHostFolder)); CPPUNIT_ASSERT_OK(hr); // Copy to regular interface pointer so we can test for memory // leaks in tearDown() m_spFolder.CopyTo(&m_pFolder); } void tearDown() { try { m_spFolder.Release(); if (m_pFolder) // Test for leaking refs { CPPUNIT_ASSERT_ZERO(m_pFolder->Release()); m_pFolder = NULL; } } catch(...) { // Shut down COM ::CoUninitialize(); throw; } // Shut down COM ::CoUninitialize(); } protected: /** * Test that the class responds to IUnknown::QueryInterface correctly. * * This test will be roughly the same for *any* valid COM object except * one that implementa IHTMLDOMTextNode2 as this has been chosen to test * failure. * The cases being tested are based on those explained by Raymond Chen: * http://blogs.msdn.com/oldnewthing/archive/2004/03/26/96777.aspx */ void testQueryInterface() { HRESULT hr; // Supports IUnknown (valid COM object)? IUnknown *pUnk; hr = m_pFolder->QueryInterface(&pUnk); CPPUNIT_ASSERT_OK(hr); pUnk->Release(); // Supports IShellFolder2 (valid self!)? IShellFolder2 *pFolder; hr = m_pFolder->QueryInterface(&pFolder); CPPUNIT_ASSERT_OK(hr); pFolder->Release(); // Says no properly (Very unlikely to support this - must return NULL) IHTMLDOMTextNode2 *pShell = (IHTMLDOMTextNode2 *)this; hr = m_pFolder->QueryInterface(&pShell); if (SUCCEEDED(hr)) { pShell->Release(); CPPUNIT_ASSERT(FAILED(hr)); } CPPUNIT_ASSERT(pShell == NULL); } void testGetCLSID() { CComQIPtr spPersist(m_spFolder); CPPUNIT_ASSERT(spPersist); CLSID clsid; HRESULT hr = spPersist->GetClassID(&clsid); CPPUNIT_ASSERT_OK(hr); // Check that CLSID is correct LPOLESTR pszUuid = NULL; ::StringFromCLSID(clsid, &pszUuid); CString strExpectedUuid = L"{b816a83a-5022-11dc-9153-0090f5284f85}"; CString strActualUuid = pszUuid; ::CoTaskMemFree(pszUuid); CPPUNIT_ASSERT_EQUAL( strExpectedUuid.MakeLower(), strActualUuid.MakeLower()); } void testInitialize() { CComQIPtr spPersist(m_spFolder); CPPUNIT_ASSERT(spPersist); // Get Swish PIDL as HostFolder root PIDLIST_ABSOLUTE pidlSwish = _GetSwishPidl(); // Initialise HostFolder with its PIDL HRESULT hr = spPersist->Initialize(pidlSwish); ::ILFree(pidlSwish); CPPUNIT_ASSERT_OK(hr); } void testGetPIDL() { HRESULT hr; CComQIPtr spPersist(m_spFolder); CPPUNIT_ASSERT(spPersist); // Get Swish PIDL as HostFolder root PIDLIST_ABSOLUTE pidlSwish = _GetSwishPidl(); // We will leak this PIDL if the tests below fail // Initialise HostFolder with its PIDL hr = spPersist->Initialize(pidlSwish); CPPUNIT_ASSERT_OK(hr); // Read the PIDL back - should be identical PIDLIST_ABSOLUTE pidl; hr = spPersist->GetCurFolder(&pidl); CPPUNIT_ASSERT_OK(hr); CPPUNIT_ASSERT(::ILIsEqual(pidl, pidlSwish)); ::ILFree(pidl); ::ILFree(pidlSwish); } /** * Get the PIDL which represents the HostFolder (Swish icon) in Explorer. */ PIDLIST_ABSOLUTE _GetSwishPidl() { HRESULT hr; PIDLIST_ABSOLUTE pidl; CComPtr spDesktop; hr = ::SHGetDesktopFolder(&spDesktop); CPPUNIT_ASSERT_OK(hr); hr = spDesktop->ParseDisplayName(NULL, NULL, L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\" L"::{B816A83A-5022-11DC-9153-0090F5284F85}", NULL, reinterpret_cast(&pidl), NULL); CPPUNIT_ASSERT_OK(hr); return pidl; } CComPtr m_spFolder; IShellFolder2 *m_pFolder; }; class CHostFolderPostInitialize_test : public CHostFolderPreInitialize_test { public: CHostFolderPostInitialize_test() : CHostFolderPreInitialize_test() {} void setUp() { __super::setUp(); // Initialise HostFolder with its PIDL CComQIPtr spPersist(m_spFolder); PIDLIST_ABSOLUTE pidlSwish = _GetSwishPidl(); HRESULT hr = spPersist->Initialize(pidlSwish); ::ILFree(pidlSwish); CPPUNIT_ASSERT_OK(hr); } }; // Tests for following configuration: // ComputerPIDL\SwishPIDL\HOSTPIDL // where this RemoteFolder is rooted at: // ComputerPIDL\SwishPIDL static const wchar_t *DN_FRIENDLY_RELATIVE = L"Test PIDL"; static const wchar_t *DN_FRIENDLY_ABSOLUTE = L"sftp://user@test.example.com//home/user/dir"; static const wchar_t *DN_PARSING_RELATIVE = L"sftp://user@test.example.com:22//home/user/dir"; static const wchar_t *DN_PARSING_ABSOLUTE = L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\" L"::{B816A83A-5022-11DC-9153-0090F5284F85}\\" L"sftp://user@test.example.com:22//home/user/dir"; static const wchar_t *DN_ADDRESSBAR_RELATIVE = L"sftp://user@test.example.com//home/user/dir"; static const wchar_t *DN_ADDRESSBAR_ABSOLUTE = L"sftp://user@test.example.com//home/user/dir"; static const wchar_t *DN_PARSINGADDRESSBAR_RELATIVE = L"sftp://user@test.example.com:22//home/user/dir"; static const wchar_t *DN_PARSINGADDRESSBAR_ABSOLUTE = L"Computer\\Swish\\sftp://user@test.example.com:22//home/user/dir"; static const wchar_t *DN_EDITING_RELATIVE = L"Test PIDL"; static const wchar_t *DN_EDITING_ABSOLUTE = L"Test PIDL"; class CHostFolderDisplayName_test : public CHostFolderPostInitialize_test { CPPUNIT_TEST_SUITE( CHostFolderDisplayName_test ); CPPUNIT_TEST( testDisplayNormal ); CPPUNIT_TEST( testDisplayInFolder ); CPPUNIT_TEST( testParsingNormal ); CPPUNIT_TEST( testParsingInFolder ); CPPUNIT_TEST( testAddressbarNormal ); CPPUNIT_TEST( testAddressbarInFolder ); CPPUNIT_TEST( testEditingNormal ); CPPUNIT_TEST( testEditingInFolder ); CPPUNIT_TEST( testParsingAddressbarNormal ); CPPUNIT_TEST( testParsingAddressbarInFolder ); CPPUNIT_TEST( testParseDisplayName ); CPPUNIT_TEST_SUITE_END(); public: CHostFolderDisplayName_test() : CHostFolderPostInitialize_test() {} protected: void testDisplayNormal() { _testName(DN_FRIENDLY_ABSOLUTE, SHGDN_NORMAL); } void testDisplayInFolder() { _testName(DN_FRIENDLY_RELATIVE, SHGDN_INFOLDER); } void testParsingNormal() { _testName(DN_PARSING_ABSOLUTE, SHGDN_FORPARSING); } void testParsingInFolder() { _testName(DN_PARSING_RELATIVE, SHGDN_INFOLDER | SHGDN_FORPARSING); } void testAddressbarNormal() { _testName(DN_ADDRESSBAR_ABSOLUTE, SHGDN_FORADDRESSBAR); } void testAddressbarInFolder() { _testName( DN_ADDRESSBAR_RELATIVE, SHGDN_INFOLDER | SHGDN_FORADDRESSBAR); } void testEditingNormal() { _testName(DN_EDITING_ABSOLUTE, SHGDN_FOREDITING); } void testEditingInFolder() { _testName(DN_EDITING_RELATIVE, SHGDN_INFOLDER | SHGDN_FOREDITING); } void testParsingAddressbarNormal() { _testName( DN_PARSINGADDRESSBAR_ABSOLUTE, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING); } void testParsingAddressbarInFolder() { _testName( DN_PARSINGADDRESSBAR_RELATIVE, SHGDN_INFOLDER | SHGDN_FORADDRESSBAR | SHGDN_FORPARSING); } void testParseDisplayName() { HRESULT hr; PIDLIST_RELATIVE pidl = NULL; wchar_t wszDisplayName[MAX_PATH]; wcscpy_s(wszDisplayName, ARRAYSIZE(wszDisplayName), DN_PARSING_RELATIVE); hr = m_spFolder->ParseDisplayName( NULL, NULL, wszDisplayName, NULL, &pidl, NULL); CPPUNIT_ASSERT_OK(hr); // Check return against PIDL PITEMID_CHILD pidlTest = _CreateTestPidl(); BOOL fPidlsMatch = ::ILIsEqual( reinterpret_cast(pidl), reinterpret_cast(pidlTest)); ::ILFree(pidl); ::ILFree(pidlTest); CPPUNIT_ASSERT(fPidlsMatch); } private: void _testName(PCWSTR pwszName, SHGDNF uFlags) { CString strActual(_GetDisplayName(uFlags)); CString strExpected(pwszName); CPPUNIT_ASSERT_EQUAL(strExpected, strActual); } PITEMID_CHILD _CreateTestPidl() { // Create test HOSTPIDL return MakeHostPidl( L"user", L"test.example.com", L"/home/user/dir", 22, L"Test PIDL"); } CString _GetDisplayName(SHGDNF uFlags) { HRESULT hr; // Create test HOSTPIDL PITEMID_CHILD pidl = _CreateTestPidl(); STRRET strret; hr = m_spFolder->GetDisplayNameOf(pidl, uFlags, &strret); CPPUNIT_ASSERT_OK(hr); PWSTR pwszName; hr = ::StrRetToStr(&strret, pidl, &pwszName); CPPUNIT_ASSERT_OK(hr); CString strName(pwszName); ::CoTaskMemFree(pwszName); if (strret.uType == STRRET_WSTR) ::CoTaskMemFree(strret.pOleStr); return strName; } }; CPPUNIT_TEST_SUITE_REGISTRATION( CHostFolderPreInitialize_test ); CPPUNIT_TEST_SUITE_REGISTRATION( CHostFolderDisplayName_test ); }}} // namespace test::swish::com_dll ================================================ FILE: test/shell_folder-com_dll/Module.cpp ================================================ /** @file ATL Module required for ATL support. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "swish/atl.hpp" namespace test { namespace swish { namespace com_dll { using ATL::CAtlModule; /** * ATL module needed to use ATL-based objects, e.g. CMockSftpConsumer. */ class CModule : public CAtlModule { public : virtual HRESULT AddCommonRGSReplacements(IRegistrarBase*) throw() { return S_OK; } }; }}} // namespace test::swish::com_dll test::swish::com_dll::CModule _AtlModule; ///< Global module instance ================================================ FILE: test/shell_folder-com_dll/RemoteFolder_test.cpp ================================================ /** @file Testing CRemoteFolder via the external COM interfaces. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "pidl.hpp" // Custom PIDL functions #include "test/common/CppUnitExtensions.h" #include "swish/shell_folder/Swish.h" // for HostFolder UUID #include "swish/atl.hpp" // Common ATL setup #include // For IHTMLDOMTextNode2 namespace test { namespace swish { namespace com_dll { using pidl::MakeHostPidl; using pidl::MakeRemotePidl; using ATL::CComPtr; using ATL::CComQIPtr; using ATL::CString; class CRemoteFolderPreInitialize_test : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE( CRemoteFolderPreInitialize_test ); CPPUNIT_TEST( testQueryInterface ); CPPUNIT_TEST( testGetCLSID ); CPPUNIT_TEST( testInitialize ); CPPUNIT_TEST_SUITE_END(); public: CRemoteFolderPreInitialize_test() : m_pFolder(NULL) { HRESULT hr; // Start up COM hr = ::CoInitialize(NULL); CPPUNIT_ASSERT_OK(hr); // One-off tests // Store RemoteFolder CLSID CLSID CLSID_Folder; hr = ::CLSIDFromProgID(OLESTR("Swish.RemoteFolder"), &CLSID_Folder); CPPUNIT_ASSERT_OK(hr); // Check that CLSID was correctly constructed from ProgID LPOLESTR pszUuid = NULL; hr = ::StringFromCLSID( CLSID_Folder, &pszUuid ); CString strExpectedUuid = L"{b816a83c-5022-11dc-9153-0090f5284f85}"; CString strActualUuid = pszUuid; ::CoTaskMemFree(pszUuid); CPPUNIT_ASSERT_EQUAL( strExpectedUuid.MakeLower(), strActualUuid.MakeLower()); // Check that CLSID was correctly constructed from __uuidof() pszUuid = NULL; hr = ::StringFromCLSID( __uuidof(CRemoteFolder), &pszUuid ); strExpectedUuid = L"{b816a83c-5022-11dc-9153-0090f5284f85}"; strActualUuid = pszUuid; ::CoTaskMemFree(pszUuid); CPPUNIT_ASSERT_EQUAL( strExpectedUuid.MakeLower(), strActualUuid.MakeLower()); // Shut down COM ::CoUninitialize(); } void setUp() { HRESULT hr; // Start up COM hr = ::CoInitialize(NULL); CPPUNIT_ASSERT_OK(hr); // Create instance of folder using CLSID hr = m_spFolder.CoCreateInstance(__uuidof(CRemoteFolder)); CPPUNIT_ASSERT_OK(hr); // Copy to regular interface pointer so we can test for memory // leaks in tearDown() m_spFolder.CopyTo(&m_pFolder); } void tearDown() { try { m_spFolder.Release(); if (m_pFolder) // Test for leaking refs { CPPUNIT_ASSERT_ZERO(m_pFolder->Release()); m_pFolder = NULL; } } catch(...) { // Shut down COM ::CoUninitialize(); throw; } // Shut down COM ::CoUninitialize(); } protected: /** * Test that the class responds to IUnknown::QueryInterface correctly. * * This test will be roughly the same for *any* valid COM object except * one that implements IHTMLDOMTextNode2 as this has been chosen to test * failure. * The cases being tested are based on those explained by Raymond Chen: * http://blogs.msdn.com/oldnewthing/archive/2004/03/26/96777.aspx */ void testQueryInterface() { HRESULT hr; // Supports IUnknown (valid COM object)? IUnknown *pUnk; hr = m_pFolder->QueryInterface(&pUnk); CPPUNIT_ASSERT_OK(hr); pUnk->Release(); // Supports IShellFolder2 (valid self!)? IShellFolder2 *pFolder; hr = m_pFolder->QueryInterface(&pFolder); CPPUNIT_ASSERT_OK(hr); pFolder->Release(); // Says no properly (Very unlikely to support this - must return NULL) IHTMLDOMTextNode2 *pShell = (IHTMLDOMTextNode2 *)this; hr = m_pFolder->QueryInterface(&pShell); if (SUCCEEDED(hr)) { pShell->Release(); CPPUNIT_ASSERT(FAILED(hr)); } CPPUNIT_ASSERT(pShell == NULL); } void testGetCLSID() { CComQIPtr spPersist(m_spFolder); CPPUNIT_ASSERT(spPersist); CLSID clsid; HRESULT hr = spPersist->GetClassID(&clsid); CPPUNIT_ASSERT_OK(hr); // Check that CLSID is correct LPOLESTR pszUuid = NULL; ::StringFromCLSID(clsid, &pszUuid); CString strExpectedUuid = L"{b816a83c-5022-11dc-9153-0090f5284f85}"; CString strActualUuid = pszUuid; ::CoTaskMemFree(pszUuid); CPPUNIT_ASSERT_EQUAL( strExpectedUuid.MakeLower(), strActualUuid.MakeLower()); } void testInitialize() { CComQIPtr spPersist(m_spFolder); CPPUNIT_ASSERT(spPersist); // Get Swish PIDL + HOSTPIDL + REMOTEPIDL as RemoteFolder root PIDLIST_ABSOLUTE pidl = _CreateRootPidl(); // Initialise RemoteFolder with its PIDL HRESULT hr = spPersist->Initialize(pidl); ::ILFree(pidl); CPPUNIT_ASSERT_OK(hr); } void testGetPIDL() { HRESULT hr; CComQIPtr spPersist(m_spFolder); CPPUNIT_ASSERT(spPersist); // Get Swish PIDL + HOSTPIDL + REMOTEPIDL as RemoteFolder root PIDLIST_ABSOLUTE pidlRoot = _CreateRootPidl(); // We will leak this PIDL if the tests below fail // Initialise RemoteFolder with its PIDL hr = spPersist->Initialize(pidlRoot); CPPUNIT_ASSERT_OK(hr); // Read the PIDL back - should be identical PIDLIST_ABSOLUTE pidl; hr = spPersist->GetCurFolder(&pidl); CPPUNIT_ASSERT_OK(hr); CPPUNIT_ASSERT(::ILIsEqual(pidl, pidlRoot)); ::ILFree(pidl); ::ILFree(pidlRoot); } /** * Get root PIDL appropriate for current test. */ virtual PIDLIST_ABSOLUTE _CreateRootPidl() { return _CreateRootRemotePidl(); } /** * Get the PIDL which represents the HostFolder (Swish icon) in Explorer. */ PIDLIST_ABSOLUTE _GetSwishPidl() { HRESULT hr; PIDLIST_ABSOLUTE pidl; CComPtr spDesktop; hr = ::SHGetDesktopFolder(&spDesktop); CPPUNIT_ASSERT_OK(hr); hr = spDesktop->ParseDisplayName(NULL, NULL, L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\" L"::{B816A83A-5022-11DC-9153-0090F5284F85}", NULL, reinterpret_cast(&pidl), NULL); CPPUNIT_ASSERT_OK(hr); return pidl; } /** * Get an absolute PIDL that ends in a REMOTEPIDL to root RemoteFolder on. */ PIDLIST_ABSOLUTE _CreateRootRemotePidl() { // Create test absolute HOSTPIDL PIDLIST_ABSOLUTE pidlHost = _CreateRootHostPidl(); // Create root child REMOTEPIDL PITEMID_CHILD pidlRemote = MakeRemotePidl( L"dir", true, L"owner", L"group", 1001, 1002, false, 0677, 1024); // Concatenate to make absolute pidl to RemoteFolder root PIDLIST_ABSOLUTE pidl = ::ILCombine(pidlHost, pidlRemote); ::ILFree(pidlRemote); ::ILFree(pidlHost); return pidl; } /** * Get an absolute PIDL that ends in a HOSTPIDL to root RemoteFolder on. */ PIDLIST_ABSOLUTE _CreateRootHostPidl() { // Create absolute PIDL to Swish icon PIDLIST_ABSOLUTE pidlSwish = _GetSwishPidl(); // Create test child HOSTPIDL PITEMID_CHILD pidlHost = MakeHostPidl( L"user", L"test.example.com", L"/home/user", 22, L"Test PIDL"); // Concatenate to make absolute pidl to RemoteFolder root PIDLIST_ABSOLUTE pidl = ::ILCombine(pidlSwish, pidlHost); ::ILFree(pidlSwish); ::ILFree(pidlHost); return pidl; } CComPtr m_spFolder; IShellFolder2 *m_pFolder; }; class CRemoteFolderPostInitialize_test : public CRemoteFolderPreInitialize_test { public: CRemoteFolderPostInitialize_test() : CRemoteFolderPreInitialize_test() {} void setUp() { __super::setUp(); // Initialise RemoteFolder with its PIDL CComQIPtr spPersist(m_spFolder); PIDLIST_ABSOLUTE pidl = _CreateRootPidl(); HRESULT hr = spPersist->Initialize(pidl); ::ILFree(pidl); CPPUNIT_ASSERT_OK(hr); } }; /** * Base class of display name tests. */ class CRemoteFolderDisplayName_test : public CRemoteFolderPostInitialize_test { public: CRemoteFolderDisplayName_test() : CRemoteFolderPostInitialize_test() {} void _testName(PCWSTR pwszName, SHGDNF uFlags) { CString strActual(_GetDisplayName(uFlags)); CString strExpected(pwszName); CPPUNIT_ASSERT_EQUAL(strExpected, strActual); } CString _GetDisplayName(SHGDNF uFlags) { HRESULT hr; // Create test PIDL PITEMID_CHILD pidl = _CreateTestPidl(); STRRET strret; hr = m_spFolder->GetDisplayNameOf(pidl, uFlags, &strret); CPPUNIT_ASSERT_OK(hr); PWSTR pwszName; hr = ::StrRetToStr(&strret, pidl, &pwszName); CPPUNIT_ASSERT_OK(hr); CString strName(pwszName); ::CoTaskMemFree(pwszName); if (strret.uType == STRRET_WSTR) ::CoTaskMemFree(strret.pOleStr); return strName; } virtual PITEMID_CHILD _CreateTestPidl() PURE; }; // Tests for following configuration: // ComputerPIDL\SwishPIDL\HOSTPIDL\REMOTEPIDL\REMOTEPIDL // where this RemoteFolder is rooted at: // ComputerPIDL\SwishPIDL\HOSTPIDL\REMOTEPIDL static const wchar_t *DN1_FRIENDLY_RELATIVE = L"TestFile"; static const wchar_t *DN1_FRIENDLY_ABSOLUTE = L"TestFile"; static const wchar_t *DN1_PARSING_RELATIVE = L"TestFile.bmp"; static const wchar_t *DN1_PARSING_ABSOLUTE = L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\" L"::{B816A83A-5022-11DC-9153-0090F5284F85}\\" L"sftp://user@test.example.com:22//home/user/dir/TestFile.bmp"; static const wchar_t *DN1_ADDRESSBAR_RELATIVE = L"TestFile"; static const wchar_t *DN1_ADDRESSBAR_ABSOLUTE = L"sftp://user@test.example.com//home/user/dir/TestFile"; static const wchar_t *DN1_PARSINGADDRESSBAR_RELATIVE = L"TestFile.bmp"; static const wchar_t *DN1_PARSINGADDRESSBAR_ABSOLUTE = L"Computer\\Swish\\sftp://user@test.example.com:22/" L"/home/user/dir/TestFile.bmp"; static const wchar_t *DN1_EDITING_RELATIVE = L"TestFile.bmp"; static const wchar_t *DN1_EDITING_ABSOLUTE = L"TestFile.bmp"; class CRemoteFolderDisplayName1_test : public CRemoteFolderDisplayName_test { CPPUNIT_TEST_SUITE( CRemoteFolderDisplayName1_test ); CPPUNIT_TEST( testDisplayNormal ); CPPUNIT_TEST( testDisplayInFolder ); CPPUNIT_TEST( testParsingNormal ); CPPUNIT_TEST( testParsingInFolder ); CPPUNIT_TEST( testAddressbarNormal ); CPPUNIT_TEST( testAddressbarInFolder ); CPPUNIT_TEST( testEditingNormal ); CPPUNIT_TEST( testEditingInFolder ); CPPUNIT_TEST( testParsingAddressbarNormal ); CPPUNIT_TEST( testParsingAddressbarInFolder ); CPPUNIT_TEST_SUITE_END(); public: CRemoteFolderDisplayName1_test() : CRemoteFolderDisplayName_test() {} protected: void testDisplayNormal() { _testName(DN1_FRIENDLY_ABSOLUTE, SHGDN_NORMAL); } void testDisplayInFolder() { _testName(DN1_FRIENDLY_RELATIVE, SHGDN_INFOLDER); } void testParsingNormal() { _testName(DN1_PARSING_ABSOLUTE, SHGDN_FORPARSING); } void testParsingInFolder() { _testName(DN1_PARSING_RELATIVE, SHGDN_INFOLDER | SHGDN_FORPARSING); } void testAddressbarNormal() { _testName(DN1_ADDRESSBAR_ABSOLUTE, SHGDN_FORADDRESSBAR); } void testAddressbarInFolder() { _testName( DN1_ADDRESSBAR_RELATIVE, SHGDN_INFOLDER | SHGDN_FORADDRESSBAR); } void testEditingNormal() { _testName(DN1_EDITING_ABSOLUTE, SHGDN_FOREDITING); } void testEditingInFolder() { _testName(DN1_EDITING_RELATIVE, SHGDN_INFOLDER | SHGDN_FOREDITING); } void testParsingAddressbarNormal() { _testName( DN1_PARSINGADDRESSBAR_ABSOLUTE, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING); } void testParsingAddressbarInFolder() { _testName( DN1_PARSINGADDRESSBAR_RELATIVE, SHGDN_INFOLDER | SHGDN_FORADDRESSBAR | SHGDN_FORPARSING); } private: PITEMID_CHILD _CreateTestPidl() { // Create test REMOTEPIDL return MakeRemotePidl( L"TestFile.bmp", false, L"me", L"us", 1001, 1002, false, 0677, 511, NULL); } }; // Tests for following configuration: // ComputerPIDL\SwishPIDL\HOSTPIDL\REMOTEPIDL // where this RemoteFolder is rooted at: // ComputerPIDL\SwishPIDL\HOSTPIDL static const wchar_t *DN2_FRIENDLY_RELATIVE = L"TestDirectory.ext"; static const wchar_t *DN2_FRIENDLY_ABSOLUTE = L"TestDirectory.ext"; static const wchar_t *DN2_PARSING_RELATIVE = L"TestDirectory.ext"; static const wchar_t *DN2_PARSING_ABSOLUTE = L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\" L"::{B816A83A-5022-11DC-9153-0090F5284F85}\\" L"sftp://user@test.example.com:22//home/user/TestDirectory.ext"; static const wchar_t *DN2_ADDRESSBAR_RELATIVE = L"TestDirectory.ext"; static const wchar_t *DN2_ADDRESSBAR_ABSOLUTE = L"sftp://user@test.example.com//home/user/TestDirectory.ext"; static const wchar_t *DN2_PARSINGADDRESSBAR_RELATIVE = L"TestDirectory.ext"; static const wchar_t *DN2_PARSINGADDRESSBAR_ABSOLUTE = L"Computer\\Swish\\sftp://user@test.example.com:22/" L"/home/user/TestDirectory.ext"; static const wchar_t *DN2_EDITING_RELATIVE = L"TestDirectory.ext"; static const wchar_t *DN2_EDITING_ABSOLUTE = L"TestDirectory.ext"; class CRemoteFolderDisplayName2_test : public CRemoteFolderDisplayName_test { CPPUNIT_TEST_SUITE( CRemoteFolderDisplayName2_test ); CPPUNIT_TEST( testDisplayNormal ); CPPUNIT_TEST( testDisplayInFolder ); CPPUNIT_TEST( testParsingNormal ); CPPUNIT_TEST( testParsingInFolder ); CPPUNIT_TEST( testAddressbarNormal ); CPPUNIT_TEST( testAddressbarInFolder ); CPPUNIT_TEST( testEditingNormal ); CPPUNIT_TEST( testEditingInFolder ); CPPUNIT_TEST( testParsingAddressbarNormal ); CPPUNIT_TEST( testParsingAddressbarInFolder ); CPPUNIT_TEST_SUITE_END(); public: CRemoteFolderDisplayName2_test() : CRemoteFolderDisplayName_test() {} protected: void testDisplayNormal() { _testName(DN2_FRIENDLY_ABSOLUTE, SHGDN_NORMAL); } void testDisplayInFolder() { _testName(DN2_FRIENDLY_RELATIVE, SHGDN_INFOLDER); } void testParsingNormal() { _testName(DN2_PARSING_ABSOLUTE, SHGDN_FORPARSING); } void testParsingInFolder() { _testName(DN2_PARSING_RELATIVE, SHGDN_INFOLDER | SHGDN_FORPARSING); } void testAddressbarNormal() { _testName(DN2_ADDRESSBAR_ABSOLUTE, SHGDN_FORADDRESSBAR); } void testAddressbarInFolder() { _testName( DN2_ADDRESSBAR_RELATIVE, SHGDN_INFOLDER | SHGDN_FORADDRESSBAR); } void testEditingNormal() { _testName(DN2_EDITING_ABSOLUTE, SHGDN_FOREDITING); } void testEditingInFolder() { _testName(DN2_EDITING_RELATIVE, SHGDN_INFOLDER | SHGDN_FOREDITING); } void testParsingAddressbarNormal() { _testName( DN2_PARSINGADDRESSBAR_ABSOLUTE, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING); } void testParsingAddressbarInFolder() { _testName( DN2_PARSINGADDRESSBAR_RELATIVE, SHGDN_INFOLDER | SHGDN_FORADDRESSBAR | SHGDN_FORPARSING); } private: PITEMID_CHILD _CreateTestPidl() { // Create test REMOTEPIDL return MakeRemotePidl( L"TestDirectory.ext", true, L"me", L"us", 1001, 1002, false, 0677, 511, NULL); } /** * Get root PIDL appropriate for current test. */ virtual PIDLIST_ABSOLUTE _CreateRootPidl() { return _CreateRootHostPidl(); } }; CPPUNIT_TEST_SUITE_REGISTRATION( CRemoteFolderPreInitialize_test ); CPPUNIT_TEST_SUITE_REGISTRATION( CRemoteFolderDisplayName1_test ); CPPUNIT_TEST_SUITE_REGISTRATION( CRemoteFolderDisplayName2_test ); }}} // namespace test::swish::com_dll ================================================ FILE: test/shell_folder-com_dll/main.cpp ================================================ /** @file Main test runner implementation. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include #include #include #include #include #include /** * Run all tests displaying their names and status. * * If a test fails, display the error in a compiler compatible form. */ int main() { // Create the event manager and test controller CPPUNIT_NS::TestResult controller; // Add a listener that collects test result CPPUNIT_NS::TestResultCollector result; controller.addListener(&result); // Add a listener that print dots as test run. CPPUNIT_NS::TextTestProgressListener progress; controller.addListener(&progress); // Add the top suite to the test runner CPPUNIT_NS::TestRunner runner; runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest()); runner.run(controller); // Print test in a compiler compatible format. CPPUNIT_NS::CompilerOutputter outputter(&result, CPPUNIT_NS::stdCOut()); outputter.write(); return 0; } ================================================ FILE: test/shell_folder-com_dll/pidl.cpp ================================================ /** @file Custom PIDL functions for use only by tests. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include "pidl.hpp" namespace { // private #include /** * Duplicate of HostItemId defined in HostPidl.h. * These must be kept in sync. */ struct HostItemId { USHORT cb; DWORD dwFingerprint; WCHAR wszLabel[MAX_LABEL_LENZ]; WCHAR wszUser[MAX_USERNAME_LENZ]; WCHAR wszHost[MAX_HOSTNAME_LENZ]; WCHAR wszPath[MAX_PATH_LENZ]; USHORT uPort; static const DWORD FINGERPRINT = 0x496c1066; }; /** * Duplicate of RemoteItemId defined in RemotePidl.h. * These must be kept in sync. */ struct RemoteItemId { USHORT cb; DWORD dwFingerprint; bool fIsFolder; bool fIsLink; WCHAR wszFilename[MAX_FILENAME_LENZ]; WCHAR wszOwner[MAX_USERNAME_LENZ]; WCHAR wszGroup[MAX_USERNAME_LENZ]; ULONG uUid; ULONG uGid; DWORD dwPermissions; //WORD wPadding; ULONGLONG uSize; DATE dateModified; DATE dateAccessed; static const DWORD FINGERPRINT = 0x533aaf69; }; #include } namespace test { namespace swish { namespace com_dll { namespace pidl { PITEMID_CHILD MakeHostPidl( PCWSTR user, PCWSTR host, PCWSTR path, USHORT port, PCWSTR label) { // Allocate enough memory to hold HostItemId structure & terminator static size_t cbItem = sizeof(HostItemId) + sizeof(USHORT); HostItemId* item = static_cast(::CoTaskMemAlloc(cbItem)); ::ZeroMemory(item, cbItem); // Fill members of the PIDL with data item->cb = sizeof(*item); item->dwFingerprint = HostItemId::FINGERPRINT; // Sign with fingerprint ::wcscpy_s(item->wszUser, ARRAYSIZE(item->wszUser), user); ::wcscpy_s(item->wszHost, ARRAYSIZE(item->wszHost), host); item->uPort = port; ::wcscpy_s(item->wszPath, ARRAYSIZE(item->wszPath), path); ::wcscpy_s(item->wszLabel, ARRAYSIZE(item->wszLabel), label); return reinterpret_cast(item); } PITEMID_CHILD MakeRemotePidl( PCWSTR filename, bool fIsFolder, PCWSTR owner, PCWSTR group, ULONG uUid, ULONG uGid, bool fIsLink, DWORD dwPermissions, ULONGLONG uSize, DATE dateModified, DATE dateAccessed) { // Allocate enough memory to hold RemoteItemId structure & terminator static size_t cbItem = sizeof RemoteItemId + sizeof USHORT; RemoteItemId* item = static_cast( ::CoTaskMemAlloc(cbItem)); ::ZeroMemory(item, cbItem); // Fill members of the PIDL with data item->cb = sizeof(*item); item->dwFingerprint = RemoteItemId::FINGERPRINT; // Sign with fprint ::wcscpy_s(item->wszFilename, ARRAYSIZE(item->wszFilename), filename); ::wcscpy_s(item->wszOwner, ARRAYSIZE(item->wszOwner), owner); ::wcscpy_s(item->wszGroup, ARRAYSIZE(item->wszGroup), group); item->uUid = uUid; item->uGid = uGid; item->dwPermissions = dwPermissions; item->uSize = uSize; item->dateModified = dateModified; item->dateAccessed = dateAccessed; item->fIsFolder = fIsFolder; item->fIsLink = fIsLink; return reinterpret_cast(item); } }}}} // namespace test::swish::com_dll::pidl ================================================ FILE: test/shell_folder-com_dll/pidl.hpp ================================================ /** @file Custom PIDL functions for use only by tests. @if license Copyright (C) 2009 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #define STRICT_TYPED_ITEMIDS ///< Better type safety for PIDLs (must be ///< before or ). #include // For PIDLs and PIDL-handling functions namespace test { namespace swish { namespace com_dll { /** * PIDL-making helpers so that tests have no dependencies other than * the COM interfaces to CHostFolder and CRemoteFolder in swish.h. * * @note * These functions omit most error-checking so must only be used for tests. */ namespace pidl { PITEMID_CHILD MakeHostPidl( PCWSTR user, PCWSTR host, PCWSTR path, USHORT port=22, PCWSTR label=L""); PITEMID_CHILD MakeRemotePidl( PCWSTR filename, bool fIsFolder=false, PCWSTR owner=L"", PCWSTR group=L"", ULONG uUid=0, ULONG uGid=0, bool fIsLink=false, DWORD dwPermissions=0, ULONGLONG uSize=0, DATE dateModified=0, DATE dateAccessed=0); }}}} // namespace test::swish::com_dll::pidl ================================================ FILE: test/shell_folder-dialogue/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES # Need to get resources from shell_folder project: # http://stackoverflow.com/a/1631078/67013 KbdInteractiveDialog_test.cpp) swish_test_suite( SUBJECT shell_folder VARIANT dialogue SOURCES ${SOURCES} LIBRARIES ${CMAKE_BINARY_DIR}/swish/shell_folder/shell_folder.dir/${CMAKE_CFG_INTDIR}/shell_folder.res ${Boost_LIBRARIES} LABELS gui) ================================================ FILE: test/shell_folder-dialogue/KbdInteractiveDialog_test.cpp ================================================ /** @file Basic testing of the 'Keyboard-interactive Authentication' dialogue box. @if license Copyright (C) 2009, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. @endif */ #include #include #include #include // pair, make_pair #include #include "swish/shell_folder/KbdInteractiveDialog.h" using washer::last_error; using std::make_pair; using std::pair; using std::string; using std::vector; BOOST_AUTO_TEST_SUITE( KbdInteractiveDialog_tests ) namespace { const DWORD CLICK_DELAY = 700; /** * Sends a button click to the Cancel button of the dialog programmatically. */ DWORD WINAPI ClickCancelThread(LPVOID lpThreadParam) { CKbdInteractiveDialog *pDlg = ((CKbdInteractiveDialog *)lpThreadParam); ::Sleep(CLICK_DELAY); // Post left mouse button up/down directly to Cancel button ::PostMessage( pDlg->GetDlgItem(IDCANCEL), WM_LBUTTONDOWN, MK_LBUTTON, NULL); ::PostMessage(pDlg->GetDlgItem(IDCANCEL), WM_LBUTTONUP, NULL, NULL); return 0; } /** * Sends a button click to the OK button of the dialog programmatically. */ DWORD WINAPI ClickOKThread(LPVOID lpThreadParam) { CKbdInteractiveDialog *pDlg = ((CKbdInteractiveDialog *)lpThreadParam); ::Sleep(CLICK_DELAY); // Post left mouse button up/down directly to OK button ::PostMessage( pDlg->GetDlgItem(IDOK), WM_LBUTTONDOWN, MK_LBUTTON, NULL); ::PostMessage(pDlg->GetDlgItem(IDOK), WM_LBUTTONUP, NULL, NULL); return 0; } void _testModalDisplay(CKbdInteractiveDialog& dlg, bool fClickCancel = true) { // Launch thread which will send button click to dialog DWORD dwThreadId; HANDLE hClickThread = ::CreateThread( NULL, 0, (fClickCancel) ? ClickCancelThread : ClickOKThread, &dlg, 0, &dwThreadId ); BOOST_REQUIRE( hClickThread ); // Launch dialog (blocks until dialog ends) and check button ID INT_PTR rc = dlg.DoModal(); if (rc == -1) BOOST_THROW_EXCEPTION(last_error()); BOOST_CHECK_EQUAL(rc, (INT_PTR) ((fClickCancel) ? IDCANCEL : IDOK)); // Check that thread terminated ::Sleep(CLICK_DELAY); DWORD dwThreadStatus; ::GetExitCodeThread(hClickThread, &dwThreadStatus); BOOST_CHECK( STILL_ACTIVE != dwThreadStatus ); // Cleanup BOOST_CHECK( ::CloseHandle(hClickThread) ); hClickThread = NULL; } } BOOST_AUTO_TEST_CASE( single_prompt ) { vector> prompts; prompts.push_back(make_pair(string("Test prompt:"), true)); CKbdInteractiveDialog dlg( "server-sent name", "server-sent instruction", prompts); _testModalDisplay(dlg); } BOOST_AUTO_TEST_CASE( single_prompt_no_instruction ) { vector> prompts; prompts.push_back(make_pair(string("Test prompt:"), true)); CKbdInteractiveDialog dlg("server-sent name", "", prompts); _testModalDisplay(dlg); } BOOST_AUTO_TEST_CASE( single_prompt_no_instruction_nor_name ) { vector> prompts; prompts.push_back(make_pair(string("Test prompt:"), true)); CKbdInteractiveDialog dlg("", "", prompts); _testModalDisplay(dlg); } BOOST_AUTO_TEST_CASE( long_instruction ) { vector> prompts; prompts.push_back(make_pair(string("Test prompt:"), true)); CKbdInteractiveDialog dlg( "server-sent name", "A very very very very long instruction which, as permitted " "by the [IETF RFC 4256] SFTP specification, can contain " "linebreaks in\r\n" "Windows style\r\nUnix style\nlegacy MacOS style\rall of which " "should behave correctly.", prompts); _testModalDisplay(dlg); } BOOST_AUTO_TEST_CASE( multiple_prompts ) { vector> prompts; prompts.push_back(make_pair(string("Test prompt 1:"), true)); prompts.push_back(make_pair(string("Test prompt 2:"), false)); prompts.push_back(make_pair(string("Test prompt 3:"), true)); CKbdInteractiveDialog dlg("", "", prompts); _testModalDisplay(dlg); } BOOST_AUTO_TEST_CASE( long_prompt ) { vector> prompts; prompts.push_back(make_pair(string("Test prompt 1:"), true)); prompts.push_back( make_pair(string("Test prompt 2 which is much longer than all the " "other prompts:"), false)); prompts.push_back(make_pair(string("Test prompt 3:"), true)); CKbdInteractiveDialog dlg("", "", prompts); _testModalDisplay(dlg); } BOOST_AUTO_TEST_CASE( empty_responses_ok_clicked ) { vector> prompts; prompts.push_back(make_pair(string("Test prompt 1:"), true)); prompts.push_back(make_pair(string("Test prompt 2:"), false)); prompts.push_back(make_pair(string("Test prompt 3:"), true)); CKbdInteractiveDialog dlg("", "", prompts); _testModalDisplay(dlg, false); vector responses = dlg.GetResponses(); BOOST_CHECK_EQUAL(responses.size(), 3U); BOOST_CHECK_EQUAL(responses[0], ""); BOOST_CHECK_EQUAL(responses[1], ""); BOOST_CHECK_EQUAL(responses[2], ""); } BOOST_AUTO_TEST_CASE( empty_responses_cancel_clicked ) { vector> prompts; prompts.push_back(make_pair(string("Test prompt 1:"), true)); prompts.push_back(make_pair(string("Test prompt 2:"), false)); prompts.push_back(make_pair(string("Test prompt 3:"), true)); CKbdInteractiveDialog dlg("", "", prompts); _testModalDisplay(dlg, true); vector responses = dlg.GetResponses(); BOOST_CHECK_EQUAL(responses.size(), 0U); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: test/ssh/CMakeLists.txt ================================================ # Copyright 2015, 2016 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . hunter_add_package(Boost.Process) # Underscores to distinguish from same-name Swish fixtures. Can be renamed when # we split the projects. add_library(openssh_fixture_ openssh_fixture.cpp openssh_fixture.hpp) target_link_libraries(openssh_fixture_ PUBLIC ${Boost_LIBRARIES} PRIVATE Boost::Process) add_library(session_fixture_ session_fixture.cpp session_fixture.hpp) target_link_libraries(session_fixture_ PUBLIC openssh_fixture_ ssh) add_library(sftp_fixture_ sftp_fixture.cpp sftp_fixture.hpp) target_link_libraries(sftp_fixture_ PUBLIC session_fixture_) set(INTEGRATION_TESTS auth_test filesystem_test filesystem_construction_test host_key_test session_test input_stream_test output_stream_test stream_threading_test io_stream_test) set(UNIT_TESTS knownhost_test path_test) set(TEST_RUNNER_ARGUMENTS --result_code=yes --build_info=yes --log_level=test_suite) set(TEST_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}") include(CMakeParseArguments) # ssh_test_suite(SUBJECT test-target TESTS ... LIBRARIES ... [LABELS ...]) function(SSH_TEST_SUITE) set(options) set(oneValueArgs SUBJECT) set(multiValueArgs TESTS LIBRARIES LABELS) cmake_parse_arguments(SSH_TEST_SUITE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) foreach(_TEST_NAME ${SSH_TEST_SUITE_TESTS}) set(_TEST_EXE_NAME "test-${SSH_TEST_SUITE_SUBJECT}-${_TEST_NAME}") set(_TEST_SOURCE_FILE "${_TEST_NAME}.cpp") add_executable(${_TEST_EXE_NAME} module.cpp ${_TEST_SOURCE_FILE}) target_link_libraries(${_TEST_EXE_NAME} PRIVATE ${SSH_TEST_SUITE_SUBJECT} ${SSH_TEST_SUITE_LIBRARIES}) add_dependencies(BUILD_ALL_TESTS ${_TEST_EXE_NAME}) add_test( NAME ${_TEST_EXE_NAME} COMMAND ${_TEST_EXE_NAME} ${TEST_RUNNER_ARGUMENTS} WORKING_DIRECTORY "${TEST_DATA_DIR}") if(MEMORY_LEAKS_ARE_FAILURES) # Don't hide memory leak detection. The detector can't change the error # code so the test appears successful otherwise. set_tests_properties(${_TEST_EXE_NAME} PROPERTIES FAIL_REGULAR_EXPRESSION "Detected memory leaks") endif() if(SSH_TEST_SUITE_LABELS) set_tests_properties( ${_TEST_EXE_NAME} PROPERTIES LABELS "${SSH_TEST_SUITE_LABELS}") endif() endforeach() endfunction() ssh_test_suite( SUBJECT ssh TESTS ${INTEGRATION_TESTS} LIBRARIES ${Boost_LIBRARIES} openssh_fixture_ session_fixture_ sftp_fixture_ LABELS integration) ssh_test_suite( SUBJECT ssh VARIANT unit TESTS ${UNIT_TESTS} LIBRARIES ${Boost_LIBRARIES} LABELS unit) ================================================ FILE: test/ssh/auth_test.cpp ================================================ /** @file Tests for session authentication. @if license Copyright (C) 2010, 2012, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #include "session_fixture.hpp" // session_fixture #include // test subject #include // BOOST_CONCEPT_ASSERT #include #include // RandomAccessRangeConcept #include #include #include #include #include #include #include using boost::RandomAccessRangeConcept; using boost::move; using boost::size; using boost::system::system_error; using ssh::session; using ssh::agent_identities; using ssh::identity; using test::ssh::session_fixture; using std::auto_ptr; using std::exception; using std::string; using std::vector; BOOST_FIXTURE_TEST_SUITE(auth_tests, session_fixture) BOOST_AUTO_TEST_CASE( available_auth_methods ) { session& s = test_session(); vector methods = s.authentication_methods(user()); // 'publickey' is the only required method BOOST_REQUIRE( find(methods.begin(), methods.end(), "publickey") != methods.end()); } /** * New sessions must not be authenticated. * * Assumes the server doesn't support authentication method 'none'. */ BOOST_AUTO_TEST_CASE( intial_state ) { session& s = test_session(); BOOST_CHECK(!s.authenticated()); } // The next two test cases, password and kb-int are very limited. We can't set // the password or kb-int responses that the Cygwin OpenSSH server is expecting // so we only test the failure case. Would love to know a way round this! /** * Try password authentication. * * This will fail as we can't set a password on our fixture server. * * @todo Find a way to test the success case with the fixture server. */ BOOST_AUTO_TEST_CASE( password_fail ) { session& s = test_session(); vector methods = s.authentication_methods(user()); BOOST_REQUIRE( find(methods.begin(), methods.end(), "password") != methods.end()); BOOST_CHECK(!s.authenticate_by_password(user(), "dummy password")); BOOST_CHECK(!s.authenticated()); } namespace { /** * Callback for interactive authentication that responds with nonsense to * every request. */ class nonsense_interactor { public: template vector operator()( const string& /* request_name */, const string& /* instructions */, PromptRange prompts) { BOOST_CONCEPT_ASSERT((RandomAccessRangeConcept)); return vector(size(prompts), "gobbleygook"); } }; /** * Callback for interactive authentication that responds with too few * responses. */ class short_interactor { public: template vector operator()( const string& /* request_name */, const string& /* instructions */, PromptRange /*prompts*/) { BOOST_CONCEPT_ASSERT((RandomAccessRangeConcept)); return vector(); } }; class bob_exception {}; /** * Callback for interactive authentication that responds with too few * responses. */ class exception_interactor { public: template vector operator()( const string& /* request_name */, const string& /* instructions */, PromptRange /*prompts*/) { // Use custom exception so we can identify that the correct // exception is bubbled up in the test throw bob_exception(); } }; } /** * Try keyboard-interactive authentication but give the wrong responses. * * This will fail as we can't get Cygwin OpenSSH to use kb-int * authentication. The server will say it is supported when it isn't. * * @todo Find a way to test the case with the fixture server. */ BOOST_AUTO_TEST_CASE( kbint_fail_wrong ) { session& s = test_session(); vector methods = s.authentication_methods(user()); BOOST_REQUIRE( find(methods.begin(), methods.end(), "keyboard-interactive") != methods.end()); // FIXME: Will throw because Cygwin server refuses kb-int after claiming // to support it. Suppressing test that, currently, cannot pass. //BOOST_CHECK(!s.authenticate_interactively(user(), nonsense_interactor())); BOOST_CHECK(!s.authenticated()); } /** * Try keyboard-interactive authentication but return no responses. * * This will fail as we can't get Cygwin OpenSSH to use kb-int * authentication. The server will say it is supported when it isn't. * * @todo Find a way to test the case with the fixture server. */ BOOST_AUTO_TEST_CASE( kbint_fail_short ) { session& s = test_session(); vector methods = s.authentication_methods(user()); BOOST_REQUIRE( find(methods.begin(), methods.end(), "keyboard-interactive") != methods.end()); // FIXME: Will throw because Cygwin server refuses kb-int after claiming // to support it. Suppressing test that, currently, cannot pass. //BOOST_CHECK(!s.authenticate_interactively(user(), short_interactor())); BOOST_CHECK(!s.authenticated()); } /** * Try keyboard-interactive authentication but return no responses. * * This will fail as we can't get Cygwin OpenSSH to use kb-int * authentication. The server will say it is supported when it isn't. * * @todo Find a way to test the case with the fixture server. */ BOOST_AUTO_TEST_CASE( kbint_fail_exception ) { session& s = test_session(); vector methods = s.authentication_methods(user()); BOOST_REQUIRE( find(methods.begin(), methods.end(), "keyboard-interactive") != methods.end()); BOOST_CHECK_THROW( s.authenticate_interactively(user(), exception_interactor()), // bob_exception); exception); // FIXME: Will throw wrong kind of exception because Cygwin server refuses // kb-int after claiming to support it. Suppressing test that, currently, // cannot pass. BOOST_CHECK(!s.authenticated()); } /** * Try pubkey authentication with public key that should fail. */ BOOST_AUTO_TEST_CASE( pubkey_wrong_public ) { session& s = test_session(); BOOST_CHECK_THROW( s.authenticate_by_key_files( user(), wrong_public_key_path(), private_key_path(), ""), system_error); BOOST_CHECK(!s.authenticated()); } /** * Try pubkey authentication with private key that should fail. */ BOOST_AUTO_TEST_CASE( pubkey_wrong_private ) { session& s = test_session(); BOOST_CHECK_THROW( s.authenticate_by_key_files( user(), public_key_path(), wrong_private_key_path(), ""), system_error); BOOST_CHECK(!s.authenticated()); } /** * Try pubkey authentication with both keys (but matching pair!) that * should fail. */ BOOST_AUTO_TEST_CASE( pubkey_wrong_pair ) { session& s = test_session(); BOOST_CHECK_THROW( s.authenticate_by_key_files( user(), wrong_public_key_path(), wrong_private_key_path(), ""), system_error); BOOST_CHECK(!s.authenticated()); } /** * Try pubkey authentication with a key public that can't be parsed. */ BOOST_AUTO_TEST_CASE( pubkey_invalid_public ) { session& s = test_session(); BOOST_CHECK_THROW( s.authenticate_by_key_files( user(), private_key_path(), private_key_path(), ""), system_error); BOOST_CHECK(!s.authenticated()); } /** * Try pubkey authentication with a key private that can't be parsed. */ BOOST_AUTO_TEST_CASE( pubkey_invalid_private ) { session& s = test_session(); BOOST_CHECK_THROW( s.authenticate_by_key_files( user(), public_key_path(), public_key_path(), ""), system_error); BOOST_CHECK(!s.authenticated()); } /** * Pubkey authentication with correct keys. */ BOOST_AUTO_TEST_CASE( pubkey ) { session& s = test_session(); BOOST_CHECK(!s.authenticated()); s.authenticate_by_key_files(user(), public_key_path(), private_key_path(), ""); BOOST_CHECK(s.authenticated()); } /** * Authentication carries across to move-constructed session. */ BOOST_AUTO_TEST_CASE( move_construct_after_auth ) { session& s = test_session(); s.authenticate_by_key_files( user(), public_key_path(), private_key_path(), ""); session t(move(s)); BOOST_CHECK(t.authenticated()); } /** * Authentication carries across to move-assigned session. */ BOOST_AUTO_TEST_CASE( move_assign_after_auth ) { session& s = test_session(); s.authenticate_by_key_files( user(), public_key_path(), private_key_path(), ""); auto_ptr socket(connect_additional_socket()); session t(socket->native()); BOOST_CHECK(!t.authenticated()); t = move(s); BOOST_CHECK(t.authenticated()); } /** * Request connection to agent. Allowed to fail but not catastrophically. */ BOOST_AUTO_TEST_CASE( agent ) { session& s = test_session(); BOOST_CHECK(!s.authenticated()); try { agent_identities identities = s.agent_identities(); BOOST_FOREACH(identity i, identities) { try { i.authenticate(user()); BOOST_CHECK(s.authenticated()); return; } catch(const system_error&) {} BOOST_CHECK(!s.authenticated()); } } catch (system_error&) { /* agent not running - failure ok */ } } /** * Agent copy behaviour. */ BOOST_AUTO_TEST_CASE( agent_copy ) { session& s = test_session(); BOOST_CHECK(!s.authenticated()); try { agent_identities identities = s.agent_identities(); agent_identities identities2 = identities; BOOST_FOREACH(identity i, identities) { } BOOST_FOREACH(identity i, identities2) { } } catch (system_error&) { /* agent not running - failure ok */ } } /** * Agent idempotence - creating the object more than once should be ok. */ BOOST_AUTO_TEST_CASE( agent_idempotence ) { session& s = test_session(); BOOST_CHECK(!s.authenticated()); try { agent_identities identities = s.agent_identities(); agent_identities identities2 = s.agent_identities(); BOOST_FOREACH(identity i, identities) { } BOOST_FOREACH(identity i, identities2) { } } catch (system_error&) { /* agent not running - failure ok */ } } /** * Agent move-construct behaviour. */ BOOST_AUTO_TEST_CASE( agent_move_construct ) { session& s = test_session(); BOOST_CHECK(!s.authenticated()); try { agent_identities identities = s.agent_identities(); agent_identities identities2(move(identities)); BOOST_FOREACH(identity i, identities2) { } } catch (system_error&) { /* agent not running - failure ok */ } } /** * Agent move-assign behaviour. */ BOOST_AUTO_TEST_CASE( agent_move_assign ) { session& s = test_session(); BOOST_CHECK(!s.authenticated()); try { agent_identities identities = s.agent_identities(); agent_identities identities2 = s.agent_identities(); identities2 = move(identities); BOOST_FOREACH(identity i, identities2) { } } catch (system_error&) { /* agent not running - failure ok */ } } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/filesystem_construction_test.cpp ================================================ // Copyright 2010, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "session_fixture.hpp" #include // test subject #include #include #include #include #include // auto_ptr using ssh::filesystem::ofstream; using ssh::filesystem::sftp_filesystem; using ssh::session; using boost::move; using boost::system::system_error; using test::ssh::session_fixture; using std::auto_ptr; BOOST_AUTO_TEST_SUITE(filesystem_construction_test) BOOST_FIXTURE_TEST_CASE(construct_fail, session_fixture) { session& s = test_session(); // Session not authenticated so SFTP not possible BOOST_CHECK_THROW(s.connect_to_filesystem(), system_error); } // This tests the very basic requirements of any sensible relationship between // a filesystem and a session. It must be possible to create a filesystem // before moving the session. That's it. // // In particular, we destroy the filesystem before moving the object because // we don't want to test an added requirement that the filesystem's lifetime // can extend beyond the session's move. Whatever else we might decide the // semantics of the session-filesystem relationship should be now or in the // future, this tests must pass. Anything else would mean moving depends on // what you've used the session for in the past, which would just be broken. // // In other words, even the most careful caller would run into trouble // if this test failed. BOOST_FIXTURE_TEST_CASE(move_session_after_connecting_filesystem, session_fixture) { session& s = test_session(); s.authenticate_by_key_files(user(), public_key_path(), private_key_path(), ""); { sftp_filesystem(s.connect_to_filesystem()); } session(move(s)); } // This builds slightly on the previous test by checking that the filesystem // can be destroyed _after_ the session is moved. It still isn't a test that // the filesystem is usable afterwards (though we probably want that // property too), just that the object is valid (can be destroyed). // // In an earlier version, the filesystem destructor tried to use the moved // session causing a crash. It's very hard sometimes to ensure the filesystem // is destroyed before the exact (non-moved) session it came from, so its // important we allow destruction to happen after moving the session (but // before moved-to session is destroyed). BOOST_FIXTURE_TEST_CASE(move_session_with_live_filesystem_connection, session_fixture) { session& s = test_session(); s.authenticate_by_key_files(user(), public_key_path(), private_key_path(), ""); sftp_filesystem fs = s.connect_to_filesystem(); session s2(move(s)); // The rules are that the last session must outlive the last FS so moving // FS to inner scope ensures this sftp_filesystem(move(fs)); } // This is the third part of the session-movement tests. It strengthens the // requirements a bit more to ensure the filesystem is not just valid for // destruction but also still functions as a filesystem connection. BOOST_FIXTURE_TEST_CASE(moving_session_leaves_working_filesystem, session_fixture) { session& s = test_session(); s.authenticate_by_key_files(user(), public_key_path(), private_key_path(), ""); sftp_filesystem fs = s.connect_to_filesystem(); session s2(move(s)); // The rules are that the last session must outlive the last FS so moving // FS to inner scope ensures this sftp_filesystem fs2(move(fs)); ofstream(fs2, "/tmp/bob.txt").close(); BOOST_CHECK(exists(fs2, "/tmp/bob.txt")); } BOOST_FIXTURE_TEST_CASE(swap_session_with_live_filesystem_connection, session_fixture) { // Both sockets must outlive both session objects auto_ptr socket1(connect_additional_socket()); auto_ptr socket2(connect_additional_socket()); session s(socket1->native()); session t(socket2->native()); s.authenticate_by_key_files(user(), public_key_path(), private_key_path(), ""); t.authenticate_by_key_files(user(), public_key_path(), private_key_path(), ""); sftp_filesystem fs = s.connect_to_filesystem(); boost::swap(t, s); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/filesystem_test.cpp ================================================ // Copyright 2010, 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "session_fixture.hpp" #include "sftp_fixture.hpp" #include // test subject #include #include // bind #include // uintmax_t #include // BOOST_FOREACH #include #include #include #include #include #include #include // random_generator #include // to_string #include // find, sort, transform #include #include #include using ssh::filesystem::directory_iterator; using ssh::filesystem::file_attributes; using ssh::filesystem::file_status; using ssh::filesystem::file_type; using ssh::filesystem::ofstream; using ssh::filesystem::overwrite_behaviour; using ssh::filesystem::path; using ssh::filesystem::perms; using ssh::filesystem::sftp_file; using ssh::filesystem::sftp_filesystem; using ssh::session; using boost::bind; using boost::move; using boost::packaged_task; using boost::system::system_error; using boost::test_tools::predicate_result; using boost::thread; using boost::uintmax_t; using boost::uuids::random_generator; using test::ssh::session_fixture; using test::ssh::sftp_fixture; using std::auto_ptr; using std::find; using std::make_pair; using std::pair; using std::string; using std::vector; namespace { predicate_result directory_is_empty(sftp_filesystem& fs, const ::ssh::filesystem::path& p) { predicate_result result(true); directory_iterator it = fs.directory_iterator(p); size_t entry_count = 0; while (it != fs.directory_iterator()) { if (it->path().filename() != "." && it->path().filename() != "..") { ++entry_count; } ++it; } if (entry_count != 0) { result = false; result.message() << "Directory is not empty; contains " << entry_count << " entries"; } return result; } } namespace { class filesystem_fixture : public sftp_fixture { public: // The following functions return the link and target path as a pair. Both // paths are relative to the sandbox, regardless of whether the symlink was // created with a relative or absolute path pair create_relative_symlink_in_sandbox() { path link = sandbox() / "link"; path target = new_file_in_sandbox().filename(); create_symlink(link, target); return make_pair(link.filename(), target.filename()); } pair create_absolute_symlink_in_sandbox() { path link = sandbox() / "link"; path target = absolute_sandbox() / new_file_in_sandbox().filename(); create_symlink(link, target); return make_pair(link.filename(), target.filename()); } pair create_broken_symlink_in_sandbox() { path link = sandbox() / "link"; path target = "i don't exist"; create_symlink(link, target); return make_pair(link.filename(), target.filename()); } }; } // Tests assume an authenticated session and established SFTP filesystem BOOST_FIXTURE_TEST_SUITE(channel_running_tests, filesystem_fixture) /** * List an empty directory. * * Will contain . and .. */ BOOST_AUTO_TEST_CASE(empty_dir) { sftp_filesystem& c = filesystem(); BOOST_CHECK(directory_is_empty(c, sandbox())); } /** * List a directory that doesn't exist. Must throw. */ BOOST_AUTO_TEST_CASE(missing_dir) { sftp_filesystem& c = filesystem(); BOOST_CHECK_THROW(c.directory_iterator("/i/dont/exist"), system_error); } BOOST_AUTO_TEST_CASE(swap_filesystems) { sftp_filesystem& fs1 = filesystem(); sftp_filesystem fs2 = test_session().connect_to_filesystem(); boost::swap(fs1, fs2); BOOST_CHECK(directory_is_empty(fs1, sandbox())); BOOST_CHECK(directory_is_empty(fs2, sandbox())); } BOOST_AUTO_TEST_CASE(move_construct) { sftp_filesystem& c = filesystem(); sftp_filesystem d(move(c)); BOOST_CHECK(directory_is_empty(d, sandbox())); } BOOST_AUTO_TEST_CASE(move_assign) { sftp_filesystem& c = filesystem(); sftp_filesystem d(test_session().connect_to_filesystem()); d = move(c); BOOST_CHECK(directory_is_empty(d, sandbox())); } namespace { string filename_getter(const sftp_file& directory_entry) { return directory_entry.path().filename().native(); } } BOOST_AUTO_TEST_CASE(dir_with_one_file) { path test_file = new_file_in_sandbox(); vector files; transform(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator(), back_inserter(files), filename_getter); sort(files.begin(), files.end()); vector expected; expected.push_back(test_file.filename().native()); BOOST_CHECK_EQUAL_COLLECTIONS(files.begin(), files.end(), expected.begin(), expected.end()); } BOOST_AUTO_TEST_CASE(dir_with_multiple_files) { path test_file1 = new_file_in_sandbox(); path test_file2 = new_file_in_sandbox(); vector files(filesystem().directory_iterator(sandbox()), filesystem().directory_iterator()); sort(files.begin(), files.end()); vector::const_iterator it = files.begin(); BOOST_CHECK(it->path().filename() == path(test_file1.filename().wstring()) || it->path().filename() == path(test_file2.filename().wstring())); it++; BOOST_CHECK(it->path().filename() == path(test_file1.filename().wstring()) || it->path().filename() == path(test_file2.filename().wstring())); it++; } BOOST_AUTO_TEST_CASE(move_construct_iterator) { path test_file1 = new_file_in_sandbox(); path test_file2 = new_file_in_sandbox(); directory_iterator it = filesystem().directory_iterator(sandbox()); string path_before_move = it->path(); directory_iterator itm(move(it)); BOOST_CHECK_EQUAL(itm->path(), path_before_move); itm++; BOOST_CHECK_NE(itm->path(), path_before_move); itm++; BOOST_CHECK(itm == filesystem().directory_iterator()); } BOOST_AUTO_TEST_CASE(can_create_relative_symlink) { path link = create_relative_symlink_in_sandbox().first; BOOST_CHECK(exists(filesystem(), sandbox() / link)); BOOST_CHECK_EQUAL(find_file_in_sandbox(link).attributes().type(), file_attributes::symbolic_link); } BOOST_AUTO_TEST_CASE(can_create_absolute_symlink) { path link = create_relative_symlink_in_sandbox().first; BOOST_CHECK(exists(filesystem(), sandbox() / link)); BOOST_CHECK_EQUAL(find_file_in_sandbox(link).attributes().type(), file_attributes::symbolic_link); } BOOST_AUTO_TEST_CASE(can_create_broken_symlink) { path link = create_broken_symlink_in_sandbox().first; BOOST_CHECK(exists(filesystem(), sandbox() / link)); BOOST_CHECK_EQUAL(find_file_in_sandbox(link).attributes().type(), file_attributes::symbolic_link); } BOOST_AUTO_TEST_CASE(relative_symlinks_are_resolved_to_their_relative_target) { pair ends = create_relative_symlink_in_sandbox(); path resolved_target = filesystem().resolve_link_target(sandbox() / ends.first); BOOST_CHECK_EQUAL(resolved_target, ends.second); } BOOST_AUTO_TEST_CASE(absolute_symlinks_are_resolved_to_their_absolute_target) { pair ends = create_absolute_symlink_in_sandbox(); path resolved_target = filesystem().resolve_link_target(sandbox() / ends.first); BOOST_CHECK_EQUAL(resolved_target, absolute_sandbox() / ends.second); } BOOST_AUTO_TEST_CASE(broken_symlinks_are_resolved_to_their_non_existent_target) { pair ends = create_broken_symlink_in_sandbox(); path resolved_target = filesystem().resolve_link_target(sandbox() / ends.first); BOOST_CHECK_EQUAL(resolved_target, ends.second); } /** * Resolve a symlink to a symlink. The result should be the path of the * middle symlink, rather than the middle symlink's target. */ BOOST_AUTO_TEST_CASE(resolving_symlink_to_symlink_returns_middle_link) { pair ends = create_relative_symlink_in_sandbox(); path target = ends.second; path middle_link = ends.first; path link_to_link = sandbox() / "link2"; create_symlink(link_to_link, middle_link); path resolved_target = filesystem().resolve_link_target(link_to_link); BOOST_CHECK_EQUAL(resolved_target, middle_link); } BOOST_AUTO_TEST_CASE(canonicalising_relative_symlink_returns_absolute_path) { pair ends = create_relative_symlink_in_sandbox(); path canonical_target = filesystem().canonical_path(sandbox() / ends.first); BOOST_CHECK_EQUAL(canonical_target, absolute_sandbox() / ends.second); } BOOST_AUTO_TEST_CASE(canonicalising_absolute_symlink_returns_absolute_path) { pair ends = create_absolute_symlink_in_sandbox(); path canonical_target = filesystem().canonical_path(sandbox() / ends.first); BOOST_CHECK_EQUAL(canonical_target, absolute_sandbox() / ends.second); } BOOST_AUTO_TEST_CASE( canonicalising_symlink_to_symlink_return_absolute_path_of_final_target) { pair ends = create_relative_symlink_in_sandbox(); path target = ends.second; path middle_link = ends.first; path link_to_link = sandbox() / "link2"; create_symlink(link_to_link, middle_link); path canonical_target = filesystem().canonical_path(link_to_link); BOOST_CHECK_EQUAL(canonical_target, absolute_sandbox() / target); } BOOST_AUTO_TEST_CASE(attributes_file) { path subject = new_file_in_sandbox(); file_attributes attrs = filesystem().attributes(subject, false); BOOST_CHECK_EQUAL(attrs.type(), file_attributes::normal_file); attrs = filesystem().attributes(subject, true); BOOST_CHECK_EQUAL(attrs.type(), file_attributes::normal_file); } BOOST_AUTO_TEST_CASE(attributes_directory) { path subject = sandbox() / "testdir"; create_directory(filesystem(), subject); file_attributes attrs = filesystem().attributes(subject, false); BOOST_CHECK_EQUAL(attrs.type(), file_attributes::directory); attrs = filesystem().attributes(subject, true); BOOST_CHECK_EQUAL(attrs.type(), file_attributes::directory); } BOOST_AUTO_TEST_CASE(attributes_link) { pair ends = create_relative_symlink_in_sandbox(); path link = sandbox() / ends.first; file_attributes attrs = filesystem().attributes(link, false); BOOST_CHECK_EQUAL(attrs.type(), file_attributes::symbolic_link); attrs = filesystem().attributes(link, true); BOOST_CHECK_EQUAL(attrs.type(), file_attributes::normal_file); } BOOST_AUTO_TEST_CASE(attributes_double_link) { pair ends = create_relative_symlink_in_sandbox(); path middle_link = ends.first; path link_to_link = sandbox() / "link2"; create_symlink(link_to_link, middle_link); file_attributes attrs = filesystem().attributes(link_to_link, true); BOOST_CHECK_EQUAL(attrs.type(), file_attributes::normal_file); } BOOST_AUTO_TEST_CASE(attributes_broken_link) { pair ends = create_broken_symlink_in_sandbox(); BOOST_CHECK_THROW(filesystem().attributes(ends.first, true), system_error); } BOOST_AUTO_TEST_CASE(default_directory) { path resolved_target = filesystem().canonical_path(""); BOOST_CHECK_EQUAL(resolved_target, "/home/swish"); } BOOST_AUTO_TEST_CASE(remove_nothing) { path target = "gibberish"; bool already_existed = remove(filesystem(), target); BOOST_CHECK(!exists(filesystem(), target)); BOOST_CHECK(!already_existed); } BOOST_AUTO_TEST_CASE(remove_file) { path target = new_file_in_sandbox(); bool already_existed = remove(filesystem(), target); BOOST_CHECK(!exists(filesystem(), target)); BOOST_CHECK(already_existed); } BOOST_AUTO_TEST_CASE(remove_empty_dir) { path target = new_directory_in_sandbox(); bool already_existed = remove(filesystem(), target); BOOST_CHECK(!exists(filesystem(), target)); BOOST_CHECK(already_existed); } BOOST_AUTO_TEST_CASE(remove_non_empty_dir) { path target = new_directory_in_sandbox(); create_directory(filesystem(), target / "bob"); BOOST_CHECK_THROW(remove(filesystem(), target), system_error); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(remove_link) { path target = new_file_in_sandbox(); path link = sandbox() / "link"; create_symlink(link, target); bool already_existed = remove(filesystem(), link); BOOST_CHECK(!exists(filesystem(), link)); BOOST_CHECK(exists(filesystem(), target)); // should only delete the link BOOST_CHECK(already_existed); } BOOST_AUTO_TEST_CASE(remove_nothing_recursive) { path target = "gibberish"; uintmax_t count = remove_all(filesystem(), target); BOOST_CHECK(!exists(filesystem(), target)); BOOST_CHECK_EQUAL(count, 0U); } BOOST_AUTO_TEST_CASE(remove_file_recursive) { path target = new_file_in_sandbox(); uintmax_t count = remove_all(filesystem(), target); BOOST_CHECK(!exists(filesystem(), target)); BOOST_CHECK_EQUAL(count, 1U); } BOOST_AUTO_TEST_CASE(remove_empty_dir_recursive) { path target = new_directory_in_sandbox(); uintmax_t count = remove_all(filesystem(), target); BOOST_CHECK(!exists(filesystem(), target)); BOOST_CHECK_EQUAL(count, 1U); } BOOST_AUTO_TEST_CASE(remove_non_empty_dir_recursive) { path target = new_directory_in_sandbox(); create_directory(filesystem(), target / "bob"); ofstream(filesystem(), target / "bob" / "sally"); ofstream(filesystem(), target / "alice"); // Either side of bob alphabetically ofstream(filesystem(), target / "jim"); uintmax_t count = remove_all(filesystem(), target); BOOST_CHECK(!exists(filesystem(), target)); BOOST_CHECK_EQUAL(count, 5U); } BOOST_AUTO_TEST_CASE(remove_link_recursive) { path target = new_directory_in_sandbox(); create_directory(filesystem(), target / "bob"); path link = sandbox() / "link"; create_symlink(link, target); uintmax_t count = remove_all(filesystem(), link); BOOST_CHECK(!exists(filesystem(), link)); BOOST_CHECK(exists(filesystem(), target)); // should only delete the link BOOST_CHECK( exists(filesystem(), target / "bob")); // should only delete the link BOOST_CHECK_EQUAL(count, 1U); } BOOST_AUTO_TEST_CASE(rename_file) { path test_file = new_file_in_sandbox(); path target = sandbox() / "target"; rename(filesystem(), test_file, target, overwrite_behaviour::prevent_overwrite); BOOST_CHECK(!exists(filesystem(), test_file)); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(rename_file_obstacle_no_overwrite) { path test_file = new_file_in_sandbox(); path target = new_file_in_sandbox("target"); BOOST_CHECK_THROW(rename(filesystem(), test_file, target, overwrite_behaviour::prevent_overwrite), system_error); BOOST_CHECK(exists(filesystem(), test_file)); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(rename_file_obstacle_allow_overwrite) { path test_file = new_file_in_sandbox(); path target = new_file_in_sandbox("target"); // Using OpenSSH server which only supports SFTP 3 (no overwrite) so // failure expected BOOST_CHECK_THROW(rename(filesystem(), test_file, target, overwrite_behaviour::allow_overwrite), system_error); BOOST_CHECK(exists(filesystem(), test_file)); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(rename_file_obstacle_atomic_overwrite) { path test_file = new_file_in_sandbox(); path target = new_file_in_sandbox("target"); // Using OpenSSH server which only supports SFTP 3 (no overwrite) so // failure expected BOOST_CHECK_THROW(rename(filesystem(), test_file, target, overwrite_behaviour::atomic_overwrite), system_error); BOOST_CHECK(exists(filesystem(), test_file)); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(exists_true) { path test_file = new_file_in_sandbox(); BOOST_CHECK(exists(filesystem(), test_file)); } BOOST_AUTO_TEST_CASE(exists_false) { path test_file = sandbox() / "I do not exist"; BOOST_CHECK(!exists(filesystem(), test_file)); } BOOST_AUTO_TEST_CASE(is_directory_returns_true_for_directories) { path target = new_directory_in_sandbox(); BOOST_CHECK(is_directory(filesystem(), target)); } BOOST_AUTO_TEST_CASE(is_directory_returns_false_for_files) { path target = new_file_in_sandbox(); BOOST_CHECK(!is_directory(filesystem(), target)); } BOOST_AUTO_TEST_CASE(is_directory_returns_false_for_non_existent_path) { BOOST_CHECK(!is_directory(filesystem(), "i do not exist")); } BOOST_AUTO_TEST_CASE(is_regular_file_returns_false_for_directories) { path target = new_directory_in_sandbox(); BOOST_CHECK(!is_regular_file(filesystem(), target)); } BOOST_AUTO_TEST_CASE(is_regular_file_returns_false_for_files) { path target = new_file_in_sandbox(); BOOST_CHECK(is_regular_file(filesystem(), target)); } BOOST_AUTO_TEST_CASE(is_regular_file_returns_false_for_non_existent_path) { BOOST_CHECK(!is_regular_file(filesystem(), "i do not exist")); } BOOST_AUTO_TEST_CASE(new_directory) { path target = new_directory_in_sandbox(); remove(filesystem(), target); BOOST_CHECK(create_directory(filesystem(), target)); BOOST_CHECK(exists(filesystem(), target)); BOOST_CHECK(is_directory(filesystem(), target)); } BOOST_AUTO_TEST_CASE(new_directory_already_there) { path target = new_directory_in_sandbox(); BOOST_CHECK(!create_directory(filesystem(), target)); BOOST_CHECK(exists(filesystem(), target)); BOOST_CHECK(is_directory(filesystem(), target)); } BOOST_AUTO_TEST_CASE(new_directory_already_there_wrong_type) { path target = new_file_in_sandbox(); BOOST_CHECK_THROW(create_directory(filesystem(), target), system_error); BOOST_CHECK(exists(filesystem(), target)); BOOST_CHECK(!is_directory(filesystem(), target)); } BOOST_AUTO_TEST_CASE(status_returns_correct_file_permissions) { path target = new_file_in_sandbox(); perms permissions = status(filesystem(), target).permissions(); BOOST_CHECK_EQUAL(permissions, perms::owner_read | perms::owner_write | perms::group_read | perms::others_read); } BOOST_AUTO_TEST_CASE(status_returns_correct_file_type) { path target = new_file_in_sandbox(); file_type type = status(filesystem(), target).type(); BOOST_CHECK_EQUAL(type, file_type::regular); } BOOST_AUTO_TEST_CASE(status_returns_correct_directory_permissions) { path target = new_directory_in_sandbox(); perms permissions = status(filesystem(), target).permissions(); BOOST_CHECK_EQUAL(permissions, perms::owner_all | perms::group_read | perms::group_exec | perms::others_read | perms::others_exec); } BOOST_AUTO_TEST_CASE(status_returns_correct_directory_type) { path target = new_directory_in_sandbox(); file_type type = status(filesystem(), target).type(); BOOST_CHECK_EQUAL(type, file_type::directory); } BOOST_AUTO_TEST_CASE(status_does_not_throw_if_file_doesnt_exist) { path target = "i don't exist"; file_status s = status(filesystem(), target); BOOST_CHECK_EQUAL(s.permissions(), perms::unknown); BOOST_CHECK_EQUAL(s.type(), file_type::not_found); } BOOST_AUTO_TEST_CASE(can_set_file_permissions_exactly) { path target = new_file_in_sandbox(); permissions(filesystem(), target, perms::group_write); perms new_permissions = status(filesystem(), target).permissions(); BOOST_CHECK_EQUAL(new_permissions, perms::group_write); } BOOST_AUTO_TEST_CASE(can_set_file_permissions_to_none) { path target = new_file_in_sandbox(); permissions(filesystem(), target, perms::none); perms new_permissions = status(filesystem(), target).permissions(); BOOST_CHECK_EQUAL(new_permissions, perms::none); } BOOST_AUTO_TEST_CASE(can_add_file_permissions) { path target = new_file_in_sandbox(); permissions(filesystem(), target, perms::add_perms | perms::group_write); perms new_permissions = status(filesystem(), target).permissions(); BOOST_CHECK_EQUAL(new_permissions, perms::group_write | perms::owner_read | perms::owner_write | perms::group_read | perms::others_read); } BOOST_AUTO_TEST_CASE(can_remove_file_permissions) { path target = new_file_in_sandbox(); permissions(filesystem(), target, perms::remove_perms | perms::group_read); perms new_permissions = status(filesystem(), target).permissions(); BOOST_CHECK_EQUAL(new_permissions, perms::owner_read | perms::owner_write | perms::others_read); } BOOST_AUTO_TEST_CASE(file_size_is_returned_with_sensible_value) { string data = "mary had a little lamb"; path target = new_file_in_sandbox_containing_data(data); BOOST_CHECK_EQUAL(file_size(filesystem(), target), data.size()); } BOOST_AUTO_TEST_CASE(file_size_of_non_file_throws_error) { BOOST_CHECK_THROW(file_size(filesystem(), "/dev/console"), system_error); } BOOST_AUTO_TEST_CASE(last_write_time_returns_sensible_timestamp) { path target = new_file_in_sandbox(); time_t write_time = last_write_time(filesystem(), target); time_t now = time(0); BOOST_CHECK_LE(write_time, now); BOOST_CHECK_GT(write_time, now - 5); } BOOST_AUTO_TEST_CASE(last_write_time_of_non_file_throws_error) { BOOST_CHECK_THROW(last_write_time(filesystem(), "/dev/console"), system_error); } BOOST_AUTO_TEST_CASE(empty_file_is_empty) { path target = new_file_in_sandbox(); BOOST_CHECK(is_empty(filesystem(), target)); } BOOST_AUTO_TEST_CASE(non_empty_file_is_not_empty) { string data = "mary had a little lamb"; path target = new_file_in_sandbox_containing_data(data); BOOST_CHECK(!is_empty(filesystem(), target)); } BOOST_AUTO_TEST_CASE(empty_directory_is_empty) { path target = new_directory_in_sandbox(); BOOST_CHECK(is_empty(filesystem(), target)); } BOOST_AUTO_TEST_CASE(non_empty_directory_is_not_empty) { new_file_in_sandbox(); BOOST_CHECK(!is_empty(filesystem(), sandbox())); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/fixture_dsakey ================================================ -----BEGIN DSA PRIVATE KEY----- MIIBvAIBAAKBgQCtiYdgpPvFtfi7Ba44DiB+1x8kojjT0nRvn2hU2aa4p4fXI8kd 6Hc57VQO/lLhR9eFpxjP7m+jGwF468Q6NU8xiC71ucep0OoXS7u8RcoIoWfLDtZi DDlahnZTW04mB5fFxo2y7dYl31vE4TPdSxhqpkvnIBIstMFh2M7Dl0w8/QIVAP95 u6dg1OW6gGsRgiircsy1A9tzAoGBAIzwc5FCnJnzAJm9Hjv0AFV5l/i/DQulZ9pu EILkNiHCfDR+lTJ8VxAR7J3pgjmvYzeeRvi519ez1YriktDt66kIknQOcHB8ghyg U+dff79SkDcpg8LnX5xb3cVMgABujA0sSpaW1wwm64RXdvmoQvWu6ympUT0l0dEd oYVkb4ytAoGAJ+CGwV/1S4j1GVwa6pSP0nj4V86GWXosTTBg7GT+rKWu8lrxIcr6 FzLWgFi/gHoMrgnKWGxO1yF7vkoYM5Yfo84oBYiH+MgpiBuOrZrgzacHsA66JJbU frESRFWZl2blIPr6Gyjj6cVGgMabK3yCiTRi0v7hwffpm0rKyKv7GooCFQCyaA6T tkJunHP+F0Xg/WAUV6tcqA== -----END DSA PRIVATE KEY----- ================================================ FILE: test/ssh/fixture_dsakey.pub ================================================ ssh-dss AAAAB3NzaC1kc3MAAACBAK2Jh2Ck+8W1+LsFrjgOIH7XHySiONPSdG+faFTZprinh9cjyR3odzntVA7+UuFH14WnGM/ub6MbAXjrxDo1TzGILvW5x6nQ6hdLu7xFygihZ8sO1mIMOVqGdlNbTiYHl8XGjbLt1iXfW8ThM91LGGqmS+cgEiy0wWHYzsOXTDz9AAAAFQD/ebunYNTluoBrEYIoq3LMtQPbcwAAAIEAjPBzkUKcmfMAmb0eO/QAVXmX+L8NC6Vn2m4QguQ2IcJ8NH6VMnxXEBHsnemCOa9jN55G+LnX17PViuKS0O3rqQiSdA5wcHyCHKBT519/v1KQNymDwudfnFvdxUyAAG6MDSxKlpbXDCbrhFd2+ahC9a7rKalRPSXR0R2hhWRvjK0AAACAJ+CGwV/1S4j1GVwa6pSP0nj4V86GWXosTTBg7GT+rKWu8lrxIcr6FzLWgFi/gHoMrgnKWGxO1yF7vkoYM5Yfo84oBYiH+MgpiBuOrZrgzacHsA66JJbUfrESRFWZl2blIPr6Gyjj6cVGgMabK3yCiTRi0v7hwffpm0rKyKv7Goo= awl03@bounty ================================================ FILE: test/ssh/fixture_hostkey ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEoQIBAAKCAQEArrr/JuJmaZligyfS8vcNur+mWR2ddDQtVdhHzdKUUoR6/Om6 cvxpe61H1YZO1xCpLUBXmkki4HoNtYOpPB2W4V+8U4BDeVBD5crypEOE1+7BAm99 fnEDxYIOZq2/jTP0yQmzCpWYS3COyFmkOL7sfX1wQMeW5zQT2WKcxC6FSWbhDqrB eNEGi687hJJoJ7YXgY/IdiYW5NcOuqRSWljjGS3dAJsHHWk4nJbhjEDXbPaeduMA wQU9i6ELfP3r+q6wdu0P4jWaoo3De1aYxnToV/ldXykpipON4NPamsb6Ph2qlJQK ypq7J4iQgkIIbCU1A31+4ExvcIVoxLQw/aTSbwIBIwKCAQAd9Cu9heWrs+UAinvv Iwmq/EhnDGQijJoOt1zEMrpXSekyq7mQDgN0SZdJLPeSlSRQ5nVq5/dZroYB3A5i E7N3F7nibcJskWq5rcMyGjQHwod8wqfMiGcL6mjeZu2jLXprm0NDpJ3DyicbCA2G EhnpoHmktIBE5FsslI/nHer2o6OA/kVWSEjak+pvI1pm22T8QOBBfY0yAX7B0ebk 8o4lB4cdLf3In7Q0ahpHNOwIPdRvQ2c4Tm/DcfUBkTW2ZYGUd45cFsyHqXZscNNy GX2Wcy/FLEvQ6zBFJsNLpxCYsUyBxfSDygn9dx9RQfiWFXjdRaRPpyRAr+BTXkLU yvabAoGBANt7sxfjvu/SLkRc7TnBoJ0h/AL7Mcuu9PJmOnis4boyF9ZxqbiRiS3J yK+EKxfC0S+xf5WJ5uf7dVGnOXHXKaRl4xH90iRtryNlvtILZwHw1DTqRFxv9jtz tTRrYMEHAnMKzadgDfV/lv4iJ6nwFzK76GQ7RQNZYiGTMEh3pUNjAoGBAMvNLGpz FxhpIh+fVvRjawKgGVP87T482WOUdsF18EEPFMe6D7DO5xpLuJi+C7QkvMI8WjvD /3RGvaSh9Wt7ikLZpeogiSJy121HsEqheTR5hTx2t72ClrjZvIhLbQMRu6PqGPu/ HOC2urEGGYm7O2vnftwpuG3zIVVLM2KstPCFAoGBAM7w+VEJ7opYdMQdGi8kRvqN wbmrAxCAY0ryrCijALbexgS0T5DDu9q28Gr49W4stpquq35dc0/BNBnJje7+EVHc aGFrqOCErHHU9b66Sy23LnsIxBykFAwrRHNAq66u1mx35nk9Tv1pq58nhHun21u4 fAa7ijZblwm2qd3tJsqBAoGAEXf8ficfPJtMEVbM8GBLADmbxV7Sga1xuBQKLdbo tR6MwKmMUPvKqnuE2eRnZzZZUnoznrkHRHsXkcS9Q7ohyzc6G2Hf3mGdb8RQ8HQ9 lsiWZESwqdf+SlvOVNND27EQFV01V2gnC/JnxgfWTaJVjOf07ky4CWycdQZyHmaT Ko8CgYB58jOyXMdo2ggOCG/HX2H92KPPpFUBFCX27fCue8BZLD5quIltpXupx5oj EyltgvPcmNDgvdSadkHvP5s6nykS+n5we+d9yIIJF/BfETWsXjR3ooip+trqiirw 0aHqUDFcYn9unm2wtrMYYViiDLRijNwLZ2sG0JIU4JHyseh+NA== -----END RSA PRIVATE KEY----- ================================================ FILE: test/ssh/fixture_hostkey.pub ================================================ ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArrr/JuJmaZligyfS8vcNur+mWR2ddDQtVdhHzdKUUoR6/Om6cvxpe61H1YZO1xCpLUBXmkki4HoNtYOpPB2W4V+8U4BDeVBD5crypEOE1+7BAm99fnEDxYIOZq2/jTP0yQmzCpWYS3COyFmkOL7sfX1wQMeW5zQT2WKcxC6FSWbhDqrBeNEGi687hJJoJ7YXgY/IdiYW5NcOuqRSWljjGS3dAJsHHWk4nJbhjEDXbPaeduMAwQU9i6ELfP3r+q6wdu0P4jWaoo3De1aYxnToV/ldXykpipON4NPamsb6Ph2qlJQKypq7J4iQgkIIbCU1A31+4ExvcIVoxLQw/aTSbw== awl03@bounty ================================================ FILE: test/ssh/fixture_rsakey ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEoQIBAAKCAQEAnak1T7zHJ+hVRFBDQ9pf1KVzmd5gaNc7y7NPmL13aOG3sYeJ evi1x1WM/R3tb8XnUnzZUX9GJN0MYovvZsw9bknG1mDP72LFbGp/gzPddGIKHBBp vceDaJ85sM/ME3XOtD7uuXQuNAuEHwEzSMMiSIEMcQS+lXIcMLr5xPLEkyNvqsO5 RqSjMTLHKHgY8gLWx7oQ1avokhwuDxF7P3Pqtj+rW2Te6vR0i1H6EyFPsBkzkgNX b33cus8M1CnTmYTSgJgmHO2LLcGpjQ5sL8T/PWIWHaSqTnkrFXEMysgoteXnAYIL jzyBaqq2WV4KA3TluGdAP2p8gC32QtKmIuis3QIBIwKCAQB1Hpyhoi2LXCIVfXPM AU6AtWvRY12PtdSl8uqr+nX2JATNBZlUCTaUE6qQJNxEZyDeMNvzZdxV5gkzQ2Fi TpQIyRddbH01fJKoTxzlHzbLfAeCj9mFqicahOkHAMN8K6Ddqxe89zhD60w0SgjW 91tLzZQ2sxE70RxBdPQOpbaZLxmUZSVxRgf5djotyZqB4CcGblKCEZYJ9ZemgCnF gEcSsqcn0Jxfu+aEJ4WinN2orWs+okfgsUu9G9Ozwcy9Ptq1LkIzcwwTIpL7TTDd LMvhql39a07SysepjFRHxjvXh8Gv+SsLvKQPJHheVv8XoG0dZd+9/Eden9rHKoVm vGPLAoGBANGDQtv5K/md/3sRGeJ6Ir3/Ao+WMe8C5onck+hW4y/2yQqm3ZLzyZon KdWRj2q4dnxFZyoyDgX0UHLpM4aSsMRjn4C6vcPLcYaZ9CGB5FWPGZrq+q6vuMGK V9/fo4ZNFkNK3wo4WCSgxC1Y8XUJc3klOvPVjsmVxZaeZnkukkAFAoGBAMCkqe/S hrKITzjZuyGN90a2Nq+3xMNGuc400Qvoi27D1OcSn7SJ/K3tVWbENOH3CAlkmlZT 46IM2SRRmM0bxF3aThEwnsD5yPqgz+tcweX+gK3nXnP5JZfYF1kArXk80/eYhNE9 PwnJNXDQMoxaM0/X6BVgQyt03/Q12lH9u0j5AoGAR9U7fp6Su/uoDO/rnhs/HJHy P9u5WULSsuyKe4uBF8JTjp+cbOXeuIJ0vkCI8WPQ2iZsg37gPI5Hd9rtGDJLPATm OsOuxslowG9MY0J6K/aMb6EFfbiXHckIL3/gS02hO6SkPgSwgZY0odVaGX+VThtk q18ppDNZr/vLXL+CmZsCgYEAlJxIlG80tZxaXw5dKIN1nPL2/JUUIZz1vFShQ7Nk P4EglP+9B52lqr5mc9kwHAe1vhpobns6kvP393IlawbKrsz6ZQg/8/PkLw5XQIli YPeH1pyKsTyKtvcn9DO5BcE1zaGLB9ApULEpOcUuTwPBLvcDfjRREuUhywT44Coi w0MCgYAX5yc7/Z3R6M30rGsrgb1Y2siHYsi2LCygUj7TDGQYpaZN4afPJOT5H/Nr 7x7dgZkbOR6PQFm00VgML0XxKih59t0dcQ+2qk1LX5JDKRF/1kER3np6dpceteDu cC+MEHB/KvijnviAtBZGvD0O7oZgvbkKHESu2igXpAnfXPZFvw== -----END RSA PRIVATE KEY----- ================================================ FILE: test/ssh/fixture_rsakey.pub ================================================ ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAnak1T7zHJ+hVRFBDQ9pf1KVzmd5gaNc7y7NPmL13aOG3sYeJevi1x1WM/R3tb8XnUnzZUX9GJN0MYovvZsw9bknG1mDP72LFbGp/gzPddGIKHBBpvceDaJ85sM/ME3XOtD7uuXQuNAuEHwEzSMMiSIEMcQS+lXIcMLr5xPLEkyNvqsO5RqSjMTLHKHgY8gLWx7oQ1avokhwuDxF7P3Pqtj+rW2Te6vR0i1H6EyFPsBkzkgNXb33cus8M1CnTmYTSgJgmHO2LLcGpjQ5sL8T/PWIWHaSqTnkrFXEMysgoteXnAYILjzyBaqq2WV4KA3TluGdAP2p8gC32QtKmIuis3Q== awl03@bounty ================================================ FILE: test/ssh/fixture_wrong_dsakey ================================================ -----BEGIN DSA PRIVATE KEY----- MIIBuwIBAAKBgQCE1v/lL1VvjlJMyG7q0wAgl2tqVMzy5h1RVOtDS8bTlXLJg7ks T63wTmXlp2HedgKkfHCu7AKsjPyg1CTrvRBa8BFEvMoUDARonMwql34aiKVMy/t0 /ehnmCQV+ZMFpsVFnphJpZuXLTW1F3pnEbSNud5sACjbWb51uly5AUynuwIVAOhj rbNOaAtC1oYki8CVwpkQ8rHhAoGAYSepXRF3GJSjseYgJ2bCgcJS0L9agcvKAf+F dc+ZDJOchhnZC/hGHsjAfg62KowwKuOYsbcR3S4LJxiERcmRabww+kUIL1E8bLaQ RbOygNsHU8LyBdSx3WqC2WEOpVkTAjYDWTkbN+qkb53IBoI0GwFt5P9GHvQcAGkj GJQAWWYCgYAt7vxpDC5Xs6GxbaUupfIP95ZTMx2LqqFjqfT/81nypIHVyIlCnWMi a0mWGe4qXmHSyk6ZYnsk7Ll6WxdwUrFhd75qERyXlRK2x/v/Q3h9IOwChpHdSFx/ Tq1Zl9vMx3tmS1H0YF9tUdN7g8S5XTUSvYA+0Lzxs/9zOU5fa55+pAIVAKV45RLf hg2GNXvO68Q4tt3F6kSP -----END DSA PRIVATE KEY----- ================================================ FILE: test/ssh/fixture_wrong_dsakey.pub ================================================ ssh-dss AAAAB3NzaC1kc3MAAACBAITW/+UvVW+OUkzIburTACCXa2pUzPLmHVFU60NLxtOVcsmDuSxPrfBOZeWnYd52AqR8cK7sAqyM/KDUJOu9EFrwEUS8yhQMBGiczCqXfhqIpUzL+3T96GeYJBX5kwWmxUWemEmlm5ctNbUXemcRtI253mwAKNtZvnW6XLkBTKe7AAAAFQDoY62zTmgLQtaGJIvAlcKZEPKx4QAAAIBhJ6ldEXcYlKOx5iAnZsKBwlLQv1qBy8oB/4V1z5kMk5yGGdkL+EYeyMB+DrYqjDAq45ixtxHdLgsnGIRFyZFpvDD6RQgvUTxstpBFs7KA2wdTwvIF1LHdaoLZYQ6lWRMCNgNZORs36qRvncgGgjQbAW3k/0Ye9BwAaSMYlABZZgAAAIAt7vxpDC5Xs6GxbaUupfIP95ZTMx2LqqFjqfT/81nypIHVyIlCnWMia0mWGe4qXmHSyk6ZYnsk7Ll6WxdwUrFhd75qERyXlRK2x/v/Q3h9IOwChpHdSFx/Tq1Zl9vMx3tmS1H0YF9tUdN7g8S5XTUSvYA+0Lzxs/9zOU5fa55+pA== awl03@bounty ================================================ FILE: test/ssh/host_key_test.cpp ================================================ /** @file Tests for host_key object. @if license Copyright (C) 2010, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #include "session_fixture.hpp" // session_fixture #include #include // test subject #include // session #include #include #include #include #include #include #include using ssh::detail::session_state; using ssh::host_key; using ssh::session; using test::ssh::session_fixture; using boost::make_shared; using boost::shared_ptr; using boost::system::error_code; using boost::system::system_error; using std::string; const string EXPECTED_HOSTKEY = "AAAAB3NzaC1yc2EAAAABIwAAAQEArrr/JuJmaZligyfS8vcNur+mWR2ddDQtVdhHzdKU" "UoR6/Om6cvxpe61H1YZO1xCpLUBXmkki4HoNtYOpPB2W4V+8U4BDeVBD5crypEOE1+7B" "Am99fnEDxYIOZq2/jTP0yQmzCpWYS3COyFmkOL7sfX1wQMeW5zQT2WKcxC6FSWbhDqrB" "eNEGi687hJJoJ7YXgY/IdiYW5NcOuqRSWljjGS3dAJsHHWk4nJbhjEDXbPaeduMAwQU9" "i6ELfP3r+q6wdu0P4jWaoo3De1aYxnToV/ldXykpipON4NPamsb6Ph2qlJQKypq7J4iQ" "gkIIbCU1A31+4ExvcIVoxLQw/aTSbw=="; BOOST_FIXTURE_TEST_SUITE(host_key_tests, session_fixture) namespace { string base64_decode(const string& input) { shared_ptr session = make_shared(); char* data; unsigned int data_len; int rc = libssh2_base64_decode( session->session_ptr(), &data, &data_len, input.data(), input.size()); if (rc) { string message; error_code ec = ssh::detail::last_error_code( session->session_ptr(), message); BOOST_THROW_EXCEPTION(system_error(ec, message)); } string out(data, data_len); free(data); return out; } } /** * Server hostkey corresponds to test key when connected. */ BOOST_AUTO_TEST_CASE( hostkey ) { session& s = test_session(); host_key key = s.hostkey(); string expected = base64_decode(EXPECTED_HOSTKEY); BOOST_CHECK_EQUAL(key.key(), expected); BOOST_CHECK_EQUAL(key.algorithm(), ssh::hostkey_type::ssh_rsa); BOOST_CHECK_EQUAL(key.algorithm_name(), "ssh-rsa"); BOOST_CHECK(!key.is_base64()); } /** * Hashed (MD5) hostkey should print as: * 0C 0E D1 A5 BB 10 27 5F 76 92 4C E1 87 CE 5C 5E * in hex. */ BOOST_AUTO_TEST_CASE( hostkey_md5 ) { host_key key = test_session().hostkey(); string hex_hash(ssh::hexify(key.md5_hash(), " ", true)); BOOST_CHECK_EQUAL( hex_hash, "0C 0E D1 A5 BB 10 27 5F 76 92 4C E1 87 CE 5C 5E"); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/input_stream_test.cpp ================================================ // Copyright 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "sftp_fixture.hpp" #include // test subject #include #include #include // random_generator #include // to_string #include #include using ssh::filesystem::ifstream; using ssh::filesystem::openmode; using ssh::filesystem::path; using ssh::filesystem::perms; using ssh::filesystem::sftp_filesystem; using boost::uuids::random_generator; using boost::system::system_error; using test::ssh::sftp_fixture; using std::runtime_error; using std::string; using std::vector; namespace { // the large data must fill more than one stream buffer (currently set to // 32768 (see DEFAULT_BUFFER_SIZE) string large_data() { string data; for (int i = 0; i < 32000; ++i) { data.push_back('a'); data.push_back('m'); data.push_back('z'); } return data; } string large_binary_data() { string data; for (int i = 0; i < 32000; ++i) { data.push_back('a'); data.push_back('\n'); data.push_back('\0'); data.push_back('\r'); data.push_back('\n'); data.push_back(-1); } return data; } void make_file_read_only(sftp_filesystem& filesystem, const path& target) { permissions(filesystem, target, perms::owner_read); } const wchar_t WIDE_STRING1[] = L"\x92e\x939\x938\x941\x938"; } BOOST_FIXTURE_TEST_SUITE(ifstream_tests, sftp_fixture) BOOST_AUTO_TEST_CASE(input_stream_multiple_streams) { path target1 = new_file_in_sandbox(); path target2 = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); ifstream s1(chan, target1); ifstream s2(chan, target2); } BOOST_AUTO_TEST_CASE(input_stream_multiple_streams_to_same_file) { path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); ifstream s1(chan, target); ifstream s2(chan, target); } BOOST_AUTO_TEST_CASE(input_stream_readable) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ifstream s(filesystem(), target); string bob; BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "gobbledy"); BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "gook"); BOOST_CHECK(!(s >> bob)); BOOST_CHECK(s.eof()); } BOOST_AUTO_TEST_CASE(input_stream_unicode_readable) { path target = new_file_in_sandbox_containing_data(WIDE_STRING1, "gobbledy gook"); ifstream s(filesystem(), target); string bob; BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "gobbledy"); BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "gook"); BOOST_CHECK(!(s >> bob)); BOOST_CHECK(s.eof()); } BOOST_AUTO_TEST_CASE(input_stream_readable_multiple_buffers) { // large enough to span multiple buffers string expected_data(large_data()); path target = new_file_in_sandbox_containing_data(expected_data); sftp_filesystem& chan = filesystem(); ifstream input_stream(chan, target); string bob; vector buffer(expected_data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), expected_data.begin(), expected_data.end()); } // Test with Boost.IOStreams buffer disabled. // Should call directly to libssh2 BOOST_AUTO_TEST_CASE(input_stream_readable_no_buffer) { string expected_data("gobbeldy gook"); path target = new_file_in_sandbox_containing_data(expected_data); sftp_filesystem& chan = filesystem(); ifstream input_stream(chan, target, openmode::in, 0); string bob; vector buffer(expected_data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), expected_data.begin(), expected_data.end()); } BOOST_AUTO_TEST_CASE(input_stream_readable_binary_data) { string expected_data("gobbledy gook\0after-null\x12\11", 26); path target = new_file_in_sandbox_containing_data(expected_data); sftp_filesystem& chan = filesystem(); ifstream input_stream(chan, target); string bob; vector buffer(expected_data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), expected_data.begin(), expected_data.end()); } BOOST_AUTO_TEST_CASE(input_stream_readable_binary_data_multiple_buffers) { // large enough to span multiple buffers string expected_data(large_binary_data()); path target = new_file_in_sandbox_containing_data(expected_data); sftp_filesystem& chan = filesystem(); ifstream input_stream(chan, target); string bob; vector buffer(expected_data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), expected_data.begin(), expected_data.end()); } BOOST_AUTO_TEST_CASE(input_stream_readable_binary_data_stream_op) { string expected_data("gobbledy gook\0after-null\x12\x11", 26); path target = new_file_in_sandbox_containing_data(expected_data); sftp_filesystem& chan = filesystem(); ifstream input_stream(chan, target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gobbledy"); BOOST_CHECK(input_stream >> bob); const char* gn = "gook\0after-null\x12\x11"; BOOST_CHECK_EQUAL_COLLECTIONS(bob.begin(), bob.end(), gn, gn + 17); BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(input_stream_does_not_create_by_default) { random_generator generator; path target = to_string(generator()); BOOST_REQUIRE(!exists(filesystem(), target)); BOOST_CHECK_THROW(ifstream(filesystem(), target), system_error); BOOST_CHECK(!exists(filesystem(), target)); } /* FIXME: find why this is failing in libssh2 BOOST_AUTO_TEST_CASE( input_stream_does_not_create_with_ridiculously_large_filename ) { // We intentionally pass a large amount of data as the filename. // When we did this accidentally, we found it was not getting an error code but hit an assertion because opening the file failed. path target = large_data(); BOOST_REQUIRE(!exists(filesystem(), target)); BOOST_CHECK_THROW( ifstream(filesystem(), target), system_error); BOOST_CHECK(!exists(filesystem(), target)); } */ BOOST_AUTO_TEST_CASE(input_stream_opens_read_only_by_default) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); ifstream(filesystem(), target); } BOOST_AUTO_TEST_CASE(input_stream_in_flag_does_not_create) { random_generator generator; path target = to_string(generator()); BOOST_CHECK_THROW(ifstream(filesystem(), target, openmode::in), system_error); BOOST_CHECK(!exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(input_stream_std_in_flag_does_not_create) { random_generator generator; path target = to_string(generator()); BOOST_CHECK_THROW(ifstream(filesystem(), target, std::ios_base::in), system_error); BOOST_CHECK(!exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(input_stream_in_flag_opens_read_only) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); ifstream(filesystem(), target, openmode::in); } BOOST_AUTO_TEST_CASE(input_stream_out_flag_does_not_create) { // Because ifstream forces in as well as out an in suppresses creation random_generator generator; path target = to_string(generator()); BOOST_CHECK_THROW(ifstream(filesystem(), target, openmode::out), system_error); BOOST_CHECK(!exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(input_stream_out_flag_fails_to_open_read_only) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); BOOST_CHECK_THROW(ifstream(filesystem(), target, openmode::out), system_error); } BOOST_AUTO_TEST_CASE(input_stream_out_trunc_flag_creates) { random_generator generator; path target = to_string(generator()); ifstream input_stream(filesystem(), target, openmode::out | openmode::trunc); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(input_stream_std_out_trunc_flag_creates) { random_generator generator; path target = to_string(generator()); ifstream input_stream(filesystem(), target, std::ios_base::out | std::ios_base::trunc); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(input_stream_out_trunc_nocreate_flag_fails) { random_generator generator; path target = to_string(generator()); BOOST_CHECK_THROW(ifstream(filesystem(), target, openmode::out | openmode::trunc | openmode::nocreate), system_error); BOOST_CHECK(!exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(input_stream_out_trunc_noreplace_flag_fails) { path target = new_file_in_sandbox(); BOOST_CHECK_THROW(ifstream(filesystem(), target, openmode::out | openmode::trunc | openmode::noreplace), system_error); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(input_stream_seek_input_absolute) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ifstream s(filesystem(), target); s.seekg(1, std::ios_base::beg); string bob; BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "obbledy"); } BOOST_AUTO_TEST_CASE(input_stream_seek_input_relative) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ifstream s(filesystem(), target); s.seekg(1, std::ios_base::cur); s.seekg(1, std::ios_base::cur); string bob; BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "bbledy"); } BOOST_AUTO_TEST_CASE(input_stream_seek_input_end) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ifstream s(filesystem(), target); s.seekg(-3, std::ios_base::end); string bob; BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "ook"); } BOOST_AUTO_TEST_CASE(input_stream_seek_input_too_far_absolute) { path target = new_file_in_sandbox(); ifstream s(filesystem(), target); s.exceptions(std::ios_base::badbit | std::ios_base::eofbit | std::ios_base::failbit); s.seekg(1, std::ios_base::beg); string bob; BOOST_CHECK_THROW(s >> bob, runtime_error); } BOOST_AUTO_TEST_CASE(input_stream_seek_input_too_far_relative) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ifstream s(filesystem(), target); s.exceptions(std::ios_base::badbit | std::ios_base::eofbit | std::ios_base::failbit); s.seekg(9, std::ios_base::cur); s.seekg(4, std::ios_base::cur); string bob; BOOST_CHECK_THROW(s >> bob, runtime_error); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/io_stream_test.cpp ================================================ // Copyright 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "sftp_fixture.hpp" #include // test subject #include #include #include #include using ssh::filesystem::ifstream; using ssh::filesystem::fstream; using ssh::filesystem::openmode; using ssh::filesystem::path; using ssh::filesystem::perms; using ssh::filesystem::sftp_filesystem; using boost::system::system_error; using test::ssh::sftp_fixture; using std::runtime_error; using std::string; using std::vector; namespace { // the large data must fill more than one stream buffer (currently set to // 32768 (see DEFAULT_BUFFER_SIZE) string large_data() { string data; for (int i = 0; i < 32000; ++i) { data.push_back('a'); data.push_back('m'); data.push_back('z'); } return data; } void make_file_read_only(sftp_filesystem& filesystem, const path& target) { permissions(filesystem, target, perms::owner_read); } } BOOST_FIXTURE_TEST_SUITE(io_stream_test, sftp_fixture) BOOST_AUTO_TEST_CASE(io_stream_multiple_streams) { path target1 = new_file_in_sandbox(); path target2 = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); fstream s1(chan, target1); fstream s2(chan, target2); } BOOST_AUTO_TEST_CASE(io_stream_multiple_streams_to_same_file) { path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); fstream s1(chan, target); fstream s2(chan, target); } BOOST_AUTO_TEST_CASE(io_stream_fails_to_open_read_only_by_default) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); BOOST_CHECK_THROW(fstream(filesystem(), target), system_error); } BOOST_AUTO_TEST_CASE(io_stream_out_flag_fails_to_open_read_only) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); BOOST_CHECK_THROW(fstream(filesystem(), target, openmode::out), system_error); } BOOST_AUTO_TEST_CASE(io_stream_in_out_flag_fails_to_open_read_only) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); BOOST_CHECK_THROW( fstream(filesystem(), target, openmode::in | openmode::out), system_error); } BOOST_AUTO_TEST_CASE(io_stream_in_flag_opens_read_only) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); fstream(filesystem(), target, openmode::in); } BOOST_AUTO_TEST_CASE(io_stream_readable) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); fstream s(filesystem(), target); string bob; BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "gobbledy"); BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "gook"); BOOST_CHECK(!(s >> bob)); BOOST_CHECK(s.eof()); } BOOST_AUTO_TEST_CASE(io_stream_readable_binary_data) { string expected_data("gobbledy gook\0after-null\x12\11", 26); path target = new_file_in_sandbox_containing_data(expected_data); sftp_filesystem& chan = filesystem(); fstream io_stream(chan, target); string bob; vector buffer(expected_data.size()); BOOST_CHECK(io_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), expected_data.begin(), expected_data.end()); } BOOST_AUTO_TEST_CASE(io_stream_readable_binary_data_stream_op) { string expected_data("gobbledy gook\0after-null\x12\x11", 26); path target = new_file_in_sandbox_containing_data(expected_data); sftp_filesystem& chan = filesystem(); fstream io_stream(chan, target); string bob; BOOST_CHECK(io_stream >> bob); BOOST_CHECK_EQUAL(bob, "gobbledy"); BOOST_CHECK(io_stream >> bob); const char* gn = "gook\0after-null\x12\x11"; BOOST_CHECK_EQUAL_COLLECTIONS(bob.begin(), bob.end(), gn, gn + 17); BOOST_CHECK(!(io_stream >> bob)); BOOST_CHECK(io_stream.eof()); } BOOST_AUTO_TEST_CASE(io_stream_writeable) { path target = new_file_in_sandbox(); { fstream io_stream(filesystem(), target); io_stream << "gobbledy gook"; } ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gobbledy"); BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gook"); BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(io_stream_write_multiple_buffers) { // large enough to span multiple buffers string data(large_data()); path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); fstream io_stream(chan, target); BOOST_CHECK(io_stream.write(data.data(), data.size())); io_stream.flush(); ifstream input_stream(filesystem(), target); string bob; vector buffer(data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), data.begin(), data.end()); BOOST_CHECK(!input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK(input_stream.eof()); } // Test with Boost.IOStreams buffer disabled. // Should call directly to libssh2 BOOST_AUTO_TEST_CASE(io_stream_write_no_buffer) { string data("gobbeldy gook"); path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); fstream io_stream(chan, target, openmode::in | openmode::out, 0); BOOST_CHECK(io_stream.write(data.data(), data.size())); ifstream input_stream(filesystem(), target); string bob; vector buffer(data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), data.begin(), data.end()); BOOST_CHECK(!input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK(input_stream.eof()); } // An IO stream may be able to open a read-only file when given the in flag, // but it should still fail to write to it BOOST_AUTO_TEST_CASE(io_stream_read_only_write_fails) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); fstream s(filesystem(), target, openmode::in); BOOST_CHECK(s << "gobbledy gook"); BOOST_CHECK(!s.flush()); // Failure happens on the flush ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK_EQUAL(bob, string()); BOOST_CHECK(input_stream.eof()); } // Flush is not called explicitly so failure will happen in destructor BOOST_AUTO_TEST_CASE(io_stream_read_only_write_fails_no_flush) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); { fstream s(filesystem(), target, openmode::in); BOOST_CHECK(s << "gobbledy gook"); // No explicit flush } ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK_EQUAL(bob, string()); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(io_stream_write_binary_data) { string data("gobbledy gook\0after-null\x12\x11", 26); path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); fstream io_stream(chan, target); BOOST_CHECK(io_stream.write(data.data(), data.size())); io_stream.flush(); ifstream input_stream(filesystem(), target); string bob; vector buffer(data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), data.begin(), data.end()); BOOST_CHECK(!input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(io_stream_write_binary_data_stream_op) { string data("gobbledy gook\0after-null\x12\x11", 26); path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); fstream io_stream(chan, target); BOOST_CHECK(io_stream << data); io_stream.flush(); ifstream input_stream(filesystem(), target); string bob; vector buffer(data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), data.begin(), data.end()); BOOST_CHECK(!input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(io_stream_seek_input_absolute) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); fstream s(filesystem(), target); s.seekg(1, std::ios_base::beg); string bob; BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "obbledy"); } BOOST_AUTO_TEST_CASE(io_stream_seek_input_relative) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); fstream s(filesystem(), target); s.seekg(1, std::ios_base::cur); s.seekg(1, std::ios_base::cur); string bob; BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "bbledy"); } BOOST_AUTO_TEST_CASE(io_stream_seek_input_end) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); fstream s(filesystem(), target); s.seekg(-3, std::ios_base::end); string bob; BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "ook"); } BOOST_AUTO_TEST_CASE(io_stream_seek_input_too_far_absolute) { path target = new_file_in_sandbox(); fstream s(filesystem(), target); s.exceptions(std::ios_base::badbit | std::ios_base::eofbit | std::ios_base::failbit); s.seekg(1, std::ios_base::beg); string bob; BOOST_CHECK_THROW(s >> bob, runtime_error); } BOOST_AUTO_TEST_CASE(io_stream_seek_input_too_far_relative) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); fstream s(filesystem(), target); s.exceptions(std::ios_base::badbit | std::ios_base::eofbit | std::ios_base::failbit); s.seekg(9, std::ios_base::cur); s.seekg(4, std::ios_base::cur); string bob; BOOST_CHECK_THROW(s >> bob, runtime_error); } BOOST_AUTO_TEST_CASE(io_stream_seek_output_absolute) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); fstream s(filesystem(), target); s.seekp(1, std::ios_base::beg); BOOST_CHECK(s << "r"); s.flush(); ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "grbbledy"); } BOOST_AUTO_TEST_CASE(io_stream_seek_output_relative) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); fstream s(filesystem(), target); s.seekp(1, std::ios_base::cur); s.seekp(1, std::ios_base::cur); BOOST_CHECK(s << "r"); s.flush(); ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gorbledy"); } BOOST_AUTO_TEST_CASE(io_stream_seek_output_end) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); fstream s(filesystem(), target); s.seekp(-3, std::ios_base::end); BOOST_CHECK(s << "r"); s.flush(); ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gobbledy"); BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "grok"); } BOOST_AUTO_TEST_CASE(io_stream_seek_interleaved) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); fstream s(filesystem(), target); s.seekp(1, std::ios_base::beg); BOOST_CHECK(s << "r"); s.seekg(2, std::ios_base::cur); string bob; BOOST_CHECK(s >> bob); // not "bbledy" because read and write head are combined BOOST_CHECK_EQUAL(bob, "ledy"); s.seekp(-4, std::ios_base::end); BOOST_CHECK(s << "ahh"); BOOST_CHECK(s >> bob); BOOST_CHECK_EQUAL(bob, "k"); s.flush(); ifstream input_stream(filesystem(), target); BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "grbbledy"); BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "ahhk"); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/knownhost_test.cpp ================================================ /** @file Tests for ssh knownhost interface. @if license Copyright (C) 2010, 2011, 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #include "ssh/knownhost.hpp" #include // list_of() #include // BOOST_FOREACH #include // shared_ptr #include #include #include #include using ssh::knownhost_search_result; using ssh::knownhost_collection; using ssh::knownhost; using ssh::knownhost_iterator; using ssh::openssh_knownhost_collection; using boost::assign::list_of; using boost::filesystem::path; using boost::filesystem::ifstream; using boost::shared_ptr; using boost::test_tools::predicate_result; using std::string; using std::vector; namespace { struct test_datum { test_datum( string name, string ip, string key_algo, string key, string fail_key, string comment) : name(name), ip(ip), key_algo(key_algo), key(key), fail_key(fail_key), comment(comment) {} string name; string ip; string key_algo; string key; string fail_key; string comment; }; } const string KEY_A = "AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZni" "x8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nC" "qtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbo" "dUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEl" "dwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4Q" "FeigFf3QY7rGUgBEm/wMgxggdvLUCQ=="; const string KEY_B = "AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezs" "HRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEY" "ho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaM" "VMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS" "51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yD" "FZYbAkXz8QjhGL/qTywA7Afglyt5/w=="; const string KEY_C = "AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb" "2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt4" "02tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgs" "rs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYD" "UvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WG" "I3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAa" "kCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z" "+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/I" "QcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w=="; const string KEY_UNKNOWN_FORMAT = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/q" "Jfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1" "E7E="; const vector test_data = list_of (test_datum( "host1.example.com", "192.168.0.1", "ssh-rsa", KEY_A, KEY_B, "test@swish")) // The next key is not recognised by libssh2 (yet). We use it to test that // unrecognised keys are handled gracefully. Added in the middle to catch // if it halts processing silently - the later keys won't be processed. // Key format name is "unknown" because actual format name isn't exposed (test_datum( "unrecognisedkey.example.com", "192.168.2.1", "unknown", KEY_UNKNOWN_FORMAT, KEY_A, "test@swish")) (test_datum( "host2.example.com", "10.0.0.1", "ssh-rsa", KEY_B, KEY_C, "")) (test_datum( "host3.example.com", "192.168.1.1", "ssh-dss", KEY_C, KEY_A, "test@swish")); const string FAIL_HOST = "i-dontexist-in-the-host-file.example.com"; BOOST_AUTO_TEST_SUITE(knownhost_tests) /** * Create and destroy without leaking. */ BOOST_AUTO_TEST_CASE( create ) { knownhost_collection kh; } BOOST_AUTO_TEST_SUITE(openssh_knownhost_tests) /** * Initialise with known_host entries. */ BOOST_AUTO_TEST_CASE( init_from_file ) { openssh_knownhost_collection kh("test_known_hosts"); } /** * Initialise with hashed known_host entries. */ BOOST_AUTO_TEST_CASE( init_from_hashed_file ) { openssh_knownhost_collection kh("test_known_hosts_hashed"); } /** * Initialise with a file that doesn't exist. */ BOOST_AUTO_TEST_CASE( init_fail ) { path bad_path = "i-dont-exist"; BOOST_REQUIRE(!exists(bad_path)); BOOST_CHECK_THROW( (openssh_knownhost_collection(bad_path)), std::runtime_error); BOOST_CHECK(!exists(bad_path)); } /** * Saved file lines should match original except with each entry on its * own line. I.e.: * host3.example.com,192.168.1.1 ssh-dss * becomes: * 192.168.1.1 ssh-dss * host3.example.com ssh-dss */ BOOST_AUTO_TEST_CASE( roundtrip ) { openssh_knownhost_collection kh("test_known_hosts"); vector lines; kh.save(kh.begin(), kh.end(), back_inserter(lines)); ifstream file("test_known_hosts_out"); BOOST_REQUIRE_EQUAL_COLLECTIONS( lines.begin(), lines.end(), std::istream_iterator(file), std::istream_iterator()); } namespace { /** * Check known_host matches the expected data. */ template predicate_result entry_matches_impl( const T& actual, const test_datum& expected, bool use_name, bool hashed_name) { predicate_result res(false); string actual_name = actual.name(); string expected_name; if (hashed_name) expected_name = ""; else expected_name = (use_name) ? expected.name : expected.ip; if (actual_name != expected_name) res.message() << "Host names or IPs don't match [" << actual_name << " != " << expected_name << "]"; else if (actual.key() != expected.key) res.message() << "Keys don't match [" << actual.key() << " != " << expected.key << "]"; /* TODO: test the comments once the libssh2 API is fixed. else if (actual.comment() != expected.comment) res.message() << "Comments don't match [" << actual.comment() << " != " << expected.comment << "]"; */ else if (actual.key_algo() != expected.key_algo) res.message() << "Algorithms don't match [" << actual.key_algo() << " != " << expected.key_algo << "]"; else if (!hashed_name && !actual.is_name_plain()) res.message() << "Should be plain-text"; else if (hashed_name && actual.is_name_plain()) res.message() << "Shouldn't be plain-text"; else if (!hashed_name && actual.is_name_sha1()) res.message() << "Should be SHA1-encoded"; else if (hashed_name && !actual.is_name_sha1()) res.message() << "Shouldn't be SHA1-encoded"; else if (actual.is_name_custom()) res.message() << "Shouldn't be custom encoded"; else return true; return res; } /** * Check that a known_host matches the expected data. * * The host name is expected to be the IP address.. */ template predicate_result entry_matches_ip( const T& entry, const test_datum& expected) { return entry_matches_impl(entry, expected, false, false); } /** * Check that a known_host matches the expected data. * * The host name is expected to be the IP address.. */ template predicate_result entry_matches( const T& entry, const test_datum& expected) { return entry_matches_impl(entry, expected, true, false); } /** * Check that a hashed known_host matches the expected data. */ template predicate_result hashed_entry_matches( const T& entry, const test_datum& expected) { return entry_matches_impl(entry, expected, false, true); } } /** * Initialise with known_host entries and test retrieval. * * The iterator should keep working after the collection is destroyed * (this isn't strictly needed but as its easy for is to implement, its * a nice feature to enforce). */ BOOST_AUTO_TEST_CASE( iterate_entries ) { knownhost_iterator it; knownhost_iterator end; { openssh_knownhost_collection kh("test_known_hosts"); it = kh.begin(); end = kh.end(); } // There should be one entry for IP and one for hostname BOOST_FOREACH(const test_datum& datum, test_data) { BOOST_REQUIRE(it != end); BOOST_CHECK(entry_matches_ip(*it++, datum)); BOOST_REQUIRE(it != end); BOOST_CHECK(entry_matches(*it++, datum)); } BOOST_REQUIRE(it == end); } /** * Initialise with *hashed* known_host entries and test retrieval. */ BOOST_AUTO_TEST_CASE( iterate_hashed_entries ) { knownhost_iterator it; knownhost_iterator end; { openssh_knownhost_collection kh("test_known_hosts_hashed"); it = kh.begin(); end = kh.end(); } // Two entries per host even though we cannot see which is IP and // which hostname BOOST_FOREACH(const test_datum& datum, test_data) { BOOST_REQUIRE(it != end); BOOST_CHECK(hashed_entry_matches(*it++, datum)); BOOST_REQUIRE(it != end); BOOST_CHECK(hashed_entry_matches(*it++, datum)); } BOOST_REQUIRE(it == end); } /** * Iterators should no affect each other. */ BOOST_AUTO_TEST_CASE( iterator_independence ) { openssh_knownhost_collection kh("test_known_hosts"); knownhost_iterator it1 = kh.begin(); BOOST_CHECK(entry_matches_ip(*it1++, test_data[0])); knownhost_iterator it2 = kh.begin(); BOOST_CHECK(entry_matches(*it1++, test_data[0])); BOOST_CHECK(entry_matches_ip(*it2++, test_data[0])); BOOST_CHECK(entry_matches(*it2++, test_data[0])); BOOST_CHECK(entry_matches_ip(*it2++, test_data[1])); BOOST_CHECK(entry_matches(*it2++, test_data[1])); BOOST_CHECK(entry_matches_ip(*it1++, test_data[1])); BOOST_CHECK(entry_matches(*it1++, test_data[1])); BOOST_CHECK(entry_matches_ip(*it1++, test_data[2])); BOOST_CHECK(entry_matches(*it1++, test_data[2])); BOOST_CHECK(entry_matches_ip(*it2++, test_data[2])); BOOST_CHECK(entry_matches(*it2++, test_data[2])); BOOST_CHECK(entry_matches_ip(*it1++, test_data[3])); BOOST_CHECK(entry_matches(*it1++, test_data[3])); BOOST_REQUIRE(it1 == kh.end()); BOOST_CHECK(entry_matches_ip(*it2++, test_data[3])); BOOST_CHECK(entry_matches(*it2++, test_data[3])); BOOST_REQUIRE(it2 == kh.end()); } namespace { /** * Return first knownhost in file. */ knownhost get_host_but_destroy_collection_and_iterator() { openssh_knownhost_collection kh("test_known_hosts"); return *(kh.begin()); } } /** * knownhosts should outlive their iterator and collection. * * They do this by keeping the raw libssh2 collection alive inside them. Ooo, * spooky! */ BOOST_AUTO_TEST_CASE( knownhost_lifetime ) { knownhost host = get_host_but_destroy_collection_and_iterator(); BOOST_CHECK(entry_matches_ip(host, test_data[0])); } void do_find_match_test( const boost::filesystem::path& file, bool is_hashed) { openssh_knownhost_collection kh(file); // Find each datum twice, once by IP once by name BOOST_FOREACH(const test_datum& datum, test_data) { knownhost_search_result result = kh.find(datum.name, datum.key, true); BOOST_CHECK(result.match()); BOOST_CHECK(!result.mismatch()); BOOST_CHECK(!result.not_found()); BOOST_REQUIRE(result.host() != kh.end()); if (!is_hashed) BOOST_CHECK_EQUAL(result.host()->name(), datum.name); BOOST_CHECK_EQUAL(result.host()->key(), datum.key); result = kh.find(datum.ip, datum.key, true); BOOST_CHECK(result.match()); BOOST_CHECK(!result.mismatch()); BOOST_CHECK(!result.not_found()); BOOST_REQUIRE(result.host() != kh.end()); if (!is_hashed) BOOST_CHECK_EQUAL(result.host()->name(), datum.ip); BOOST_CHECK_EQUAL(result.host()->key(), datum.key); } } /** * Search for all the test entries. Each one should result in a match. */ BOOST_AUTO_TEST_CASE( find_match ) { do_find_match_test("test_known_hosts", false); } /** * Search for all the test entries in hashed collection. * Each one should result in a match. */ BOOST_AUTO_TEST_CASE( find_match_hashed ) { do_find_match_test("test_known_hosts_hashed", true); } void do_find_mismatch_test( const boost::filesystem::path& file, bool is_hashed) { openssh_knownhost_collection kh(file); // Find each datum twice, once by IP once by name BOOST_FOREACH(const test_datum& datum, test_data) { knownhost_search_result result = kh.find(datum.name, datum.fail_key, true); BOOST_CHECK(!result.match()); BOOST_CHECK(result.mismatch()); BOOST_CHECK(!result.not_found()); BOOST_REQUIRE(result.host() != kh.end()); if (!is_hashed) BOOST_CHECK_EQUAL(result.host()->name(), datum.name); BOOST_CHECK_EQUAL(result.host()->key(), datum.key); result = kh.find(datum.ip, datum.fail_key, true); BOOST_CHECK(!result.match()); BOOST_CHECK(result.mismatch()); BOOST_CHECK(!result.not_found()); BOOST_REQUIRE(result.host() != kh.end()); if (!is_hashed) BOOST_CHECK_EQUAL(result.host()->name(), datum.ip); BOOST_CHECK_EQUAL(result.host()->key(), datum.key); } } /** * Search for each test host with a key that doesn't match. */ BOOST_AUTO_TEST_CASE( find_mismatch ) { do_find_mismatch_test("test_known_hosts", false); } /** * Search for each test host with a key that doesn't match. */ BOOST_AUTO_TEST_CASE( find_mismatch_hashed ) { do_find_mismatch_test("test_known_hosts_hashed", true); } /** * Search for a non-existent hostname in the collection. * This should fail to find a match. */ BOOST_AUTO_TEST_CASE( find_fail ) { openssh_knownhost_collection kh("test_known_hosts"); knownhost_search_result result = kh.find(FAIL_HOST, KEY_A, true); BOOST_CHECK(!result.match()); BOOST_CHECK(!result.mismatch()); BOOST_CHECK(result.not_found()); BOOST_CHECK(result.host() == kh.end()); } /** * Search for a non-existent hostname in the collection. * This should fail to find a match. */ BOOST_AUTO_TEST_CASE( find_fail_hashed ) { openssh_knownhost_collection kh("test_known_hosts_hashed"); knownhost_search_result result = kh.find(FAIL_HOST, KEY_A, true); BOOST_CHECK(!result.match()); BOOST_CHECK(!result.mismatch()); BOOST_CHECK(result.not_found()); BOOST_CHECK(result.host() == kh.end()); } void do_erase_test( const openssh_knownhost_collection& kh, const test_datum& datum, bool is_hashed) { std::string expected_ip = (is_hashed) ? "" : datum.ip; std::string expected_name = (is_hashed) ? "" : datum.name; // find target entry by IP address knownhost_search_result ip_result = kh.find(datum.ip, datum.key, true); BOOST_CHECK_EQUAL(ip_result.host()->name(), expected_ip); BOOST_CHECK_EQUAL(ip_result.host()->key(), datum.key); // erase it which should give us pointer to next entry // (the hostname version of the entry) knownhost_iterator next = ip_result.host(); ++next; BOOST_CHECK(erase(ip_result.host()) == next); BOOST_CHECK_EQUAL(next->name(), expected_name); BOOST_CHECK_EQUAL(next->key(), datum.key); // searching for this host entry should also work and give an // equal iterator knownhost_search_result host_result = kh.find(datum.name, datum.key, true); BOOST_CHECK(host_result.match()); BOOST_CHECK_EQUAL(host_result.host()->name(), expected_name); BOOST_CHECK_EQUAL(host_result.host()->key(), datum.key); BOOST_CHECK(next == host_result.host()); // but searching for the IP entry we just deleted should fail to // find anything ip_result = kh.find(datum.ip, datum.key, true); BOOST_CHECK(ip_result.not_found()); // erase host entry as well erase(host_result.host()); // searching for it again should fail this time host_result = kh.find(datum.name, datum.key, true); BOOST_CHECK(host_result.not_found()); } void do_erase_test_loop(const boost::filesystem::path& file, bool is_hashed) { BOOST_FOREACH(const test_datum& datum, test_data) { openssh_knownhost_collection kh(file); do_erase_test(kh, datum, is_hashed); } } /** * Erase one item from a collection. * * We test this for all entries with a fresh collection each time. * * The item in question should be gone but the other items should still * exist. * @warning Strictly speaking we erase two items at a time due to the * relationship between host and IP entries. This may be fragile. */ BOOST_AUTO_TEST_CASE( erase_plain ) { do_erase_test_loop("test_known_hosts", false); } /** * Erase one item from a collection of hashed entries. */ BOOST_AUTO_TEST_CASE( erase_hashed ) { do_erase_test_loop("test_known_hosts_hashed", true); } /** * Erase all items from a collection. * * The item in question should be gone but the other items should still * exist. * @warning Strictly speaking we erase two items at a time due to the * relationship between host and IP entries. This may be fragile. */ BOOST_AUTO_TEST_CASE( erase_all ) { openssh_knownhost_collection kh("test_known_hosts"); BOOST_FOREACH(const test_datum& datum, test_data) { do_erase_test(kh, datum, false); } BOOST_CHECK(kh.begin() == kh.end()); } /** * Erase the last item in the collection. */ BOOST_AUTO_TEST_CASE( erase_last ) { openssh_knownhost_collection kh("test_known_hosts"); knownhost_search_result result = kh.find( test_data[test_data.size() - 1].name, test_data[test_data.size() - 1].key, true); BOOST_REQUIRE(result.host() != kh.end()); knownhost_iterator next = erase(result.host()); BOOST_REQUIRE(next == kh.end()); result = kh.find( test_data[test_data.size() - 1].name, test_data[test_data.size() - 1].key, true); BOOST_CHECK(result.not_found()); } /** * Add an item to the collection. */ BOOST_AUTO_TEST_CASE( add ) { openssh_knownhost_collection kh("test_known_hosts"); kh.add("new.example.com", KEY_B, ssh::hostkey_type::ssh_dss, true); knownhost_search_result result = kh.find("new.example.com", KEY_B, true); BOOST_CHECK(result.match()); BOOST_REQUIRE(result.host() != kh.end()); BOOST_CHECK_EQUAL(result.host()->name(), "new.example.com"); BOOST_CHECK_EQUAL(result.host()->key(), KEY_B); } /** * Lines must be written back exactly as they are read with exception of: * - comma-separated host names being split into separate lines * - newlines stripped * - tabs replaced with spaces */ BOOST_AUTO_TEST_CASE( load_save ) { const vector lines = list_of ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA==") ("host.example.com,192.0.32.10 ssh-rsa AAAAB3NzaC1yc2EAA==") ("hostalias1,hostalias2 ssh-rsa AAAAB3NzaC1yc2EAA==") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== ") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA==\t") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA==\n") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== \n") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test@swish") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test swish") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA==\ttest swish") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test swish\n") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test swish ") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test swish \n") ("host.example.com unknown-key-format blahblahblahkey test swish \n") ("host.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNT" "YAAAAIbmlzdHAyNTYAAABBBClh5K95Rz/k4WSPZ9rc8UnFSSSPtsu5z+hs19xbpusWB" "2MwAU3+PYOjEUZZ9XuRMA+yKxOy1Qc/08uQWs1tyX8= swish@default") // Hashed version of the above ("|1|FI75NN2BtS542+iqaY9PWHJlXfc=|vGXV2w0kCLXWOqRF8uf+njij1MI= " "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzd" "HAyNTYAAABBBClh5K95Rz/k4WSPZ9rc8UnFSSSPtsu5z+hs19xbpusWB2MwAU" "3+PYOjEUZZ9XuRMA+yKxOy1Qc/08uQWs1tyX8= swish@default") ("|1|wWleTRHpe2S17RMX0bNldkfB/6Y=|8KTu5EjSLKwlkr0JoNo2QA3uhJs= " "ssh-rsa AAAAB3NzaC1yc2EAA==") // this one will fail with libssh2 < 1.2.8 ("host1,host2,host3,192.168.1.1 ssh-rsa AAAAB3NzaC1yc2EAA=="); const vector expected_output = list_of ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA==") ("192.0.32.10 ssh-rsa AAAAB3NzaC1yc2EAA==") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA==") ("hostalias2 ssh-rsa AAAAB3NzaC1yc2EAA==") ("hostalias1 ssh-rsa AAAAB3NzaC1yc2EAA==") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== ") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== ") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA==") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== ") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test@swish") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test swish") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test swish") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test swish") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test swish ") ("host.example.com ssh-rsa AAAAB3NzaC1yc2EAA== test swish ") ("host.example.com unknown-key-format blahblahblahkey test swish ") ("host.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNT" "YAAAAIbmlzdHAyNTYAAABBBClh5K95Rz/k4WSPZ9rc8UnFSSSPtsu5z+hs19xbpusWB" "2MwAU3+PYOjEUZZ9XuRMA+yKxOy1Qc/08uQWs1tyX8= swish@default") ("|1|FI75NN2BtS542+iqaY9PWHJlXfc=|vGXV2w0kCLXWOqRF8uf+njij1MI= " "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzd" "HAyNTYAAABBBClh5K95Rz/k4WSPZ9rc8UnFSSSPtsu5z+hs19xbpusWB2MwAU" "3+PYOjEUZZ9XuRMA+yKxOy1Qc/08uQWs1tyX8= swish@default") ("|1|wWleTRHpe2S17RMX0bNldkfB/6Y=|8KTu5EjSLKwlkr0JoNo2QA3uhJs= " "ssh-rsa AAAAB3NzaC1yc2EAA==") ("192.168.1.1 ssh-rsa AAAAB3NzaC1yc2EAA==") ("host3 ssh-rsa AAAAB3NzaC1yc2EAA==") ("host2 ssh-rsa AAAAB3NzaC1yc2EAA==") ("host1 ssh-rsa AAAAB3NzaC1yc2EAA=="); openssh_knownhost_collection kh(lines.begin(), lines.end()); vector output; kh.save(kh.begin(), kh.end(), back_inserter(output)); for (size_t i = 0; i < output.size(); ++i) { string o = output[i]; string e = expected_output[i]; BOOST_CHECK_EQUAL(o, e); BOOST_CHECK_EQUAL_COLLECTIONS( o.begin(), o.end(), e.begin(), e.end()); } } BOOST_AUTO_TEST_SUITE_END(); BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/module.cpp ================================================ /** @file Unit test module. @if license Copyright (C) 2010 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #define BOOST_TEST_MODULE ssh tests #include ================================================ FILE: test/ssh/openssh_fixture.cpp ================================================ // Copyright 2009, 2010, 2011, 2012, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "openssh_fixture.hpp" #include #include #include #include #include #include #include #include // find_executable_in_path #include #include #include #include #include // this_thread #include #include #include #include #include #include using boost::assign::list_of; using boost::io::quoted; using boost::filesystem::path; using boost::optional; using boost::process::behavior::pipe; using boost::process::child; using boost::process::context; using boost::process::environment; using boost::process::find_executable_in_path; using boost::process::pistream; using boost::process::self; using boost::process::stderr_id; using boost::process::stdout_id; using std::map; using std::ostream_iterator; using std::ostringstream; using std::runtime_error; using std::string; using std::vector; using std::wstring; namespace { // private const string SSHD_PRIVATE_KEY_FILE = "fixture_dsakey"; const string SSHD_PUBLIC_KEY_FILE = "fixture_dsakey.pub"; const string SSHD_WRONG_PRIVATE_KEY_FILE = "fixture_wrong_dsakey"; const string SSHD_WRONG_PUBLIC_KEY_FILE = "fixture_wrong_dsakey.pub"; template string error_message_from_stderr(const string& command, const ArgSequence& arguments, child& process) { pistream command_stderr(process.get_handle(stderr_id)); ostringstream message; message << quoted(command) << " "; copy(arguments.begin(), arguments.end(), ostream_iterator(message, " ")); message << " failed: "; message << command_stderr.rdbuf() << std::flush; return message.str(); } template Out single_value_from_executable(const path& executable, const ArgSequence& arguments) { context ctx; ctx.env = self::get_environment(); ctx.streams[stdout_id] = pipe(); ctx.streams[stderr_id] = pipe(); child process = create_child(executable.string(), arguments, ctx); pistream command_stdout(process.get_handle(stdout_id)); Out out; command_stdout >> out; int status = process.wait(); // TODO: Check if process exited, with WIFEXITED - may need to upgrade // Boost.Process to the 2012 version to get mitigate.hpp, which handles this // portably. if (status == 0) { return out; } else { BOOST_THROW_EXCEPTION(runtime_error(error_message_from_stderr( executable.string(), arguments, process))); } } template Out single_value_from_command(const string& command, const ArgSequence& arguments) { path command_executable = find_executable_in_path(command); return single_value_from_executable(command_executable, arguments); } template Out single_value_from_docker_command(const ArgSequence& arguments) { return single_value_from_command("docker", arguments); } template Out single_value_from_docker_machine_command(const ArgSequence& arguments) { return single_value_from_command("docker-machine", arguments); } template void run_docker_command(const ArgSequence& arguments) { single_value_from_docker_command(arguments); } optional docker_machine_name() { const string docker_machine_name_variable = "DOCKER_MACHINE_NAME"; boost::process::environment environment = self::get_environment(); if (environment.count(docker_machine_name_variable) == 1) { return environment[docker_machine_name_variable]; } else { return optional(); } } } extern "C" { extern void ERR_free_strings(); extern void ERR_remove_state(unsigned long); extern void EVP_cleanup(); extern void CRYPTO_cleanup_all_ex_data(); extern void ENGINE_cleanup(); extern void CONF_modules_unload(int); extern void CONF_modules_free(); extern void RAND_cleanup(); } namespace { struct global_fixture { global_fixture() { // Ensure the docker image has been built vector build_command = (list_of(string("build")), "-t", "ssh2pp/openssh_server", "ssh_server"); run_docker_command(build_command); } ~global_fixture() { // We call this here as a bit of a hack to stop memory-leak // detection incorrectly detecting OpenSSL global data as a // leak ::RAND_cleanup(); ::ENGINE_cleanup(); ::CONF_modules_unload(1); ::CONF_modules_free(); ::EVP_cleanup(); ::ERR_free_strings(); ::ERR_remove_state(0); ::CRYPTO_cleanup_all_ex_data(); } }; } BOOST_GLOBAL_FIXTURE(global_fixture); namespace test { namespace ssh { openssh_fixture::openssh_fixture() { vector docker_command = (list_of(string("run")), "--detach", "-P", "ssh2pp/openssh_server"); m_container_id = single_value_from_docker_command(docker_command); m_host = ask_docker_for_host(); m_port = ask_docker_for_port(); } openssh_fixture::~openssh_fixture() { try { vector stop_command = (list_of(string("stop")), m_container_id); run_docker_command(stop_command); } catch (...) { } } string openssh_fixture::host() const { return m_host; } string openssh_fixture::ask_docker_for_host() const { optional active_docker_machine = docker_machine_name(); if (active_docker_machine) { // This can be flaky when tests run in parallel (see // https://github.com/docker/machine/issues/2612), so we retry a few // times with exponential backoff if it fails int attempt_no = 0; boost::posix_time::milliseconds wait_time(100); while (true) { ++attempt_no; try { vector machine_ip_command = (list_of(string("ip")), active_docker_machine); return single_value_from_docker_machine_command( machine_ip_command); } catch (const runtime_error&) { if (attempt_no > 5) { throw; } else { wait_time *= 2; } } boost::this_thread::sleep(wait_time); } } else { vector inspect_host_command = (list_of(string("inspect")), "--format", "{{ .NetworkSettings.IPAddress }}", m_container_id); return single_value_from_docker_command(inspect_host_command); } } string openssh_fixture::user() const { return "swish"; } int openssh_fixture::port() const { return m_port; } int openssh_fixture::ask_docker_for_port() const { vector inspect_host_command = (list_of(string("inspect")), "--format", "{{ index (index (index .NetworkSettings.Ports \"22/tcp\") " "0) \"HostPort\" }}", m_container_id); return single_value_from_docker_command(inspect_host_command); } /** * The private half of a key-pair that is expected to authenticate successfully * with the fixture server. */ path openssh_fixture::private_key_path() const { return SSHD_PRIVATE_KEY_FILE; } /** * The public half of a key-pair that is expected to authenticate successfully * with the fixture server. */ path openssh_fixture::public_key_path() const { return SSHD_PUBLIC_KEY_FILE; } /** * The private half of a key-pair that is expected to fail to authenticate * with the fixture server. * * This must be in the same format as the successful key-pair so that the key * mismatches rather than format mismatches are the cause of authentication * failure regardless of which combination of keys is passed. */ path openssh_fixture::wrong_private_key_path() const { return SSHD_WRONG_PRIVATE_KEY_FILE; } /** * The public half of a key-pair that is expected to fail to authenticate * with the fixture server. * * This must be in the same format as the successful key-pair so that the * key mismatches rather than format mismatches are the cause of * authentication * failure regardless of which combination of keys is passed. */ path openssh_fixture::wrong_public_key_path() const { return SSHD_WRONG_PUBLIC_KEY_FILE; } } } // namespace test::ssh ================================================ FILE: test/ssh/openssh_fixture.hpp ================================================ // Copyright 2009, 2010, 2012, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SSH_OPENSSH_FIXTURE_HPP #define SSH_OPENSSH_FIXTURE_HPP #include // path #include namespace test { namespace ssh { /** * Fixture that starts and stops an OpenSSH server. */ class openssh_fixture { public: openssh_fixture(); virtual ~openssh_fixture(); std::string host() const; std::string user() const; int port() const; boost::filesystem::path private_key_path() const; boost::filesystem::path public_key_path() const; boost::filesystem::path wrong_private_key_path() const; boost::filesystem::path wrong_public_key_path() const; private: std::string m_container_id; std::string m_host; int m_port; std::string ask_docker_for_host() const; int ask_docker_for_port() const; }; } } // namespace test::ssh #endif ================================================ FILE: test/ssh/output_stream_test.cpp ================================================ // Copyright 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "sftp_fixture.hpp" #include // test subject #include #include #include // random_generator #include // to_string #include #include using ssh::filesystem::ifstream; using ssh::filesystem::ofstream; using ssh::filesystem::openmode; using ssh::filesystem::path; using ssh::filesystem::perms; using ssh::filesystem::sftp_filesystem; using boost::uuids::random_generator; using boost::system::system_error; using test::ssh::sftp_fixture; using std::string; using std::vector; namespace { // the large data must fill more than one stream buffer (currently set to // 32768 (see DEFAULT_BUFFER_SIZE) string large_data() { string data; for (int i = 0; i < 32000; ++i) { data.push_back('a'); data.push_back('m'); data.push_back('z'); } return data; } string large_binary_data() { string data; for (int i = 0; i < 32000; ++i) { data.push_back('a'); data.push_back('\n'); data.push_back('\0'); data.push_back('\r'); data.push_back('\n'); data.push_back(-1); } return data; } void make_file_read_only(sftp_filesystem& filesystem, const path& target) { permissions(filesystem, target, perms::owner_read); } } BOOST_FIXTURE_TEST_SUITE(ofstream_tests, sftp_fixture) BOOST_AUTO_TEST_CASE(output_stream_multiple_streams) { path target1 = new_file_in_sandbox(); path target2 = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); ofstream s1(chan, target1); ofstream s2(chan, target2); } BOOST_AUTO_TEST_CASE(output_stream_multiple_streams_to_same_file) { path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); ofstream s1(chan, target); ofstream s2(chan, target); } BOOST_AUTO_TEST_CASE(output_stream_is_writeable) { path target = new_file_in_sandbox(); { ofstream output_stream(filesystem(), target); output_stream << "gobbledy gook"; } ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gobbledy"); BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gook"); BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(output_stream_write_multiple_buffers) { // large enough to span multiple buffers string data(large_data()); path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); { ofstream output_stream(chan, target); BOOST_CHECK(output_stream.write(data.data(), data.size())); } ifstream input_stream(filesystem(), target); string bob; vector buffer(data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), data.begin(), data.end()); BOOST_CHECK(!input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK(input_stream.eof()); } // Test with Boost.IOStreams buffer disabled. // Should call directly to libssh2 BOOST_AUTO_TEST_CASE(output_stream_write_no_buffer) { string data("gobbeldy gook"); path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); ofstream output_stream(chan, target, openmode::out, 0); BOOST_CHECK(output_stream.write(data.data(), data.size())); ifstream input_stream(filesystem(), target); string bob; vector buffer(data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), data.begin(), data.end()); BOOST_CHECK(!input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(output_stream_write_binary_data) { string data("gobbledy gook\0after-null\x12\x11", 26); path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); { ofstream output_stream(chan, target); BOOST_CHECK(output_stream.write(data.data(), data.size())); } ifstream input_stream(filesystem(), target); string bob; vector buffer(data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), data.begin(), data.end()); BOOST_CHECK(!input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(output_stream_write_binary_data_multiple_buffers) { // large enough to span multiple buffers string data(large_binary_data()); path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); { ofstream output_stream(chan, target); BOOST_CHECK(output_stream.write(data.data(), data.size())); } ifstream input_stream(filesystem(), target); string bob; vector buffer(data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), data.begin(), data.end()); BOOST_CHECK(!input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(output_stream_write_binary_data_stream_op) { string data("gobbledy gook\0after-null\x12\x11", 26); path target = new_file_in_sandbox(); sftp_filesystem& chan = filesystem(); { ofstream output_stream(chan, target); BOOST_CHECK(output_stream << data); } ifstream input_stream(filesystem(), target); string bob; vector buffer(data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), data.begin(), data.end()); BOOST_CHECK(!input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(output_stream_creates_by_default) { random_generator generator; path target = to_string(generator()); ofstream output_stream(filesystem(), target); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_nocreate_flag) { path target = new_file_in_sandbox(); ofstream(filesystem(), target, openmode::nocreate); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_nocreate_flag_fails) { random_generator generator; path target = to_string(generator()); BOOST_CHECK_THROW(ofstream(filesystem(), target, openmode::nocreate), system_error); BOOST_CHECK(!exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_noreplace_flag) { random_generator generator; path target = to_string(generator()); ofstream(filesystem(), target, openmode::noreplace); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_noreplace_flag_fails) { path target = new_file_in_sandbox(); BOOST_CHECK_THROW(ofstream(filesystem(), target, openmode::noreplace), system_error); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_out_flag_creates) { random_generator generator; path target = to_string(generator()); ofstream output_stream(filesystem(), target, openmode::out); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_out_flag_truncates) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); { ofstream output_stream(filesystem(), target, openmode::out); BOOST_CHECK(exists(filesystem(), target)); BOOST_CHECK(output_stream << "abcdef"); } ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "abcdef"); BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(output_stream_out_nocreate_flag) { path target = new_file_in_sandbox(); ofstream output_stream(filesystem(), target, openmode::out | openmode::nocreate); BOOST_CHECK(output_stream << "abcdef"); } BOOST_AUTO_TEST_CASE(output_stream_out_nocreate_flag_fails) { random_generator generator; path target = to_string(generator()); BOOST_CHECK_THROW( ofstream(filesystem(), target, openmode::out | openmode::nocreate), system_error); BOOST_CHECK(!exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_out_noreplace_flag) { random_generator generator; path target = to_string(generator()); ofstream output_stream(filesystem(), target, openmode::out | openmode::noreplace); BOOST_CHECK(exists(filesystem(), target)); BOOST_CHECK(output_stream << "abcdef"); } BOOST_AUTO_TEST_CASE(output_stream_out_noreplace_flag_fails) { path target = new_file_in_sandbox(); BOOST_CHECK_THROW( ofstream(filesystem(), target, openmode::out | openmode::noreplace), system_error); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_in_flag_does_not_create) { // In flag suppresses creation. Matches standard lib ofstream. random_generator generator; path target = to_string(generator()); BOOST_CHECK_THROW(ofstream(filesystem(), target, openmode::in), system_error); BOOST_CHECK(!exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_in_out_does_not_create) { random_generator generator; path target = to_string(generator()); BOOST_CHECK_THROW( ofstream(filesystem(), target, openmode::in | openmode::out), system_error); BOOST_CHECK(!exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_in_out_flag_updates) { // Unlike the out flag for output-only streams, which truncates, the // out flag on an input stream leaves the existing contents because the // input stream forces the in flag and in|out means update existing path target = new_file_in_sandbox_containing_data("gobbledy gook"); { ofstream output_stream(filesystem(), target, openmode::in | openmode::out); BOOST_CHECK(exists(filesystem(), target)); BOOST_CHECK(output_stream << "abcdef"); } ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "abcdefdy"); BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gook"); BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(output_stream_out_trunc_flag_creates) { random_generator generator; path target = to_string(generator()); ofstream output_stream(filesystem(), target, openmode::out | openmode::trunc); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_out_trunc_nocreate_flag) { path target = new_file_in_sandbox(); ofstream output_stream(filesystem(), target, openmode::out | openmode::trunc | openmode::nocreate); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_out_trunc_nocreate_flag_fails) { random_generator generator; path target = to_string(generator()); BOOST_CHECK_THROW(ofstream output_stream(filesystem(), target, openmode::out | openmode::trunc | openmode::nocreate), system_error); BOOST_CHECK(!exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_out_trunc_noreplace_flag) { random_generator generator; path target = to_string(generator()); ofstream output_stream(filesystem(), target, openmode::out | openmode::trunc | openmode::noreplace); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_out_trunc_noreplace_flag_fails) { path target = new_file_in_sandbox(); BOOST_CHECK_THROW(ofstream output_stream(filesystem(), target, openmode::out | openmode::trunc | openmode::noreplace), system_error); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_out_trunc_flag_truncates) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); { ofstream output_stream(filesystem(), target, openmode::out | openmode::trunc); BOOST_CHECK(output_stream << "abcdef"); } ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "abcdef"); BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(output_stream_in_out_trunc_flag_creates) { random_generator generator; path target = to_string(generator()); ofstream output_stream(filesystem(), target, openmode::in | openmode::out | openmode::trunc); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_in_out_trunc_flag_truncates) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); { ofstream output_stream(filesystem(), target, openmode::in | openmode::out | openmode::trunc); BOOST_CHECK(output_stream << "abcdef"); } ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "abcdef"); BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(output_stream_out_append_flag_creates) { random_generator generator; path target = to_string(generator()); ofstream output_stream(filesystem(), target, openmode::out | openmode::app); BOOST_CHECK(exists(filesystem(), target)); } BOOST_AUTO_TEST_CASE(output_stream_out_append_flag_appends) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); { ofstream output_stream(filesystem(), target, openmode::out | openmode::app); BOOST_CHECK(output_stream << "abcdef"); } ifstream input_stream(filesystem(), target); string bob; /* If the tests fail here, the version of OpenSSH being used is probably old and doesn't support FXF_APPEND */ BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gobbledy"); BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gookabcdef"); BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK(input_stream.eof()); } BOOST_AUTO_TEST_CASE(output_stream_fails_to_open_read_only_by_default) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); BOOST_CHECK_THROW(ofstream(filesystem(), target), system_error); } BOOST_AUTO_TEST_CASE(output_stream_out_flag_fails_to_open_read_only) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); BOOST_CHECK_THROW(ofstream(filesystem(), target, openmode::out), system_error); } BOOST_AUTO_TEST_CASE(output_stream_in_out_flag_fails_to_open_read_only) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); BOOST_CHECK_THROW( ofstream(filesystem(), target, openmode::in | openmode::out), system_error); } // Because output streams force out flag, they can't open read-only files BOOST_AUTO_TEST_CASE(output_stream_in_flag_fails_to_open_read_only) { path target = new_file_in_sandbox(); make_file_read_only(filesystem(), target); BOOST_CHECK_THROW(ofstream(filesystem(), target, openmode::in), system_error); } // By default ostreams overwrite the file so seeking will cause subsequent // output to write after the file end. The skipped bytes should be filled // with NUL BOOST_AUTO_TEST_CASE(output_stream_seek_output_absolute_overshoot) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ofstream s(filesystem(), target); s.seekp(2, std::ios_base::beg); BOOST_CHECK(s << "r"); s.flush(); ifstream input_stream(filesystem(), target); string expected_data("\0\0r", 3); vector buffer(expected_data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), expected_data.begin(), expected_data.end()); } BOOST_AUTO_TEST_CASE(output_stream_seek_output_absolute) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ofstream s(filesystem(), target, openmode::in); s.seekp(1, std::ios_base::beg); BOOST_CHECK(s << "r"); s.flush(); ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "grbbledy"); } // By default ostreams overwrite the file so seeking will cause subsequent // output to write after the file end. The skipped bytes should be filled // with NUL BOOST_AUTO_TEST_CASE(output_stream_seek_output_relative_overshoot) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ofstream s(filesystem(), target); s.seekp(1, std::ios_base::cur); s.seekp(1, std::ios_base::cur); BOOST_CHECK(s << "r"); s.flush(); ifstream input_stream(filesystem(), target); string expected_data("\0\0r", 3); vector buffer(expected_data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), expected_data.begin(), expected_data.end()); } BOOST_AUTO_TEST_CASE(output_stream_seek_output_relative) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ofstream s(filesystem(), target, openmode::in); s.seekp(1, std::ios_base::cur); s.seekp(1, std::ios_base::cur); BOOST_CHECK(s << "r"); s.flush(); ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gorbledy"); } // By default ostreams overwrite the file. Seeking TO the end of this empty // file will just start writing from the beginning. No NUL bytes are // inserted anywhere BOOST_AUTO_TEST_CASE(output_stream_seek_output_end) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ofstream s(filesystem(), target); s.seekp(0, std::ios_base::end); BOOST_CHECK(s << "r"); s.flush(); ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "r"); BOOST_CHECK(!(input_stream >> bob)); BOOST_CHECK_EQUAL(bob, "r"); } // By default ostreams overwrite the file. Seeking past the end will cause // subsequent output to write after the file end. The skipped bytes will // be filled with NUL. BOOST_AUTO_TEST_CASE(output_stream_seek_output_end_overshoot) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ofstream s(filesystem(), target); s.seekp(3, std::ios_base::end); BOOST_CHECK(s << "r"); s.flush(); ifstream input_stream(filesystem(), target); string expected_data("\0\0\0r", 4); vector buffer(expected_data.size()); BOOST_CHECK(input_stream.read(&buffer[0], buffer.size())); BOOST_CHECK_EQUAL_COLLECTIONS(buffer.begin(), buffer.end(), expected_data.begin(), expected_data.end()); } BOOST_AUTO_TEST_CASE(output_stream_seek_output_before_end) { path target = new_file_in_sandbox_containing_data("gobbledy gook"); ofstream s(filesystem(), target, openmode::in); s.seekp(-3, std::ios_base::end); BOOST_CHECK(s << "r"); s.flush(); ifstream input_stream(filesystem(), target); string bob; BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "gobbledy"); BOOST_CHECK(input_stream >> bob); BOOST_CHECK_EQUAL(bob, "grok"); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/path_test.cpp ================================================ /** @file Tests for ssh filesystem path. @if license Copyright (C) 2015 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #include #include #include #include #include #include #include using ssh::filesystem::path; using boost::locale::conv::from_utf; using boost::locale::util::get_system_locale; //using boost::filesystem::path; using std::string; using std::stringstream; using std::wstring; using std::wstringstream; namespace std { ostream& operator<<(ostream& out, const wstring& wide_in) { out << from_utf(wide_in, get_system_locale()); return out; } inline ostream& operator<<(ostream& out, const wchar_t* wide_in) { out << wstring(wide_in); return out; } } BOOST_AUTO_TEST_SUITE(path_tests) BOOST_AUTO_TEST_CASE( default_path_is_empty ) { const path p; BOOST_CHECK(p.empty()); } BOOST_AUTO_TEST_CASE( default_path_filename_is_empty ) { const path p; BOOST_CHECK(p.filename().empty()); } BOOST_AUTO_TEST_CASE( default_path_is_equal_to_itself ) { const path p; BOOST_CHECK_EQUAL(p, p); } BOOST_AUTO_TEST_CASE( default_path_is_equal_to_another_default_path ) { const path p; const path q; BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( default_path_is_equal_to_a_constructed_copy ) { const path p; const path q(p); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( default_path_is_equal_to_an_assigned_copy ) { const path p; path q; q = p; BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( default_path_is_different_to_a_single_segment_path ) { const path p; const path q("other path"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( default_path_converts_explicity_to_empty_string ) { const path p; BOOST_CHECK_EQUAL(p.native(), path::string_type()); } BOOST_AUTO_TEST_CASE( default_path_streams_nothing_to_narrow_string ) { const path p; stringstream ss; ss << p; BOOST_CHECK_EQUAL(ss.str(), string()); } BOOST_AUTO_TEST_CASE( default_path_streams_nothing_to_wide_string ) { const path p; wstringstream ss; ss << p; BOOST_CHECK_EQUAL(ss.str(), wstring()); } BOOST_AUTO_TEST_CASE( default_path_converts_implicitly_to_empty_string ) { const path p; const path::string_type s = p; BOOST_CHECK_EQUAL(s, path::string_type()); } BOOST_AUTO_TEST_CASE( default_path_is_at_end_of_iteration ) { const path p; BOOST_CHECK(p.begin() == p.end()); BOOST_CHECK_EQUAL(std::distance(p.begin(), p.end()), 0); } BOOST_AUTO_TEST_CASE( default_path_is_relative ) { const path p; BOOST_CHECK(p.is_relative()); } BOOST_AUTO_TEST_CASE( default_path_is_not_absolute ) { const path p; BOOST_CHECK(!p.is_absolute()); } BOOST_AUTO_TEST_CASE( default_path_has_no_parent_path ) { const path p; BOOST_CHECK(!p.has_parent_path()); } BOOST_AUTO_TEST_CASE( default_path_parent_path_is_empty ) { const path p; BOOST_CHECK(p.parent_path().empty()); } BOOST_AUTO_TEST_CASE( default_path_has_no_relative_path ) { const path p; BOOST_CHECK(!p.has_relative_path()); } BOOST_AUTO_TEST_CASE( default_path_relative_path_is_empty ) { const path p; BOOST_CHECK(p.relative_path().empty()); } BOOST_AUTO_TEST_CASE( root_path_is_not_empty ) { const path p("/"); BOOST_CHECK(!p.empty()); } BOOST_AUTO_TEST_CASE( root_path_is_equal_to_itself ) { const path p("/"); BOOST_CHECK_EQUAL(p, p); } BOOST_AUTO_TEST_CASE( root_path_filename_is_the_root_path ) { const path p("/"); BOOST_CHECK_EQUAL(p.filename(), path("/")); } BOOST_AUTO_TEST_CASE( root_path_is_equal_to_another_root_path ) { const path p("/"); const path q("/"); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( root_path_is_different_to_a_non_root_relative_path ) { const path p("/"); const path q("foo"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( root_path_is_different_to_a_non_root_absolute_path ) { const path p("/"); const path q("/foo"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( root_path_is_different_to_a_default_path ) { const path p("/"); const path q; BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( root_path_is_equal_to_a_constructed_copy ) { const path p("/"); const path q(p); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( root_path_is_equal_to_an_assigned_copy ) { const path p("/"); path q; q = p; BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( root_path_converts_explicity_to_original_string ) { const path p("/"); BOOST_CHECK_EQUAL(p.native(), "/"); } BOOST_AUTO_TEST_CASE( root_path_converts_implicitly_to_original_string ) { const path p("/"); const path::string_type s = p; BOOST_CHECK_EQUAL(s, "/"); } BOOST_AUTO_TEST_CASE( root_path_can_iterate_once ) { const path p("/"); BOOST_CHECK(p.begin() != p.end()); BOOST_CHECK_EQUAL(std::distance(p.begin(), p.end()), 1); } BOOST_AUTO_TEST_CASE( root_path_iterator_produces_original_path ) { const path p("/"); BOOST_REQUIRE(p.begin() != p.end()); BOOST_CHECK_EQUAL(*(p.begin()), path("/")); } BOOST_AUTO_TEST_CASE( root_path_iteration_is_bidirectional ) { const path p("/"); BOOST_REQUIRE(p.begin() != p.end()); path::iterator it = --p.end(); BOOST_CHECK_EQUAL(*it, path("/")); } BOOST_AUTO_TEST_CASE( root_path_is_not_relative ) { const path p("/"); BOOST_CHECK(!p.is_relative()); } BOOST_AUTO_TEST_CASE( root_path_is_absolute ) { const path p("/"); BOOST_CHECK(p.is_absolute()); } BOOST_AUTO_TEST_CASE( root_path_has_no_parent_path ) { const path p("/"); BOOST_CHECK(!p.has_parent_path()); } BOOST_AUTO_TEST_CASE( root_path_parent_path_is_empty ) { const path p("/"); BOOST_CHECK(p.parent_path().empty()); } BOOST_AUTO_TEST_CASE( root_path_has_no_relative_path ) { const path p("/"); BOOST_CHECK(!p.has_relative_path()); } BOOST_AUTO_TEST_CASE( root_path_relative_path_is_empty ) { const path p("/"); BOOST_CHECK(p.relative_path().empty()); } BOOST_AUTO_TEST_CASE( dot_path_is_not_empty ) { const path p("."); BOOST_CHECK(!p.empty()); } BOOST_AUTO_TEST_CASE( dot_path_is_equal_to_itself ) { const path p("."); BOOST_CHECK_EQUAL(p, p); } BOOST_AUTO_TEST_CASE( dot_path_filename_is_the_dot_path ) { const path p("."); BOOST_CHECK_EQUAL(p.filename(), path(".")); } BOOST_AUTO_TEST_CASE( dot_path_is_equal_to_another_dot_path ) { const path p("."); const path q("."); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( dot_path_is_different_to_a_non_dot_path ) { const path p("."); const path q("foo"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( dot_path_is_different_to_the_root_path ) { const path p("."); const path q("/"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( dot_path_is_different_to_a_directory_path ) { const path p("."); const path q("foo/"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( dot_path_is_different_to_a_default_path ) { const path p("."); const path q; BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( dot_path_is_equal_to_a_constructed_copy ) { const path p("."); const path q(p); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( dot_path_is_equal_to_an_assigned_copy ) { const path p("."); path q; q = p; BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( dot_path_converts_explicity_to_dot_string ) { const path p("."); BOOST_CHECK_EQUAL(p.native(), "."); } BOOST_AUTO_TEST_CASE( dot_path_converts_implicitly_to_dot_string ) { const path p("."); const path::string_type s = p; BOOST_CHECK_EQUAL(s, "."); } BOOST_AUTO_TEST_CASE( dot_path_can_iterate_once ) { const path p("."); BOOST_CHECK(p.begin() != p.end()); BOOST_CHECK_EQUAL(std::distance(p.begin(), p.end()), 1); } BOOST_AUTO_TEST_CASE( dot_path_iterator_produces_original_path ) { const path p("."); BOOST_REQUIRE(p.begin() != p.end()); BOOST_CHECK_EQUAL(*(p.begin()), path(".")); } BOOST_AUTO_TEST_CASE( dot_path_iteration_is_bidirectional ) { const path p("."); BOOST_REQUIRE(p.begin() != p.end()); path::iterator it = --p.end(); BOOST_CHECK_EQUAL(*it, path(".")); } BOOST_AUTO_TEST_CASE( dot_path_is_relative ) { const path p("."); BOOST_CHECK(p.is_relative()); } BOOST_AUTO_TEST_CASE( dot_path_is_not_absolute ) { const path p("."); BOOST_CHECK(!p.is_absolute()); } BOOST_AUTO_TEST_CASE( dot_path_has_no_parent_path ) { const path p("."); BOOST_CHECK(!p.has_parent_path()); } BOOST_AUTO_TEST_CASE( dot_path_parent_path_is_empty ) { const path p("."); BOOST_CHECK(p.parent_path().empty()); } BOOST_AUTO_TEST_CASE( dot_path_has_relative_path ) { const path p("."); BOOST_CHECK(p.has_relative_path()); } BOOST_AUTO_TEST_CASE( dot_path_is_its_own_relative_path ) { const path p("."); BOOST_CHECK_EQUAL(p.relative_path(), p); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_not_empty ) { const path p("/Test Filename.txt"); BOOST_CHECK(!p.empty()); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_filename_excludes_root ) { const path p("/Test Filename.txt"); BOOST_CHECK_EQUAL(p.filename(), path("Test Filename.txt")); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_equal_to_itself ) { const path p("/Test Filename.txt"); BOOST_CHECK_EQUAL(p, p); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_equal_to_another_path_from_equal_source ) { const path p("/Test Filename.txt"); const path q("/Test Filename.txt"); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_different_to_another_path_from_different_source ) { const path p("/Test Filename.txt"); const path q("/Test Filename.txp"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_different_to_similar_relative_path ) { const path p("/Test Filename.txt"); const path q("Test Filename.txt"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_equality_is_case_sensitive ) { const path p("/Test Filename.txt"); const path q("/Test filename.txt"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_equal_to_a_constructed_copy ) { const path p("/Test Filename.txt"); const path q(p); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_equal_to_an_assigned_copy ) { const path p("/Test Filename.txt"); path q; q = p; BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_less_than_lexi_greater_source ) { const path p("/Test Filename.txs"); const path q("/Test Filename.txt"); BOOST_CHECK_LT(p, q); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_greater_than_lexi_less_source ) { const path p("/Test Filename.txt"); const path q("/Test Filename.txs"); BOOST_CHECK_GT(p, q); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_converts_explicity_to_original_string ) { const path p("/Test Filename.txt"); BOOST_CHECK_EQUAL(p.native(), "/Test Filename.txt"); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_converts_implicitly_to_original_string ) { const path p("/Test Filename.txt"); const path::string_type s = p; BOOST_CHECK_EQUAL(s, "/Test Filename.txt"); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_can_iterate_twice ) { const path p("/Test Filename.txt"); BOOST_CHECK(p.begin() != p.end()); BOOST_CHECK_EQUAL(std::distance(p.begin(), p.end()), 2); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_iterator_produces_root_and_filename_single_segment_paths ) { const path p("/Test Filename.txt"); BOOST_REQUIRE(p.begin() != p.end()); path::iterator it = p.begin(); BOOST_CHECK_EQUAL(*it++, path("/")); BOOST_CHECK_EQUAL(*it++, path("Test Filename.txt")); BOOST_CHECK(it == p.end()); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_iteration_is_bidirectional ) { const path p("/Test Filename.txt"); BOOST_REQUIRE(p.begin() != p.end()); path::iterator it = --p.end(); BOOST_CHECK_EQUAL(*it--, path("Test Filename.txt")); BOOST_CHECK_EQUAL(*it, path("/")); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_not_relative ) { const path p("/Test Filename.txt"); BOOST_CHECK(!p.is_relative()); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_is_absolute ) { const path p("/Test Filename.txt"); BOOST_CHECK(p.is_absolute()); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_has_parent_path ) { const path p("/Test Filename.txt"); BOOST_CHECK(p.has_parent_path()); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_parent_path_is_root_path ) { const path p("/Test Filename.txt"); BOOST_CHECK_EQUAL(p.parent_path(), path("/")); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_has_relative_path ) { const path p("/Test Filename.txt"); BOOST_CHECK(p.has_relative_path()); } BOOST_AUTO_TEST_CASE( single_segment_absolute_path_relative_path_is_filename_path ) { const path p("/Test Filename.txt"); BOOST_CHECK_EQUAL(p.relative_path(), p.filename()); } BOOST_AUTO_TEST_CASE( single_segment_relative_path_filename_is_the_single_segment ) { const path p("foo"); BOOST_CHECK_EQUAL(p, path("foo")); } BOOST_AUTO_TEST_CASE( single_segment_relative_path_has_no_parent_path ) { const path p("foo"); BOOST_CHECK(!p.has_parent_path()); } BOOST_AUTO_TEST_CASE( single_segment_relative_path_parent_path_is_empty ) { const path p("foo"); BOOST_CHECK(p.parent_path().empty()); } BOOST_AUTO_TEST_CASE( single_segment_relative_path_has_relative_path ) { const path p("foo"); BOOST_CHECK(p.has_relative_path()); } BOOST_AUTO_TEST_CASE( single_segment_is_its_own_relative_path ) { const path p("foo"); BOOST_CHECK_EQUAL(p.relative_path(), p); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_is_not_empty ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK(!p.empty()); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_filename_is_last_segment ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK_EQUAL(p.filename(), path("Test Filename.txt")); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_is_equal_to_itself ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK_EQUAL(p, p); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_is_equal_to_another_path_from_equal_source ) { const path p("Test Dir/Test Filename.txt"); const path q("Test Dir/Test Filename.txt"); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_is_different_to_another_path_with_same_dir_different_file ) { const path p("Test Dir/Test Filename.txt"); const path q("Test Dir/Test Filename.txp"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_is_different_to_another_path_with_different_dir_same_file ) { const path p("Test Dir/Test Filename.txt"); const path q("Test Dir 2/Test Filename.txt"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_equality_is_case_sensitive ) { const path p("Test Dir/Test Filename.txt"); const path q("Test Dir/Test filename.txt"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_is_equal_to_a_constructed_copy ) { const path p("Test Dir/Test Filename.txt"); const path q(p); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_is_equal_to_an_assigned_copy ) { const path p("Test Dir/Test Filename.txt"); path q; q = p; BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_compares_less_than_lexi_by_segment ) { const path p("a/ad"); const path q("a+/c"); BOOST_CHECK_LT(p, q); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_compares_greater_than_lexi_by_segment ) { const path p("a+/c"); const path q("a/ad"); BOOST_CHECK_GT(p, q); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_converts_explicity_to_original_string ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK_EQUAL(p.native(), "Test Dir/Test Filename.txt"); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_converts_implicitly_to_original_string ) { const path p("Test Dir/Test Filename.txt"); const path::string_type s = p; BOOST_CHECK_EQUAL(s, "Test Dir/Test Filename.txt"); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_can_iterate_twice ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK(p.begin() != p.end()); BOOST_CHECK_EQUAL(std::distance(p.begin(), p.end()), 2); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_iterator_produces_dir_and_file_single_segment_paths ) { const path p("Test Dir/Test Filename.txt"); BOOST_REQUIRE(p.begin() != p.end()); path::iterator it = p.begin(); BOOST_CHECK_EQUAL(*it++, path("Test Dir")); BOOST_CHECK_EQUAL(*it++, path("Test Filename.txt")); BOOST_CHECK(it == p.end()); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_iteration_is_bidirectional ) { const path p("Test Dir/Test Filename.txt"); BOOST_REQUIRE(p.begin() != p.end()); path::iterator it = --p.end(); BOOST_CHECK_EQUAL(*it--, path("Test Filename.txt")); BOOST_CHECK_EQUAL(*it, path("Test Dir")); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_is_relative ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK(p.is_relative()); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_is_not_absolute ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK(!p.is_absolute()); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_has_parent_path ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK(p.has_parent_path()); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_parent_path_omits_last_segment ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK_EQUAL(p.parent_path(), path("Test Dir")); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_has_relative_path ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK(p.has_relative_path()); } BOOST_AUTO_TEST_CASE( multi_segment_relative_path_is_its_own_relative_path ) { const path p("Test Dir/Test Filename.txt"); BOOST_CHECK_EQUAL(p.relative_path(), p); } // NOTE: This behaviour seems very odd an anti-STL (non-interchangeable equal // values) however it seems to be required by the current C++ Standard proposal // (iteration ignores multiple separators, equality based on iteration). // Boost.Filesystem in 1.49 did _not_ have this beahviour, but I suspect the // recent changes to bring it into line with the standard may have introduced // that behaviour. // // TODO: Test what latest Boost.Filesystem behaviour is and, if necessary file a // bug with Boost and/or the C++ standard. BOOST_AUTO_TEST_CASE( multiple_adjacent_separators_do_not_affect_path_equality ) { const path p("foo//bar"); BOOST_CHECK_EQUAL(p, path("foo//bar")); BOOST_CHECK_EQUAL(p, path("foo/bar")); BOOST_CHECK_EQUAL(p, path("foo///bar")); } BOOST_AUTO_TEST_CASE( multiple_adjacent_separators_do_not_affect_iteration ) { const path p("foo//bar"); BOOST_REQUIRE(p.begin() != p.end()); path::iterator it = p.begin(); BOOST_CHECK_EQUAL(*it++, path("foo")); BOOST_CHECK_EQUAL(*it++, path("bar")); BOOST_CHECK(it == p.end()); } BOOST_AUTO_TEST_CASE( multiple_adjacent_separators_do_not_affect_filename ) { const path p("foo//bar"); BOOST_CHECK_EQUAL(p.filename(), path("bar")); } BOOST_AUTO_TEST_CASE( directory_path_is_not_empty ) { const path p("foo/bar/"); BOOST_CHECK(!p.empty()); } BOOST_AUTO_TEST_CASE( directory_path_filename_is_dot ) { const path p("foo/bar/"); BOOST_CHECK_EQUAL(p.filename(), path(".")); } BOOST_AUTO_TEST_CASE( directory_path_is_equal_to_itself ) { const path p("foo/bar/"); BOOST_CHECK_EQUAL(p, p); } BOOST_AUTO_TEST_CASE( directory_path_is_not_equal_to_similar_file_path ) { const path p("foo/bar/"); const path q("foo/bar"); BOOST_CHECK_NE(p, q); } BOOST_AUTO_TEST_CASE( directory_path_is_equal_to_another_path_from_equal_source ) { const path p("foo/bar/"); const path q("foo/bar/"); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( directory_path_is_equal_to_a_constructed_copy ) { const path p("foo/bar/"); const path q(p); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( directory_path_is_equal_to_an_assigned_copy ) { const path p("foo/bar/"); path q; q = p; BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( directory_path_is_less_than_lexi_greater_source ) { const path p("foo/baq/"); const path q("foo/bar/"); BOOST_CHECK_LT(p, q); } BOOST_AUTO_TEST_CASE( directory_path_is_greater_than_lexi_less_source ) { const path p("foo/bar/"); const path q("foo/baq/"); BOOST_CHECK_GT(p, q); } BOOST_AUTO_TEST_CASE( directory_path_converts_explicity_to_original_string ) { const path p("foo/bar/"); BOOST_CHECK_EQUAL(p.native(), "foo/bar/"); } BOOST_AUTO_TEST_CASE( directory_path_converts_implicitly_to_original_string ) { const path p("foo/bar/"); const path::string_type s = p; BOOST_CHECK_EQUAL(s, "foo/bar/"); } BOOST_AUTO_TEST_CASE( directory_path_iterates_once_more_than_number_of_names ) { const path p("foo/bar/"); BOOST_CHECK(p.begin() != p.end()); BOOST_CHECK_EQUAL(std::distance(p.begin(), p.end()), 3); } BOOST_AUTO_TEST_CASE( directory_path_iterator_produces_filename_single_segments_followed_by_dot ) { const path p("foo/bar/"); BOOST_REQUIRE(p.begin() != p.end()); path::iterator it = p.begin(); BOOST_CHECK_EQUAL(*it++, path("foo")); BOOST_CHECK_EQUAL(*it++, path("bar")); BOOST_CHECK_EQUAL(*it++, path(".")); BOOST_CHECK(it == p.end()); } BOOST_AUTO_TEST_CASE( directory_path_iteration_is_bidirectional ) { const path p("foo/bar/"); BOOST_REQUIRE(p.begin() != p.end()); path::iterator it = --p.end(); BOOST_CHECK_EQUAL(*it--, path(".")); BOOST_CHECK_EQUAL(*it--, path("bar")); BOOST_CHECK_EQUAL(*it, path("foo")); } BOOST_AUTO_TEST_CASE( directory_path_has_parent_path ) { const path p("foo/bar/"); BOOST_CHECK(p.has_parent_path()); } BOOST_AUTO_TEST_CASE( directory_path_parent_path_omit_trailing_slash ) { const path p("foo/bar/"); BOOST_CHECK_EQUAL(p.parent_path(), path("foo/bar")); } BOOST_AUTO_TEST_CASE( directory_path_has_relative_path ) { const path p("foo/bar/"); BOOST_CHECK(p.has_relative_path()); } BOOST_AUTO_TEST_CASE( directory_path_is_its_own_relative_path ) { const path p("foo/bar/"); BOOST_CHECK_EQUAL(p.relative_path(), p); } BOOST_AUTO_TEST_CASE( dotted_directory_path_has_parent_path ) { const path p("foo/bar/."); BOOST_CHECK(p.has_parent_path()); } BOOST_AUTO_TEST_CASE( dotted_directory_path_filename_is_dot ) { const path p("foo/bar/."); BOOST_CHECK_EQUAL(p.filename(), path(".")); } BOOST_AUTO_TEST_CASE( dotted_directory_path_parent_path_omit_trailing_slash_and_dot ) { const path p("foo/bar/."); BOOST_CHECK_EQUAL(p.parent_path(), path("foo/bar")); } BOOST_AUTO_TEST_CASE( relative_directory_path_is_relative ) { const path p("foo/bar/"); BOOST_CHECK(p.is_relative()); } BOOST_AUTO_TEST_CASE( absolute_directory_path_is_not_relative ) { const path p("/foo/bar/"); BOOST_CHECK(!p.is_relative()); } BOOST_AUTO_TEST_CASE( relative_directory_path_is_not_absolute ) { const path p("foo/bar/"); BOOST_CHECK(!p.is_absolute()); } BOOST_AUTO_TEST_CASE( absolute_directory_path_is_absolute ) { const path p("/foo/bar/"); BOOST_CHECK(p.is_absolute()); } BOOST_AUTO_TEST_CASE( concatenating_relative_paths_returns_concantenation ) { const path p("foo/bar"); const path q("baz/woz"); BOOST_CHECK_EQUAL(p / q, path("foo/bar/baz/woz")); } BOOST_AUTO_TEST_CASE( concatenating_relative_paths_leaves_both_operands_unchanged ) { const path p("foo/bar"); const path q("baz/woz"); p / q; BOOST_CHECK_EQUAL(p, path("foo/bar")); BOOST_CHECK_EQUAL(q, path("baz/woz")); } BOOST_AUTO_TEST_CASE( concatenating_relative_paths_inserts_single_separator ) { const path p("foo/bar"); const path q("baz/woz"); BOOST_CHECK_EQUAL((p / q).native(), "foo/bar/baz/woz"); } BOOST_AUTO_TEST_CASE( appending_relative_path_to_another_returns_concatenation ) { path p("foo/bar"); const path q("baz/woz"); BOOST_CHECK_EQUAL(p /= q, path("foo/bar/baz/woz")); } BOOST_AUTO_TEST_CASE( appending_relative_path_to_another_changes_latter_to_concatenation ) { path p("foo/bar"); const path q("baz/woz"); p /= q; BOOST_CHECK_EQUAL(p, path("foo/bar/baz/woz")); } BOOST_AUTO_TEST_CASE( appending_relative_path_to_another_leaves_former_unchanged ) { path p("foo/bar"); const path q("baz/woz"); p /= q; BOOST_CHECK_EQUAL(q, path("baz/woz")); } BOOST_AUTO_TEST_CASE( appending_relative_path_to_another_inserts_single_separator ) { path p("foo/bar"); const path q("baz/woz"); p /= q; BOOST_CHECK_EQUAL(p.native(), "foo/bar/baz/woz"); } BOOST_AUTO_TEST_CASE( concatenating_relative_directory_paths_returns_concatenation ) { const path p("foo/bar/"); const path q("baz/woz/"); BOOST_CHECK_EQUAL(p / q, path("foo/bar/baz/woz/")); } BOOST_AUTO_TEST_CASE( concatenating_relative_directory_paths_leaves_both_unchanged ) { const path p("foo/bar/"); const path q("baz/woz/"); p / q; BOOST_CHECK_EQUAL(p, path("foo/bar/")); BOOST_CHECK_EQUAL(q, path("baz/woz/")); } BOOST_AUTO_TEST_CASE( concatenating_relative_directory_paths_inserts_single_separator ) { const path p("foo/bar/"); const path q("baz/woz/"); BOOST_CHECK_EQUAL((p / q).native(), path("foo/bar/baz/woz/")); } BOOST_AUTO_TEST_CASE( appending_relative_directory_path_to_another_returns_concatenation ) { path p("foo/bar/"); const path q("baz/woz/"); BOOST_CHECK_EQUAL(p /= q, path("foo/bar/baz/woz/")); } BOOST_AUTO_TEST_CASE( appending_relative_directory_path_to_another_changes_latter_to_concatenation ) { path p("foo/bar/"); const path q("baz/woz/"); p /= q; BOOST_CHECK_EQUAL(p, path("foo/bar/baz/woz/")); } BOOST_AUTO_TEST_CASE( appending_relative_directory_path_to_another_leaves_former_unchanged ) { path p("foo/bar/"); const path q("baz/woz/"); p /= q; BOOST_CHECK_EQUAL(q, path("baz/woz/")); } BOOST_AUTO_TEST_CASE( appending_relative_directory_path_to_another_inserts_single_separator ) { path p("foo/bar/"); const path q("baz/woz/"); p /= q; BOOST_CHECK_EQUAL(q.native(), "baz/woz/"); } BOOST_AUTO_TEST_CASE( concatenating_relative_and_absolute_returns_concatenation ) { const path p("foo/bar"); const path q("/baz/woz"); BOOST_CHECK_EQUAL(p / q, path("foo/bar/baz/woz")); } BOOST_AUTO_TEST_CASE( concatenating_relative_and_absolute_leaves_both_unchanged ) { const path p("foo/bar"); const path q("/baz/woz"); p / q; BOOST_CHECK_EQUAL(p, path("foo/bar")); BOOST_CHECK_EQUAL(q, path("/baz/woz")); } BOOST_AUTO_TEST_CASE( concatenating_relative_and_absolute_doesnt_insert_separator ) { const path p("foo/bar"); const path q("/baz/woz"); BOOST_CHECK_EQUAL((p / q).native(), "foo/bar/baz/woz"); } BOOST_AUTO_TEST_CASE( appending_absolute_to_relative_returns_concatenation ) { path p("foo/bar"); const path q("/baz/woz"); BOOST_CHECK_EQUAL(p /= q, path("foo/bar/baz/woz")); } BOOST_AUTO_TEST_CASE( appending_absolute_to_relative_changes_latter_to_concatenation ) { path p("foo/bar"); const path q("/baz/woz"); p /= q; BOOST_CHECK_EQUAL(p, path("foo/bar/baz/woz")); } BOOST_AUTO_TEST_CASE( appending_absolute_to_relative_another_leaves_former_unchanged ) { path p("foo/bar"); const path q("/baz/woz"); p /= q; BOOST_CHECK_EQUAL(q, path("/baz/woz")); } BOOST_AUTO_TEST_CASE( appending_absolute_to_relative_doesnt_insert_separator ) { path p("foo/bar"); const path q("/baz/woz"); p /= q; BOOST_CHECK_EQUAL(q.native(), "/baz/woz"); } BOOST_AUTO_TEST_CASE( concatenating_relative_directory_and_absolute_returns_concatenation ) { const path p("foo/bar/"); const path q("/baz/woz"); BOOST_CHECK_EQUAL(p / q, path("foo/bar/baz/woz")); } BOOST_AUTO_TEST_CASE( concatenating_relative_directory_and_absolute_leaves_both_unchanged ) { const path p("foo/bar/"); const path q("/baz/woz"); p / q; BOOST_CHECK_EQUAL(p, path("foo/bar/")); BOOST_CHECK_EQUAL(q, path("/baz/woz")); } BOOST_AUTO_TEST_CASE( concatenating_relative_directory_and_absolute_collapses_adjoining_separators ) { const path p("foo/bar/"); const path q("/baz/woz"); BOOST_CHECK_EQUAL((p / q).native(), "foo/bar/baz/woz"); } BOOST_AUTO_TEST_CASE( appending_absolute_to_relative_directory_returns_concatenation ) { path p("foo/bar/"); const path q("/baz/woz"); BOOST_CHECK_EQUAL(p /= q, path("foo/bar/baz/woz")); } BOOST_AUTO_TEST_CASE( appending_absolute_to_relative_directory_changes_latter_to_concatenation ) { path p("foo/bar/"); const path q("/baz/woz"); p /= q; BOOST_CHECK_EQUAL(p, path("foo/bar/baz/woz")); } BOOST_AUTO_TEST_CASE( appending_absolute_to_relative_directory_leaves_former_unchanged ) { path p("foo/bar/"); const path q("/baz/woz"); p /= q; BOOST_CHECK_EQUAL(q, path("/baz/woz")); } BOOST_AUTO_TEST_CASE( appending_absolute_to_relative_directory_collapses_adjoining_separators ) { path p("foo/bar/"); const path q("/baz/woz"); p /= q; BOOST_CHECK_EQUAL(p.native(), "foo/bar/baz/woz"); } BOOST_AUTO_TEST_CASE( concatenating_default_and_relative_returns_the_latter ) { const path p; const path q("foo/bar"); BOOST_CHECK_EQUAL(p / q, q); } BOOST_AUTO_TEST_CASE( concatenating_default_and_relative_leaves_both_unchanged ) { const path p; const path q("foo/bar"); p / q; BOOST_CHECK_EQUAL(p, path()); BOOST_CHECK_EQUAL(q, path("foo/bar")); } BOOST_AUTO_TEST_CASE( appending_relative_to_default_returns_the_former ) { path p; const path q("foo/bar"); BOOST_CHECK_EQUAL(p /= q, q); } BOOST_AUTO_TEST_CASE( appending_relative_to_default_changes_latter_to_equal_former ) { path p; const path q("foo/bar"); p /= q; BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( appending_relative_to_default_leaves_former_unchanged ) { path p; const path q("foo/bar"); p /= q; BOOST_CHECK_EQUAL(q, path("foo/bar")); } BOOST_AUTO_TEST_CASE( concatenating_root_and_relative_returns_concatenation ) { const path p("/"); const path q("foo/bar"); BOOST_CHECK_EQUAL(p / q, path("/foo/bar")); } BOOST_AUTO_TEST_CASE(concatenating_root_and_relative_leaves_both_unchanged ) { const path p("/"); const path q("foo/bar"); p / q; BOOST_CHECK_EQUAL(p, path("/")); BOOST_CHECK_EQUAL(q, path("foo/bar")); } BOOST_AUTO_TEST_CASE( concatenating_root_and_relative_doesnt_insert_separator ) { const path p("/"); const path q("foo/bar"); BOOST_CHECK_EQUAL((p / q).native(), "/foo/bar"); } BOOST_AUTO_TEST_CASE( appending_relative_to_root_returns_concatenation ) { path p("/"); const path q("foo/bar"); BOOST_CHECK_EQUAL(p /= q, path("/foo/bar")); } BOOST_AUTO_TEST_CASE( appending_relative_to_root_changes_latter_to_concatenation ) { path p("/"); const path q("foo/bar"); p /= q; BOOST_CHECK_EQUAL(p, path("/foo/bar")); } BOOST_AUTO_TEST_CASE( appending_relative_to_root_leaves_former_unchanged ) { path p("/"); const path q("foo/bar"); p /= q; BOOST_CHECK_EQUAL(q, path("foo/bar")); } BOOST_AUTO_TEST_CASE( appending_relative_to_root_doesnt_insert_separator ) { path p("/"); const path q("foo/bar"); p /= q; BOOST_CHECK_EQUAL(p.native(), "/foo/bar"); } BOOST_AUTO_TEST_CASE( concatenating_root_and_root_paths_returns_root_path ) { const path p("/"); const path q("/"); BOOST_CHECK_EQUAL(p / q, path("/")); } BOOST_AUTO_TEST_CASE( concatenating_root_and_root_paths_leaves_both_unchanged ) { const path p("/"); const path q("/"); p / q; BOOST_CHECK_EQUAL(p, path("/")); BOOST_CHECK_EQUAL(q, path("/")); } BOOST_AUTO_TEST_CASE( concatenating_root_and_root_paths_collapses_separators ) { const path p("/"); const path q("/"); BOOST_CHECK_EQUAL((p / q).native(), "/"); } BOOST_AUTO_TEST_CASE( appending_root_path_to_root_path_returns_root_path ) { path p("/"); const path q("/"); BOOST_CHECK_EQUAL(p /= q, path("/")); } BOOST_AUTO_TEST_CASE( appending_root_path_to_root_path_leaves_both_operands_unchanged ) { path p("/"); const path q("/"); p /= q; BOOST_CHECK_EQUAL(p, path("/")); BOOST_CHECK_EQUAL(q, path("/")); } BOOST_AUTO_TEST_CASE( appending_root_path_to_root_path_collapses_separators ) { path p("/"); const path q("/"); p /= q; BOOST_CHECK_EQUAL(p.native(), "/"); } namespace { const char UTF8_STRING1[] = "\xe0\xa4\xae\xe0\xa4\xb9\xe0\xa4\xb8\xe0\xa5\x81\xe0\xa4\xb8"; const wchar_t WIDE_STRING1[] = L"\x92e\x939\x938\x941\x938"; const char UTF8_STRING2[] = "\xe4\xb8\xad\xe5\x9c\x8b"; const wchar_t WIDE_STRING2[] = L"\x4e2d\x570b"; const char UTF8_CONCATENATION[] = "\xe0\xa4\xae\xe0\xa4\xb9\xe0\xa4\xb8\xe0\xa5\x81\xe0\xa4\xb8/" "\xe4\xb8\xad\xe5\x9c\x8b"; const wchar_t WIDE_CONCATENATION[] = L"\x92e\x939\x938\x941\x938/\x4e2d\x570b"; } BOOST_AUTO_TEST_CASE( path_created_from_wide_string_is_equal_to_another ) { const path p(WIDE_STRING1); const path q(WIDE_STRING1); BOOST_CHECK_EQUAL(p, q); } BOOST_AUTO_TEST_CASE( path_created_from_ascii_wide_string_is_equal_to_narrow_equivalent ) { // A non-ASCII narrow path may or may not be interpreted as the same string // as the wide path, depending on OS. On Windows, narrow strings are in the // local code page const path p(L"hello.txt"); const path q("hello.txt"); BOOST_CHECK_EQUAL(p, q); } // TODO: Create and test a constructor that takes a std::locale parameter to // guide how the string is interpreted. Allows passing UTF8 char strings on // Windows BOOST_AUTO_TEST_CASE( path_created_from_wide_string_converts_explicitly_to_original_string ) { const path p(WIDE_STRING1); BOOST_CHECK_EQUAL(p.wstring(), WIDE_STRING1); } BOOST_AUTO_TEST_CASE( path_created_from_wide_string_converts_explicitly_to_utf8_string ) { const path p(WIDE_STRING1); BOOST_CHECK_EQUAL(p.u8string(), UTF8_STRING1); } BOOST_AUTO_TEST_CASE( native_string_is_utf8 ) { const path p(WIDE_STRING1); BOOST_CHECK_EQUAL(p.native(), UTF8_STRING1); } BOOST_AUTO_TEST_CASE( narrow_string_accessor_is_utf8 ) { const path p(WIDE_STRING1); string narrow(p.string()); BOOST_CHECK_EQUAL(narrow, UTF8_STRING1); } BOOST_AUTO_TEST_CASE( wide_string_accessor_preserves_wide_string ) { const path p(WIDE_STRING1); wstring wide(p.string()); BOOST_CHECK_EQUAL(wide, WIDE_STRING1); } BOOST_AUTO_TEST_CASE( string_conversion_to_local_codepage_works ) { // Can't test non-ASCII conversion because the chars may not be supported in // the local codepage const path p("hello"); BOOST_CHECK_EQUAL(p.string(), "hello"); } BOOST_AUTO_TEST_CASE( implicit_string_conversion_is_utf8 ) { const path p(WIDE_STRING1); const path::string_type s = p; BOOST_CHECK_EQUAL(s, UTF8_STRING1); } BOOST_AUTO_TEST_CASE( path_contructs_implicitly_from_narrow_pointer ) { const char* s = "hello"; path p = s; BOOST_CHECK_EQUAL(p.string(), "hello"); } BOOST_AUTO_TEST_CASE( path_contructs_implicitly_from_wide_pointer ) { const wchar_t* s = L"hello"; path p = s; BOOST_CHECK_EQUAL(p.string(), "hello"); } BOOST_AUTO_TEST_CASE( path_contructs_implicitly_from_narrow_string ) { const string s = "hello"; path p = s; BOOST_CHECK_EQUAL(p.string(), "hello"); } BOOST_AUTO_TEST_CASE( path_contructs_implicitly_from_wide_string ) { const wstring s = L"hello"; path p = s; BOOST_CHECK_EQUAL(p.string(), "hello"); } BOOST_AUTO_TEST_CASE( path_contructs_from_narrow_range ) { const string s = "hello"; path p(s.begin(), s.end()); BOOST_CHECK_EQUAL(p.string(), "hello"); } BOOST_AUTO_TEST_CASE( path_contructs_from_wide_range ) { const wstring s = L"hello"; path p(s.begin(), s.end()); BOOST_CHECK_EQUAL(p.string(), "hello"); } BOOST_AUTO_TEST_CASE( appending_wide_string_to_path_extends_path ) { path p(WIDE_STRING1); p /= WIDE_STRING2; const path q(WIDE_CONCATENATION); BOOST_CHECK_EQUAL(p, q); BOOST_CHECK_EQUAL(p.u8string(), UTF8_CONCATENATION); } BOOST_AUTO_TEST_CASE( concatenating_wide_string_and_path_returns_concatenation ) { const path p(WIDE_STRING1); const path q(WIDE_CONCATENATION); BOOST_CHECK_EQUAL(p / WIDE_STRING2, q); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/session_fixture.cpp ================================================ // Copyright 2010, 2011, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "session_fixture.hpp" #include // system_error #include // BOOST_THROW_EXCEPTION #include #include // basic_ostringstream #include // logic_error #include using ssh::session; using boost::system::system_error; using std::auto_ptr; using std::locale; using std::string; using std::ostringstream; using std::logic_error; namespace test { namespace ssh { namespace { /** * Locale-independent port number to port string conversion. */ string port_to_string(long port) { ostringstream stream; stream.imbue(locale::classic()); // force locale-independence stream << port; if (!stream) BOOST_THROW_EXCEPTION( logic_error("Unable to convert port number to string")); return stream.str(); } } void open_socket_to_host(boost::asio::io_service& io, boost::asio::ip::tcp::socket& socket, const string& host_name, int port) { using boost::asio::ip::tcp; tcp::resolver resolver(io); typedef tcp::resolver::query Lookup; Lookup query(host_name, port_to_string(port)); tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); tcp::resolver::iterator end; boost::system::error_code error = boost::asio::error::host_not_found; while (error && endpoint_iterator != end) { socket.close(); socket.connect(*endpoint_iterator++, error); } if (error) BOOST_THROW_EXCEPTION(system_error(error)); } session_fixture::session_fixture() : m_io(0), m_socket(m_io), m_session(session(open_socket(host(), port()).native())) { } session& session_fixture::test_session() { return m_session; } auto_ptr session_fixture::connect_additional_socket() { auto_ptr socket( new boost::asio::ip::tcp::socket(m_io)); open_socket_to_host(m_io, *socket, host(), port()); return socket; } boost::asio::ip::tcp::socket& session_fixture::open_socket(const string& host_name, int port) { open_socket_to_host(m_io, m_socket, host_name, port); return m_socket; } } } // namespace test::ssh ================================================ FILE: test/ssh/session_fixture.hpp ================================================ // Copyright 2010, 2011, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SSH_TEST_FIXTURES_SESSION_FIXTURE_HPP #define SSH_TEST_FIXTURES_SESSION_FIXTURE_HPP #include "openssh_fixture.hpp" #include #include // Boost sockets #include namespace test { namespace ssh { /** * Fixture serving ssh::session objects connected to a running server. */ class session_fixture : virtual public openssh_fixture { public: session_fixture(); ::ssh::session& test_session(); std::auto_ptr connect_additional_socket(); private: boost::asio::ip::tcp::socket& open_socket(const std::string& host_name, int port); boost::asio::io_service m_io; ///< Boost IO system boost::asio::ip::tcp::socket m_socket; ::ssh::session m_session; }; void open_socket_to_host(boost::asio::io_service& io, boost::asio::ip::tcp::socket& socket, const std::string& host_name, int port); } } // namespace test::ssh #endif ================================================ FILE: test/ssh/session_test.cpp ================================================ /** @file Tests for session existence. @if license Copyright (C) 2013 Alexander Lamaison This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. In addition, as a special exception, the the copyright holders give you permission to combine this program with free software programs or the OpenSSL project's "OpenSSL" library (or with modified versions of it, with unchanged license). You may copy and distribute such a system following the terms of the GNU GPL for this program and the licenses of the other code concerned. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. @endif */ #include "openssh_fixture.hpp" #include "session_fixture.hpp" // open_socket_to_host #include // test subject #include #include using ssh::session; using test::ssh::openssh_fixture; using test::ssh::open_socket_to_host; using boost::asio::io_service; using boost::asio::ip::tcp; using boost::move; BOOST_FIXTURE_TEST_SUITE(session_tests, openssh_fixture) BOOST_AUTO_TEST_CASE(default_message) { io_service io; tcp::socket socket(io); open_socket_to_host(io, socket, host(), port()); session s(socket.native()); } BOOST_AUTO_TEST_CASE(custom_message) { io_service io; tcp::socket socket(io); open_socket_to_host(io, socket, host(), port()); session s(socket.native(), "blah"); } BOOST_AUTO_TEST_CASE(swap) { io_service io; // BOTH sockets must be created before first session. Once swapped, // second socket is used by first session, so must outlive it. tcp::socket socket1(io); tcp::socket socket2(io); open_socket_to_host(io, socket1, host(), port()); session s1(socket1.native()); open_socket_to_host(io, socket2, host(), port()); session s2(socket2.native()); boost::swap(s1, s2); } BOOST_AUTO_TEST_CASE(move_construct) { io_service io; tcp::socket socket(io); open_socket_to_host(io, socket, host(), port()); session s1(socket.native()); session s2(move(s1)); } BOOST_AUTO_TEST_CASE(move_assign) { io_service io; tcp::socket socket1(io); open_socket_to_host(io, socket1, host(), port()); session s1(socket1.native()); tcp::socket socket2(io); open_socket_to_host(io, socket2, host(), port()); session s2(socket2.native()); s2 = move(s1); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/sftp_fixture.cpp ================================================ // Copyright 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "sftp_fixture.hpp" #include #include #include #include #include // random_generator #include // to_string #include using ssh::filesystem::directory_iterator; using ssh::filesystem::ofstream; using ssh::filesystem::path; using ssh::filesystem::sftp_file; using ssh::filesystem::sftp_filesystem; using ssh::session; using boost::bind; using boost::uuids::random_generator; using test::ssh::session_fixture; using std::string; namespace { bool filename_matches(const string& filename, const sftp_file& remote_file) { return filename == remote_file.path().filename(); } } namespace test { namespace ssh { sftp_fixture::sftp_fixture() : m_filesystem(authenticate_and_create_sftp()) { } sftp_filesystem& sftp_fixture::filesystem() { return m_filesystem; } path sftp_fixture::sandbox() const { return "sandbox"; } path sftp_fixture::absolute_sandbox() const { return "/home/swish/sandbox"; } sftp_file sftp_fixture::find_file_in_sandbox(const string& filename) { directory_iterator it = filesystem().directory_iterator(sandbox()); directory_iterator pos = find_if(it, filesystem().directory_iterator(), bind(filename_matches, filename, _1)); BOOST_REQUIRE(pos != filesystem().directory_iterator()); return *pos; } path sftp_fixture::new_file_in_sandbox() { random_generator generator; path filename = to_string(generator()); return new_file_in_sandbox(filename); } path sftp_fixture::new_file_in_sandbox(const path& filename) { path file = sandbox() / filename; ofstream(filesystem(), file).close(); return file; } path sftp_fixture::new_file_in_sandbox_containing_data(const string& data) { path p = new_file_in_sandbox(); ofstream s(filesystem(), p); s.write(data.data(), data.size()); return p; } path sftp_fixture::new_file_in_sandbox_containing_data(const path& name, const string& data) { path p = new_file_in_sandbox(name); ofstream s(filesystem(), p); s.write(data.data(), data.size()); return p; } path sftp_fixture::new_directory_in_sandbox() { random_generator generator; path directory_name = to_string(generator()); path directory = sandbox() / directory_name; create_directory(filesystem(), directory); return directory; } void sftp_fixture::create_symlink(const path& link, const path& target) { // Passing arguments in the wrong order to work around OpenSSH bug ::ssh::filesystem::create_symlink(filesystem(), target, link); } sftp_filesystem sftp_fixture::authenticate_and_create_sftp() { session& s = test_session(); s.authenticate_by_key_files(user(), public_key_path(), private_key_path(), ""); return s.connect_to_filesystem(); } } } ================================================ FILE: test/ssh/sftp_fixture.hpp ================================================ // Copyright 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef TEST_SSH_SFTP_FIXTURE_HPP #define TEST_SSH_SFTP_FIXTURE_HPP #include "session_fixture.hpp" #include #include namespace test { namespace ssh { class sftp_fixture : public session_fixture { public: sftp_fixture(); ::ssh::filesystem::sftp_filesystem& filesystem(); ::ssh::filesystem::path sandbox() const; ::ssh::filesystem::path absolute_sandbox() const; ::ssh::filesystem::sftp_file find_file_in_sandbox(const std::string& filename); ::ssh::filesystem::path new_file_in_sandbox(); ::ssh::filesystem::path new_file_in_sandbox(const ::ssh::filesystem::path& filename); ::ssh::filesystem::path new_file_in_sandbox_containing_data(const std::string& data); ::ssh::filesystem::path new_file_in_sandbox_containing_data(const ::ssh::filesystem::path& name, const std::string& data); ::ssh::filesystem::path new_directory_in_sandbox(); void create_symlink(const ::ssh::filesystem::path& link, const ::ssh::filesystem::path& target); private: ::ssh::filesystem::sftp_filesystem authenticate_and_create_sftp(); ::ssh::filesystem::sftp_filesystem m_filesystem; }; } } #endif ================================================ FILE: test/ssh/ssh_server/Dockerfile ================================================ # Copyright 2016 Alexander Lamaison # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . FROM debian:jessie RUN apt-get update \ && apt-get install -y openssh-server \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* RUN mkdir /var/run/sshd # Chmodding because, when building on Windows, files are copied in with # -rwxr-xr-x permissions. # # Copying to a temp location, then moving because chmodding the copied file has # no effect (Docker AUFS-related bug maybe?) COPY ssh_host_rsa_key /tmp/etc/ssh/ssh_host_rsa_key RUN mv /tmp/etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key RUN chmod 600 /etc/ssh/ssh_host_rsa_key RUN adduser --disabled-password --gecos 'Test user for Swish integration tests' swish RUN echo 'swish:my test password' | chpasswd RUN sed -i 's/ChallengeResponseAuthentication no/ChallengeResponseAuthentication yes/' /etc/ssh/sshd_config # SSH login fix. Otherwise user is kicked off after login RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd USER swish RUN mkdir -p /home/swish/.ssh RUN mkdir -p /home/swish/sandbox COPY authorized_keys /tmp/swish/.ssh/authorized_keys RUN cp /tmp/swish/.ssh/authorized_keys /home/swish/.ssh/authorized_keys RUN chmod 600 /home/swish/.ssh/authorized_keys USER root EXPOSE 22 # # -e gives logs via 'docker logs' CMD ["/usr/sbin/sshd", "-D", "-e"] ================================================ FILE: test/ssh/ssh_server/authorized_keys ================================================ ssh-dss AAAAB3NzaC1kc3MAAACBAK2Jh2Ck+8W1+LsFrjgOIH7XHySiONPSdG+faFTZprinh9cjyR3odzntVA7+UuFH14WnGM/ub6MbAXjrxDo1TzGILvW5x6nQ6hdLu7xFygihZ8sO1mIMOVqGdlNbTiYHl8XGjbLt1iXfW8ThM91LGGqmS+cgEiy0wWHYzsOXTDz9AAAAFQD/ebunYNTluoBrEYIoq3LMtQPbcwAAAIEAjPBzkUKcmfMAmb0eO/QAVXmX+L8NC6Vn2m4QguQ2IcJ8NH6VMnxXEBHsnemCOa9jN55G+LnX17PViuKS0O3rqQiSdA5wcHyCHKBT519/v1KQNymDwudfnFvdxUyAAG6MDSxKlpbXDCbrhFd2+ahC9a7rKalRPSXR0R2hhWRvjK0AAACAJ+CGwV/1S4j1GVwa6pSP0nj4V86GWXosTTBg7GT+rKWu8lrxIcr6FzLWgFi/gHoMrgnKWGxO1yF7vkoYM5Yfo84oBYiH+MgpiBuOrZrgzacHsA66JJbUfrESRFWZl2blIPr6Gyjj6cVGgMabK3yCiTRi0v7hwffpm0rKyKv7Goo= awl03@bounty ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAnak1T7zHJ+hVRFBDQ9pf1KVzmd5gaNc7y7NPmL13aOG3sYeJevi1x1WM/R3tb8XnUnzZUX9GJN0MYovvZsw9bknG1mDP72LFbGp/gzPddGIKHBBpvceDaJ85sM/ME3XOtD7uuXQuNAuEHwEzSMMiSIEMcQS+lXIcMLr5xPLEkyNvqsO5RqSjMTLHKHgY8gLWx7oQ1avokhwuDxF7P3Pqtj+rW2Te6vR0i1H6EyFPsBkzkgNXb33cus8M1CnTmYTSgJgmHO2LLcGpjQ5sL8T/PWIWHaSqTnkrFXEMysgoteXnAYILjzyBaqq2WV4KA3TluGdAP2p8gC32QtKmIuis3Q== awl03@bounty ================================================ FILE: test/ssh/ssh_server/ssh_host_rsa_key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEoQIBAAKCAQEArrr/JuJmaZligyfS8vcNur+mWR2ddDQtVdhHzdKUUoR6/Om6 cvxpe61H1YZO1xCpLUBXmkki4HoNtYOpPB2W4V+8U4BDeVBD5crypEOE1+7BAm99 fnEDxYIOZq2/jTP0yQmzCpWYS3COyFmkOL7sfX1wQMeW5zQT2WKcxC6FSWbhDqrB eNEGi687hJJoJ7YXgY/IdiYW5NcOuqRSWljjGS3dAJsHHWk4nJbhjEDXbPaeduMA wQU9i6ELfP3r+q6wdu0P4jWaoo3De1aYxnToV/ldXykpipON4NPamsb6Ph2qlJQK ypq7J4iQgkIIbCU1A31+4ExvcIVoxLQw/aTSbwIBIwKCAQAd9Cu9heWrs+UAinvv Iwmq/EhnDGQijJoOt1zEMrpXSekyq7mQDgN0SZdJLPeSlSRQ5nVq5/dZroYB3A5i E7N3F7nibcJskWq5rcMyGjQHwod8wqfMiGcL6mjeZu2jLXprm0NDpJ3DyicbCA2G EhnpoHmktIBE5FsslI/nHer2o6OA/kVWSEjak+pvI1pm22T8QOBBfY0yAX7B0ebk 8o4lB4cdLf3In7Q0ahpHNOwIPdRvQ2c4Tm/DcfUBkTW2ZYGUd45cFsyHqXZscNNy GX2Wcy/FLEvQ6zBFJsNLpxCYsUyBxfSDygn9dx9RQfiWFXjdRaRPpyRAr+BTXkLU yvabAoGBANt7sxfjvu/SLkRc7TnBoJ0h/AL7Mcuu9PJmOnis4boyF9ZxqbiRiS3J yK+EKxfC0S+xf5WJ5uf7dVGnOXHXKaRl4xH90iRtryNlvtILZwHw1DTqRFxv9jtz tTRrYMEHAnMKzadgDfV/lv4iJ6nwFzK76GQ7RQNZYiGTMEh3pUNjAoGBAMvNLGpz FxhpIh+fVvRjawKgGVP87T482WOUdsF18EEPFMe6D7DO5xpLuJi+C7QkvMI8WjvD /3RGvaSh9Wt7ikLZpeogiSJy121HsEqheTR5hTx2t72ClrjZvIhLbQMRu6PqGPu/ HOC2urEGGYm7O2vnftwpuG3zIVVLM2KstPCFAoGBAM7w+VEJ7opYdMQdGi8kRvqN wbmrAxCAY0ryrCijALbexgS0T5DDu9q28Gr49W4stpquq35dc0/BNBnJje7+EVHc aGFrqOCErHHU9b66Sy23LnsIxBykFAwrRHNAq66u1mx35nk9Tv1pq58nhHun21u4 fAa7ijZblwm2qd3tJsqBAoGAEXf8ficfPJtMEVbM8GBLADmbxV7Sga1xuBQKLdbo tR6MwKmMUPvKqnuE2eRnZzZZUnoznrkHRHsXkcS9Q7ohyzc6G2Hf3mGdb8RQ8HQ9 lsiWZESwqdf+SlvOVNND27EQFV01V2gnC/JnxgfWTaJVjOf07ky4CWycdQZyHmaT Ko8CgYB58jOyXMdo2ggOCG/HX2H92KPPpFUBFCX27fCue8BZLD5quIltpXupx5oj EyltgvPcmNDgvdSadkHvP5s6nykS+n5we+d9yIIJF/BfETWsXjR3ooip+trqiirw 0aHqUDFcYn9unm2wtrMYYViiDLRijNwLZ2sG0JIU4JHyseh+NA== -----END RSA PRIVATE KEY----- ================================================ FILE: test/ssh/stream_threading_test.cpp ================================================ // Copyright 2013, 2016 Alexander Lamaison // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "sftp_fixture.hpp" #include // test subject #include #include #include // packaged_task #include #include using ssh::filesystem::ifstream; using ssh::filesystem::fstream; using ssh::filesystem::path; using ssh::filesystem::sftp_filesystem; using boost::bind; using boost::packaged_task; using boost::thread; using test::ssh::sftp_fixture; using std::string; namespace { // the large data must fill more than one stream buffer (currently set to // 32768 (see DEFAULT_BUFFER_SIZE) string large_data() { string data; for (int i = 0; i < 32000; ++i) { data.push_back('a'); data.push_back('m'); data.push_back('z'); } return data; } } BOOST_FIXTURE_TEST_SUITE(stream_threading_test, sftp_fixture) namespace { string get_first_token(ifstream& stream) { string r; stream >> r; return r; } } BOOST_AUTO_TEST_CASE(stream_read_on_different_threads) { path target1 = new_file_in_sandbox_containing_data("humpty dumpty sat"); path target2 = new_file_in_sandbox_containing_data("on the wall"); sftp_filesystem& chan = filesystem(); ifstream s1(chan, target1); ifstream s2(chan, target2); packaged_task p1(boost::bind(get_first_token, boost::ref(s1))); packaged_task p2(boost::bind(get_first_token, boost::ref(s2))); thread(boost::ref(p1)).detach(); thread(boost::ref(p2)).detach(); BOOST_CHECK_EQUAL(p1.get_future().get(), "humpty"); BOOST_CHECK_EQUAL(p2.get_future().get(), "on"); } // There was a bug in our session locking that meant we locked the session // when opening a file but didn't when closing it (because it happened in // the shared_ptr destructor). This test case triggers that bug by opening // a file (locks and unlocks session), starting to read from a second file // (locks) session and then closing the first file. This will cause all // sorts of bad behaviour of the closure doesn't lock the session so we can // detect it if it regresses. BOOST_AUTO_TEST_CASE(parallel_file_closing) { string data = large_data(); path read_me = new_file_in_sandbox_containing_data(data); path test_me = new_file_in_sandbox(); ifstream stream1(filesystem(), read_me); ifstream stream2(filesystem(), test_me); // Using a long-running stream read operation to make sure the session // is still locked when we try to close the other file packaged_task ps(bind(get_first_token, boost::ref(stream1))); thread(boost::ref(ps)).detach(); thread(bind(&ifstream::close, &stream2)).detach(); BOOST_CHECK_EQUAL(ps.get_future().get(), data); } BOOST_AUTO_TEST_SUITE_END(); ================================================ FILE: test/ssh/test_known_hosts ================================================ host1.example.com,192.168.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZnix8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nCqtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbodUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEldwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4QFeigFf3QY7rGUgBEm/wMgxggdvLUCQ== test@swish unrecognisedkey.example.com,192.168.2.1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/qJfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1E7E= host2.example.com,10.0.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezsHRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEYho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaMVMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yDFZYbAkXz8QjhGL/qTywA7Afglyt5/w== host3.example.com,192.168.1.1 ssh-dss AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt402tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgsrs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYDUvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WGI3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAakCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/IQcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w== test@swish ================================================ FILE: test/ssh/test_known_hosts_hashed ================================================ # There are two entries for each host, first the hashed IP address, second # the hashed host. If you regenerate this file from test_known_hosts # using ssh-keygen -H and the erasure tests start failing, ssh-keygen probably # reordered them so that the host has is first. Just swap each pair of lines # to fix this. |1|zgCaq7/YEx5K8JidOuvMUgLen5M=|2E8Pgkzd8Izo5yUCTkdUHJYxmOE= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZnix8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nCqtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbodUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEldwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4QFeigFf3QY7rGUgBEm/wMgxggdvLUCQ== |1|t3DdA7tkFZOFbi/s0eZ7J5+EFoo=|pZVcJk4rGYIZTdmM56xF75BUBIA= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZnix8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nCqtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbodUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEldwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4QFeigFf3QY7rGUgBEm/wMgxggdvLUCQ== |1|omREKWLfSo4emtLIvwB/TQrLwAk=|banp/Ra/d10UwM5WwLtzMUeKd7M= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/qJfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1E7E= |1|TdmIYPmnBFnDMkYhDtKjnoIyW8M=|tsCyyCykyxVRnt2NejdekCgEqFo= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/qJfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1E7E= |1|QoctyHBi5b2zysdvpnPn/nh7jXI=|UhxbMpvA8OkmFMJBm5ym3Enfurc= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezsHRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEYho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaMVMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yDFZYbAkXz8QjhGL/qTywA7Afglyt5/w== |1|sf6lAuYow3JBSdiZQ99BP4Qnbqc=|LeWkRRPlTMoztEws6an/K6vHwNY= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezsHRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEYho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaMVMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yDFZYbAkXz8QjhGL/qTywA7Afglyt5/w== |1|MNSfXoAF6FmLI6GUb1hOh+rzd40=|eM/07xIcp1edKt3h4kUsnzyjKjY= ssh-dss AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt402tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgsrs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYDUvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WGI3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAakCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/IQcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w== |1|oS2oUoU87z0tnrn6xoZlietQCyo=|k1m5Wo5oSXL5ddO0LgnMtXwg5FY= ssh-dss AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt402tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgsrs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYDUvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WGI3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAakCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/IQcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w== ================================================ FILE: test/ssh/test_known_hosts_out ================================================ 192.168.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZnix8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nCqtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbodUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEldwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4QFeigFf3QY7rGUgBEm/wMgxggdvLUCQ== test@swish host1.example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9QcrMH117S7SNIzhExJJmbKlCqxcIt2QQ5B4gZnix8RJci8U/z2P1noALl+oJ59gD9IuJZBXxjDQhxCRHWuvwNPax4BvtZwew0VnXlrs75nCqtFVwcWPUlSU5ycp958YJ3uKQs9yQffgu+LDU29QJ+r7yQSx/YJPgD+DpVeWG1YNqRbodUYQKWktto3OFJi4cO8t7fAteK+u+x26JQdMtplj/xrR8FNNghMyT7Rckh54/KrEdbEldwXTbp1bm9zDny9OSK6cwVjAk8zdNHCLx9/uurlSNcDRZXCDx3yRJiv8Q4ne0kmbMm4QFeigFf3QY7rGUgBEm/wMgxggdvLUCQ== test@swish 192.168.2.1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/qJfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1E7E= unrecognisedkey.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOnHj5Uw9UIVhG/qJfqz4MXX0AqaIvsX/cO3Y2rR6qRo6HUDS4mD3QPLQxw2tDTs12Iji5v/mWUerKPwnRx1E7E= 10.0.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezsHRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEYho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaMVMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yDFZYbAkXz8QjhGL/qTywA7Afglyt5/w== host2.example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvKS1ply6S6xcb/pxnJQQEB+y123axJUKsYEk2ezsHRNZP920FNM1KXGMmm+i7KugMk7dz46pkE/p4qJ4qVfoeDKojR4GiP1WleKQniTIdgEYho7OmopOUszST1Qo5PK9e2gvVQcsyE6xEJkBdMlBWqfm/2vfyr92IPW1wtR3j3YYCcaMVMdpo0tHiK4qmVJIGcs4BRYRSeWzSFaFdmkhEM7iRxCgQDLykjQEZcKmF5KUEf+SxfNS51B0O4D2aoamsYaAC849HBJgMS/I5CxLAah2uMQXnZwJrCIUZcZDUQrC7LnSgd86P+yDFZYbAkXz8QjhGL/qTywA7Afglyt5/w== 192.168.1.1 ssh-dss AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt402tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgsrs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYDUvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WGI3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAakCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/IQcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w== test@swish host3.example.com ssh-dss AAAAB3NzaC1kc3MAAACBAL+sTKUuo0M9zhbDq414IEA8S3FJWliDJNaO3isqDuh3aEEb2wyDrsTf5b6R73RsrAD6K5b3xfMox7LhjwET3D63OpNmU+SUEJl3oJ/yujPHE87aOkt402tB82+yed6V2/Wy4eLcihi4r4VJie9WaBbezvxYbB+hV8YpaoktvI5PAAAAFQCmyKgsrs/7HtA/WVk2iT4av4dmuQAAAIB4hWeAov90067UdbadIq67v7JM8gFBHRertp33nSYDUvMwqCguiTEnBiOCvdKqGRy6RnnmXgMFqqqE6mHDOMZRQdVCn6M402CYJQ0+HefsC3WGI3DLIygHJgAjUswb8qg83ddYhcgLqF4vGqoqUr4Cxsgy3k9zOXEH+NoCylXW9gAAAIAakCvnTYROP7rqRx7zAlHElQnbjH7D1/6yBvt2JmkPHxmsxQPhiwrlTJqkkCztunLmvO4Z+BoB23HQ6utyC4ZBA40dB/Bpq+jbQUq1RLmhlHULqVT/2Z9QLHHcygBddKrUZznsk1/IQcyLHk77/cxQn6dW+B/7G7AdBc4MYMGM/w== test@swish ================================================ FILE: test/versions/CMakeLists.txt ================================================ # Copyright (C) 2015 Alexander Lamaison # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . set(SOURCES version_test.cpp) swish_test_suite( SUBJECT versions SOURCES ${SOURCES} LIBRARIES ${Boost_LIBRARIES} LABELS unit) ================================================ FILE: test/versions/version_test.cpp ================================================ /** @file Unit tests for version info. @if license Copyright (C) 2013 Alexander Lamaison This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . If you modify this Program, or any covered work, by linking or combining it with the OpenSSL project's OpenSSL library (or a modified version of that library), containing parts covered by the terms of the OpenSSL or SSLeay licenses, the licensors of this Program grant you additional permission to convey the resulting work. @endif */ #include #include #include using std::string; BOOST_AUTO_TEST_SUITE( version_test ) /** * Sensible snapshot version result. */ BOOST_AUTO_TEST_CASE( snapshot ) { string version = swish::snapshot_version(); BOOST_WARN_MESSAGE( !version.empty(), "Legal, but unfortunate, snapshot description"); } BOOST_AUTO_TEST_CASE( release_numeric ) { swish::structured_version version = swish::release_version(); BOOST_CHECK_LT(version.major(), 50); BOOST_CHECK_GE(version.major(), 0); BOOST_CHECK_LT(version.minor(), 500); BOOST_CHECK_GE(version.minor(), 0); BOOST_CHECK_LT(version.bugfix(), 500); BOOST_CHECK_GE(version.bugfix(), 0); } /** * Sensible release version string result. */ BOOST_AUTO_TEST_CASE( release_string ) { string version = swish::release_version().as_string(); BOOST_CHECK_MESSAGE( !version.empty(), "Release version not allowed to be empty"); } BOOST_AUTO_TEST_CASE( timestamp ) { BOOST_CHECK_MESSAGE( !swish::build_date().empty(), "Build date not allowed to be empty"); BOOST_CHECK_MESSAGE( !swish::build_time().empty(), "Build time not allowed to be empty"); } BOOST_AUTO_TEST_SUITE_END() ================================================ FILE: thirdparty/taskdialog98/TaskDialog.h ================================================ #if !defined(AFX_TASKDIALOG_H__20073232_5AA0_1E88_3A6D_0080AD509054__INCLUDED_) #define AFX_TASKDIALOG_H__20073232_5AA0_1E88_3A6D_0080AD509054__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 ///////////////////////////////////////////////////////////////////////////// // CTask98Dialog - Task Dialog for legacy Windows platforms // // Written by Bjarke Viksoe (bjarke@viksoe.dk) // Copyright (c) 2007 Bjarke Viksoe. // // Thanks to David Brown and Yarp of senosoft.com // for submitting several fixes. // // This code may be used in compiled form in any way you desire. This // source file may be redistributed by any means PROVIDING it is // not sold for profit without the authors written consent, and // providing that this notice and the authors name is included. // // This file is provided "as is" with no expressed or implied warranty. // The author accepts no liability if it causes any damage to you or your // computer whatsoever. It's free, so don't hassle me about it. // // Beware of bugs. // Dual licensed under the GPL with permission: // // Copyright (c) 2007 Bjarke Viksoe // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #include "icons.h" #include "atlapp.h" #include "atlctrls.h" #include "atldlgs.h" #define USER32_IDS_OK 800 #define USER32_IDS_CANCEL 801 #define USER32_IDS_RETRY 803 #define USER32_IDS_YES 805 #define USER32_IDS_NO 806 #define USER32_IDS_CLOSE 807 ///////////////////////////////////////////////////////////////////////// // TaskDialog declares // #if _WIN32_WINNT < 0x0501 #ifdef _WIN32 #include #endif // _WIN32 #define MAX_LINKID_TEXT 48 #define L_MAX_URL_LENGTH (2048 + 32 + sizeof("://")) typedef struct tagLITEM { UINT mask; int iLink; UINT state; UINT stateMask; WCHAR szID[MAX_LINKID_TEXT]; WCHAR szUrl[L_MAX_URL_LENGTH]; } LITEM, *PLITEM; typedef struct tagNMLINK { NMHDR hdr; LITEM item; } NMLINK, *PNMLINK; class CLinkCtrl { public: static LPCTSTR GetWndClassName() { return _T("SysLink"); } }; #define BCM_FIRST 0x1600 // Button control messages #define BCM_SETIMAGELIST (BCM_FIRST + 0x0002) typedef struct { HIMAGELIST himl; RECT margin; UINT uAlign; } BUTTON_IMAGELIST, *PBUTTON_IMAGELIST; #ifdef _WIN32 #include #endif // _WIN32 #endif // _WIN32_WINNT #if _WIN32_WINNT < 0x0600 && !defined(TD_WARNING_ICON) #ifdef _WIN32 #include #endif // _WIN32 typedef HRESULT (CALLBACK *PFTASKDIALOGCALLBACK)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData); enum _TASKDIALOG_FLAGS { TDF_ENABLE_HYPERLINKS = 0x0001, TDF_USE_HICON_MAIN = 0x0002, TDF_USE_HICON_FOOTER = 0x0004, TDF_ALLOW_DIALOG_CANCELLATION = 0x0008, TDF_USE_COMMAND_LINKS = 0x0010, TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020, TDF_EXPAND_FOOTER_AREA = 0x0040, TDF_EXPANDED_BY_DEFAULT = 0x0080, TDF_VERIFICATION_FLAG_CHECKED = 0x0100, TDF_SHOW_PROGRESS_BAR = 0x0200, TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400, TDF_CALLBACK_TIMER = 0x0800, TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000, TDF_RTL_LAYOUT = 0x2000, TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000, TDF_CAN_BE_MINIMIZED = 0x8000 }; typedef int TASKDIALOG_FLAGS; typedef enum _TASKDIALOG_MESSAGES { TDM_NAVIGATE_PAGE = WM_USER + 101, TDM_CLICK_BUTTON = WM_USER + 102, TDM_SET_MARQUEE_PROGRESS_BAR = WM_USER + 103, TDM_SET_PROGRESS_BAR_STATE = WM_USER + 104, TDM_SET_PROGRESS_BAR_RANGE = WM_USER + 105, TDM_SET_PROGRESS_BAR_POS = WM_USER + 106, TDM_SET_PROGRESS_BAR_MARQUEE = WM_USER + 107, TDM_SET_ELEMENT_TEXT = WM_USER + 108, TDM_CLICK_RADIO_BUTTON = WM_USER + 110, TDM_ENABLE_BUTTON = WM_USER + 111, TDM_ENABLE_RADIO_BUTTON = WM_USER + 112, TDM_CLICK_VERIFICATION = WM_USER + 113, TDM_UPDATE_ELEMENT_TEXT = WM_USER + 114, TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE = WM_USER + 115, TDM_UPDATE_ICON = WM_USER + 116 } TASKDIALOG_MESSAGES; typedef enum _TASKDIALOG_NOTIFICATIONS { TDN_CREATED = 0, TDN_NAVIGATED = 1, TDN_BUTTON_CLICKED = 2, TDN_HYPERLINK_CLICKED = 3, TDN_TIMER = 4, TDN_DESTROYED = 5, TDN_RADIO_BUTTON_CLICKED = 6, TDN_DIALOG_CONSTRUCTED = 7, TDN_VERIFICATION_CLICKED = 8, TDN_HELP = 9, TDN_EXPANDO_BUTTON_CLICKED = 10 } TASKDIALOG_NOTIFICATIONS; typedef struct _TASKDIALOG_BUTTON { int nButtonID; PCWSTR pszButtonText; } TASKDIALOG_BUTTON; typedef enum _TASKDIALOG_ELEMENTS { TDE_CONTENT, TDE_EXPANDED_INFORMATION, TDE_FOOTER, TDE_MAIN_INSTRUCTION } TASKDIALOG_ELEMENTS; typedef enum _TASKDIALOG_ICON_ELEMENTS { TDIE_ICON_MAIN, TDIE_ICON_FOOTER } TASKDIALOG_ICON_ELEMENTS; #define TD_WARNING_ICON MAKEINTRESOURCEW(-1) #define TD_ERROR_ICON MAKEINTRESOURCEW(-2) #define TD_INFORMATION_ICON MAKEINTRESOURCEW(-3) #define TD_SHIELD_ICON MAKEINTRESOURCEW(-4) enum _TASKDIALOG_COMMON_BUTTON_FLAGS { TDCBF_OK_BUTTON = 0x0001, // selected control return value IDOK TDCBF_YES_BUTTON = 0x0002, // selected control return value IDYES TDCBF_NO_BUTTON = 0x0004, // selected control return value IDNO TDCBF_CANCEL_BUTTON = 0x0008, // selected control return value IDCANCEL TDCBF_RETRY_BUTTON = 0x0010, // selected control return value IDRETRY TDCBF_CLOSE_BUTTON = 0x0020 // selected control return value IDCLOSE }; typedef int TASKDIALOG_COMMON_BUTTON_FLAGS; typedef struct _TASKDIALOGCONFIG { UINT cbSize; HWND hwndParent; HINSTANCE hInstance; // used for MAKEINTRESOURCE() strings TASKDIALOG_FLAGS dwFlags; // TASKDIALOG_FLAGS (TDF_XXX) flags TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons; // TASKDIALOG_COMMON_BUTTON (TDCBF_XXX) flags PCWSTR pszWindowTitle; // string or MAKEINTRESOURCE() union { HICON hMainIcon; PCWSTR pszMainIcon; }; PCWSTR pszMainInstruction; PCWSTR pszContent; UINT cButtons; const TASKDIALOG_BUTTON *pButtons; int nDefaultButton; UINT cRadioButtons; const TASKDIALOG_BUTTON *pRadioButtons; int nDefaultRadioButton; PCWSTR pszVerificationText; PCWSTR pszExpandedInformation; PCWSTR pszExpandedControlText; PCWSTR pszCollapsedControlText; union { HICON hFooterIcon; PCWSTR pszFooterIcon; }; PCWSTR pszFooter; PFTASKDIALOGCALLBACK pfCallback; LONG_PTR lpCallbackData; UINT cxWidth; } TASKDIALOGCONFIG; #ifdef _WIN32 #include #endif // _WIN32 #endif // _WIN32_WINNT ///////////////////////////////////////////////////////////////////////// // TaskDialog dialog implementation // template< class T > class CTask98DialogImpl : public WTL::CIndirectDialogImpl, public WTL::CDialogBaseUnits { public: enum { IDC_TASKDLG_OK = IDOK, IDC_TASKDLG_YES = IDYES, IDC_TASKDLG_NO = IDNO, IDC_TASKDLG_CANCEL = IDCANCEL, IDC_TASKDLG_RETRY = IDRETRY, IDC_TASKDLG_CLOSE = IDCLOSE, // IDC_TASKDLG_TITLETEXT = 0x7400, IDC_TASKDLG_INSTRUCTIONSTEXT, IDC_TASKDLG_INSTRUCTIONSICON, IDC_TASKDLG_CONTENTTEXT, IDC_TASKDLG_VERIFYBUTTON, IDC_TASKDLG_EXPANDERICON, IDC_TASKDLG_EXPANDERTEXT, IDC_TASKDLG_EXPANDERHIDDENBUTTON, IDC_TASKDLG_EXTRATEXT, IDC_TASKDLG_FOOTERTEXT, IDC_TASKDLG_FOOTERICON, IDC_TASKDLG_PROGRESSBAR, // IDC_TASKDLG_CUSTOMBUTTON_FIRST, IDC_TASKDLG_CUSTOMBUTTON_LAST = IDC_TASKDLG_CUSTOMBUTTON_FIRST + 20, IDC_TASKDLG_RADIOBUTTON_FIRST, IDC_TASKDLG_RADIOBUTTON_LAST = IDC_TASKDLG_RADIOBUTTON_FIRST + 20, }; enum { TIMERID_TIGGER = 4050, TIMERID_HOVERLINK = 4051, WIDTH_PROBE = 99999, }; enum { MAX_TEXT_LENGTH = 2048 }; CTask98DialogImpl() : m_bNavigated(false) { ::ZeroMemory(&m_cfg, sizeof(m_cfg)); m_cfg.cbSize = sizeof(TASKDIALOGCONFIG); m_cfg.hInstance = ModuleHelper::GetResourceInstance(); m_cfg.pfCallback = T::TaskDialogCallback; m_cfg.lpCallbackData = (LONG_PTR) static_cast(this); _Reset(); } // Message map BEGIN_MSG_MAP(CTask98DialogImpl) MESSAGE_HANDLER(WM_INITDIALOG, OnWmInitDialog) MESSAGE_HANDLER(WM_DESTROY, OnWmDestroy) MESSAGE_HANDLER(WM_HELP, OnWmHelp) MESSAGE_HANDLER(WM_TIMER, OnWmTimer) MESSAGE_HANDLER(WM_ERASEBKGND, OnWmEraseBkgnd) MESSAGE_HANDLER(WM_CTLCOLORSTATIC, OnWmCtlColor) MESSAGE_HANDLER(WM_CTLCOLORBTN, OnWmCtlColor) MESSAGE_HANDLER(WM_SYSCOMMAND, OnSysCommand) MESSAGE_HANDLER(WM_DRAWITEM, OnWmDrawItem) MESSAGE_HANDLER(TDM_CLICK_BUTTON, OnMsgClickButton) MESSAGE_HANDLER(TDM_CLICK_VERIFICATION, OnMsgClickVerification) MESSAGE_HANDLER(TDM_CLICK_RADIO_BUTTON, OnMsgClickRadioButton) MESSAGE_HANDLER(TDM_ENABLE_BUTTON, OnMsgEnableButton) MESSAGE_HANDLER(TDM_ENABLE_RADIO_BUTTON, OnMsgEnableRadioButton) MESSAGE_HANDLER(TDM_SET_PROGRESS_BAR_POS, OnMsgSetProgressBarPos) MESSAGE_HANDLER(TDM_SET_PROGRESS_BAR_RANGE, OnMsgSetProgressBarRange) MESSAGE_HANDLER(TDM_SET_PROGRESS_BAR_STATE, OnMsgSetProgressBarState) MESSAGE_HANDLER(TDM_SET_PROGRESS_BAR_MARQUEE, OnMsgSetProgressBarMarquee) MESSAGE_HANDLER(TDM_SET_MARQUEE_PROGRESS_BAR, OnMsgSetMarqueeProgressBar) MESSAGE_HANDLER(TDM_SET_ELEMENT_TEXT, OnMsgSetElementText) MESSAGE_HANDLER(TDM_UPDATE_ELEMENT_TEXT, OnMsgUpdateElementText) COMMAND_HANDLER(IDC_TASKDLG_EXPANDERICON, STN_CLICKED, OnMsgExpandoClick); COMMAND_HANDLER(IDC_TASKDLG_EXPANDERTEXT, STN_CLICKED, OnMsgExpandoClick); COMMAND_HANDLER(IDC_TASKDLG_EXPANDERHIDDENBUTTON, STN_CLICKED, OnMsgExpandoClick); COMMAND_HANDLER(IDC_TASKDLG_EXPANDERICON, STN_DBLCLK, OnMsgExpandoClick); COMMAND_HANDLER(IDC_TASKDLG_EXPANDERTEXT, STN_DBLCLK, OnMsgExpandoClick); COMMAND_HANDLER(IDC_TASKDLG_EXPANDERHIDDENBUTTON, STN_DBLCLK, OnMsgExpandoClick); COMMAND_ID_HANDLER(IDC_TASKDLG_VERIFYBUTTON, OnMsgVerificationClick); COMMAND_RANGE_HANDLER(1, 64, OnMsgCommonButtonClick) COMMAND_RANGE_HANDLER(IDC_TASKDLG_CUSTOMBUTTON_FIRST, IDC_TASKDLG_CUSTOMBUTTON_LAST, OnMsgCustomButtonClick) COMMAND_RANGE_HANDLER(IDC_TASKDLG_RADIOBUTTON_FIRST, IDC_TASKDLG_RADIOBUTTON_LAST, OnMsgRadioClick) NOTIFY_HANDLER(IDC_TASKDLG_CONTENTTEXT, NM_CLICK, OnMsgHyperlinkClicked) NOTIFY_HANDLER(IDC_TASKDLG_FOOTERTEXT, NM_CLICK, OnMsgHyperlinkClicked) NOTIFY_HANDLER(IDC_TASKDLG_EXTRATEXT, NM_CLICK, OnMsgHyperlinkClicked) END_MSG_MAP() // Operations bool SetConfig(const TASKDIALOGCONFIG* pConfig) { ATLASSERT(pConfig); m_cfg = *pConfig; if( m_cfg.cButtons > 20 ) return false; if( m_cfg.cRadioButtons > 20 ) return false; if( (m_cfg.dwFlags & (TDF_USE_COMMAND_LINKS|TDF_USE_COMMAND_LINKS_NO_ICON)) != 0 && m_cfg.cButtons == 0 ) return FALSE; if( m_cfg.pszExpandedInformation != NULL && m_cfg.pszExpandedControlText == NULL ) m_cfg.pszExpandedControlText = L"Hide &details"; if( m_cfg.pszExpandedInformation != NULL && m_cfg.pszCollapsedControlText == NULL ) m_cfg.pszCollapsedControlText= L"Show &details"; return true; } bool IsNavigated() const { return m_bNavigated; } void GetDialogResult(int* pnButton, int* pnRadioButton, BOOL* pfVerificationFlagChecked) const { ATLASSERT(!::IsWindow(m_hWnd)); if( pnButton != NULL ) *pnButton = m_iButtonResult; if( pnRadioButton != NULL ) *pnRadioButton = m_iRadioResult; if( pfVerificationFlagChecked != NULL ) *pfVerificationFlagChecked = m_bVerificationResult; } BOOL NavigatePage(TASKDIALOGCONFIG* pTaskConfig) { ATLASSERT(::IsWindow(m_hWnd)); if( !SetConfig(pTaskConfig) ) return FALSE; m_bNavigated = true; // BUG: Unlike the real Task Dialog we destroy the window and recreate // a new dialog. There will be flicker and possible reposition of window. return EndDialog(IDCANCEL); } BOOL ClickButton(UINT uID) { ATLASSERT(::IsWindow(m_hWnd)); CButton ctrl = GetDlgItem(uID); for( UINT i = 0; i < m_cfg.cButtons; i++ ) { if( m_cfg.pButtons[i].nButtonID == (int) uID ) ctrl = GetDlgItem(IDC_TASKDLG_CUSTOMBUTTON_FIRST + i); } if( !ctrl.IsWindow() ) return FALSE; ctrl.Click(); return TRUE; } BOOL EnableButton(UINT uID, BOOL bEnable) { ATLASSERT(::IsWindow(m_hWnd)); CButton ctrl = GetDlgItem(uID); for( UINT i = 0; i < m_cfg.cButtons; i++ ) { if( m_cfg.pButtons[i].nButtonID == (int) uID ) ctrl = GetDlgItem(IDC_TASKDLG_CUSTOMBUTTON_FIRST + i); } if( !ctrl.IsWindow() ) return FALSE; return ctrl.EnableWindow(bEnable); } BOOL ClickRadioButton(UINT uID) { ATLASSERT(::IsWindow(m_hWnd)); CButton ctrl; for( UINT i = 0; i < m_cfg.cRadioButtons; i++ ) { if( m_cfg.pRadioButtons[i].nButtonID == (int) uID ) ctrl = GetDlgItem(IDC_TASKDLG_RADIOBUTTON_FIRST + i); } if( !ctrl.IsWindow() ) return FALSE; ctrl.Click(); return TRUE; } BOOL EnableRadioButton(UINT uID, BOOL bEnable) { ATLASSERT(::IsWindow(m_hWnd)); CButton ctrl; for( UINT i = 0; i < m_cfg.cRadioButtons; i++ ) { if( m_cfg.pRadioButtons[i].nButtonID == (int) uID ) ctrl = GetDlgItem(IDC_TASKDLG_RADIOBUTTON_FIRST + i); } if( !ctrl.IsWindow() ) return FALSE; return ctrl.EnableWindow(bEnable); } BOOL ClickVerification(BOOL bChecked, BOOL bTakeFocus) { BOOL bRes = CheckDlgButton(IDC_TASKDLG_VERIFYBUTTON, bChecked ? BST_CHECKED : BST_UNCHECKED); if( bRes && bTakeFocus ) CWindow(GetDlgItem(IDC_TASKDLG_VERIFYBUTTON)).SetFocus(); return bRes; } BOOL SetElementText(TASKDIALOG_ELEMENTS Element, LPCWSTR pstrText) { ATLASSERT(::IsWindow(m_hWnd)); UINT uID = 0; switch( Element) { case TDE_CONTENT: uID = IDC_TASKDLG_CONTENTTEXT; break; case TDE_MAIN_INSTRUCTION: uID = IDC_TASKDLG_INSTRUCTIONSTEXT; break; case TDE_EXPANDED_INFORMATION: uID = IDC_TASKDLG_EXTRATEXT; break; case TDE_FOOTER: uID = IDC_TASKDLG_FOOTERTEXT; break; } if( uID == 0 ) return FALSE; LPTSTR pstrBuffer = (LPTSTR) malloc(MAX_TEXT_LENGTH * sizeof(TCHAR)); if( pstrBuffer == NULL ) return FALSE; _LoadString(pstrText, pstrBuffer, MAX_TEXT_LENGTH); SetDlgItemText(uID, pstrBuffer); free(pstrBuffer); // BUG: We need to reconstruct the dialog to make space for the new text return TRUE; } BOOL UpdateElementText(TASKDIALOG_ELEMENTS Element, LPCWSTR pstrText) { return SetElementText(Element, pstrText); } BOOL UpdateIcon(TASKDIALOG_ICON_ELEMENTS Element, LPCWSTR pstrIcon) { ATLASSERT(::IsWindow(m_hWnd)); UINT nCtlId = 0; switch( Element ) { case TDIE_ICON_FOOTER: nCtlId = IDC_TASKDLG_FOOTERICON; m_iconMain = _LoadIcon(pstrIcon, m_Metrics.cxyLargeIcon); break; case TDIE_ICON_MAIN: nCtlId = IDC_TASKDLG_INSTRUCTIONSICON; m_iconFooter = _LoadIcon(pstrIcon, m_Metrics.cxySmallIcon); break; default: return FALSE; } CWindow wnd = GetDlgItem(nCtlId); if( !wnd.IsWindow() ) return FALSE; return wnd.Invalidate(); } BOOL SetProgressBarPos(UINT nPos) { ATLASSERT(::IsWindow(GetDlgItem(IDC_TASKDLG_PROGRESSBAR))); return SendDlgItemMessage(IDC_TASKDLG_PROGRESSBAR, PBM_SETPOS, nPos) != 0; } BOOL SetProgressBarRange(UINT nMin, UINT nMax) { ATLASSERT(::IsWindow(GetDlgItem(IDC_TASKDLG_PROGRESSBAR))); return SendDlgItemMessage(IDC_TASKDLG_PROGRESSBAR, PBM_SETRANGE, 0, MAKELPARAM(nMin, nMax)) != 0; } BOOL SetProgressBarState(int nNewState) { // Not sure why these assertions were here; marquee works fine on XP //ATLASSERT(RunTimeHelper::IsVista()); ATLASSERT(::IsWindow(GetDlgItem(IDC_TASKDLG_PROGRESSBAR))); #ifndef PBM_SETSTATE const UINT PBM_SETSTATE = WM_USER + 16; #endif // PBM_SETMARQUEE return SendDlgItemMessage(IDC_TASKDLG_PROGRESSBAR, PBM_SETSTATE, (WPARAM) nNewState, 0) != 0; } BOOL SetProgressBarMarquee(int iMarquee, UINT uTimer) { // Not sure why these assertions were here; marquee works fine on XP //ATLASSERT(RunTimeHelper::IsVista()); ATLASSERT(::IsWindow(GetDlgItem(IDC_TASKDLG_PROGRESSBAR))); #ifndef PBM_SETMARQUEE const UINT PBM_SETMARQUEE = WM_USER + 10; #endif // PBS_MARQUEE CWindow wnd = GetDlgItem(IDC_TASKDLG_PROGRESSBAR); if( !wnd.IsWindow() ) return FALSE; return wnd.SendMessage(PBM_SETMARQUEE, (WPARAM) iMarquee, uTimer) != 0; } void SetMarqueeProgressBar(BOOL bMarquee) { // Not sure why these assertions were here; marquee works fine on XP //ATLASSERT(RunTimeHelper::IsVista()); ATLASSERT(::IsWindow(GetDlgItem(IDC_TASKDLG_PROGRESSBAR))); #ifndef PBS_MARQUEE const DWORD PBS_MARQUEE = 0x08; #endif // PBS_MARQUEE CWindow wnd = GetDlgItem(IDC_TASKDLG_PROGRESSBAR); if( !wnd.IsWindow() ) return; wnd.ModifyStyle(bMarquee ? 0 : PBS_MARQUEE, bMarquee ? PBS_MARQUEE : 0); SetProgressBarMarquee(1, 30); } // Default TaskDialog callback implementation static HRESULT CALLBACK TaskDialogCallback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) { ATLASSERT(lpRefData!=0); T* pT = (T*) lpRefData; ATLASSERT(hwnd==pT->m_hWnd); hwnd; BOOL bRet = FALSE; switch( msg ) { case TDN_DIALOG_CONSTRUCTED: pT->OnDialogConstructed(); break; case TDN_CREATED: pT->OnCreated(); break; case TDN_BUTTON_CLICKED: bRet = pT->OnButtonClicked((int) wParam); break; case TDN_RADIO_BUTTON_CLICKED: pT->OnRadioButtonClicked((int) wParam); break; case TDN_HYPERLINK_CLICKED: pT->OnHyperlinkClicked((LPCWSTR) lParam); break; case TDN_EXPANDO_BUTTON_CLICKED: pT->OnExpandoButtonClicked(wParam != 0); break; case TDN_VERIFICATION_CLICKED: pT->OnVerificationClicked(wParam != 0); break; case TDN_HELP: pT->OnHelp(); break; case TDN_TIMER: bRet = pT->OnTimer((DWORD) wParam); break; case TDN_NAVIGATED: pT->OnNavigated(); break; case TDN_DESTROYED: pT->OnDestroyed(); break; default: ATLTRACE2(atlTraceUI, 0, _T("Unknown notification received in CTask98DialogImpl::TaskDialogCallback\n")); break; } return (HRESULT) bRet; } // Overrideables - notification handlers void OnDialogConstructed() { } void OnCreated() { } BOOL OnButtonClicked(int /*nButton*/) { return FALSE; // don't prevent dialog to close } void OnRadioButtonClicked(int /*nRadioButton*/) { } void OnHyperlinkClicked(LPCWSTR /*pszHREF*/) { } void OnExpandoButtonClicked(bool /*bExpanded*/) { } void OnVerificationClicked(bool /*bChecked*/) { } void OnHelp() { } BOOL OnTimer(DWORD /*dwTickCount*/) { return FALSE; // don't reset counter } void OnNavigated() { } void OnDestroyed() { } // Data Members TASKDIALOGCONFIG m_cfg; // Copy of configuration bool m_bNavigated; // Dialog was navigated? int m_iButtonResult; // Button result int m_iRadioResult; // Radio-button result BOOL m_bVerificationResult; // Verification button result DWORD m_dwTick; // Start tick when dialog was shown UINT m_nDefCtlId; // Default dialog button UINT m_nHoverId; // Currently hover Command Link button ID SIZE m_sizeDialog; // Recommended dialog size (in pixels) CFont m_fontTitle; // Title font CFont m_fontText; // Text font CBrush m_brWhite; // Brush for white (top) background CBrush m_brGrey; // Brush for grey (bottom) background CIcon m_iconMain; // Icon in Main Instructions text CIcon m_iconFooter; // Icon in Footer text CIcon m_iconArrowNormal; // Command Link arrow CIcon m_iconArrowHot; // Command Link arrow (hot) CIcon m_iconChevronLess; // Chevron icon (less) CIcon m_iconChevronMore; // Chevron icon (more) bool m_bCreated; // Dialog fully initialized yet? bool m_bExpanded; // Expando-area is expanded? bool m_bHasCustomLinks; // Has custom Command Link buttons? struct { SIZE sizeDialogPadding; SIZE sizeButtonPadding; SIZE sizeLinkPadding; SIZE sizeRadioButton; INT cxRadioIndent; INT cxButtonGap; INT cxButtonsDivider; INT cxySmallIcon; INT cxyLargeIcon; INT cxyArrowIcon; INT cxyExpanderIcon; INT cxLargeIconGap; INT cxSmallIconGap; INT cyInstructionsGap; INT cyContentGap; INT cyRadioGap; INT cyExpanderGap; INT cyButtonLineGap; INT cyProgressBar; COLORREF clrTitleText; COLORREF clrDividerDark; COLORREF clrDividerLight; COLORREF clrButtonDivider; COLORREF clrBkTop; COLORREF clrBkBottom; COLORREF clrCmdLinkSelect; LONG iButtonLinePos; LONG iFooterLinePos; LONG iExpandedLinePos; SIZE sizeButtons; INT cxMinButton; INT cxMinCommandLink; INT cxMaxVerification; INT cxBestMainInstruction; INT cxBestContent; INT cxBestRadioButton; INT cxBestCommandLink; INT cxBestProgressBar; } m_Metrics; // Construction void DoInitTemplate() { m_Template.Reset(); ::InitCommonControls(); bool bIsCommCtrl6 = RunTimeHelper::IsCommCtrl6(); bool bIsVista = RunTimeHelper::IsVista(); CWindowDC dc = HWND_DESKTOP; if( !m_fontTitle.IsNull() ) m_fontTitle.DeleteObject(); if( !m_fontText.IsNull() ) m_fontText.DeleteObject(); CLogFont lfMsgBox; lfMsgBox.SetMessageBoxFont(); m_fontText.CreateFontIndirect(&lfMsgBox); CLogFont lfTitle = lfMsgBox; lfTitle.MakeLarger(bIsCommCtrl6 ? 4 : 2); lfTitle.MakeBolder(bIsCommCtrl6 ? 0 : 1); m_fontTitle.CreateFontIndirect(&lfTitle); InitDialogBaseUnits(m_fontText, m_cfg.hwndParent); SIZE baseUnit = GetDialogBaseUnits(); m_Metrics.sizeDialogPadding.cx = baseUnit.cx * 3 / 2; m_Metrics.sizeDialogPadding.cy = baseUnit.cy * 2 / 3; m_Metrics.sizeButtonPadding.cx = baseUnit.cx * 3; m_Metrics.sizeButtonPadding.cy = baseUnit.cy / 4; m_Metrics.sizeLinkPadding.cx = baseUnit.cx * 2; m_Metrics.sizeLinkPadding.cy = baseUnit.cy / 2; m_Metrics.sizeRadioButton.cx = ::GetSystemMetrics(SM_CXMENUCHECK) + 6; m_Metrics.sizeRadioButton.cy = ::GetSystemMetrics(SM_CYMENUCHECK); m_Metrics.cxRadioIndent = baseUnit.cx * 3 / 2; m_Metrics.cxButtonGap = baseUnit.cx * 1; m_Metrics.cxButtonsDivider = 40; m_Metrics.cxySmallIcon = ::GetSystemMetrics(SM_CYSMICON); m_Metrics.cxyLargeIcon = ::GetSystemMetrics(SM_CYICON); m_Metrics.cxyArrowIcon = 20; m_Metrics.cxyExpanderIcon = 20; m_Metrics.cxLargeIconGap = baseUnit.cx * 3 / 2; m_Metrics.cxSmallIconGap = baseUnit.cx * 3 / 2; m_Metrics.cyInstructionsGap = baseUnit.cy * 3 / 4; m_Metrics.cyContentGap = baseUnit.cy * 3 / 2; m_Metrics.cyRadioGap = baseUnit.cy / 2; m_Metrics.cyExpanderGap = baseUnit.cy / 3; m_Metrics.cyButtonLineGap = baseUnit.cy * 2 / 3; m_Metrics.cyProgressBar = baseUnit.cy * 1; m_Metrics.iButtonLinePos = 0; m_Metrics.iFooterLinePos = 0; m_Metrics.iExpandedLinePos = 0; m_Metrics.sizeButtons.cx = 0; m_Metrics.sizeButtons.cy = 0; m_Metrics.cxMinButton = baseUnit.cx * 4; // +8 DLU m_Metrics.cxMinCommandLink = baseUnit.cx * 60; m_Metrics.cxMaxVerification = baseUnit.cx * 40; m_Metrics.cxBestMainInstruction = baseUnit.cx * 60; m_Metrics.cxBestContent = baseUnit.cx * 50; m_Metrics.cxBestCommandLink = baseUnit.cx * 70; m_Metrics.cxBestProgressBar = baseUnit.cx * 45; m_Metrics.cxBestRadioButton = baseUnit.cx * 60; m_Metrics.clrTitleText = bIsCommCtrl6 ? RGB(0,51,153) : ::GetSysColor(COLOR_BTNTEXT); m_Metrics.clrCmdLinkSelect = bIsCommCtrl6 ? RGB(140,232,255) : RGB(140,140,140); m_Metrics.clrBkTop = ::GetSysColor(bIsVista ? COLOR_WINDOW : COLOR_BTNFACE); m_Metrics.clrBkBottom = ::GetSysColor(COLOR_BTNFACE); m_Metrics.clrDividerDark = ::GetSysColor(COLOR_BTNSHADOW); m_Metrics.clrDividerLight = ::GetSysColor(COLOR_BTNHIGHLIGHT); m_Metrics.clrButtonDivider = ::GetSysColor(bIsCommCtrl6 ? COLOR_BTNSHADOW : COLOR_BTNFACE); if( !m_brWhite.IsNull() ) m_brWhite.DeleteObject(); if( !m_brGrey.IsNull() ) m_brGrey.DeleteObject(); m_brWhite.CreateSolidBrush(m_Metrics.clrBkTop); m_brGrey.CreateSolidBrush(m_Metrics.clrBkBottom); if( m_cfg.dwCommonButtons == 0 && m_cfg.cButtons == 0 ) m_cfg.dwCommonButtons = TDCBF_OK_BUTTON; // Determine size of dialog. First try to determine the optimal width of // the dialog. Then calculate the height of the dialog based on that width. m_sizeDialog = _LayoutControls(WIDTH_PROBE, false); const LONG CX_MIN_DIALOG = baseUnit.cx * 60; const LONG CX_MAX_DIALOG = baseUnit.cx * 150; if( m_sizeDialog.cx < CX_MIN_DIALOG ) m_sizeDialog.cx = CX_MIN_DIALOG; if( m_sizeDialog.cx > CX_MAX_DIALOG ) m_sizeDialog.cx = CX_MAX_DIALOG; if( m_cfg.cxWidth > 0 ) m_sizeDialog.cx = MapDialogUnitsX(m_cfg.cxWidth); LONG cxWidth = m_sizeDialog.cx; m_sizeDialog = _LayoutControls(cxWidth, false); m_sizeDialog.cx = cxWidth; if( (m_cfg.dwCommonButtons & TDCBF_CANCEL_BUTTON) != 0 ) m_cfg.dwFlags |= TDF_ALLOW_DIALOG_CANCELLATION; DWORD dwHelpID = 0; DWORD dwExStyle = 0; DWORD dwStyle = WS_POPUP | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | DS_MODALFRAME | DS_ABSALIGN; if( (m_cfg.dwFlags & TDF_CAN_BE_MINIMIZED) != 0 ) dwStyle |= WS_MINIMIZEBOX; if( (m_cfg.dwFlags & TDF_ALLOW_DIALOG_CANCELLATION) != 0 ) dwStyle |= WS_SYSMENU; if( (m_cfg.dwFlags & TDF_POSITION_RELATIVE_TO_WINDOW) == 0 ) dwStyle |= DS_CENTER; #ifndef WS_EX_LAYOUTRTL const UINT WS_EX_LAYOUTRTL = 0x00400000L; #endif // WS_EX_LAYOUTRTL if( (m_cfg.dwFlags & TDF_RTL_LAYOUT) != 0 ) dwExStyle |= WS_EX_LAYOUTRTL | WS_EX_RIGHT; TCHAR szCaption[120] = { 0 }; _LoadString(m_cfg.pszWindowTitle, szCaption, sizeof(szCaption) / sizeof(TCHAR)); WORD lfHeight = (WORD) lfMsgBox.lfHeight; if( lfMsgBox.lfHeight < 0 ) lfHeight = (WORD) (-MulDiv(72, lfMsgBox.lfHeight, GetDeviceCaps(dc, LOGPIXELSY))); SIZE dluDialog = MapDialogPixels(m_sizeDialog); RECT rc = _CenterDialog(dluDialog); m_Template.Create(true, szCaption, (short) rc.left, (short) rc.top, (short) dluDialog.cx, (short) dluDialog.cy, dwStyle, dwExStyle, lfMsgBox.lfFaceName, lfHeight, (WORD) lfMsgBox.lfWeight, lfMsgBox.lfItalic, lfMsgBox.lfCharSet, dwHelpID); } void DoInitControls() { _LayoutControls(m_sizeDialog.cx, true); } // Message handler LRESULT OnWmInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { // Not accepting callbacks at this moment... m_bCreated = false; _Reset(); // Font assignment SendMessageToDescendants(WM_SETFONT, (WPARAM) (HFONT) m_fontText); SendDlgItemMessage(IDC_TASKDLG_INSTRUCTIONSTEXT, WM_SETFONT, (WPARAM) (HFONT) m_fontTitle); // Create a timer? if( m_bHasCustomLinks ) SetTimer(TIMERID_HOVERLINK, 100); if( (m_cfg.dwFlags & TDF_CALLBACK_TIMER) != 0 ) SetTimer(TIMERID_TIGGER, 200); // Set verification checkmark if( (m_cfg.dwFlags & TDF_VERIFICATION_FLAG_CHECKED) != 0 ) CheckDlgButton(IDC_TASKDLG_VERIFYBUTTON, BST_CHECKED); // Set Marquee Progress Bar if( (m_cfg.dwFlags & TDF_SHOW_MARQUEE_PROGRESS_BAR) != 0 ) SetMarqueeProgressBar(TRUE); // Set default radio button (may be first button) if( (m_cfg.dwFlags & TDF_NO_DEFAULT_RADIO_BUTTON) == 0 && m_cfg.cRadioButtons > 0 ) { CButton ctrl; for( UINT n = 0; !ctrl.IsWindow() && n < m_cfg.cRadioButtons; n++ ) { if( m_cfg.nDefaultRadioButton == m_cfg.pRadioButtons[n].nButtonID ) ctrl = GetDlgItem(IDC_TASKDLG_RADIOBUTTON_FIRST + n); } if( !ctrl.IsWindow() ) ctrl = GetDlgItem(IDC_TASKDLG_RADIOBUTTON_FIRST); if( ctrl.IsWindow() ) ctrl.Click(); } // Clear arrow image on lines? if( !m_bHasCustomLinks && (m_cfg.dwFlags & TDF_USE_COMMAND_LINKS_NO_ICON) != 0 ) { for( UINT n = 0; n < m_cfg.cButtons; n++ ) { CButton ctrl = GetDlgItem(IDC_TASKDLG_CUSTOMBUTTON_FIRST + n); BUTTON_IMAGELIST bil = { (HIMAGELIST) -1, 0 }; ctrl.SendMessage(BCM_SETIMAGELIST, 0, (LPARAM) &bil); } } // Set expansion level if( m_cfg.pszExpandedInformation != NULL && (m_cfg.dwFlags & TDF_EXPANDED_BY_DEFAULT) == 0 ) { SendMessage(WM_COMMAND, MAKEWPARAM(IDC_TASKDLG_EXPANDERICON, STN_CLICKED)); } // Set default dialog button if( m_nDefCtlId > 0 && m_nDefCtlId != (UINT) -1 ) { SendMessage(DM_SETDEFID, m_nDefCtlId); CWindow(GetDlgItem(m_nDefCtlId)).SetFocus(); } // Get icons... m_iconMain = (HICON) ((m_cfg.dwFlags & TDF_USE_HICON_MAIN) != 0 ? m_cfg.hMainIcon : _LoadIcon(m_cfg.pszMainIcon, m_Metrics.cxyLargeIcon)); m_iconFooter = (HICON) ((m_cfg.dwFlags & TDF_USE_HICON_FOOTER) != 0 ? m_cfg.hFooterIcon : _LoadIcon(m_cfg.pszFooterIcon, m_Metrics.cxySmallIcon)); m_iconArrowHot = _LoadIcon(TaskDlgArrowHot_ico, TaskDlgArrowHot_ico_len, 20); m_iconArrowNormal = _LoadIcon(TaskDlgArrowNormal_ico, TaskDlgArrowNormal_ico_len, 20); m_iconChevronLess = _LoadIcon(TaskDlgChevronLess_ico, TaskDlgChevronLess_ico_len, 20); m_iconChevronMore = _LoadIcon(TaskDlgChevronMore_ico, TaskDlgChevronMore_ico_len, 20); if( !m_iconMain.IsNull() && GetParent() == NULL ) SetIcon(m_iconMain, FALSE); // Play that funky music... _PlaySound(); // Ready to accept user input. // Send inital callback messages. m_bCreated = true; _DoCallback(TDN_DIALOG_CONSTRUCTED); _DoCallback(m_bNavigated ? TDN_NAVIGATED : TDN_CREATED); m_bNavigated = false; return 0; } LRESULT OnWmDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { KillTimer(TIMERID_TIGGER); KillTimer(TIMERID_HOVERLINK); _DoCallback(TDN_DESTROYED); m_bVerificationResult = IsDlgButtonChecked(IDC_TASKDLG_VERIFYBUTTON); bHandled = FALSE; return 0; } LRESULT OnWmHelp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { _DoCallback(TDN_HELP); return 0; } LRESULT OnWmTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled) { if( wParam == TIMERID_TIGGER ) { BOOL bReset = (BOOL) _DoCallback(TDN_TIMER, ::GetTickCount() - m_dwTick); if( bReset ) m_dwTick = ::GetTickCount(); } if( wParam == TIMERID_HOVERLINK ) { POINT pt = { 0 }; ::GetCursorPos(&pt); ScreenToClient(&pt); UINT nHoverId = 0; HWND hWnd = ::ChildWindowFromPoint(m_hWnd, pt); if( hWnd != NULL ) { UINT nCtlId = ::GetDlgCtrlID(hWnd); if( nCtlId >= IDC_TASKDLG_CUSTOMBUTTON_FIRST && nCtlId <= IDC_TASKDLG_CUSTOMBUTTON_LAST ) nHoverId = nCtlId; } if( m_nHoverId != nHoverId ) { if( m_nHoverId != 0 ) CWindow(GetDlgItem(m_nHoverId)).Invalidate(); m_nHoverId = nHoverId; if( m_nHoverId != 0 ) CWindow(GetDlgItem(m_nHoverId)).Invalidate(); } } bHandled = FALSE; return 0; } LRESULT OnWmEraseBkgnd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { CClientDC dc = m_hWnd; RECT rcClient = { 0 }; GetClientRect(&rcClient); RECT rcTop = { 0, 0, rcClient.right, m_Metrics.iButtonLinePos }; RECT rcBottom = { 0, m_Metrics.iButtonLinePos, rcClient.right, rcClient.bottom }; dc.FillSolidRect(&rcTop, m_Metrics.clrBkTop); dc.FillSolidRect(&rcBottom, m_Metrics.clrBkBottom); RECT rcButtonLine = { 0, rcBottom.top, rcClient.right, rcBottom.top + 1 }; dc.FillSolidRect(&rcButtonLine, m_Metrics.clrButtonDivider); if( m_Metrics.iFooterLinePos > 0 ) { RECT rcFooterLine1 = { 0, m_Metrics.iFooterLinePos, rcClient.right, m_Metrics.iFooterLinePos + 1 }; dc.FillSolidRect(&rcFooterLine1, m_Metrics.clrDividerDark); RECT rcFooterLine2 = { 0, m_Metrics.iFooterLinePos + 1, rcClient.right, m_Metrics.iFooterLinePos + 2 }; dc.FillSolidRect(&rcFooterLine2, m_Metrics.clrDividerLight); } if( m_Metrics.iExpandedLinePos > 0 ) { RECT rcExpandoLine1 = { 0, m_Metrics.iExpandedLinePos, rcClient.right, m_Metrics.iExpandedLinePos + 1 }; dc.FillSolidRect(&rcExpandoLine1, m_Metrics.clrDividerDark); RECT rcExpandoLine2 = { 0, m_Metrics.iExpandedLinePos + 1, rcClient.right, m_Metrics.iExpandedLinePos + 2 }; dc.FillSolidRect(&rcExpandoLine2, m_Metrics.clrDividerLight); } return 1; } LRESULT OnWmCtlColor(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) { LRESULT lRes = DefWindowProc(); UINT nCtlID = ::GetDlgCtrlID((HWND) lParam); CDCHandle dc = (HDC) wParam; switch( nCtlID ) { case IDC_TASKDLG_INSTRUCTIONSICON: dc.SetBkMode(TRANSPARENT); dc.SetBkColor(m_Metrics.clrBkTop); lRes = (LRESULT) (HBRUSH) m_brWhite; break; case IDC_TASKDLG_INSTRUCTIONSTEXT: dc.SetTextColor(m_Metrics.clrTitleText); dc.SetBkMode(TRANSPARENT); dc.SetBkColor(m_Metrics.clrBkTop); lRes = (LRESULT) (HBRUSH) m_brWhite; break; case IDC_TASKDLG_CONTENTTEXT: dc.SetBkMode(TRANSPARENT); dc.SetBkColor(m_Metrics.clrBkTop); lRes = (LRESULT) (HBRUSH) m_brWhite; break; case IDC_TASKDLG_FOOTERICON: case IDC_TASKDLG_FOOTERTEXT: case IDC_TASKDLG_EXPANDERICON: case IDC_TASKDLG_EXPANDERTEXT: case IDC_TASKDLG_VERIFYBUTTON: dc.SetBkMode(TRANSPARENT); dc.SetBkColor(m_Metrics.clrBkBottom); lRes = (LRESULT) (HBRUSH) m_brGrey; break; case IDC_TASKDLG_OK: case IDC_TASKDLG_NO: case IDC_TASKDLG_YES: case IDC_TASKDLG_RETRY: case IDC_TASKDLG_CLOSE: case IDC_TASKDLG_CANCEL: dc.SetBkMode(TRANSPARENT); dc.SetBkColor(m_Metrics.clrBkBottom); lRes = (LRESULT) (HBRUSH) m_brGrey; break; default: { COLORREF clrBack; if( nCtlID >= IDC_TASKDLG_RADIOBUTTON_FIRST && nCtlID <= IDC_TASKDLG_RADIOBUTTON_LAST ) { clrBack = m_Metrics.clrBkTop; lRes = (LRESULT) (HBRUSH) m_brWhite; } else if( nCtlID >= IDC_TASKDLG_CUSTOMBUTTON_FIRST && nCtlID <= IDC_TASKDLG_CUSTOMBUTTON_LAST && (m_cfg.dwFlags & (TDF_USE_COMMAND_LINKS|TDF_USE_COMMAND_LINKS_NO_ICON)) != 0 ) { clrBack = m_Metrics.clrBkTop; lRes = (LRESULT) (HBRUSH) m_brWhite; } else if( nCtlID == IDC_TASKDLG_EXTRATEXT && (m_cfg.dwFlags & TDF_EXPAND_FOOTER_AREA) == 0 ) { clrBack = m_Metrics.clrBkTop; lRes = (LRESULT) (HBRUSH) m_brWhite; } else { clrBack = m_Metrics.clrBkBottom; lRes = (LRESULT) (HBRUSH) m_brGrey; } dc.SetBkMode(TRANSPARENT); dc.SetBkColor(clrBack); } } return lRes; } LRESULT OnSysCommand(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled) { if( wParam == SC_CLOSE ) { if( (m_cfg.dwFlags & TDF_ALLOW_DIALOG_CANCELLATION) == 0 ) return 0; } bHandled = FALSE; return 0; } LRESULT OnWmDrawItem(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) { LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT) lParam; if( m_bHasCustomLinks && lpDIS->CtlID >= IDC_TASKDLG_CUSTOMBUTTON_FIRST && lpDIS->CtlID <= IDC_TASKDLG_CUSTOMBUTTON_LAST ) { _CustomDrawCommandLink(lpDIS); return 0; } if( lpDIS->CtlID == IDC_TASKDLG_INSTRUCTIONSICON ) { _CustomDrawIcon(lpDIS, m_Metrics.clrBkTop, m_iconMain, m_Metrics.cxyLargeIcon); return 0; } if( lpDIS->CtlID == IDC_TASKDLG_FOOTERICON ) { _CustomDrawIcon(lpDIS, m_Metrics.clrBkBottom, m_iconFooter, m_Metrics.cxySmallIcon); return 0; } if( lpDIS->CtlID == IDC_TASKDLG_EXPANDERICON ) { _CustomDrawIcon(lpDIS, m_Metrics.clrBkBottom, m_bExpanded ? m_iconChevronLess : m_iconChevronMore, m_Metrics.cxyExpanderIcon); return 0; } bHandled = FALSE; return 0; } // TaskDialog API messages LRESULT OnMsgClickButton(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { ClickButton(wParam); return 0; } LRESULT OnMsgClickRadioButton(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { ClickRadioButton(wParam); return 0; } LRESULT OnMsgClickVerification(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) { ClickVerification((BOOL) wParam, (BOOL) lParam); return 0; } LRESULT OnMsgEnableButton(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) { return (LRESULT) EnableButton(wParam, (BOOL) lParam); } LRESULT OnMsgEnableRadioButton(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) { return (LRESULT) EnableRadioButton(wParam, (BOOL) lParam); } LRESULT OnMsgSetElementText(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) { return (LRESULT) SetElementText((TASKDIALOG_ELEMENTS) wParam, (LPCWSTR) lParam); } LRESULT OnMsgUpdateElementText(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) { return (LRESULT) UpdateElementText((TASKDIALOG_ELEMENTS) wParam, (LPCWSTR) lParam); } LRESULT OnMsgSetProgressBarPos(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { SetProgressBarPos(wParam); return 0; } LRESULT OnMsgSetProgressBarRange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/) { SetProgressBarRange(LOWORD(lParam), HIWORD(lParam)); return 0; } LRESULT OnMsgSetMarqueeProgressBar(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { SetMarqueeProgressBar((BOOL) wParam); return 0; } LRESULT OnMsgSetProgressBarState(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { return (LRESULT) SetProgressBarState((int) wParam); } LRESULT OnMsgSetProgressBarMarquee(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) { return (LRESULT) SetProgressBarMarquee(wParam, lParam); } // Command message handlers LRESULT OnMsgCommonButtonClick(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { UINT nBtnID = wID; if( _DoCallback(TDN_BUTTON_CLICKED, nBtnID) == S_FALSE ) return 0; m_iButtonResult = (int) nBtnID; EndDialog(wID); return 0; } LRESULT OnMsgCustomButtonClick(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { UINT nBtnID = m_cfg.pButtons[wID - IDC_TASKDLG_CUSTOMBUTTON_FIRST].nButtonID; if( _DoCallback(TDN_BUTTON_CLICKED, nBtnID) == S_FALSE ) return 0; m_iButtonResult = (int) nBtnID; EndDialog(wID); return 0; } LRESULT OnMsgExpandoClick(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { m_bExpanded = !m_bExpanded; _DoCallback(TDN_EXPANDO_BUTTON_CLICKED, m_bExpanded); _DoExpandCollapse(); return 0; } LRESULT OnMsgVerificationClick(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { _DoCallback(TDN_VERIFICATION_CLICKED, IsDlgButtonChecked(IDC_TASKDLG_VERIFYBUTTON) == BST_CHECKED); return 0; } LRESULT OnMsgRadioClick(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { m_iRadioResult = (int) m_cfg.pRadioButtons[wID - IDC_TASKDLG_RADIOBUTTON_FIRST].nButtonID; _DoCallback(TDN_RADIO_BUTTON_CLICKED, (WPARAM) m_iRadioResult); return 0; } LRESULT OnMsgHyperlinkClicked(int /*idCtrl*/, LPNMHDR pnmh, BOOL& /*bHandled*/) { NMLINK* pLink = (NMLINK*) pnmh; _DoCallback(TDN_HYPERLINK_CLICKED, 0, (LPARAM) static_cast(pLink->item.szID[0] != '\0' ? pLink->item.szID : pLink->item.szUrl)); return 0; } // Implementation void _Reset() { m_iRadioResult = 0; m_iButtonResult = IDCANCEL; m_bExpanded = true; m_nHoverId = 0; m_dwTick = ::GetTickCount(); } SIZE _LayoutControls(int cxMaxDialog, bool bCreateControls) { SIZE sizeEmpty = { 0 }; LPTSTR pstrBuffer = (LPTSTR) malloc(MAX_TEXT_LENGTH * sizeof(TCHAR)); if( pstrBuffer == NULL ) return sizeEmpty; LONG cxDialog, cxExpander, cxMaxText; SIZE sizeTitle, sizeText, sizeTemp; LONG cyExpander; DWORD dwStyle, dwExStyle; UINT n; bool bIsCommCtrl6 = RunTimeHelper::IsCommCtrl6(); bool bIsVista = RunTimeHelper::IsVista(); LONG ypos = m_Metrics.sizeDialogPadding.cy; cxDialog = 0; m_nDefCtlId = 0; // Main icon LONG xpos = m_Metrics.sizeDialogPadding.cx; LONG cyIcon = 0; if( m_cfg.pszMainIcon != NULL || (m_cfg.dwFlags & TDF_USE_HICON_MAIN) != 0 ) { RECT rcIcon = { xpos, ypos, xpos + m_Metrics.cxyLargeIcon, ypos + m_Metrics.cxyLargeIcon }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | SS_OWNERDRAW; dwExStyle = 0; _AddControl(CStatic::GetWndClassName(), IDC_TASKDLG_INSTRUCTIONSICON, rcIcon, dwStyle, dwExStyle, _T("")); } xpos += (rcIcon.right - rcIcon.left) + m_Metrics.cxLargeIconGap; cyIcon = m_Metrics.cxyLargeIcon; } // Main Instructions text if( m_cfg.pszMainInstruction != NULL ) { cxMaxText = cxMaxDialog - xpos - m_Metrics.sizeDialogPadding.cx; if( cxMaxDialog == WIDTH_PROBE ) cxMaxText = m_Metrics.cxBestMainInstruction; sizeText = _GetTextSize(m_cfg.pszMainInstruction, pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK | DT_NOPREFIX, m_fontTitle, cxMaxText); if( sizeText.cy < m_Metrics.cxyLargeIcon ) sizeText.cy = m_Metrics.cxyLargeIcon; RECT rcItem = { xpos, ypos, xpos + sizeText.cx, ypos + sizeText.cy }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX; dwExStyle = 0; _AddControl(CStatic::GetWndClassName(), IDC_TASKDLG_INSTRUCTIONSTEXT, rcItem, dwStyle, dwExStyle, pstrBuffer); } if( rcItem.bottom - rcItem.top < cyIcon ) rcItem.bottom = rcItem.top + cyIcon; ypos += (rcItem.bottom - rcItem.top) + m_Metrics.cyInstructionsGap; if( rcItem.right > cxDialog ) cxDialog = rcItem.right; } // Content text if( m_cfg.pszContent != NULL ) { cxMaxText = cxMaxDialog - xpos - m_Metrics.sizeDialogPadding.cx; if( cxMaxDialog == WIDTH_PROBE ) cxMaxText = m_Metrics.cxBestContent; sizeText = _GetTextSizeHREF(m_cfg.pszContent, pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK | DT_NOPREFIX, m_fontText, cxMaxText); if( cxMaxDialog == WIDTH_PROBE && sizeText.cx > cxMaxText * 2 ) sizeText.cx = m_Metrics.cxBestContent; RECT rcItem = { xpos, ypos, xpos + sizeText.cx, ypos + sizeText.cy }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX; dwExStyle = 0; LPCTSTR pstrClassName = bIsCommCtrl6 ? CLinkCtrl::GetWndClassName() : CStatic::GetWndClassName(); if( !bIsCommCtrl6 ) _RemoveHREF(pstrBuffer); _AddControl(pstrClassName, IDC_TASKDLG_CONTENTTEXT, rcItem, dwStyle, dwExStyle, pstrBuffer); } if( cyIcon > 0 && m_cfg.pszMainInstruction == NULL && rcItem.bottom - rcItem.top < cyIcon ) rcItem.bottom = rcItem.top + cyIcon; ypos += (rcItem.bottom - rcItem.top); if( rcItem.right > cxDialog ) cxDialog = rcItem.right; } ypos += (m_Metrics.cyContentGap / 2); // Expando in Content area if( m_cfg.pszExpandedInformation != NULL && ((m_cfg.dwFlags & TDF_EXPAND_FOOTER_AREA) == 0) ) { cxMaxText = cxMaxDialog - xpos - m_Metrics.sizeDialogPadding.cx; sizeText = _GetTextSizeHREF(m_cfg.pszExpandedInformation, pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK | DT_NOPREFIX, m_fontText, cxMaxText); RECT rcItem = { xpos, ypos, xpos + sizeText.cx, ypos + sizeText.cy }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX; dwExStyle = 0; LPCTSTR pstrClassName = bIsCommCtrl6 ? CLinkCtrl::GetWndClassName() : CStatic::GetWndClassName(); if( !bIsCommCtrl6 ) _RemoveHREF(pstrBuffer); _AddControl(pstrClassName, IDC_TASKDLG_EXTRATEXT, rcItem, dwStyle, dwExStyle, pstrBuffer); } if( rcItem.right > cxDialog ) cxDialog = rcItem.right; ypos += (rcItem.bottom - rcItem.top) + m_Metrics.cyButtonLineGap; _AlignDLU(ypos, HTBOTTOM); } // Progress Bar if( (m_cfg.dwFlags & (TDF_SHOW_PROGRESS_BAR|TDF_SHOW_MARQUEE_PROGRESS_BAR)) != 0 ) { cxMaxText = cxMaxDialog - xpos - m_Metrics.sizeDialogPadding.cx; if( cxMaxDialog == WIDTH_PROBE ) cxMaxText = m_Metrics.cxBestProgressBar; RECT rcItem = { xpos, ypos, xpos + cxMaxText, ypos + m_Metrics.cyProgressBar }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | PBS_SMOOTH; dwExStyle = 0; _AddControl(CProgressBarCtrl::GetWndClassName(), IDC_TASKDLG_PROGRESSBAR, rcItem, dwStyle, dwExStyle, pstrBuffer); } if( rcItem.right > cxDialog ) cxDialog = rcItem.right; ypos += (rcItem.bottom - rcItem.top) + m_Metrics.cyInstructionsGap; } // Radio buttons xpos += m_Metrics.cxRadioIndent; if( m_cfg.cRadioButtons > 0 ) { for( n = 0; n < m_cfg.cRadioButtons; n++ ) { cxMaxText = cxMaxDialog - xpos - m_Metrics.sizeDialogPadding.cx - m_Metrics.sizeRadioButton.cx; if( cxMaxDialog == WIDTH_PROBE ) cxMaxText = m_Metrics.cxBestRadioButton; sizeText = _GetTextSize(m_cfg.pRadioButtons[n].pszButtonText, pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK, m_fontText, cxMaxText); sizeText.cx += m_Metrics.sizeRadioButton.cx; if( sizeText.cy < m_Metrics.sizeRadioButton.cy ) sizeText.cy = m_Metrics.sizeRadioButton.cy; RECT rcButton = { xpos, ypos, xpos + sizeText.cx, ypos + sizeText.cy }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTORADIOBUTTON | BS_TOP | BS_MULTILINE; dwExStyle = 0; if( n == 0 ) dwStyle |= WS_GROUP; _AddControl(CButton::GetWndClassName(), (WORD) (IDC_TASKDLG_RADIOBUTTON_FIRST + n), rcButton, dwStyle, dwExStyle, pstrBuffer); } if( rcButton.right > cxDialog ) cxDialog = rcButton.right; ypos += sizeText.cy + m_Metrics.cyRadioGap; } ypos += (m_Metrics.cyContentGap / 2) - m_Metrics.cyRadioGap; } // Command Links m_bHasCustomLinks = false; if( m_cfg.cButtons > 0 && (m_cfg.dwFlags & (TDF_USE_COMMAND_LINKS|TDF_USE_COMMAND_LINKS_NO_ICON)) != 0 ) { _AlignDLU(ypos, HTBOTTOM); if( m_cfg.cRadioButtons > 0 ) ypos += (m_Metrics.cyContentGap / 2); for( n = 0; n < m_cfg.cButtons; n++ ) { _LoadString(m_cfg.pButtons[n].pszButtonText, pstrBuffer, MAX_TEXT_LENGTH); LPCTSTR pstrTitle = NULL, pstrText = NULL; SIZE_T cchTitle = 0, cchText = 0; _SplitCommandText(pstrBuffer, pstrTitle, cchTitle, pstrText, cchText); LONG cxPadding = (m_Metrics.sizeLinkPadding.cx * 2); if( (m_cfg.dwFlags & TDF_USE_COMMAND_LINKS_NO_ICON) == 0) cxPadding += m_Metrics.cxyArrowIcon + m_Metrics.cxSmallIconGap; cxMaxText = cxMaxDialog - xpos - m_Metrics.sizeDialogPadding.cx - cxPadding; if( cxMaxDialog == WIDTH_PROBE ) cxMaxText = m_Metrics.cxBestCommandLink - cxPadding; sizeTitle = _GetTextSize(pstrTitle, cchTitle, DT_WORDBREAK, m_fontTitle, cxMaxText); sizeText = _GetTextSize(pstrText, cchText, DT_NOPREFIX | DT_WORDBREAK, m_fontText, cxMaxText); if( sizeTitle.cx > sizeText.cx ) sizeText.cx = sizeTitle.cx; sizeText.cx += cxPadding; if( sizeText.cx < m_Metrics.cxMinCommandLink ) sizeText.cx = m_Metrics.cxMinCommandLink; RECT rcButton = { xpos, ypos, cxMaxDialog - m_Metrics.sizeDialogPadding.cx, ypos + sizeTitle.cy + sizeText.cy + (m_Metrics.sizeLinkPadding.cy * 2) }; _AlignDLU(rcButton.bottom, HTBOTTOM); if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_TOP | BS_MULTILINE; dwExStyle = 0; if( n == 0 ) dwStyle |= WS_GROUP; if( bIsVista ) { #ifndef BS_COMMANDLINK const DWORD BS_COMMANDLINK = 0x0000000E; const DWORD BS_DEFCOMMANDLINK = 0x0000000F; #endif // BS_COMMANDLINK if( m_cfg.pButtons[n].nButtonID == m_cfg.nDefaultButton ) { dwStyle |= BS_DEFCOMMANDLINK; m_nDefCtlId = (UINT) -1; } else { dwStyle |= BS_COMMANDLINK; } } else { m_bHasCustomLinks = true; if( m_cfg.pButtons[n].nButtonID == m_cfg.nDefaultButton ) m_nDefCtlId = IDC_TASKDLG_CUSTOMBUTTON_FIRST + n; dwStyle |= BS_OWNERDRAW; } _AddControl(CButton::GetWndClassName(), (WORD) (IDC_TASKDLG_CUSTOMBUTTON_FIRST + n), rcButton, dwStyle, dwExStyle, pstrBuffer); } if( xpos + sizeText.cx > cxDialog ) cxDialog = xpos + sizeText.cx; ypos += (rcButton.bottom - rcButton.top); } ypos += (m_Metrics.cyContentGap / 2); } // Position the 'fold' - the line between white and grey areas m_Metrics.iButtonLinePos = ypos; if( m_cfg.pszExpandedInformation != NULL || m_cfg.pszVerificationText != NULL || m_cfg.cButtons > 0 && (m_cfg.dwFlags & (TDF_USE_COMMAND_LINKS|TDF_USE_COMMAND_LINKS_NO_ICON)) == 0 || m_cfg.dwCommonButtons != 0 || m_cfg.pszFooter != NULL ) // only add padding if something actually comes below the fold { ypos += m_Metrics.cyButtonLineGap; } // Expander area xpos = m_Metrics.sizeDialogPadding.cx; cyExpander = 0; cxExpander = 0; if( m_cfg.pszExpandedInformation != NULL ) { RECT rcIcon = { xpos, ypos, xpos + m_Metrics.cxyExpanderIcon, ypos + m_Metrics.cxyExpanderIcon }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | SS_OWNERDRAW | SS_NOTIFY; dwExStyle = 0; _AddControl(CStatic::GetWndClassName(), IDC_TASKDLG_EXPANDERICON, rcIcon, dwStyle, dwExStyle, _T("")); } LONG xoffset = m_Metrics.cxyExpanderIcon + m_Metrics.cxSmallIconGap; sizeTemp = _GetTextSize(m_cfg.pszCollapsedControlText, pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK, m_fontText, m_Metrics.cxMaxVerification); sizeText = _GetTextSize(m_cfg.pszExpandedControlText, pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK, m_fontText, m_Metrics.cxMaxVerification); if( sizeTemp.cx > sizeText.cx ) sizeText.cx = sizeTemp.cx; if( sizeTemp.cy > sizeText.cy ) sizeText.cy = sizeTemp.cy; sizeText.cx += xoffset; LONG yoffset = 0; if( sizeText.cy < m_Metrics.cxyExpanderIcon ) yoffset = (m_Metrics.cxyExpanderIcon / 2) - (sizeText.cy / 2); RECT rcItem = { xpos + xoffset, ypos + yoffset, xpos + xoffset + sizeText.cx, ypos + yoffset + sizeText.cy }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOTIFY; dwExStyle = 0; _AddControl(CStatic::GetWndClassName(), IDC_TASKDLG_EXPANDERTEXT, rcItem, dwStyle, dwExStyle, pstrBuffer); } if( rcItem.right > cxExpander ) cxExpander = rcItem.right; cyExpander += (rcItem.bottom - rcItem.top) + (yoffset * 2); if( m_cfg.pszVerificationText != NULL ) cyExpander += m_Metrics.cyExpanderGap; // Add hidden button which we use to catch accelerator if( bCreateControls ) { dwStyle = WS_CHILD | WS_TABSTOP; dwExStyle = 0; RECT rcZero = { 0 }; _AddControl(CButton::GetWndClassName(), IDC_TASKDLG_EXPANDERHIDDENBUTTON, rcZero, dwStyle, dwExStyle, _T("")); } } // Verification text xpos = m_Metrics.sizeDialogPadding.cx; if( m_cfg.pszVerificationText != NULL ) { sizeText = _GetTextSize(m_cfg.pszVerificationText, pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK, m_fontText, m_Metrics.cxMaxVerification); sizeText.cx += m_Metrics.sizeRadioButton.cx; if( sizeText.cy < m_Metrics.sizeRadioButton.cy ) sizeText.cy = m_Metrics.sizeRadioButton.cy; if( sizeText.cy < m_Metrics.sizeButtons.cy && cyExpander == 0 ) cyExpander = (m_Metrics.sizeButtons.cy / 2) - (sizeText.cy / 2); const LONG cxIndent = 2; RECT rcButton = { xpos + cxIndent, ypos + cyExpander, xpos + cxIndent + sizeText.cx, ypos + cyExpander + sizeText.cy }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX | BS_TOP | BS_LEFT | BS_MULTILINE; dwExStyle = 0; _AddControl(CButton::GetWndClassName(), IDC_TASKDLG_VERIFYBUTTON, rcButton, dwStyle, dwExStyle, pstrBuffer); } if( rcButton.right > cxExpander ) cxExpander = rcButton.right; cyExpander += (rcButton.bottom - rcButton.top); } // Buttons xpos = cxExpander + m_Metrics.cxButtonsDivider; if( m_Metrics.sizeButtons.cx > 0 ) xpos += cxMaxDialog - m_Metrics.sizeDialogPadding.cx - xpos - m_Metrics.sizeButtons.cx; m_Metrics.sizeButtons.cx = 0; m_Metrics.sizeButtons.cy = 0; if( m_cfg.cButtons > 0 && (m_cfg.dwFlags & (TDF_USE_COMMAND_LINKS|TDF_USE_COMMAND_LINKS_NO_ICON)) == 0 ) { for( n = 0; n < m_cfg.cButtons; n++ ) { sizeText = _GetTextSize(m_cfg.pButtons[n].pszButtonText, pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK, m_fontText, 9999); if( sizeText.cx < m_Metrics.cxMinButton ) sizeText.cx = m_Metrics.cxMinButton; RECT rcButton = { xpos, ypos, xpos + sizeText.cx + (m_Metrics.sizeButtonPadding.cx * 2), ypos + sizeText.cy + (m_Metrics.sizeButtonPadding.cy * 2) }; _AlignDLU(rcButton.right, HTRIGHT); if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_MULTILINE; dwExStyle = 0; _AddControl(CButton::GetWndClassName(), (WORD) (IDC_TASKDLG_CUSTOMBUTTON_FIRST + n), rcButton, dwStyle, dwExStyle, pstrBuffer); } xpos = rcButton.right + m_Metrics.cxButtonGap; m_Metrics.sizeButtons.cx += (rcButton.right - rcButton.left) + m_Metrics.cxButtonGap; if( m_nDefCtlId == 0 ) m_nDefCtlId = IDC_TASKDLG_CUSTOMBUTTON_FIRST + n; if( m_cfg.nDefaultButton == m_cfg.pButtons[n].nButtonID ) m_nDefCtlId = IDC_TASKDLG_CUSTOMBUTTON_FIRST + n; if( rcButton.bottom - rcButton.top > m_Metrics.sizeButtons.cy ) m_Metrics.sizeButtons.cy = (rcButton.bottom - rcButton.top); } } if( m_cfg.dwCommonButtons != 0 ) { int buttons[] = { TDCBF_OK_BUTTON, IDOK, USER32_IDS_OK, TDCBF_YES_BUTTON, IDYES, USER32_IDS_YES, TDCBF_NO_BUTTON, IDNO, USER32_IDS_NO, TDCBF_RETRY_BUTTON, IDRETRY, USER32_IDS_RETRY, TDCBF_CANCEL_BUTTON, IDCANCEL, USER32_IDS_CANCEL, TDCBF_CLOSE_BUTTON, IDCLOSE, USER32_IDS_CLOSE, }; for( n = 0; n < sizeof(buttons) / sizeof(buttons[0]); n += 3 ) { if( (m_cfg.dwCommonButtons & buttons[n]) == 0 ) continue; sizeText = _GetTextSize(buttons[n + 2], pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK, m_fontText, 9999); if( sizeText.cx < m_Metrics.cxMinButton ) sizeText.cx = m_Metrics.cxMinButton; RECT rcButton = { xpos, ypos, xpos + sizeText.cx + (m_Metrics.sizeButtonPadding.cx * 2), ypos + sizeText.cy + (m_Metrics.sizeButtonPadding.cy * 2) }; _AlignDLU(rcButton.right, HTRIGHT); if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_MULTILINE; dwExStyle = 0; _AddControl(CButton::GetWndClassName(), (WORD) buttons[n + 1], rcButton, dwStyle, dwExStyle, pstrBuffer); } xpos = rcButton.right + m_Metrics.cxButtonGap; m_Metrics.sizeButtons.cx += (rcButton.right - rcButton.left) + m_Metrics.cxButtonGap; if( m_nDefCtlId == 0 ) m_nDefCtlId = (UINT) buttons[n + 1]; if( m_cfg.nDefaultButton == buttons[n + 1] ) m_nDefCtlId = (UINT) buttons[n + 1]; if( rcButton.bottom - rcButton.top > m_Metrics.sizeButtons.cy ) m_Metrics.sizeButtons.cy = (rcButton.bottom - rcButton.top); } } if( m_Metrics.sizeButtons.cx > 0 ) { m_Metrics.sizeButtons.cx -= m_Metrics.cxButtonGap; xpos -= m_Metrics.cxButtonGap; } if( xpos > cxDialog ) cxDialog = xpos; if( cyExpander < m_Metrics.sizeButtons.cy ) cyExpander = m_Metrics.sizeButtons.cy; ypos += cyExpander; // Footer text xpos = m_Metrics.sizeDialogPadding.cx; if( m_cfg.pszFooter != NULL ) { ypos += m_Metrics.cyButtonLineGap; m_Metrics.iFooterLinePos = ypos; ypos += m_Metrics.cyButtonLineGap; if( m_cfg.pszFooterIcon != NULL || (m_cfg.dwFlags & TDF_USE_HICON_FOOTER) != 0 ) { RECT rcItem = { xpos, ypos, xpos + m_Metrics.cxySmallIcon, ypos + m_Metrics.cxySmallIcon }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | SS_OWNERDRAW; dwExStyle = 0; _AddControl(CStatic::GetWndClassName(), IDC_TASKDLG_FOOTERICON, rcItem, dwStyle, dwExStyle, _T("")); } xpos += (rcItem.right - rcItem.left) + m_Metrics.cxSmallIconGap; } cxMaxText = cxMaxDialog - xpos - m_Metrics.sizeDialogPadding.cx; sizeText = _GetTextSizeHREF(m_cfg.pszFooter, pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK | DT_NOPREFIX, m_fontText, cxMaxText); if( sizeText.cy < m_Metrics.cxySmallIcon ) sizeText.cy = m_Metrics.cxySmallIcon; RECT rcItem = { xpos, ypos, xpos + sizeText.cx, ypos + sizeText.cy }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX; dwExStyle = 0; LPCTSTR pstrClassName = bIsCommCtrl6 ? CLinkCtrl::GetWndClassName() : CStatic::GetWndClassName(); if( !bIsCommCtrl6 ) _RemoveHREF(pstrBuffer); _AddControl(pstrClassName, IDC_TASKDLG_FOOTERTEXT, rcItem, dwStyle, dwExStyle, pstrBuffer); } if( rcItem.right > cxDialog ) cxDialog = rcItem.right; ypos += (rcItem.bottom - rcItem.top); } // Expando in Footer area xpos = m_Metrics.sizeDialogPadding.cx; if( m_cfg.pszExpandedInformation != NULL && ((m_cfg.dwFlags & TDF_EXPAND_FOOTER_AREA) != 0) ) { ypos += m_Metrics.cyButtonLineGap; m_Metrics.iExpandedLinePos = ypos; ypos += m_Metrics.cyButtonLineGap; cxMaxText = cxMaxDialog - xpos - m_Metrics.sizeDialogPadding.cx; sizeText = _GetTextSizeHREF(m_cfg.pszExpandedInformation, pstrBuffer, MAX_TEXT_LENGTH, DT_WORDBREAK | DT_NOPREFIX, m_fontText, cxMaxText); RECT rcItem = { xpos, ypos, xpos + sizeText.cx, ypos + sizeText.cy }; if( bCreateControls ) { dwStyle = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX; dwExStyle = 0; LPCTSTR pstrClassName = bIsCommCtrl6 ? CLinkCtrl::GetWndClassName() : CStatic::GetWndClassName(); if( !bIsCommCtrl6 ) _RemoveHREF(pstrBuffer); _AddControl(pstrClassName, IDC_TASKDLG_EXTRATEXT, rcItem, dwStyle, dwExStyle, pstrBuffer); } if( rcItem.right > cxDialog ) cxDialog = rcItem.right; ypos += (rcItem.bottom - rcItem.top); } cxDialog += m_Metrics.sizeDialogPadding.cx; // only add padding if anything was actually added below the fold if(ypos > m_Metrics.iButtonLinePos) { ypos += m_Metrics.sizeDialogPadding.cy; } free(pstrBuffer); SIZE sizeDialog = { cxDialog, ypos }; return sizeDialog; } void _CustomDrawIcon(LPDRAWITEMSTRUCT lpDIS, COLORREF clrBack, HICON hIcon, int cxyIcon) { CDCHandle dc = lpDIS->hDC; RECT rc = lpDIS->rcItem; dc.FillSolidRect(&rc, clrBack); dc.DrawIconEx(rc.left, rc.top, hIcon, cxyIcon, cxyIcon); } void _CustomDrawCommandLink(LPDRAWITEMSTRUCT lpDIS) { CButton ctrl = lpDIS->hwndItem; CDCHandle dc = lpDIS->hDC; RECT rc = lpDIS->rcItem; HBRUSH hOldBrush = dc.GetCurrentBrush(); HFONT hOldFont = dc.GetCurrentFont(); HPEN hOldPen = dc.GetCurrentPen(); COLORREF clrBorder = CLR_INVALID; if( lpDIS->CtlID == m_nHoverId ) { clrBorder = ::GetSysColor(COLOR_BTNSHADOW); } else { if( (lpDIS->itemAction & ODA_DRAWENTIRE ) != 0 ) dc.FillSolidRect(&rc, m_Metrics.clrBkTop); if( m_nDefCtlId == lpDIS->CtlID ) clrBorder = m_Metrics.clrCmdLinkSelect; else if( (lpDIS->itemState & ODS_FOCUS) != 0 ) clrBorder = ::GetSysColor(COLOR_HIGHLIGHT); } if( (lpDIS->itemState & ODS_SELECTED) != 0 ) clrBorder = ::GetSysColor(COLOR_3DDKSHADOW); if( clrBorder != CLR_INVALID ) { POINT ptArc = { 6, 6 }; CPen penBorder; penBorder.CreatePen(PS_SOLID, 1, clrBorder); dc.SelectPen(penBorder); dc.SelectBrush((HBRUSH) ::GetStockObject(HOLLOW_BRUSH)); dc.RoundRect(&rc, ptArc); } ::InflateRect(&rc, -m_Metrics.sizeLinkPadding.cx, -m_Metrics.sizeLinkPadding.cy); if( (m_cfg.dwFlags & TDF_USE_COMMAND_LINKS_NO_ICON) == 0 ) { dc.DrawIconEx(rc.left, rc.top + 2, lpDIS->CtlID == m_nHoverId ? m_iconArrowHot : m_iconArrowNormal, 20, 20); rc.left += m_Metrics.cxyArrowIcon + m_Metrics.cxSmallIconGap; } LONG cx = (rc.right - rc.left); TCHAR szWindowText[300] = { 0 }; ctrl.GetWindowText(szWindowText, sizeof(szWindowText) / sizeof(TCHAR)); LPCTSTR pstrTitle = NULL, pstrText = NULL; SIZE_T cchTitle = 0, cchText = 0; _SplitCommandText(szWindowText, pstrTitle, cchTitle, pstrText, cchText); SIZE sizeTitle = _GetTextSize(pstrTitle, cchTitle, DT_WORDBREAK, m_fontTitle, cx); RECT rcTitle = { rc.left, rc.top, rc.right, rc.bottom }; dc.SetTextColor(m_Metrics.clrTitleText); dc.SetBkMode(TRANSPARENT); dc.SelectFont(m_fontTitle); dc.DrawText(pstrTitle, cchTitle, &rcTitle, DT_WORDBREAK); RECT rcText = { rc.left, rc.top + sizeTitle.cy + 1, rc.right, rc.bottom }; dc.SelectFont(m_fontText); dc.DrawText(pstrText, cchText, &rcText, DT_NOPREFIX | DT_WORDBREAK); dc.SelectPen(hOldPen); dc.SelectFont(hOldFont); dc.SelectBrush(hOldBrush); } void _PlaySound() { if( m_cfg.pszMainIcon == TD_ERROR_ICON ) ::MessageBeep(MB_ICONHAND); if( m_cfg.pszMainIcon == TD_WARNING_ICON ) ::MessageBeep(MB_ICONEXCLAMATION); if( m_cfg.pszMainIcon == TD_INFORMATION_ICON ) ::MessageBeep(MB_ICONASTERISK); } void _RemoveHREF(LPTSTR pstr) const { if( (m_cfg.dwFlags & TDF_ENABLE_HYPERLINKS) == 0 ) return; LPTSTR p = pstr; while( *p != '\0' ) { if( *p == '<' ) { ATLTRACE(_T("Warning: Will remove hyperlinks from TaskDialog; no support on this platform")); while( *p != '\0' && *p != '>' ) p = ::CharNext(p); if( *p == '>' ) p = ::CharNext(p); } else { *pstr = *p; #ifdef _MBCS if( ::IsDBCSLeadByte(*p) ) pstr[1] = p[1]; #endif // _MBCS p = ::CharNext(p); pstr = ::CharNext(pstr); } } *pstr = '\0'; } RECT _CenterDialog(SIZE dluDialog) const { RECT rcArea = { 0 }; if( ::IsWindow(m_cfg.hwndParent) && (m_cfg.dwFlags & TDF_POSITION_RELATIVE_TO_WINDOW) != 0 ) { ::GetWindowRect(m_cfg.hwndParent, &rcArea); } else { ::SystemParametersInfo(SPI_GETWORKAREA, NULL, &rcArea, NULL); } rcArea = MapDialogPixels(rcArea); RECT rc = { 0 }; rc.left = rcArea.left + ((rcArea.right - rcArea.left) / 2 - dluDialog.cx / 2); rc.top = rcArea.top + ((rcArea.bottom - rcArea.top) / 2 - dluDialog.cy / 2); rc.right = rc.left + dluDialog.cx; rc.bottom = rc.top + dluDialog.cy; return rc; } HRESULT _DoCallback(UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0) { if( !m_bCreated ) return S_OK; if( m_cfg.pfCallback == NULL ) return S_OK; return m_cfg.pfCallback(m_hWnd, uMsg, wParam, lParam, m_cfg.lpCallbackData); } void _DoExpandCollapse() { if( (m_cfg.dwFlags & TDF_EXPAND_FOOTER_AREA) == 0 ) { CWindow wndText = GetDlgItem(IDC_TASKDLG_EXTRATEXT); RECT rcText = { 0 }; wndText.GetWindowRect(&rcText); int cy = (rcText.bottom - rcText.top) + m_Metrics.cyButtonLineGap; if( !m_bExpanded ) cy = -cy; HWND hWndFirst; CWindow wndChild = hWndFirst = GetWindow(GW_CHILD); while( wndChild != NULL ) { RECT rcWin = { 0 }; wndChild.GetWindowRect(&rcWin); if( wndChild != wndText && rcWin.top >= rcText.top ) { ::OffsetRect(&rcWin, 0, cy); ::MapWindowPoints(HWND_DESKTOP, m_hWnd, (LPPOINT) &rcWin, 2); wndChild.MoveWindow(&rcWin); } wndChild = wndChild.GetWindow(GW_HWNDNEXT); if( wndChild == hWndFirst ) break; } wndText.ShowWindow(m_bExpanded ? SW_SHOWNOACTIVATE: SW_HIDE); if( m_Metrics.iButtonLinePos != 0 ) m_Metrics.iButtonLinePos += cy; if( m_Metrics.iFooterLinePos != 0 ) m_Metrics.iFooterLinePos += cy; ResizeClient(-1, m_bExpanded ? m_sizeDialog.cy : m_sizeDialog.cy + cy); } else { ResizeClient(-1, m_bExpanded ? m_sizeDialog.cy : m_Metrics.iExpandedLinePos - 1); } // Change text in Expander label LPTSTR pstrBuffer = (LPTSTR) malloc(MAX_TEXT_LENGTH * sizeof(TCHAR)); if( pstrBuffer == NULL ) return; _LoadString(m_bExpanded ? m_cfg.pszExpandedControlText : m_cfg.pszCollapsedControlText, pstrBuffer, MAX_TEXT_LENGTH); SetDlgItemText(IDC_TASKDLG_EXPANDERTEXT, pstrBuffer); free(pstrBuffer); // Make sure we redraw stuff CWindow(GetDlgItem(IDC_TASKDLG_EXPANDERICON)).Invalidate(); CWindow(GetDlgItem(IDC_TASKDLG_EXPANDERTEXT)).Invalidate(); Invalidate(); } void _AddControl(ATL::_U_STRINGorID ClassName, WORD wId, RECT rc, DWORD dwStyle, DWORD dwExStyle, ATL::_U_STRINGorID Text) { rc = MapDialogPixels(rc); m_Template.AddControl(ClassName, wId, (short) rc.left, (short) rc.top, (short) (rc.right - rc.left), (short) (rc.bottom - rc.top), dwStyle, dwExStyle, Text); } HICON _LoadIcon(LPCWSTR pstr, int cxy) const { USES_CONVERSION; if( pstr == NULL ) return NULL; // Determine icon identifier and resource dll HINSTANCE hInst = m_cfg.hInstance; if( pstr == TD_ERROR_ICON ) pstr = MAKEINTRESOURCEW(IDI_ERROR), hInst = NULL; #if _WIN32_WINNT >= 0x0600 else if( pstr == TD_SHIELD_ICON ) pstr = MAKEINTRESOURCEW(IDI_SHIELD), hInst = NULL; #endif // _WIN_WINNT else if( pstr == TD_WARNING_ICON ) pstr = MAKEINTRESOURCEW(IDI_EXCLAMATION), hInst = NULL; else if( pstr == TD_INFORMATION_ICON ) pstr = MAKEINTRESOURCEW(IDI_ASTERISK), hInst = NULL; // Load icon... UINT fuLoad = LR_DEFAULTCOLOR | LR_LOADTRANSPARENT; if( hInst == NULL ) fuLoad |= LR_SHARED; LPCTSTR pstrIcon = IS_INTRESOURCE(pstr) ? (LPCTSTR) pstr : W2CT(pstr); HICON hIcon = (HICON) ::LoadImage(hInst, pstrIcon, IMAGE_ICON, cxy, cxy, fuLoad); if( hIcon == NULL ) hIcon = (HICON) ::LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, cxy, cxy, LR_DEFAULTCOLOR | LR_SHARED | LR_LOADTRANSPARENT); return hIcon; } /** * Icon offset in GRPICONDIRENTRY structure. */ SIZE_T _InMemoryOffset(WORD i) const { return 6 + (i * 0x0e); } /** * Icon offset in ICONDIRENTRY structure. */ SIZE_T _InFileOffset(WORD i) const { return 6 + (i * 0x10); } /** * Load icon from raw .ico file bytes. * * @warning This does no error checking and will cause a General * protection fault if the data passed in is not valid. * * @bug Won't work if icon file data > 64k. * * @see http://www.ragestorm.net/blogs/?p=12 */ HICON _LoadIcon(const BYTE bytes[], SIZE_T size, int cxy) const { HICON icon = NULL; BYTE* grpicondirentry = static_cast(::HeapAlloc(::GetProcessHeap(), 0, size)); if (grpicondirentry) { ::CopyMemory(grpicondirentry, bytes, size); // Convert incoming ICONDIRENTRY to GRPICONDIRENTRY WORD count = bytes[4]; for (WORD i = 0; i < count; i++) { ::CopyMemory( grpicondirentry + _InMemoryOffset(i), bytes + _InFileOffset(i), 0x0e); } // Create HICON int offset = ::LookupIconIdFromDirectoryEx( grpicondirentry, TRUE, cxy, cxy, LR_DEFAULTCOLOR); if (offset > 0) { icon = ::CreateIconFromResourceEx( grpicondirentry + offset, 0, TRUE, 0x00030000, cxy, cxy, LR_DEFAULTCOLOR); } ::HeapFree(::GetProcessHeap(), 0, grpicondirentry); } return icon; } void _LoadString(LPCWSTR pstr, LPTSTR pszBuffer, SIZE_T cchMax) const { USES_CONVERSION; ::ZeroMemory(pszBuffer, cchMax * sizeof(TCHAR)); if( pstr == NULL ) return; if( IS_INTRESOURCE(pstr) ) ::LoadString(ModuleHelper::GetResourceInstance(), LOWORD(pstr), pszBuffer, cchMax); else ::lstrcpyn(pszBuffer, W2CT(pstr), cchMax); } /** * Variation of _LoadString that loads a resource from user32.dll. */ void _LoadString(int id, LPTSTR pszBuffer, SIZE_T cchMax) const { USES_CONVERSION; ::ZeroMemory(pszBuffer, cchMax * sizeof(TCHAR)); ::LoadString(::GetModuleHandle(_T("user32.dll")), id, pszBuffer, cchMax); } void _SplitCommandText(LPTSTR pstrBuffer, LPCTSTR& pstrTitle, SIZE_T& cchTitle, LPCTSTR& pstrText, SIZE_T& cchText) const { LPTSTR pstrSel = pstrBuffer; while( *pstrSel != '\0' && *pstrSel != '\n' ) pstrSel = ::CharNext(pstrSel); if( *pstrSel == '\0' ) { pstrTitle = pstrBuffer; pstrText = NULL; cchTitle = lstrlen(pstrTitle); cchText = 0; } else { pstrTitle = pstrBuffer; pstrText = pstrSel + 1; cchText = lstrlen(pstrText); cchTitle = lstrlen(pstrBuffer) - cchText - 1; } } void _AlignDLU(LONG& pos, int iType) const { switch( iType ) { case HTLEFT: pos -= pos % GetDialogBaseUnits().cx; break; case HTRIGHT: pos += GetDialogBaseUnits().cx - (pos % GetDialogBaseUnits().cx); break; case HTTOP: pos -= pos % GetDialogBaseUnits().cy; break; case HTBOTTOM: pos += GetDialogBaseUnits().cy - (pos % GetDialogBaseUnits().cy); break; } } SIZE _GetTextSize(LPCWSTR pstr, LPTSTR pszBuffer, SIZE_T cchMax, UINT uStyle, HFONT hFont, int cxMax) const { _LoadString(pstr, pszBuffer, cchMax); return _GetTextSize(pszBuffer, (UINT) -1, uStyle, hFont, cxMax); } /** * Variation of _GetTextSize() that loads string from user32.dll resources. */ SIZE _GetTextSize(int id, LPTSTR pszBuffer, SIZE_T cchMax, UINT uStyle, HFONT hFont, int cxMax) const { _LoadString(id, pszBuffer, cchMax); return _GetTextSize(pszBuffer, (UINT) -1, uStyle, hFont, cxMax); } SIZE _GetTextSizeHREF(LPCWSTR pstr, LPTSTR pszBuffer, SIZE_T cchMax, UINT uStyle, HFONT hFont, int cxMax) const { _LoadString(pstr, pszBuffer, cchMax); _RemoveHREF(pszBuffer); SIZE sizeText = _GetTextSize(pszBuffer, (UINT) -1, uStyle, hFont, cxMax); _LoadString(pstr, pszBuffer, cchMax); return sizeText; } SIZE _GetTextSize(LPCTSTR pstr, SIZE_T cchMax, UINT uStyle, HFONT hFont, int cxMax) const { CClientDC dc = m_cfg.hwndParent; SIZE sizeText = { 0 }; if( pstr == NULL ) return sizeText; if( pstr[0] == '\0' ) return sizeText; HFONT hOldFont = dc.SelectFont(hFont); RECT rc = { 0, 0, cxMax, 9999 }; if( cchMax == MAX_TEXT_LENGTH ) cchMax = (SIZE_T) -1; dc.DrawText(pstr, cchMax, &rc, DT_CALCRECT | uStyle); dc.SelectFont(hOldFont); SIZE size = { rc.right - rc.left + 1, rc.bottom - rc.top + 1 }; return size; } }; class CTask98Dialog : public CTask98DialogImpl { }; ///////////////////////////////////////////////////////////////////////// // TaskDialog 98 helpers // inline HRESULT Task98DialogIndirect(const TASKDIALOGCONFIG* pTaskConfig, int* pnButton, int* pnRadioButton, BOOL* pfVerificationFlagChecked) { ATLASSERT(pTaskConfig); ATLASSERT(pTaskConfig->cbSize==sizeof(TASKDIALOGCONFIG)); if( pnButton != NULL ) *pnButton = 0; CTask98Dialog dlg; if( !dlg.SetConfig(pTaskConfig) ) return E_FAIL; do { dlg.DoModal(pTaskConfig->hwndParent); } while( dlg.IsNavigated() ); dlg.GetDialogResult(pnButton, pnRadioButton, pfVerificationFlagChecked); return S_OK; } inline HRESULT Task98Dialog(HWND hwndParent, HINSTANCE hInstance, LPCTSTR pszWindowTitle, LPCTSTR pszMainInstruction, LPCTSTR pszContent, TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons, LPCTSTR pszIcon, int* pnButton) { USES_CONVERSION; TASKDIALOGCONFIG cfg = { 0 }; cfg.cbSize = sizeof(cfg); cfg.hInstance = hInstance; cfg.hwndParent = hwndParent; cfg.dwCommonButtons = dwCommonButtons; cfg.pszWindowTitle = IS_INTRESOURCE(pszWindowTitle) ? (LPCWSTR) pszWindowTitle : T2CW(pszWindowTitle); cfg.pszMainInstruction = IS_INTRESOURCE(pszMainInstruction) ? (LPCWSTR) pszMainInstruction : T2CW(pszMainInstruction); cfg.pszContent = IS_INTRESOURCE(pszContent) ? (LPCWSTR) pszContent : T2CW(pszContent); cfg.pszMainIcon = IS_INTRESOURCE(pszIcon) ? (LPCWSTR) pszIcon : T2CW(pszIcon); return Task98DialogIndirect(&cfg, pnButton, NULL, NULL); } #endif // !defined(AFX_TASKDIALOG_H__20073232_5AA0_1E88_3A6D_0080AD509054__INCLUDED_) ================================================ FILE: thirdparty/taskdialog98/TaskDialogTest.cpp ================================================ // TaskDialogTest.cpp : main source file for TaskDialogTest.exe // #include "stdafx.h" #include #include #include #include #include "resource.h" #include "maindlg.h" CAppModule _Module; int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR /*lpstrCmdLine*/, int /*nCmdShow*/) { HRESULT hRes = ::CoInitialize(NULL); ATLASSERT(SUCCEEDED(hRes)); ::DefWindowProc(NULL, 0, 0, 0L); AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES); hRes = _Module.Init(NULL, hInstance); ATLASSERT(SUCCEEDED(hRes)); int nRet = 0; { CMainDlg dlgMain; nRet = dlgMain.DoModal(); } _Module.Term(); ::CoUninitialize(); return nRet; } ================================================ FILE: thirdparty/taskdialog98/TaskDialogTest.dsp ================================================ # Microsoft Developer Studio Project File - Name="TaskDialogTest" - Package Owner=<4> # Microsoft Developer Studio Generated Build File, Format Version 6.00 # ** DO NOT EDIT ** # TARGTYPE "Win32 (x86) Application" 0x0101 CFG=TaskDialogTest - Win32 Debug !MESSAGE This is not a valid makefile. To build this project using NMAKE, !MESSAGE use the Export Makefile command and run !MESSAGE !MESSAGE NMAKE /f "TaskDialogTest.mak". !MESSAGE !MESSAGE You can specify a configuration when running NMAKE !MESSAGE by defining the macro CFG on the command line. For example: !MESSAGE !MESSAGE NMAKE /f "TaskDialogTest.mak" CFG="TaskDialogTest - Win32 Debug" !MESSAGE !MESSAGE Possible choices for configuration are: !MESSAGE !MESSAGE "TaskDialogTest - Win32 Release" (based on "Win32 (x86) Application") !MESSAGE "TaskDialogTest - Win32 Debug" (based on "Win32 (x86) Application") !MESSAGE # Begin Project # PROP AllowPerConfigDependencies 0 # PROP Scc_ProjName "" # PROP Scc_LocalPath "" CPP=cl.exe MTL=midl.exe RSC=rc.exe !IF "$(CFG)" == "TaskDialogTest - Win32 Release" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 0 # PROP BASE Output_Dir "Release" # PROP BASE Intermediate_Dir "Release" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 # PROP Output_Dir "Release" # PROP Intermediate_Dir "C:\Temp" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"stdafx.h" /FD /c # ADD CPP /nologo /MT /W4 /O1 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "STRICT" /D "_ATL_MIN_CRT" /Yu"stdafx.h" /FD /c # ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x406 /d "NDEBUG" # ADD RSC /l 0x406 /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 !ELSEIF "$(CFG)" == "TaskDialogTest - Win32 Debug" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 1 # PROP BASE Output_Dir "Debug" # PROP BASE Intermediate_Dir "Debug" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 1 # PROP Output_Dir "Debug" # PROP Intermediate_Dir "C:\Temp" # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c # ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "STRICT" /FR /Yu"stdafx.h" /FD /GZ /c # ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x406 /d "_DEBUG" # ADD RSC /l 0x406 /d "_DEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept !ENDIF # Begin Target # Name "TaskDialogTest - Win32 Release" # Name "TaskDialogTest - Win32 Debug" # Begin Group "Source Files" # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" # Begin Source File SOURCE=.\stdafx.cpp # ADD CPP /Yc"stdafx.h" # End Source File # Begin Source File SOURCE=.\TaskDialogTest.cpp # End Source File # Begin Source File SOURCE=.\TaskDialogTest.rc # End Source File # End Group # Begin Group "Header Files" # PROP Default_Filter "h;hpp;hxx;hm;inl" # Begin Source File SOURCE=.\maindlg.h # End Source File # Begin Source File SOURCE=.\resource.h # End Source File # Begin Source File SOURCE=.\stdafx.h # End Source File # Begin Source File SOURCE=.\TaskDialog.h # End Source File # End Group # Begin Group "Resource Files" # PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" # Begin Source File SOURCE=.\res\TaskDialogTest.exe.manifest # End Source File # Begin Source File SOURCE=.\res\TaskDialogTest.ico # End Source File # Begin Source File SOURCE=.\res\TaskDlgArrowHot.ico # End Source File # Begin Source File SOURCE=.\res\TaskDlgArrowNormal.ico # End Source File # Begin Source File SOURCE=.\res\TaskDlgChevronLess.ico # End Source File # Begin Source File SOURCE=.\res\TaskDlgChevronMore.ico # End Source File # End Group # End Target # End Project ================================================ FILE: thirdparty/taskdialog98/TaskDialogTest.dsw ================================================ Microsoft Developer Studio Workspace File, Format Version 6.00 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! ############################################################################### Project: "TaskDialogTest"=.\TaskDialogTest.dsp - Package Owner=<4> Package=<5> {{{ }}} Package=<4> {{{ }}} ############################################################################### Global: Package=<5> {{{ }}} Package=<3> {{{ }}} ############################################################################### ================================================ FILE: thirdparty/taskdialog98/TaskDialogTest.h ================================================ // TaskDialogTest.h ================================================ FILE: thirdparty/taskdialog98/TaskDialogTest.rc ================================================ //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "atlres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDR_MAINFRAME ICON DISCARDABLE "res\\TaskDialogTest.ico" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""atlres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST ""res\\\\TaskDialogTest.exe.manifest""\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 187, 102 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,130,81,50,14 CTEXT "TaskDialogTest Application v1.0\n\n(c) Copyright 2007", IDC_STATIC,25,57,78,32 ICON IDR_MAINFRAME,IDC_STATIC,55,26,18,20 GROUPBOX "",IDC_STATIC,7,7,115,88 END IDD_MAINDLG DIALOG DISCARDABLE 0, 0, 353, 288 STYLE WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU CAPTION "TaskDialogTest" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "Close",IDOK,296,7,50,14 PUSHBUTTON "&About...",ID_APP_ABOUT,296,25,50,14 PUSHBUTTON "Test Standard Task Dialog #001",IDC_BUTTON1,14,17,180, 15 PUSHBUTTON "Test Standard Task Dialog #002",IDC_BUTTON2,14,36,180, 15 PUSHBUTTON "Test Standard Task Dialog #003",IDC_BUTTON3,13,55,180, 15 PUSHBUTTON "Test Standard Task Dialog #004",IDC_BUTTON4,13,74,180, 15 PUSHBUTTON "Test Standard Task Dialog #005",IDC_BUTTON5,13,93,180, 15 PUSHBUTTON "Test Standard Task Dialog #006",IDC_BUTTON6,13,112,180, 15 PUSHBUTTON "Test Standard Task Dialog #007",IDC_BUTTON7,13,131,180, 15 PUSHBUTTON "Test Standard Task Dialog #008",IDC_BUTTON8,13,150,180, 15 PUSHBUTTON "Test Standard Task Dialog #009",IDC_BUTTON9,13,169,180, 15 PUSHBUTTON "Test Standard Task Dialog #010",IDC_BUTTON10,13,189, 180,15 PUSHBUTTON "Test Standard Task Dialog #011",IDC_BUTTON11,13,209, 180,15 PUSHBUTTON "Test Standard Task Dialog #012",IDC_BUTTON12,13,229, 180,15 END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_ABOUTBOX, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 180 TOPMARGIN, 7 BOTTOMMARGIN, 95 END IDD_MAINDLG, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 346 TOPMARGIN, 7 BOTTOMMARGIN, 281 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Accelerator // IDR_MAINFRAME ACCELERATORS PRELOAD MOVEABLE PURE BEGIN "N", ID_FILE_NEW, VIRTKEY, CONTROL "O", ID_FILE_OPEN, VIRTKEY, CONTROL "S", ID_FILE_SAVE, VIRTKEY, CONTROL "P", ID_FILE_PRINT, VIRTKEY, CONTROL "Z", ID_EDIT_UNDO, VIRTKEY, CONTROL "X", ID_EDIT_CUT, VIRTKEY, CONTROL "C", ID_EDIT_COPY, VIRTKEY, CONTROL "V", ID_EDIT_PASTE, VIRTKEY, CONTROL VK_BACK, ID_EDIT_UNDO, VIRTKEY, ALT VK_DELETE, ID_EDIT_CUT, VIRTKEY, SHIFT VK_INSERT, ID_EDIT_COPY, VIRTKEY, CONTROL VK_INSERT, ID_EDIT_PASTE, VIRTKEY, SHIFT VK_F6, ID_NEXT_PANE, VIRTKEY VK_F6, ID_PREV_PANE, VIRTKEY, SHIFT END #ifndef _MAC ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 1,0,0,1 PRODUCTVERSION 1,0,0,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "TaskDialogTest Module\0" VALUE "FileVersion", "1, 0, 0, 1\0" VALUE "InternalName", "TaskDialogTest\0" VALUE "LegalCopyright", "Copyright 2007\0" VALUE "OriginalFilename", "TaskDialogTest.exe\0" VALUE "ProductName", "TaskDialogTest Module\0" VALUE "ProductVersion", "1, 0, 0, 1\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // !_MAC ///////////////////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE PRELOAD DISCARDABLE BEGIN IDR_MAINFRAME "TaskDialogTest" IDS_TASKDLG_CANCEL "Cancel" END STRINGTABLE DISCARDABLE BEGIN ID_FILE_NEW "Create a new document\nNew" ID_FILE_OPEN "Open an existing document\nOpen" ID_FILE_CLOSE "Close the active document\nClose" ID_FILE_SAVE "Save the active document\nSave" ID_FILE_SAVE_AS "Save the active document with a new name\nSave As" ID_FILE_PAGE_SETUP "Change the printing options\nPage Setup" ID_FILE_PRINT_SETUP "Change the printer and printing options\nPrint Setup" ID_FILE_PRINT "Print the active document\nPrint" ID_FILE_PRINT_PREVIEW "Display full pages\nPrint Preview" END STRINGTABLE DISCARDABLE BEGIN ID_APP_ABOUT "Display program information, version number and copyright\nAbout" ID_APP_EXIT "Quit the application; prompts to save documents\nExit" END STRINGTABLE DISCARDABLE BEGIN ID_NEXT_PANE "Switch to the next window pane\nNext Pane" ID_PREV_PANE "Switch back to the previous window pane\nPrevious Pane" END STRINGTABLE DISCARDABLE BEGIN ID_WINDOW_NEW "Open another window for the active document\nNew Window" ID_WINDOW_ARRANGE "Arrange icons at the bottom of the window\nArrange Icons" ID_WINDOW_CASCADE "Arrange windows so they overlap\nCascade Windows" ID_WINDOW_TILE_HORZ "Arrange windows as non-overlapping tiles\nTile Windows" ID_WINDOW_TILE_VERT "Arrange windows as non-overlapping tiles\nTile Windows" ID_WINDOW_SPLIT "Split the active window into panes\nSplit" END STRINGTABLE DISCARDABLE BEGIN ID_EDIT_CLEAR "Erase the selection\nErase" ID_EDIT_CLEAR_ALL "Erase everything\nErase All" ID_EDIT_COPY "Copy the selection and put it on the Clipboard\nCopy" ID_EDIT_CUT "Cut the selection and put it on the Clipboard\nCut" ID_EDIT_FIND "Find the specified text\nFind" ID_EDIT_PASTE "Insert Clipboard contents\nPaste" ID_EDIT_REPEAT "Repeat the last action\nRepeat" ID_EDIT_REPLACE "Replace specific text with different text\nReplace" ID_EDIT_SELECT_ALL "Select the entire document\nSelect All" ID_EDIT_UNDO "Undo the last action\nUndo" ID_EDIT_REDO "Redo the previously undone action\nRedo" END STRINGTABLE DISCARDABLE BEGIN ID_VIEW_TOOLBAR "Show or hide the toolbar\nToggle ToolBar" ID_VIEW_STATUS_BAR "Show or hide the status bar\nToggle StatusBar" END STRINGTABLE DISCARDABLE BEGIN ATL_IDS_SCSIZE "Change the window size" ATL_IDS_SCMOVE "Change the window position" ATL_IDS_SCMINIMIZE "Reduce the window to an icon" ATL_IDS_SCMAXIMIZE "Enlarge the window to full size" ATL_IDS_SCNEXTWINDOW "Switch to the next document window" ATL_IDS_SCPREVWINDOW "Switch to the previous document window" ATL_IDS_SCCLOSE "Close the active window and prompts to save the documents" END STRINGTABLE DISCARDABLE BEGIN ATL_IDS_SCRESTORE "Restore the window to normal size" ATL_IDS_SCTASKLIST "Activate Task List" ATL_IDS_MDICHILD "Activate this window" END STRINGTABLE DISCARDABLE BEGIN ATL_IDS_IDLEMESSAGE "Ready" END STRINGTABLE DISCARDABLE BEGIN ATL_IDS_MRU_FILE "Open this document" END #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "res\\TaskDialogTest.exe.manifest" ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: thirdparty/taskdialog98/icons.h ================================================ static const unsigned char TaskDlgArrowHot_ico[] = { 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x14, 0x14, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x08, 0x06, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x14, 0x14, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0xb8, 0x06, 0x00, 0x00, 0x2e, 0x06, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x2e, 0x2e, 0x00, 0x34, 0x34, 0x34, 0x00, 0x35, 0x35, 0x35, 0x00, 0x3c, 0x3c, 0x3c, 0x00, 0x3d, 0x3d, 0x3d, 0x00, 0x3f, 0x3f, 0x3f, 0x00, 0x66, 0x66, 0x66, 0x00, 0x6a, 0x6a, 0x6a, 0x00, 0x6e, 0x6e, 0x6e, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x13, 0xb8, 0x13, 0x00, 0x20, 0x90, 0x20, 0x00, 0x20, 0xbd, 0x20, 0x00, 0x23, 0xbd, 0x23, 0x00, 0x38, 0xb7, 0x38, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xdd, 0x00, 0x00, 0x0a, 0xde, 0x0a, 0x00, 0x00, 0xeb, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x00, 0x2b, 0xd6, 0x2b, 0x00, 0x24, 0xee, 0x24, 0x00, 0x43, 0xb1, 0x43, 0x00, 0x5b, 0xad, 0x5b, 0x00, 0x54, 0xb0, 0x54, 0x00, 0x54, 0xb9, 0x54, 0x00, 0x69, 0xb8, 0x69, 0x00, 0x72, 0xb8, 0x72, 0x00, 0x4b, 0xca, 0x4b, 0x00, 0x54, 0xc3, 0x54, 0x00, 0x54, 0xcc, 0x54, 0x00, 0x5a, 0xfc, 0x5a, 0x00, 0x69, 0xe1, 0x69, 0x00, 0x62, 0xff, 0x62, 0x00, 0x8b, 0x8b, 0x8b, 0x00, 0x9b, 0x9b, 0x9b, 0x00, 0xa7, 0xa7, 0xa7, 0x00, 0xab, 0xab, 0xab, 0x00, 0xae, 0xae, 0xae, 0x00, 0xbd, 0xbd, 0xbd, 0x00, 0x83, 0xe5, 0x83, 0x00, 0x88, 0xe7, 0x88, 0x00, 0x88, 0xee, 0x88, 0x00, 0x8e, 0xf5, 0x8e, 0x00, 0x8e, 0xfb, 0x8e, 0x00, 0x9c, 0xff, 0x9c, 0x00, 0x9e, 0xff, 0x9e, 0x00, 0xaf, 0xeb, 0xaf, 0x00, 0xc7, 0xc7, 0xc7, 0x00, 0xcd, 0xcd, 0xcd, 0x00, 0xce, 0xce, 0xce, 0x00, 0xd3, 0xd3, 0xd3, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xde, 0xde, 0xde, 0x00, 0xd0, 0xf3, 0xd0, 0x00, 0xd1, 0xf9, 0xd1, 0x00, 0xe3, 0xe3, 0xe3, 0x00, 0xe5, 0xe5, 0xe5, 0x00, 0xe8, 0xe8, 0xe8, 0x00, 0xe9, 0xe9, 0xe9, 0x00, 0xea, 0xea, 0xea, 0x00, 0xec, 0xec, 0xec, 0x00, 0xed, 0xed, 0xed, 0x00, 0xe7, 0xf8, 0xe7, 0x00, 0xf2, 0xf2, 0xf2, 0x00, 0xf1, 0xf5, 0xf1, 0x00, 0xf4, 0xf4, 0xf4, 0x00, 0xf4, 0xf6, 0xf4, 0x00, 0xf6, 0xf6, 0xf6, 0x00, 0xf2, 0xfa, 0xf2, 0x00, 0xf8, 0xf8, 0xf8, 0x00, 0xf9, 0xf9, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x3d, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x32, 0x26, 0x32, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x24, 0x18, 0x18, 0x31, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x1a, 0x17, 0x17, 0x30, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x3d, 0x3a, 0x38, 0x42, 0x3c, 0x16, 0x15, 0x15, 0x2f, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x2d, 0x25, 0x25, 0x25, 0x25, 0x25, 0x19, 0x14, 0x14, 0x14, 0x2e, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x34, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x21, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x0d, 0x0d, 0x0d, 0x23, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x4c, 0x4c, 0x47, 0x4c, 0x4a, 0x12, 0x0c, 0x0c, 0x22, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x1b, 0x0b, 0x0b, 0x1e, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x1f, 0x0a, 0x0a, 0x1d, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x20, 0x0f, 0x1c, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xfe, 0x3f, 0xf0, 0x00, 0xfc, 0x1f, 0xf0, 0x00, 0xfc, 0x0f, 0xf0, 0x00, 0xfe, 0x07, 0xf0, 0x00, 0xe0, 0x03, 0xf0, 0x00, 0xc0, 0x01, 0xf0, 0x00, 0xc0, 0x00, 0xf0, 0x00, 0xc0, 0x01, 0xf0, 0x00, 0xe0, 0x03, 0xf0, 0x00, 0xfe, 0x07, 0xf0, 0x00, 0xfc, 0x0f, 0xf0, 0x00, 0xfc, 0x1f, 0xf0, 0x00, 0xfe, 0x3f, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x00, 0xfa, 0xfa, 0xfa, 0x3d, 0xf6, 0xf6, 0xf6, 0x3a, 0xf6, 0xf6, 0xf6, 0x20, 0xf8, 0xf8, 0xf8, 0x00, 0xfd, 0xfd, 0xfd, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x00, 0xf9, 0xf9, 0xf9, 0x26, 0xed, 0xed, 0xed, 0x81, 0xdf, 0xdf, 0xdf, 0x9a, 0xda, 0xda, 0xda, 0x86, 0xdf, 0xdf, 0xdf, 0x4a, 0xef, 0xef, 0xef, 0x40, 0xfc, 0xfc, 0xfc, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0x17, 0xef, 0xef, 0xef, 0x35, 0xe6, 0xe6, 0xe6, 0x85, 0xcf, 0xcf, 0xcf, 0xc5, 0xc6, 0xc6, 0xc6, 0xdf, 0xc4, 0xc4, 0xc4, 0xd8, 0xc7, 0xc7, 0xc7, 0xbe, 0xd5, 0xd5, 0xd5, 0x83, 0xed, 0xed, 0xed, 0x23, 0xfc, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeb, 0xeb, 0xeb, 0x35, 0xe5, 0xe5, 0xe5, 0x91, 0xef, 0xef, 0xef, 0xcf, 0xe6, 0xe6, 0xe6, 0xf1, 0xdf, 0xdf, 0xdf, 0xfc, 0xc3, 0xc3, 0xc3, 0xfb, 0xbf, 0xbf, 0xbf, 0xf2, 0xc4, 0xc4, 0xc4, 0xc5, 0xd4, 0xd4, 0xd4, 0x65, 0xed, 0xed, 0xed, 0x0d, 0xfc, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0x00, 0xf7, 0xf7, 0xf7, 0x23, 0xe2, 0xe2, 0xe2, 0x83, 0xf1, 0xf1, 0xf1, 0xce, 0x9e, 0xff, 0x9e, 0xf5, 0x62, 0xff, 0x62, 0xfe, 0x9c, 0xff, 0x9c, 0xff, 0xed, 0xed, 0xed, 0xff, 0xc2, 0xc2, 0xc2, 0xfe, 0xbf, 0xbf, 0xbf, 0xef, 0xc4, 0xc4, 0xc4, 0xb3, 0xd4, 0xd4, 0xd4, 0x56, 0xed, 0xed, 0xed, 0x0e, 0xfb, 0xfb, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x00, 0xf7, 0xf7, 0xf7, 0x25, 0xe7, 0xe7, 0xe7, 0x7f, 0xca, 0xca, 0xca, 0xc5, 0xeb, 0xeb, 0xeb, 0xf1, 0x5a, 0xfc, 0x5a, 0xfe, 0x00, 0xf9, 0x00, 0xff, 0x00, 0xf9, 0x00, 0xff, 0x8e, 0xfb, 0x8e, 0xff, 0xec, 0xec, 0xec, 0xff, 0xc2, 0xc2, 0xc2, 0xfd, 0xbf, 0xbf, 0xbf, 0xeb, 0xc4, 0xc4, 0xc4, 0xae, 0xd4, 0xd4, 0xd4, 0x57, 0xed, 0xed, 0xed, 0x10, 0xfb, 0xfb, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0xf9, 0xf9, 0x00, 0xed, 0xed, 0xed, 0x27, 0xe1, 0xe1, 0xe1, 0x81, 0xc9, 0xc9, 0xc9, 0xc4, 0xb3, 0xb3, 0xb3, 0xee, 0xbb, 0xbb, 0xbb, 0xfd, 0xe9, 0xfa, 0xe9, 0xff, 0x24, 0xee, 0x24, 0xff, 0x00, 0xeb, 0x00, 0xff, 0x00, 0xeb, 0x00, 0xff, 0x8e, 0xf5, 0x8e, 0xff, 0xec, 0xec, 0xec, 0xff, 0xc3, 0xc3, 0xc3, 0xfd, 0xbf, 0xbf, 0xbf, 0xeb, 0xc4, 0xc4, 0xc4, 0xb1, 0xd5, 0xd5, 0xd5, 0x65, 0xef, 0xef, 0xef, 0x1e, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x00, 0xe9, 0xe9, 0xe9, 0x29, 0xe6, 0xe6, 0xe6, 0x83, 0xeb, 0xeb, 0xeb, 0xc4, 0xe0, 0xe0, 0xe0, 0xee, 0xdb, 0xdb, 0xdb, 0xfd, 0xd4, 0xd4, 0xd4, 0xff, 0xed, 0xed, 0xed, 0xff, 0xd1, 0xf9, 0xd1, 0xff, 0x0a, 0xde, 0x0a, 0xff, 0x00, 0xdd, 0x00, 0xff, 0x00, 0xdd, 0x00, 0xff, 0x88, 0xee, 0x88, 0xff, 0xef, 0xef, 0xef, 0xff, 0xc2, 0xc2, 0xc2, 0xfd, 0xc0, 0xc0, 0xc0, 0xec, 0xc8, 0xc8, 0xc8, 0xbc, 0xe3, 0xe3, 0xe3, 0x7f, 0xf9, 0xf9, 0xf9, 0x42, 0x00, 0x00, 0x00, 0x00, 0xf5, 0xf5, 0xf5, 0x40, 0xe0, 0xe0, 0xe0, 0x84, 0xf6, 0xf8, 0xf6, 0xc4, 0x83, 0xe5, 0x83, 0xee, 0x69, 0xe1, 0x69, 0xfd, 0x69, 0xe1, 0x69, 0xff, 0x69, 0xe1, 0x69, 0xff, 0x69, 0xe1, 0x69, 0xff, 0x69, 0xe1, 0x69, 0xff, 0x2b, 0xd6, 0x2b, 0xff, 0x00, 0xce, 0x00, 0xff, 0x00, 0xce, 0x00, 0xff, 0x00, 0xce, 0x00, 0xff, 0x88, 0xe7, 0x88, 0xff, 0xf0, 0xf0, 0xf0, 0xff, 0xc6, 0xc6, 0xc6, 0xfa, 0xcf, 0xcf, 0xcf, 0xdb, 0xe9, 0xe9, 0xe9, 0xa2, 0xfa, 0xfa, 0xfa, 0x4d, 0x00, 0x00, 0x00, 0x00, 0xfb, 0xfb, 0xfb, 0x42, 0xe3, 0xe3, 0xe3, 0xa9, 0xd0, 0xf3, 0xd0, 0xe7, 0x00, 0xc0, 0x00, 0xfd, 0x00, 0xc0, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xff, 0xaf, 0xeb, 0xaf, 0xfd, 0xe1, 0xe1, 0xe1, 0xe8, 0xe3, 0xe3, 0xe3, 0xb1, 0xf6, 0xf6, 0xf6, 0x72, 0xfd, 0xfd, 0xfd, 0x3d, 0x00, 0x00, 0x00, 0x00, 0xf4, 0xf4, 0xf4, 0x42, 0xdd, 0xdd, 0xdd, 0xa9, 0xfa, 0xfa, 0xfa, 0xea, 0x4b, 0xca, 0x4b, 0xfe, 0x23, 0xbd, 0x23, 0xff, 0x23, 0xbd, 0x23, 0xff, 0x23, 0xbd, 0x23, 0xff, 0x23, 0xbd, 0x23, 0xff, 0x20, 0xbd, 0x20, 0xff, 0x13, 0xb8, 0x13, 0xff, 0x00, 0xb2, 0x00, 0xff, 0x00, 0xb2, 0x00, 0xff, 0x00, 0xb2, 0x00, 0xff, 0x54, 0xcc, 0x54, 0xff, 0xf5, 0xf8, 0xf5, 0xf4, 0xd9, 0xd9, 0xd9, 0xba, 0xe8, 0xe8, 0xe8, 0x5d, 0xfc, 0xfc, 0xfc, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0x3d, 0xe5, 0xe5, 0xe5, 0x88, 0xec, 0xec, 0xec, 0xcc, 0xfa, 0xfa, 0xfa, 0xe9, 0xfa, 0xfa, 0xfa, 0xf1, 0xf9, 0xf9, 0xf9, 0xf9, 0xf7, 0xf7, 0xf7, 0xfe, 0xfb, 0xfb, 0xfb, 0xff, 0xf2, 0xfa, 0xf2, 0xff, 0x38, 0xb7, 0x38, 0xff, 0x00, 0xa4, 0x00, 0xff, 0x00, 0xa4, 0x00, 0xff, 0x54, 0xc3, 0x54, 0xfe, 0xf5, 0xf7, 0xf5, 0xf9, 0xd7, 0xd7, 0xd7, 0xd7, 0xdf, 0xdf, 0xdf, 0x7f, 0xf7, 0xf7, 0xf7, 0x15, 0xfe, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf5, 0xf5, 0xf5, 0x3b, 0xe6, 0xe6, 0xe6, 0x87, 0xdf, 0xdf, 0xdf, 0x9b, 0xd5, 0xd5, 0xd5, 0xaf, 0xc7, 0xc7, 0xc7, 0xd4, 0xcb, 0xcb, 0xcb, 0xf4, 0xf2, 0xf6, 0xf2, 0xfe, 0x43, 0xb1, 0x43, 0xff, 0x00, 0x96, 0x00, 0xff, 0x00, 0x96, 0x00, 0xff, 0x54, 0xb9, 0x54, 0xfd, 0xf5, 0xf7, 0xf5, 0xf2, 0xd7, 0xd7, 0xd7, 0xd6, 0xdf, 0xdf, 0xdf, 0x9c, 0xf6, 0xf6, 0xf6, 0x46, 0xfe, 0xfe, 0xfe, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x3e, 0xfd, 0xfd, 0xfd, 0x1d, 0xfb, 0xfb, 0xfb, 0x37, 0xe3, 0xe3, 0xe3, 0x90, 0xf5, 0xf5, 0xf5, 0xdc, 0x69, 0xb8, 0x69, 0xfb, 0x00, 0x88, 0x00, 0xff, 0x00, 0x88, 0x00, 0xfe, 0x54, 0xb0, 0x54, 0xfa, 0xf5, 0xf7, 0xf5, 0xec, 0xd7, 0xd7, 0xd7, 0xc9, 0xdf, 0xdf, 0xdf, 0x90, 0xf6, 0xf6, 0xf6, 0x3f, 0xfe, 0xfe, 0xfe, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0x00, 0xe5, 0xe5, 0xe5, 0x48, 0xf5, 0xf5, 0xf5, 0xc5, 0x72, 0xb8, 0x72, 0xf7, 0x20, 0x90, 0x20, 0xfe, 0x5b, 0xad, 0x5b, 0xf5, 0xf7, 0xf8, 0xf7, 0xdd, 0xdf, 0xdf, 0xdf, 0xba, 0xe1, 0xe1, 0xe1, 0x89, 0xf6, 0xf6, 0xf6, 0x2b, 0xfe, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x16, 0xe8, 0xe8, 0xe8, 0x58, 0xe7, 0xe7, 0xe7, 0xa9, 0xf6, 0xf6, 0xf6, 0xdd, 0xfa, 0xfa, 0xfa, 0xe7, 0xfa, 0xfa, 0xfa, 0xd0, 0xe7, 0xe7, 0xe7, 0x9a, 0xe5, 0xe5, 0xe5, 0x50, 0xf8, 0xf8, 0xf8, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xfa, 0xfa, 0x25, 0xe8, 0xe8, 0xe8, 0x71, 0xe3, 0xe3, 0xe3, 0x8c, 0xd9, 0xd9, 0xd9, 0x94, 0xe4, 0xe4, 0xe4, 0x8c, 0xe9, 0xe9, 0xe9, 0x41, 0xf9, 0xf9, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0x19, 0xfd, 0xfd, 0xfd, 0x25, 0xfd, 0xfd, 0xfd, 0x31, 0xfe, 0xfe, 0xfe, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xbf, 0xf0, 0x00, 0xfe, 0x07, 0xf0, 0x00, 0xfc, 0x03, 0xf0, 0x00, 0xf8, 0x01, 0xf0, 0x00, 0xf8, 0x00, 0xf0, 0x00, 0xe0, 0x00, 0x70, 0x00, 0xc0, 0x00, 0x30, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x80, 0x00, 0x70, 0x00, 0xc0, 0x00, 0xf0, 0x00, 0xf0, 0x01, 0xf0, 0x00, 0xf0, 0x07, 0xf0, 0x00, 0xf8, 0x0f, 0xf0, 0x00, 0xfe, 0x1f, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00 }; static const unsigned int TaskDlgArrowHot_ico_len = 3302; static const unsigned char TaskDlgArrowNormal_ico[] = { 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x14, 0x14, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x08, 0x06, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x14, 0x14, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0xb8, 0x06, 0x00, 0x00, 0x2e, 0x06, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x00, 0x35, 0x35, 0x35, 0x00, 0x36, 0x36, 0x36, 0x00, 0x3d, 0x3d, 0x3d, 0x00, 0x3e, 0x3e, 0x3e, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x20, 0x58, 0x20, 0x00, 0x40, 0x40, 0x40, 0x00, 0x67, 0x67, 0x67, 0x00, 0x68, 0x68, 0x68, 0x00, 0x6e, 0x6e, 0x6e, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x13, 0x96, 0x13, 0x00, 0x00, 0xa1, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x20, 0x9c, 0x20, 0x00, 0x23, 0x9d, 0x23, 0x00, 0x38, 0x94, 0x38, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x0a, 0xce, 0x0a, 0x00, 0x00, 0xe1, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x00, 0x2b, 0xc2, 0x2b, 0x00, 0x24, 0xe6, 0x24, 0x00, 0x43, 0x8a, 0x43, 0x00, 0x54, 0x89, 0x54, 0x00, 0x5b, 0x84, 0x5b, 0x00, 0x54, 0x97, 0x54, 0x00, 0x4b, 0xaf, 0x4b, 0x00, 0x54, 0xa5, 0x54, 0x00, 0x54, 0xb3, 0x54, 0x00, 0x69, 0x95, 0x69, 0x00, 0x72, 0x95, 0x72, 0x00, 0x5a, 0xfa, 0x5a, 0x00, 0x69, 0xd2, 0x69, 0x00, 0x62, 0xff, 0x62, 0x00, 0x89, 0x89, 0x89, 0x00, 0x8b, 0x8b, 0x8b, 0x00, 0x9a, 0x9a, 0x9a, 0x00, 0xa9, 0xa9, 0xa9, 0x00, 0xab, 0xab, 0xab, 0x00, 0xad, 0xad, 0xad, 0x00, 0xbd, 0xbd, 0xbd, 0x00, 0xbe, 0xbe, 0xbe, 0x00, 0x83, 0xd9, 0x83, 0x00, 0x88, 0xdb, 0x88, 0x00, 0x88, 0xe5, 0x88, 0x00, 0x8e, 0xf0, 0x8e, 0x00, 0x8e, 0xf9, 0x8e, 0x00, 0x9c, 0xff, 0x9c, 0x00, 0x9e, 0xff, 0x9e, 0x00, 0xaf, 0xe1, 0xaf, 0x00, 0xc5, 0xc5, 0xc5, 0x00, 0xcc, 0xcc, 0xcc, 0x00, 0xcd, 0xcd, 0xcd, 0x00, 0xd5, 0xd5, 0xd5, 0x00, 0xd8, 0xd8, 0xd8, 0x00, 0xdb, 0xdb, 0xdb, 0x00, 0xd0, 0xee, 0xd0, 0x00, 0xd1, 0xf6, 0xd1, 0x00, 0xe2, 0xe2, 0xe2, 0x00, 0xe5, 0xe5, 0xe5, 0x00, 0xe8, 0xe8, 0xe8, 0x00, 0xe9, 0xe9, 0xe9, 0x00, 0xeb, 0xeb, 0xeb, 0x00, 0xec, 0xec, 0xec, 0x00, 0xed, 0xed, 0xed, 0x00, 0xe8, 0xf9, 0xe8, 0x00, 0xf2, 0xf2, 0xf2, 0x00, 0xf2, 0xf4, 0xf2, 0x00, 0xf3, 0xf5, 0xf3, 0x00, 0xf3, 0xf6, 0xf3, 0x00, 0xf4, 0xf4, 0xf4, 0x00, 0xf5, 0xf5, 0xf5, 0x00, 0xf4, 0xf6, 0xf4, 0x00, 0xf5, 0xf6, 0xf5, 0x00, 0xf2, 0xf8, 0xf2, 0x00, 0xf8, 0xf8, 0xf8, 0x00, 0xf9, 0xf9, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x34, 0x26, 0x34, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x24, 0x18, 0x18, 0x33, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x1a, 0x17, 0x17, 0x32, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x3c, 0x3c, 0x3a, 0x44, 0x3e, 0x16, 0x15, 0x15, 0x31, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x2f, 0x25, 0x25, 0x25, 0x25, 0x25, 0x19, 0x11, 0x11, 0x11, 0x30, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x36, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x1f, 0x13, 0x13, 0x13, 0x13, 0x13, 0x0f, 0x0e, 0x0e, 0x0e, 0x21, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x51, 0x51, 0x4b, 0x51, 0x4f, 0x14, 0x08, 0x08, 0x20, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x1b, 0x07, 0x07, 0x1e, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x22, 0x06, 0x06, 0x1c, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x23, 0x09, 0x1d, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x51, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xfe, 0x3f, 0xf0, 0x00, 0xfc, 0x1f, 0xf0, 0x00, 0xfc, 0x0f, 0xf0, 0x00, 0xfe, 0x07, 0xf0, 0x00, 0xe0, 0x03, 0xf0, 0x00, 0xc0, 0x01, 0xf0, 0x00, 0xc0, 0x00, 0xf0, 0x00, 0xc0, 0x01, 0xf0, 0x00, 0xe0, 0x03, 0xf0, 0x00, 0xfe, 0x07, 0xf0, 0x00, 0xfc, 0x0f, 0xf0, 0x00, 0xfc, 0x1f, 0xf0, 0x00, 0xfe, 0x3f, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x00, 0xfb, 0xfb, 0xfb, 0x00, 0xf7, 0xf7, 0xf7, 0x29, 0xf6, 0xf6, 0xf6, 0x00, 0xf8, 0xf8, 0xf8, 0x23, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x32, 0xfa, 0xfa, 0xfa, 0x3b, 0xed, 0xed, 0xed, 0x44, 0xdf, 0xdf, 0xdf, 0x73, 0xdb, 0xdb, 0xdb, 0x54, 0xe0, 0xe0, 0xe0, 0x60, 0xf0, 0xf0, 0xf0, 0x18, 0xfc, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0x00, 0xef, 0xef, 0xef, 0x3d, 0xe6, 0xe6, 0xe6, 0x93, 0xcf, 0xcf, 0xcf, 0xb6, 0xc7, 0xc7, 0xc7, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc7, 0xc7, 0xc7, 0xae, 0xd5, 0xd5, 0xd5, 0x71, 0xee, 0xee, 0xee, 0x26, 0xfc, 0xfc, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0xec, 0xec, 0x21, 0xe5, 0xe5, 0xe5, 0x84, 0xee, 0xee, 0xee, 0xce, 0xe6, 0xe6, 0xe6, 0xef, 0xdf, 0xdf, 0xdf, 0xf6, 0xc4, 0xc4, 0xc4, 0xf6, 0xc0, 0xc0, 0xc0, 0xec, 0xc5, 0xc5, 0xc5, 0xca, 0xd5, 0xd5, 0xd5, 0x8b, 0xed, 0xed, 0xed, 0x45, 0xfc, 0xfc, 0xfc, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0x00, 0xf7, 0xf7, 0xf7, 0x10, 0xe2, 0xe2, 0xe2, 0x6d, 0xf1, 0xf1, 0xf1, 0xc0, 0x9e, 0xff, 0x9e, 0xf1, 0x62, 0xff, 0x62, 0xfe, 0x9c, 0xff, 0x9c, 0xff, 0xed, 0xed, 0xed, 0xff, 0xc3, 0xc3, 0xc3, 0xfe, 0xc0, 0xc0, 0xc0, 0xf8, 0xc5, 0xc5, 0xc5, 0xd6, 0xd5, 0xd5, 0xd5, 0x86, 0xed, 0xed, 0xed, 0x19, 0xfc, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x00, 0xf8, 0xf8, 0xf8, 0x0f, 0xe8, 0xe8, 0xe8, 0x5c, 0xcb, 0xcb, 0xcb, 0xaf, 0xeb, 0xeb, 0xeb, 0xea, 0x5a, 0xfa, 0x5a, 0xfd, 0x00, 0xf6, 0x00, 0xff, 0x00, 0xf6, 0x00, 0xff, 0x8e, 0xf9, 0x8e, 0xff, 0xec, 0xec, 0xec, 0xff, 0xc3, 0xc3, 0xc3, 0xff, 0xc0, 0xc0, 0xc0, 0xf5, 0xc5, 0xc5, 0xc5, 0xbd, 0xd5, 0xd5, 0xd5, 0x5d, 0xed, 0xed, 0xed, 0x10, 0xfc, 0xfc, 0xfc, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0xf9, 0xf9, 0x00, 0xed, 0xed, 0xed, 0x1d, 0xe1, 0xe1, 0xe1, 0x6c, 0xca, 0xca, 0xca, 0xb0, 0xb3, 0xb3, 0xb3, 0xe5, 0xbc, 0xbc, 0xbc, 0xfc, 0xea, 0xfb, 0xea, 0xff, 0x24, 0xe6, 0x24, 0xff, 0x00, 0xe1, 0x00, 0xff, 0x00, 0xe1, 0x00, 0xff, 0x8e, 0xf0, 0x8e, 0xff, 0xed, 0xed, 0xed, 0xff, 0xc4, 0xc4, 0xc4, 0xfd, 0xc0, 0xc0, 0xc0, 0xe6, 0xc5, 0xc5, 0xc5, 0xa1, 0xd5, 0xd5, 0xd5, 0x36, 0xef, 0xef, 0xef, 0x00, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x00, 0xe9, 0xe9, 0xe9, 0x19, 0xe6, 0xe6, 0xe6, 0x71, 0xe9, 0xe9, 0xe9, 0xbb, 0xdf, 0xdf, 0xdf, 0xea, 0xda, 0xda, 0xda, 0xfc, 0xd5, 0xd5, 0xd5, 0xff, 0xee, 0xee, 0xee, 0xff, 0xd1, 0xf6, 0xd1, 0xff, 0x0a, 0xce, 0x0a, 0xff, 0x00, 0xcc, 0x00, 0xff, 0x00, 0xcc, 0x00, 0xff, 0x88, 0xe5, 0x88, 0xff, 0xef, 0xef, 0xef, 0xff, 0xc3, 0xc3, 0xc3, 0xf9, 0xc1, 0xc1, 0xc1, 0xd7, 0xc9, 0xc9, 0xc9, 0x87, 0xe3, 0xe3, 0xe3, 0x2c, 0xfa, 0xfa, 0xfa, 0x10, 0x00, 0x00, 0x00, 0x00, 0xf5, 0xf5, 0xf5, 0x13, 0xe1, 0xe1, 0xe1, 0x61, 0xf7, 0xf9, 0xf7, 0xb8, 0x83, 0xd9, 0x83, 0xed, 0x69, 0xd2, 0x69, 0xfd, 0x69, 0xd2, 0x69, 0xff, 0x69, 0xd2, 0x69, 0xff, 0x69, 0xd2, 0x69, 0xff, 0x69, 0xd2, 0x69, 0xff, 0x2b, 0xc2, 0x2b, 0xff, 0x00, 0xb7, 0x00, 0xff, 0x00, 0xb7, 0x00, 0xff, 0x00, 0xb7, 0x00, 0xff, 0x88, 0xdb, 0x88, 0xff, 0xf0, 0xf0, 0xf0, 0xfe, 0xc7, 0xc7, 0xc7, 0xef, 0xd0, 0xd0, 0xd0, 0xb1, 0xe9, 0xe9, 0xe9, 0x5c, 0xfa, 0xfa, 0xfa, 0x16, 0x00, 0x00, 0x00, 0x00, 0xfb, 0xfb, 0xfb, 0x1d, 0xe3, 0xe3, 0xe3, 0x80, 0xd0, 0xee, 0xd0, 0xde, 0x00, 0xa1, 0x00, 0xfd, 0x00, 0xa1, 0x00, 0xff, 0x00, 0xa1, 0x00, 0xff, 0x00, 0xa1, 0x00, 0xff, 0x00, 0xa1, 0x00, 0xff, 0x00, 0xa1, 0x00, 0xff, 0x00, 0xa1, 0x00, 0xff, 0x00, 0xa1, 0x00, 0xff, 0x00, 0xa1, 0x00, 0xff, 0x00, 0xa1, 0x00, 0xff, 0x00, 0xa1, 0x00, 0xfe, 0xaf, 0xe1, 0xaf, 0xf9, 0xe1, 0xe1, 0xe1, 0xe1, 0xe3, 0xe3, 0xe3, 0xa1, 0xf6, 0xf6, 0xf6, 0x36, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0xf4, 0xf4, 0x14, 0xde, 0xde, 0xde, 0x87, 0xfa, 0xfa, 0xfa, 0xe4, 0x4b, 0xaf, 0x4b, 0xfe, 0x23, 0x9d, 0x23, 0xff, 0x23, 0x9d, 0x23, 0xff, 0x23, 0x9d, 0x23, 0xff, 0x23, 0x9d, 0x23, 0xff, 0x20, 0x9c, 0x20, 0xff, 0x13, 0x96, 0x13, 0xff, 0x00, 0x8c, 0x00, 0xff, 0x00, 0x8c, 0x00, 0xff, 0x00, 0x8c, 0x00, 0xfe, 0x54, 0xb3, 0x54, 0xf4, 0xf4, 0xf7, 0xf4, 0xd5, 0xd9, 0xd9, 0xd9, 0xa4, 0xe8, 0xe8, 0xe8, 0x68, 0xfd, 0xfd, 0xfd, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0x25, 0xe6, 0xe6, 0xe6, 0x78, 0xed, 0xed, 0xed, 0xbd, 0xfa, 0xfa, 0xfa, 0xe5, 0xfa, 0xfa, 0xfa, 0xf3, 0xf9, 0xf9, 0xf9, 0xfa, 0xf7, 0xf7, 0xf7, 0xfe, 0xfb, 0xfb, 0xfb, 0xff, 0xf2, 0xf8, 0xf2, 0xff, 0x38, 0x94, 0x38, 0xff, 0x00, 0x77, 0x00, 0xff, 0x00, 0x77, 0x00, 0xff, 0x54, 0xa5, 0x54, 0xf8, 0xf4, 0xf6, 0xf4, 0xd1, 0xd7, 0xd7, 0xd7, 0x87, 0xe0, 0xe0, 0xe0, 0x37, 0xf7, 0xf7, 0xf7, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf5, 0xf5, 0xf5, 0x28, 0xe7, 0xe7, 0xe7, 0x5c, 0xe0, 0xe0, 0xe0, 0x8e, 0xd5, 0xd5, 0xd5, 0xb5, 0xc8, 0xc8, 0xc8, 0xd5, 0xca, 0xca, 0xca, 0xf1, 0xf3, 0xf5, 0xf3, 0xfd, 0x43, 0x8a, 0x43, 0xff, 0x00, 0x61, 0x00, 0xff, 0x00, 0x61, 0x00, 0xff, 0x54, 0x97, 0x54, 0xfc, 0xf4, 0xf6, 0xf4, 0xdf, 0xd7, 0xd7, 0xd7, 0x95, 0xe0, 0xe0, 0xe0, 0x30, 0xf7, 0xf7, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0x0b, 0xfd, 0xfd, 0xfd, 0x22, 0xfb, 0xfb, 0xfb, 0x39, 0xe3, 0xe3, 0xe3, 0x87, 0xf5, 0xf5, 0xf5, 0xc9, 0x69, 0x95, 0x69, 0xf2, 0x00, 0x4c, 0x00, 0xfe, 0x00, 0x4c, 0x00, 0xff, 0x54, 0x89, 0x54, 0xfd, 0xf5, 0xf7, 0xf5, 0xe9, 0xd7, 0xd7, 0xd7, 0xa8, 0xe0, 0xe0, 0xe0, 0x4f, 0xf7, 0xf7, 0xf7, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0x00, 0xe5, 0xe5, 0xe5, 0x24, 0xf5, 0xf5, 0xf5, 0x90, 0x72, 0x95, 0x72, 0xdc, 0x20, 0x58, 0x20, 0xf7, 0x5b, 0x84, 0x5b, 0xf6, 0xf6, 0xf7, 0xf6, 0xe5, 0xdf, 0xdf, 0xdf, 0xb1, 0xe2, 0xe2, 0xe2, 0x5f, 0xf7, 0xf7, 0xf7, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x0a, 0xe9, 0xe9, 0xe9, 0x00, 0xe8, 0xe8, 0xe8, 0x4c, 0xf6, 0xf6, 0xf6, 0xb8, 0xfa, 0xfa, 0xfa, 0xcf, 0xf8, 0xf8, 0xf8, 0xbe, 0xe7, 0xe7, 0xe7, 0x99, 0xe6, 0xe6, 0xe6, 0x5d, 0xf8, 0xf8, 0xf8, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xfa, 0xfa, 0x0f, 0xe9, 0xe9, 0xe9, 0x4c, 0xe4, 0xe4, 0xe4, 0x74, 0xd8, 0xd8, 0xd8, 0x70, 0xe3, 0xe3, 0xe3, 0x40, 0xe9, 0xe9, 0xe9, 0x40, 0xf9, 0xf9, 0xf9, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x16, 0xfd, 0xfd, 0xfd, 0x22, 0xfd, 0xfd, 0xfd, 0x12, 0xfd, 0xfd, 0xfd, 0x00, 0xfe, 0xfe, 0xfe, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xfe, 0x07, 0xf0, 0x00, 0xfc, 0x03, 0xf0, 0x00, 0xf8, 0x01, 0xf0, 0x00, 0xf8, 0x00, 0xf0, 0x00, 0xe0, 0x00, 0x70, 0x00, 0xc0, 0x00, 0x30, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x80, 0x01, 0xf0, 0x00, 0xf0, 0x03, 0xf0, 0x00, 0xf0, 0x07, 0xf0, 0x00, 0xf8, 0x0f, 0xf0, 0x00, 0xfc, 0x1f, 0xf0, 0x00, 0xff, 0xff, 0xf0, 0x00 }; static const unsigned int TaskDlgArrowNormal_ico_len = 3302; static const unsigned char TaskDlgChevronLess_ico[] = { 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x14, 0x14, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x08, 0x06, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x14, 0x14, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0xb8, 0x06, 0x00, 0x00, 0x2e, 0x06, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x3c, 0x3c, 0x3c, 0x00, 0x3f, 0x3f, 0x3f, 0x00, 0x45, 0x45, 0x45, 0x00, 0x46, 0x46, 0x46, 0x00, 0x48, 0x48, 0x48, 0x00, 0x50, 0x50, 0x50, 0x00, 0x51, 0x51, 0x51, 0x00, 0x55, 0x55, 0x55, 0x00, 0x58, 0x58, 0x58, 0x00, 0x5a, 0x5a, 0x5a, 0x00, 0x5b, 0x5b, 0x5b, 0x00, 0x5c, 0x5c, 0x5c, 0x00, 0x5e, 0x5e, 0x5e, 0x00, 0x61, 0x61, 0x61, 0x00, 0x62, 0x60, 0x60, 0x00, 0x64, 0x64, 0x64, 0x00, 0x65, 0x65, 0x65, 0x00, 0x68, 0x68, 0x68, 0x00, 0x69, 0x69, 0x69, 0x00, 0x6a, 0x6a, 0x6a, 0x00, 0x6b, 0x6b, 0x6b, 0x00, 0x6c, 0x6c, 0x6c, 0x00, 0x6e, 0x6e, 0x6e, 0x00, 0x6f, 0x6f, 0x6f, 0x00, 0x70, 0x70, 0x70, 0x00, 0x71, 0x71, 0x71, 0x00, 0x72, 0x72, 0x72, 0x00, 0x73, 0x73, 0x73, 0x00, 0x74, 0x74, 0x74, 0x00, 0x75, 0x75, 0x75, 0x00, 0x77, 0x77, 0x77, 0x00, 0x78, 0x78, 0x78, 0x00, 0x79, 0x79, 0x79, 0x00, 0x7c, 0x7c, 0x7d, 0x00, 0x7d, 0x7d, 0x7d, 0x00, 0x7e, 0x7e, 0x7e, 0x00, 0x7f, 0x7f, 0x7f, 0x00, 0x81, 0x81, 0x81, 0x00, 0x82, 0x82, 0x82, 0x00, 0x90, 0x90, 0x90, 0x00, 0x95, 0x95, 0x95, 0x00, 0x96, 0x96, 0x97, 0x00, 0x97, 0x97, 0x97, 0x00, 0x97, 0x97, 0x98, 0x00, 0x98, 0x98, 0x98, 0x00, 0x99, 0x9a, 0x9a, 0x00, 0x9a, 0x9a, 0x9a, 0x00, 0x9b, 0x9c, 0x9d, 0x00, 0x9e, 0x9f, 0x9f, 0x00, 0xa0, 0xa1, 0xa1, 0x00, 0xa1, 0xa1, 0xa2, 0x00, 0xa1, 0xa2, 0xa2, 0x00, 0xa3, 0xa5, 0xa5, 0x00, 0xa4, 0xa4, 0xa5, 0x00, 0xa7, 0xa8, 0xa9, 0x00, 0xa9, 0xa9, 0xaa, 0x00, 0xa9, 0xaa, 0xaa, 0x00, 0xb0, 0xb3, 0xb4, 0x00, 0xb2, 0xb5, 0xb6, 0x00, 0xb3, 0xb5, 0xb6, 0x00, 0xb7, 0xb7, 0xb8, 0x00, 0xb8, 0xb9, 0xb9, 0x00, 0xba, 0xbb, 0xbc, 0x00, 0xbc, 0xbd, 0xbe, 0x00, 0xbd, 0xbe, 0xc0, 0x00, 0xbd, 0xc0, 0xc2, 0x00, 0xbe, 0xc0, 0xc1, 0x00, 0xbf, 0xc2, 0xc4, 0x00, 0xc0, 0xc1, 0xc1, 0x00, 0xc8, 0xc9, 0xcb, 0x00, 0xc9, 0xc9, 0xc9, 0x00, 0xc8, 0xca, 0xca, 0x00, 0xca, 0xca, 0xca, 0x00, 0xca, 0xcb, 0xcc, 0x00, 0xc9, 0xcc, 0xce, 0x00, 0xcb, 0xcc, 0xcc, 0x00, 0xcc, 0xcc, 0xcc, 0x00, 0xc9, 0xcd, 0xd0, 0x00, 0xca, 0xcd, 0xd0, 0x00, 0xca, 0xce, 0xd0, 0x00, 0xcd, 0xce, 0xd0, 0x00, 0xcf, 0xcf, 0xd0, 0x00, 0xce, 0xd0, 0xd1, 0x00, 0xcf, 0xd0, 0xd1, 0x00, 0xd0, 0xd1, 0xd2, 0x00, 0xd3, 0xd3, 0xd3, 0x00, 0xd0, 0xd2, 0xd4, 0x00, 0xd1, 0xd2, 0xd4, 0x00, 0xd2, 0xd5, 0xd5, 0x00, 0xd4, 0xd4, 0xd4, 0x00, 0xd2, 0xd6, 0xda, 0x00, 0xd5, 0xd6, 0xd8, 0x00, 0xd6, 0xd8, 0xd9, 0x00, 0xd7, 0xd8, 0xd9, 0x00, 0xd4, 0xd9, 0xdd, 0x00, 0xd6, 0xdb, 0xde, 0x00, 0xd7, 0xda, 0xdd, 0x00, 0xd9, 0xda, 0xdb, 0x00, 0xd9, 0xdc, 0xde, 0x00, 0xdd, 0xdd, 0xdd, 0x00, 0xde, 0xdf, 0xde, 0x00, 0xd9, 0xde, 0xe1, 0x00, 0xdb, 0xe0, 0xe3, 0x00, 0xde, 0xe2, 0xe5, 0x00, 0xe0, 0xe0, 0xe0, 0x00, 0xe2, 0xe2, 0xe2, 0x00, 0xe0, 0xe2, 0xe4, 0x00, 0xe0, 0xe4, 0xe7, 0x00, 0xe2, 0xe4, 0xe7, 0x00, 0xe2, 0xe6, 0xe7, 0x00, 0xe4, 0xe6, 0xe6, 0x00, 0xe4, 0xe7, 0xe9, 0x00, 0xe5, 0xe7, 0xe8, 0x00, 0xe6, 0xe9, 0xeb, 0x00, 0xe7, 0xe9, 0xea, 0x00, 0xe7, 0xea, 0xeb, 0x00, 0xe7, 0xe9, 0xec, 0x00, 0xe7, 0xea, 0xec, 0x00, 0xe8, 0xe9, 0xe9, 0x00, 0xe9, 0xeb, 0xeb, 0x00, 0xe9, 0xec, 0xed, 0x00, 0xea, 0xec, 0xee, 0x00, 0xeb, 0xec, 0xee, 0x00, 0xeb, 0xee, 0xef, 0x00, 0xec, 0xed, 0xee, 0x00, 0xec, 0xee, 0xee, 0x00, 0xed, 0xee, 0xef, 0x00, 0xec, 0xee, 0xf0, 0x00, 0xed, 0xef, 0xf0, 0x00, 0xee, 0xf0, 0xf2, 0x00, 0xef, 0xf1, 0xf2, 0x00, 0xf1, 0xf3, 0xf3, 0x00, 0xf1, 0xf3, 0xf4, 0x00, 0xf2, 0xf3, 0xf4, 0x00, 0xf2, 0xf4, 0xf5, 0x00, 0xf3, 0xf4, 0xf5, 0x00, 0xf4, 0xf5, 0xf5, 0x00, 0xf5, 0xf5, 0xf7, 0x00, 0xf6, 0xf6, 0xf6, 0x00, 0xf5, 0xf7, 0xf8, 0x00, 0xf8, 0xf8, 0xf9, 0x00, 0xfa, 0xfb, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x11, 0x17, 0x11, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x23, 0x31, 0x4e, 0x5b, 0x5b, 0x5b, 0x4e, 0x31, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x2a, 0x4e, 0x5b, 0x3e, 0x3a, 0x37, 0x3a, 0x40, 0x5b, 0x4e, 0x2a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x37, 0x5b, 0x40, 0x3a, 0x37, 0x37, 0x37, 0x37, 0x37, 0x3a, 0x40, 0x5b, 0x37, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x11, 0x2b, 0x5b, 0x42, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x45, 0x5b, 0x31, 0x00, 0x00, 0x00, 0x00, 0x23, 0x50, 0x4e, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x50, 0x4e, 0x28, 0x00, 0x00, 0x00, 0x31, 0x65, 0x50, 0x70, 0x7c, 0x81, 0x70, 0x50, 0x50, 0x50, 0x70, 0x7c, 0x81, 0x70, 0x50, 0x65, 0x31, 0x00, 0x00, 0x17, 0x4e, 0x61, 0x5c, 0x89, 0x09, 0x09, 0x81, 0x77, 0x5c, 0x77, 0x89, 0x09, 0x09, 0x81, 0x5c, 0x61, 0x45, 0x1e, 0x00, 0x17, 0x5c, 0x61, 0x61, 0x84, 0x09, 0x09, 0x09, 0x89, 0x77, 0x84, 0x09, 0x09, 0x09, 0x89, 0x61, 0x61, 0x58, 0x1e, 0x00, 0x17, 0x61, 0x61, 0x61, 0x7c, 0x89, 0x09, 0x09, 0x09, 0x84, 0x09, 0x09, 0x09, 0x89, 0x7c, 0x61, 0x61, 0x5b, 0x17, 0x00, 0x17, 0x61, 0x69, 0x68, 0x69, 0x7c, 0x7c, 0x09, 0x09, 0x09, 0x09, 0x09, 0x89, 0x7c, 0x69, 0x68, 0x69, 0x5b, 0x17, 0x00, 0x1e, 0x4e, 0x70, 0x69, 0x70, 0x70, 0x81, 0x8d, 0x09, 0x09, 0x09, 0x89, 0x81, 0x70, 0x70, 0x69, 0x70, 0x45, 0x1e, 0x00, 0x1e, 0x35, 0x7c, 0x77, 0x77, 0x77, 0x77, 0x89, 0x89, 0x09, 0x8d, 0x89, 0x77, 0x77, 0x77, 0x77, 0x77, 0x2a, 0x00, 0x00, 0x00, 0x28, 0x65, 0x7c, 0x81, 0x81, 0x7c, 0x81, 0x89, 0x8f, 0x89, 0x81, 0x7c, 0x81, 0x81, 0x7c, 0x58, 0x23, 0x00, 0x00, 0x00, 0x1e, 0x31, 0x7c, 0x89, 0x89, 0x89, 0x89, 0x89, 0x8d, 0x89, 0x89, 0x89, 0x89, 0x89, 0x70, 0x2a, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x23, 0x37, 0x7c, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x7c, 0x31, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x2a, 0x65, 0x89, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x89, 0x61, 0x2a, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x28, 0x31, 0x4e, 0x70, 0x70, 0x65, 0x4e, 0x2a, 0x28, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x23, 0x1e, 0x1e, 0x1e, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xfe, 0x0f, 0xf0, 0x00, 0xf0, 0x03, 0xf0, 0x00, 0xe0, 0x00, 0xf0, 0x00, 0xc0, 0x00, 0x70, 0x00, 0x80, 0x00, 0x70, 0x00, 0x80, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0xc0, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xf0, 0x00, 0xf0, 0x01, 0xf0, 0x00, 0xfc, 0x0f, 0xf0, 0x00, 0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x07, 0x89, 0x89, 0x89, 0x0e, 0x89, 0x89, 0x89, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x2a, 0x89, 0x89, 0x89, 0x9a, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0x9a, 0x89, 0x89, 0x89, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x0e, 0x89, 0x89, 0x89, 0xac, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0xa4, 0x89, 0x89, 0x89, 0x42, 0x89, 0x89, 0x89, 0x18, 0x89, 0x89, 0x89, 0x0e, 0x89, 0x89, 0x89, 0x18, 0x89, 0x89, 0x89, 0x42, 0x89, 0x89, 0x89, 0xa4, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0xac, 0x89, 0x89, 0x89, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x1f, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0xb9, 0x89, 0x89, 0x89, 0x18, 0xd4, 0xd4, 0xd4, 0xff, 0xb7, 0xb7, 0xb8, 0xff, 0xa9, 0xaa, 0xaa, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa9, 0xaa, 0xaa, 0xff, 0xba, 0xbb, 0xbc, 0xff, 0xd4, 0xd4, 0xd4, 0xff, 0x89, 0x89, 0x89, 0x18, 0x89, 0x89, 0x89, 0xb9, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x0e, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0x90, 0xd4, 0xd4, 0xd4, 0xff, 0xba, 0xbb, 0xbc, 0xff, 0xa9, 0xaa, 0xaa, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa9, 0xaa, 0xaa, 0xff, 0xba, 0xbb, 0xbc, 0xff, 0xd4, 0xd4, 0xd4, 0xff, 0x89, 0x89, 0x89, 0x90, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0xac, 0x89, 0x89, 0x89, 0xb9, 0xd4, 0xd4, 0xd4, 0xff, 0xbd, 0xbe, 0xc0, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xd4, 0xd4, 0xd4, 0xff, 0x89, 0x89, 0x89, 0xb9, 0x89, 0x89, 0x89, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x2a, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0x18, 0xcc, 0xcc, 0xcc, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xca, 0xcd, 0xd0, 0xff, 0x89, 0x89, 0x89, 0x18, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x9a, 0x89, 0x89, 0x89, 0xa4, 0xdd, 0xdd, 0xdd, 0xff, 0xca, 0xcd, 0xd0, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0xeb, 0xec, 0xee, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0xca, 0xcd, 0xd0, 0xff, 0xca, 0xcd, 0xd0, 0xff, 0xca, 0xcd, 0xd0, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0xeb, 0xec, 0xee, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0xca, 0xcd, 0xd0, 0xff, 0xdd, 0xdd, 0xdd, 0xff, 0x89, 0x89, 0x89, 0xa4, 0x89, 0x89, 0x89, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0x42, 0xd6, 0xdb, 0xde, 0xff, 0xd2, 0xd6, 0xda, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xd2, 0xd6, 0xda, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xd2, 0xd6, 0xda, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0x89, 0x89, 0x89, 0x42, 0x89, 0x89, 0x89, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0x18, 0xd6, 0xdb, 0xde, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0xef, 0xf1, 0xf2, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xef, 0xf1, 0xf2, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0x89, 0x89, 0x89, 0x18, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0x07, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0x0e, 0xd6, 0xdb, 0xde, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0xeb, 0xec, 0xee, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0xef, 0xf1, 0xf2, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xeb, 0xec, 0xee, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0x89, 0x89, 0x89, 0x0e, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0x0e, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0x18, 0xde, 0xe2, 0xe5, 0xff, 0xdb, 0xe0, 0xe3, 0xff, 0xde, 0xe2, 0xe5, 0xff, 0xeb, 0xec, 0xee, 0xff, 0xeb, 0xec, 0xee, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xeb, 0xec, 0xee, 0xff, 0xde, 0xe2, 0xe5, 0xff, 0xdb, 0xe0, 0xe3, 0xff, 0xde, 0xe2, 0xe5, 0xff, 0x89, 0x89, 0x89, 0x18, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0x07, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0x42, 0xe4, 0xe6, 0xe6, 0xff, 0xde, 0xe2, 0xe5, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xf5, 0xf7, 0xf8, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0x55, 0x55, 0x55, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0xde, 0xe2, 0xe5, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0x89, 0x89, 0x89, 0x42, 0x89, 0x89, 0x89, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x9a, 0x89, 0x89, 0x89, 0xa4, 0xeb, 0xec, 0xee, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0x55, 0x55, 0x55, 0xff, 0xf5, 0xf7, 0xf8, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xe7, 0xea, 0xec, 0xff, 0x89, 0x89, 0x89, 0xa4, 0x89, 0x89, 0x89, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x2a, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0x18, 0xeb, 0xec, 0xee, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xeb, 0xec, 0xee, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xfa, 0xfb, 0xfb, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xeb, 0xec, 0xee, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xeb, 0xec, 0xee, 0xff, 0x89, 0x89, 0x89, 0x18, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0xac, 0x89, 0x89, 0x89, 0xb9, 0xeb, 0xec, 0xee, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf5, 0xf7, 0xf8, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0x89, 0x89, 0x89, 0xb9, 0x89, 0x89, 0x89, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x0e, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0x90, 0xeb, 0xec, 0xee, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xeb, 0xec, 0xee, 0xff, 0x89, 0x89, 0x89, 0x90, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x1f, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0xb9, 0x89, 0x89, 0x89, 0x18, 0xf3, 0xf4, 0xf5, 0xff, 0xfa, 0xfb, 0xfb, 0xff, 0xfa, 0xfb, 0xfb, 0xff, 0xfa, 0xfb, 0xfb, 0xff, 0xfa, 0xfb, 0xfb, 0xff, 0xfa, 0xfb, 0xfb, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0x89, 0x89, 0x89, 0x18, 0x89, 0x89, 0x89, 0xb9, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x0e, 0x89, 0x89, 0x89, 0xac, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0xa4, 0x89, 0x89, 0x89, 0x42, 0x89, 0x89, 0x89, 0x18, 0x89, 0x89, 0x89, 0x0e, 0x89, 0x89, 0x89, 0x18, 0x89, 0x89, 0x89, 0x42, 0x89, 0x89, 0x89, 0xa4, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0xac, 0x89, 0x89, 0x89, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x89, 0x89, 0x2a, 0x89, 0x89, 0x89, 0x9a, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0xff, 0x89, 0x89, 0x89, 0xf7, 0x89, 0x89, 0x89, 0x9a, 0x89, 0x89, 0x89, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xf0, 0x00, 0xf8, 0x03, 0xf0, 0x00, 0xe0, 0x00, 0xf0, 0x00, 0xc0, 0x00, 0x70, 0x00, 0x80, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0xc0, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xf0, 0x00, 0xf8, 0x03, 0xf0, 0x00 }; static const unsigned int TaskDlgChevronLess_ico_len = 3302; static const unsigned char TaskDlgChevronMore_ico[] = { 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x14, 0x14, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x08, 0x06, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x14, 0x14, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0xb8, 0x06, 0x00, 0x00, 0x2e, 0x06, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x2a, 0x2a, 0x00, 0x3c, 0x3c, 0x3c, 0x00, 0x3f, 0x3f, 0x3f, 0x00, 0x45, 0x45, 0x45, 0x00, 0x46, 0x46, 0x46, 0x00, 0x48, 0x48, 0x48, 0x00, 0x50, 0x50, 0x50, 0x00, 0x51, 0x51, 0x51, 0x00, 0x55, 0x55, 0x55, 0x00, 0x58, 0x58, 0x58, 0x00, 0x5a, 0x5a, 0x5a, 0x00, 0x5b, 0x5b, 0x5b, 0x00, 0x5c, 0x5c, 0x5c, 0x00, 0x5e, 0x5e, 0x5e, 0x00, 0x61, 0x61, 0x61, 0x00, 0x62, 0x60, 0x60, 0x00, 0x64, 0x64, 0x64, 0x00, 0x65, 0x65, 0x65, 0x00, 0x68, 0x68, 0x68, 0x00, 0x69, 0x69, 0x69, 0x00, 0x6a, 0x6a, 0x6a, 0x00, 0x6b, 0x6b, 0x6b, 0x00, 0x6c, 0x6c, 0x6c, 0x00, 0x6e, 0x6e, 0x6e, 0x00, 0x6f, 0x6f, 0x6f, 0x00, 0x70, 0x70, 0x70, 0x00, 0x71, 0x71, 0x71, 0x00, 0x72, 0x72, 0x72, 0x00, 0x73, 0x73, 0x73, 0x00, 0x74, 0x74, 0x74, 0x00, 0x75, 0x75, 0x75, 0x00, 0x77, 0x77, 0x77, 0x00, 0x78, 0x78, 0x78, 0x00, 0x79, 0x79, 0x79, 0x00, 0x7c, 0x7c, 0x7d, 0x00, 0x7d, 0x7d, 0x7d, 0x00, 0x7e, 0x7e, 0x7e, 0x00, 0x7f, 0x7f, 0x7f, 0x00, 0x81, 0x81, 0x81, 0x00, 0x82, 0x82, 0x82, 0x00, 0x90, 0x90, 0x90, 0x00, 0x95, 0x95, 0x95, 0x00, 0x96, 0x96, 0x97, 0x00, 0x97, 0x97, 0x97, 0x00, 0x97, 0x97, 0x98, 0x00, 0x98, 0x98, 0x98, 0x00, 0x99, 0x9a, 0x9a, 0x00, 0x9a, 0x9a, 0x9a, 0x00, 0x9b, 0x9c, 0x9d, 0x00, 0x9e, 0x9f, 0x9f, 0x00, 0xa0, 0xa1, 0xa1, 0x00, 0xa1, 0xa1, 0xa2, 0x00, 0xa1, 0xa2, 0xa2, 0x00, 0xa3, 0xa5, 0xa5, 0x00, 0xa4, 0xa4, 0xa5, 0x00, 0xa7, 0xa8, 0xa9, 0x00, 0xa9, 0xa9, 0xaa, 0x00, 0xa9, 0xaa, 0xaa, 0x00, 0xb0, 0xb3, 0xb4, 0x00, 0xb2, 0xb5, 0xb6, 0x00, 0xb3, 0xb5, 0xb6, 0x00, 0xb7, 0xb7, 0xb8, 0x00, 0xb8, 0xb9, 0xb9, 0x00, 0xba, 0xbb, 0xbc, 0x00, 0xbc, 0xbd, 0xbe, 0x00, 0xbd, 0xbe, 0xc0, 0x00, 0xbd, 0xc0, 0xc2, 0x00, 0xbe, 0xc0, 0xc1, 0x00, 0xbf, 0xc2, 0xc4, 0x00, 0xc0, 0xc1, 0xc1, 0x00, 0xc6, 0xc8, 0xc9, 0x00, 0xc8, 0xc9, 0xcb, 0x00, 0xc9, 0xc9, 0xc9, 0x00, 0xc8, 0xca, 0xca, 0x00, 0xca, 0xca, 0xca, 0x00, 0xca, 0xcb, 0xcc, 0x00, 0xc9, 0xcc, 0xce, 0x00, 0xcb, 0xcc, 0xcc, 0x00, 0xcc, 0xcc, 0xcc, 0x00, 0xc9, 0xcd, 0xd0, 0x00, 0xca, 0xcd, 0xd0, 0x00, 0xca, 0xce, 0xd0, 0x00, 0xcd, 0xce, 0xd0, 0x00, 0xcf, 0xcf, 0xd0, 0x00, 0xce, 0xd0, 0xd1, 0x00, 0xcf, 0xd0, 0xd1, 0x00, 0xd0, 0xd1, 0xd2, 0x00, 0xd3, 0xd3, 0xd3, 0x00, 0xd0, 0xd2, 0xd4, 0x00, 0xd1, 0xd2, 0xd4, 0x00, 0xd2, 0xd5, 0xd5, 0x00, 0xd4, 0xd4, 0xd4, 0x00, 0xd2, 0xd6, 0xda, 0x00, 0xd5, 0xd6, 0xd8, 0x00, 0xd6, 0xd8, 0xd9, 0x00, 0xd7, 0xd8, 0xd9, 0x00, 0xd4, 0xd8, 0xdc, 0x00, 0xd6, 0xdb, 0xde, 0x00, 0xd7, 0xda, 0xdd, 0x00, 0xd9, 0xda, 0xdb, 0x00, 0xd9, 0xdc, 0xde, 0x00, 0xdc, 0xdd, 0xdf, 0x00, 0xdd, 0xdd, 0xdd, 0x00, 0xde, 0xdf, 0xde, 0x00, 0xd9, 0xde, 0xe1, 0x00, 0xdb, 0xe0, 0xe3, 0x00, 0xde, 0xe2, 0xe5, 0x00, 0xe0, 0xe0, 0xe0, 0x00, 0xe2, 0xe2, 0xe2, 0x00, 0xe0, 0xe2, 0xe4, 0x00, 0xe2, 0xe4, 0xe7, 0x00, 0xe2, 0xe6, 0xe7, 0x00, 0xe4, 0xe6, 0xe6, 0x00, 0xe4, 0xe7, 0xe9, 0x00, 0xe5, 0xe7, 0xe9, 0x00, 0xe7, 0xea, 0xeb, 0x00, 0xe7, 0xea, 0xec, 0x00, 0xe8, 0xe9, 0xe9, 0x00, 0xe9, 0xeb, 0xeb, 0x00, 0xea, 0xea, 0xeb, 0x00, 0xe8, 0xeb, 0xed, 0x00, 0xe9, 0xec, 0xed, 0x00, 0xea, 0xec, 0xee, 0x00, 0xeb, 0xec, 0xee, 0x00, 0xec, 0xed, 0xee, 0x00, 0xec, 0xee, 0xee, 0x00, 0xed, 0xee, 0xef, 0x00, 0xec, 0xee, 0xf0, 0x00, 0xed, 0xef, 0xf1, 0x00, 0xee, 0xf0, 0xf2, 0x00, 0xef, 0xf1, 0xf2, 0x00, 0xf0, 0xf2, 0xf3, 0x00, 0xf1, 0xf3, 0xf3, 0x00, 0xf1, 0xf3, 0xf4, 0x00, 0xf2, 0xf3, 0xf5, 0x00, 0xf2, 0xf4, 0xf5, 0x00, 0xf3, 0xf4, 0xf5, 0x00, 0xf4, 0xf5, 0xf5, 0x00, 0xf6, 0xf6, 0xf6, 0x00, 0xf5, 0xf7, 0xf8, 0x00, 0xf8, 0xf9, 0xf9, 0x00, 0xfa, 0xfb, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x12, 0x15, 0x11, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x20, 0x2e, 0x4b, 0x5c, 0x58, 0x5c, 0x49, 0x2e, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x2c, 0x4e, 0x56, 0x3e, 0x39, 0x37, 0x3a, 0x3f, 0x54, 0x49, 0x2c, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x35, 0x59, 0x40, 0x38, 0x37, 0x37, 0x36, 0x37, 0x37, 0x38, 0x41, 0x56, 0x33, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x12, 0x2d, 0x5b, 0x42, 0x3d, 0x3b, 0x3c, 0x3c, 0x47, 0x3c, 0x3c, 0x3b, 0x3d, 0x44, 0x56, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x23, 0x53, 0x4d, 0x45, 0x45, 0x43, 0x45, 0x66, 0x78, 0x66, 0x45, 0x43, 0x45, 0x45, 0x51, 0x48, 0x28, 0x00, 0x00, 0x00, 0x31, 0x65, 0x52, 0x50, 0x50, 0x50, 0x6e, 0x7f, 0x07, 0x7f, 0x6e, 0x50, 0x50, 0x50, 0x52, 0x63, 0x2e, 0x00, 0x00, 0x15, 0x4a, 0x63, 0x5d, 0x61, 0x61, 0x73, 0x87, 0x07, 0x07, 0x07, 0x81, 0x73, 0x61, 0x61, 0x5d, 0x63, 0x46, 0x1c, 0x00, 0x16, 0x5e, 0x62, 0x61, 0x62, 0x79, 0x80, 0x07, 0x07, 0x07, 0x07, 0x07, 0x80, 0x79, 0x62, 0x61, 0x62, 0x55, 0x1b, 0x00, 0x17, 0x5f, 0x62, 0x62, 0x75, 0x85, 0x07, 0x07, 0x07, 0x85, 0x07, 0x07, 0x07, 0x85, 0x75, 0x62, 0x62, 0x5a, 0x17, 0x00, 0x18, 0x60, 0x6a, 0x69, 0x84, 0x07, 0x07, 0x07, 0x84, 0x7a, 0x84, 0x07, 0x07, 0x07, 0x84, 0x69, 0x6a, 0x57, 0x18, 0x00, 0x19, 0x4c, 0x70, 0x6b, 0x8c, 0x07, 0x07, 0x8c, 0x82, 0x6b, 0x82, 0x8c, 0x07, 0x07, 0x88, 0x6b, 0x6f, 0x44, 0x1f, 0x00, 0x1a, 0x32, 0x74, 0x72, 0x89, 0x8b, 0x83, 0x83, 0x72, 0x72, 0x72, 0x83, 0x89, 0x89, 0x83, 0x72, 0x73, 0x2a, 0x00, 0x00, 0x00, 0x27, 0x64, 0x7e, 0x80, 0x80, 0x7b, 0x80, 0x80, 0x7b, 0x80, 0x80, 0x7b, 0x80, 0x80, 0x7c, 0x56, 0x26, 0x00, 0x00, 0x00, 0x1c, 0x2f, 0x77, 0x86, 0x86, 0x83, 0x86, 0x86, 0x83, 0x86, 0x86, 0x83, 0x86, 0x86, 0x71, 0x29, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x25, 0x34, 0x7d, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x76, 0x2c, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x2b, 0x67, 0x8b, 0x8e, 0x8e, 0x8d, 0x8e, 0x8e, 0x8a, 0x60, 0x29, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x27, 0x30, 0x4f, 0x6c, 0x6d, 0x68, 0x49, 0x2a, 0x27, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x21, 0x1e, 0x1f, 0x1c, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x00, 0xfe, 0x0f, 0xf0, 0x00, 0xf0, 0x03, 0xf0, 0x00, 0xe0, 0x00, 0xf0, 0x00, 0xc0, 0x00, 0x70, 0x00, 0x80, 0x00, 0x70, 0x00, 0x80, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0xc0, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xf0, 0x00, 0xf0, 0x01, 0xf0, 0x00, 0xfc, 0x0f, 0xf0, 0x00, 0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x07, 0x80, 0x80, 0x80, 0x0e, 0x80, 0x80, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x2a, 0x80, 0x80, 0x80, 0x9a, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0x9a, 0x80, 0x80, 0x80, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x0e, 0x80, 0x80, 0x80, 0xac, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xa4, 0x80, 0x80, 0x80, 0x42, 0x80, 0x80, 0x80, 0x18, 0x80, 0x80, 0x80, 0x0e, 0x80, 0x80, 0x80, 0x18, 0x80, 0x80, 0x80, 0x42, 0x80, 0x80, 0x80, 0xa4, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xac, 0x80, 0x80, 0x80, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x1f, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0xb9, 0x80, 0x80, 0x80, 0x18, 0xcf, 0xd0, 0xd1, 0xff, 0xb7, 0xb7, 0xb8, 0xff, 0xa9, 0xa9, 0xaa, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa9, 0xaa, 0xaa, 0xff, 0xb8, 0xb9, 0xb9, 0xff, 0xcf, 0xcf, 0xd0, 0xff, 0x80, 0x80, 0x80, 0x18, 0x80, 0x80, 0x80, 0xb9, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x0e, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0x90, 0xd0, 0xd2, 0xd4, 0xff, 0xba, 0xbb, 0xbc, 0xff, 0xa7, 0xa8, 0xa9, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa3, 0xa5, 0xa5, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa4, 0xa4, 0xa5, 0xff, 0xa7, 0xa8, 0xa9, 0xff, 0xbc, 0xbd, 0xbe, 0xff, 0xcf, 0xd0, 0xd1, 0xff, 0x80, 0x80, 0x80, 0x90, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0xac, 0x80, 0x80, 0x80, 0xb9, 0xd2, 0xd5, 0xd5, 0xff, 0xbd, 0xbe, 0xc0, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xb0, 0xb3, 0xb4, 0xff, 0xb2, 0xb5, 0xb6, 0xff, 0xb2, 0xb5, 0xb6, 0xff, 0xc6, 0xc8, 0xc9, 0xff, 0xb2, 0xb5, 0xb6, 0xff, 0xb2, 0xb5, 0xb6, 0xff, 0xb0, 0xb3, 0xb4, 0xff, 0xb3, 0xb5, 0xb6, 0xff, 0xbe, 0xc0, 0xc1, 0xff, 0xcf, 0xd0, 0xd1, 0xff, 0x80, 0x80, 0x80, 0xb9, 0x80, 0x80, 0x80, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x2a, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x18, 0xc9, 0xcc, 0xce, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbd, 0xc0, 0xc2, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xdc, 0xdd, 0xdf, 0xff, 0xea, 0xea, 0xeb, 0xff, 0xdc, 0xdd, 0xdf, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbd, 0xc0, 0xc2, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xbf, 0xc2, 0xc4, 0xff, 0xca, 0xcd, 0xd0, 0xff, 0x80, 0x80, 0x80, 0x18, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x9a, 0x80, 0x80, 0x80, 0xa4, 0xd9, 0xdc, 0xde, 0xff, 0xca, 0xce, 0xd0, 0xff, 0xc9, 0xcd, 0xd0, 0xff, 0xc9, 0xcd, 0xd0, 0xff, 0xc9, 0xcd, 0xd0, 0xff, 0xe0, 0xe2, 0xe4, 0xff, 0xed, 0xee, 0xef, 0xff, 0x50, 0x50, 0x50, 0xff, 0xed, 0xee, 0xef, 0xff, 0xe0, 0xe2, 0xe4, 0xff, 0xc9, 0xcd, 0xd0, 0xff, 0xc9, 0xcd, 0xd0, 0xff, 0xc9, 0xcd, 0xd0, 0xff, 0xca, 0xce, 0xd0, 0xff, 0xd7, 0xda, 0xdd, 0xff, 0x80, 0x80, 0x80, 0xa4, 0x80, 0x80, 0x80, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0x42, 0xd7, 0xda, 0xdd, 0xff, 0xd2, 0xd6, 0xda, 0xff, 0xd4, 0xd8, 0xdc, 0xff, 0xd4, 0xd8, 0xdc, 0xff, 0xe5, 0xe7, 0xe9, 0xff, 0xf2, 0xf3, 0xf5, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0xed, 0xef, 0xf1, 0xff, 0xe5, 0xe7, 0xe9, 0xff, 0xd4, 0xd8, 0xdc, 0xff, 0xd4, 0xd8, 0xdc, 0xff, 0xd2, 0xd6, 0xda, 0xff, 0xd7, 0xda, 0xdd, 0xff, 0x80, 0x80, 0x80, 0x42, 0x80, 0x80, 0x80, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x18, 0xd6, 0xdb, 0xde, 0xff, 0xd4, 0xd8, 0xdc, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0xe8, 0xeb, 0xed, 0xff, 0xec, 0xee, 0xf0, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xe8, 0xeb, 0xed, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0xd4, 0xd8, 0xdc, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0x80, 0x80, 0x80, 0x18, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x07, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x0e, 0xd6, 0xdb, 0xde, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xf1, 0xf3, 0xf3, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0xf1, 0xf3, 0xf3, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0xf1, 0xf3, 0xf3, 0xff, 0xe7, 0xea, 0xec, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0xd6, 0xdb, 0xde, 0xff, 0x80, 0x80, 0x80, 0x0e, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x0e, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x18, 0xdb, 0xe0, 0xe3, 0xff, 0xd9, 0xde, 0xe1, 0xff, 0xf0, 0xf2, 0xf3, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0xf0, 0xf2, 0xf3, 0xff, 0xe9, 0xec, 0xed, 0xff, 0xf0, 0xf2, 0xf3, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0xf0, 0xf2, 0xf3, 0xff, 0xd9, 0xde, 0xe1, 0xff, 0xdb, 0xe0, 0xe3, 0xff, 0x80, 0x80, 0x80, 0x18, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x07, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0x42, 0xe2, 0xe6, 0xe7, 0xff, 0xde, 0xe2, 0xe5, 0xff, 0xf5, 0xf7, 0xf8, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0xf5, 0xf7, 0xf8, 0xff, 0xee, 0xf0, 0xf2, 0xff, 0xde, 0xe2, 0xe5, 0xff, 0xee, 0xf0, 0xf2, 0xff, 0xf5, 0xf7, 0xf8, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0xf2, 0xf4, 0xf5, 0xff, 0xde, 0xe2, 0xe5, 0xff, 0xe2, 0xe4, 0xe7, 0xff, 0x80, 0x80, 0x80, 0x42, 0x80, 0x80, 0x80, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x9a, 0x80, 0x80, 0x80, 0xa4, 0xe7, 0xea, 0xeb, 0xff, 0xe4, 0xe7, 0xe9, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf6, 0xf6, 0xf6, 0xff, 0xef, 0xf1, 0xf2, 0xff, 0xef, 0xf1, 0xf2, 0xff, 0xe4, 0xe7, 0xe9, 0xff, 0xe4, 0xe7, 0xe9, 0xff, 0xe4, 0xe7, 0xe9, 0xff, 0xef, 0xf1, 0xf2, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xf3, 0xf4, 0xf5, 0xff, 0xef, 0xf1, 0xf2, 0xff, 0xe4, 0xe7, 0xe9, 0xff, 0xe5, 0xe7, 0xe9, 0xff, 0x80, 0x80, 0x80, 0xa4, 0x80, 0x80, 0x80, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x2a, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x18, 0xec, 0xee, 0xee, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xea, 0xec, 0xee, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xea, 0xec, 0xee, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xea, 0xec, 0xee, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xec, 0xee, 0xf0, 0xff, 0xeb, 0xec, 0xee, 0xff, 0x80, 0x80, 0x80, 0x18, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0xac, 0x80, 0x80, 0x80, 0xb9, 0xe9, 0xeb, 0xeb, 0xff, 0xf1, 0xf3, 0xf4, 0xff, 0xf1, 0xf3, 0xf4, 0xff, 0xef, 0xf1, 0xf2, 0xff, 0xf1, 0xf3, 0xf4, 0xff, 0xf1, 0xf3, 0xf4, 0xff, 0xef, 0xf1, 0xf2, 0xff, 0xf1, 0xf3, 0xf4, 0xff, 0xf1, 0xf3, 0xf4, 0xff, 0xef, 0xf1, 0xf2, 0xff, 0xf1, 0xf3, 0xf4, 0xff, 0xf1, 0xf3, 0xf4, 0xff, 0xe4, 0xe6, 0xe6, 0xff, 0x80, 0x80, 0x80, 0xb9, 0x80, 0x80, 0x80, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x0e, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0x90, 0xec, 0xed, 0xee, 0xff, 0xf4, 0xf5, 0xf5, 0xff, 0xf4, 0xf5, 0xf5, 0xff, 0xf4, 0xf5, 0xf5, 0xff, 0xf4, 0xf5, 0xf5, 0xff, 0xf4, 0xf5, 0xf5, 0xff, 0xf4, 0xf5, 0xf5, 0xff, 0xf4, 0xf5, 0xf5, 0xff, 0xf4, 0xf5, 0xf5, 0xff, 0xf4, 0xf5, 0xf5, 0xff, 0xe8, 0xe9, 0xe9, 0xff, 0x80, 0x80, 0x80, 0x90, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x1f, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0xb9, 0x80, 0x80, 0x80, 0x18, 0xf6, 0xf6, 0xf6, 0xff, 0xfa, 0xfb, 0xfb, 0xff, 0xfa, 0xfb, 0xfb, 0xff, 0xf8, 0xf9, 0xf9, 0xff, 0xfa, 0xfb, 0xfb, 0xff, 0xfa, 0xfb, 0xfb, 0xff, 0xf4, 0xf5, 0xf5, 0xff, 0x80, 0x80, 0x80, 0x18, 0x80, 0x80, 0x80, 0xb9, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x0e, 0x80, 0x80, 0x80, 0xac, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xa4, 0x80, 0x80, 0x80, 0x42, 0x80, 0x80, 0x80, 0x18, 0x80, 0x80, 0x80, 0x0e, 0x80, 0x80, 0x80, 0x18, 0x80, 0x80, 0x80, 0x42, 0x80, 0x80, 0x80, 0xa4, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xac, 0x80, 0x80, 0x80, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x2a, 0x80, 0x80, 0x80, 0x9a, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xf7, 0x80, 0x80, 0x80, 0x9a, 0x80, 0x80, 0x80, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xf0, 0x00, 0xf8, 0x03, 0xf0, 0x00, 0xe0, 0x00, 0xf0, 0x00, 0xc0, 0x00, 0x70, 0x00, 0x80, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0xc0, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xf0, 0x00, 0xf8, 0x03, 0xf0, 0x00 }; static const unsigned int TaskDlgChevronMore_ico_len = 3302; ================================================ FILE: thirdparty/taskdialog98/maindlg.h ================================================ // maindlg.h : interface of the CMainDlg class // ///////////////////////////////////////////////////////////////////////////// #if !defined(AFX_MAINDLG_H__DA2FA2FB_17F8_4507_9DAE_D279641E8337__INCLUDED_) #define AFX_MAINDLG_H__DA2FA2FB_17F8_4507_9DAE_D279641E8337__INCLUDED_ #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 #if _WIN32_WINNT < 0x0600 inline int AtlTaskDialog(HWND hWndParent, ATL::_U_STRINGorID WindowTitle, ATL::_U_STRINGorID MainInstructionText, ATL::_U_STRINGorID ContentText, TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons = 0U, ATL::_U_STRINGorID Icon = (LPCTSTR)NULL) { int nRet = -1; typedef HRESULT (STDAPICALLTYPE *PFN_TaskDialog)(HWND hwndParent, HINSTANCE hInstance, PCWSTR pszWindowTitle, PCWSTR pszMainInstruction, PCWSTR pszContent, TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons, PCWSTR pszIcon, int* pnButton); HMODULE m_hCommCtrlDLL = ::LoadLibrary(_T("comctl32.dll")); if(m_hCommCtrlDLL != NULL) { PFN_TaskDialog pfnTaskDialog = (PFN_TaskDialog)::GetProcAddress(m_hCommCtrlDLL, "TaskDialog"); if(pfnTaskDialog != NULL) { USES_CONVERSION; HRESULT hRet = pfnTaskDialog(hWndParent, ModuleHelper::GetResourceInstance(), IS_INTRESOURCE(WindowTitle.m_lpstr) ? (LPCWSTR) WindowTitle.m_lpstr : T2CW(WindowTitle.m_lpstr), IS_INTRESOURCE(MainInstructionText.m_lpstr) ? (LPCWSTR) MainInstructionText.m_lpstr : T2CW(MainInstructionText.m_lpstr), IS_INTRESOURCE(ContentText.m_lpstr) ? (LPCWSTR) ContentText.m_lpstr : T2CW(ContentText.m_lpstr), dwCommonButtons, IS_INTRESOURCE(Icon.m_lpstr) ? (LPCWSTR) Icon.m_lpstr : T2CW(Icon.m_lpstr), &nRet); ATLVERIFY(SUCCEEDED(hRet)); } ::FreeLibrary(m_hCommCtrlDLL); } return nRet; } #endif // _WIN32_WINNT < 0x0600 inline int AtlTaskDialogIndirect(TASKDIALOGCONFIG* pTask, int* pnButton = NULL, int* pnRadioButton = NULL, BOOL* pfVerificationFlagChecked = NULL) { // This allows apps to run on older versions of Windows typedef HRESULT (STDAPICALLTYPE *PFN_TaskDialogIndirect)(const TASKDIALOGCONFIG* pTaskConfig, int* pnButton, int* pnRadioButton, BOOL* pfVerificationFlagChecked); HRESULT hRet = E_UNEXPECTED; HMODULE m_hCommCtrlDLL = ::LoadLibrary(_T("comctl32.dll")); if(m_hCommCtrlDLL != NULL) { PFN_TaskDialogIndirect pfnTaskDialogIndirect = (PFN_TaskDialogIndirect)::GetProcAddress(m_hCommCtrlDLL, "TaskDialogIndirect"); if(pfnTaskDialogIndirect != NULL) hRet = pfnTaskDialogIndirect(pTask, pnButton, pnRadioButton, pfVerificationFlagChecked); ::FreeLibrary(m_hCommCtrlDLL); } return hRet; } class CMainDlg : public CDialogImpl { public: enum { IDD = IDD_MAINDLG }; BEGIN_MSG_MAP(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout) COMMAND_ID_HANDLER(IDOK, OnClose) COMMAND_ID_HANDLER(IDCANCEL, OnClose) COMMAND_ID_HANDLER(IDC_BUTTON1, OnTest1) COMMAND_ID_HANDLER(IDC_BUTTON2, OnTest2) COMMAND_ID_HANDLER(IDC_BUTTON3, OnTest3) COMMAND_ID_HANDLER(IDC_BUTTON4, OnTest4) COMMAND_ID_HANDLER(IDC_BUTTON5, OnTest5) COMMAND_ID_HANDLER(IDC_BUTTON6, OnTest6) COMMAND_ID_HANDLER(IDC_BUTTON7, OnTest7) COMMAND_ID_HANDLER(IDC_BUTTON8, OnTest8) COMMAND_ID_HANDLER(IDC_BUTTON9, OnTest9) COMMAND_ID_HANDLER(IDC_BUTTON10, OnTest10) COMMAND_ID_HANDLER(IDC_BUTTON11, OnTest11) COMMAND_ID_HANDLER(IDC_BUTTON12, OnTest12) END_MSG_MAP() LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { CenterWindow(); HICON hIcon = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME), IMAGE_ICON, ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR); SetIcon(hIcon, TRUE); HICON hIconSmall = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME), IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR); SetIcon(hIconSmall, FALSE); return TRUE; } LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { CSimpleDialog dlg; dlg.DoModal(); return 0; } LRESULT OnClose(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { EndDialog(wID); return 0; } LRESULT OnTest1(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { LPCTSTR pstrWindowTitle = _T("Window Title"); LPCTSTR pstrInstructionsText = _T("Test Case 1"); LPCTSTR pstrContentText1 = _T("This is Bjarke's Task Dialog"); LPCTSTR pstrContentText2 = _T("This is the Windows Vista Task Dialog"); int iRes = 0; Task98Dialog(m_hWnd, _Module.GetResourceInstance(), pstrWindowTitle,pstrInstructionsText, pstrContentText1, TDCBF_YES_BUTTON | TDCBF_OK_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_CLOSE_BUTTON | TDCBF_RETRY_BUTTON, MAKEINTRESOURCE(IDR_MAINFRAME), &iRes); AtlTaskDialog(m_hWnd, pstrWindowTitle, pstrInstructionsText, pstrContentText2, TDCBF_YES_BUTTON | TDCBF_OK_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_CLOSE_BUTTON | TDCBF_RETRY_BUTTON, MAKEINTRESOURCE(IDR_MAINFRAME)); return 0; } LRESULT OnTest2(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { LPCTSTR pstrWindowTitle = _T("Window Title"); LPCTSTR pstrInstructionsText = _T("Click on the button below"); LPCTSTR pstrContentText = _T("Choose a button. Do the right thing and read this multi-line entry. Can there be any more?? I don't know. Maybe there is."); int iRes = 0; Task98Dialog(m_hWnd, _Module.GetResourceInstance(), pstrWindowTitle,pstrInstructionsText, pstrContentText, TDCBF_OK_BUTTON, MAKEINTRESOURCE(IDR_MAINFRAME), &iRes); AtlTaskDialog(m_hWnd, pstrWindowTitle, pstrInstructionsText, pstrContentText, TDCBF_OK_BUTTON, MAKEINTRESOURCE(IDR_MAINFRAME)); return 0; } LRESULT OnTest3(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { TASKDIALOGCONFIG cfg = { 0 }; cfg.cbSize = sizeof(cfg); cfg.hInstance = _Module.GetResourceInstance(); cfg.pszWindowTitle = L"Window Title"; cfg.pszMainIcon = MAKEINTRESOURCEW(IDR_MAINFRAME); cfg.pszContent = L"This is the contents"; cfg.dwCommonButtons = TDCBF_YES_BUTTON | TDCBF_OK_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_CLOSE_BUTTON | TDCBF_RETRY_BUTTON; TASKDIALOG_BUTTON buttons[] = { { 100, L"Button #1" }, { 101, L"Button #2\nText Below" }, }; cfg.pButtons = buttons; cfg.cButtons = 2; cfg.nDefaultButton = 101; int iRes, iRadio, iVerify; Task98DialogIndirect(&cfg, &iRes, &iRadio, &iVerify); AtlTaskDialogIndirect(&cfg, &iRes, &iRadio, &iVerify); return 0; } LRESULT OnTest4(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { TASKDIALOGCONFIG cfg = { 0 }; cfg.cbSize = sizeof(cfg); cfg.hInstance = _Module.GetResourceInstance(); cfg.pszWindowTitle = L"Window Title"; cfg.pszMainIcon = TD_WARNING_ICON; cfg.pszMainInstruction = L"This is a test"; cfg.pszContent = MAKEINTRESOURCEW(IDS_TASKDLG_CANCEL); cfg.dwCommonButtons = TDCBF_YES_BUTTON | TDCBF_OK_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_CLOSE_BUTTON | TDCBF_RETRY_BUTTON; TASKDIALOG_BUTTON buttons[] = { { 100, L"Button #1" }, { 101, L"Button #2\nText Below" }, }; cfg.pButtons = buttons; cfg.cButtons = 2; cfg.nDefaultButton = 101; TASKDIALOG_BUTTON radios[] = { { 200, L"Radio #1" }, { 201, L"Radio #2\nText Below" }, { 202, L"Radio #3" }, }; cfg.pRadioButtons = radios; cfg.nDefaultRadioButton = 202; cfg.cRadioButtons = 3; int iRes, iRadio, iVerify; Task98DialogIndirect(&cfg, &iRes, &iRadio, &iVerify); AtlTaskDialogIndirect(&cfg, &iRes, &iRadio, &iVerify); return 0; } LRESULT OnTest5(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { TASKDIALOGCONFIG cfg = { 0 }; cfg.cbSize = sizeof(cfg); cfg.hwndParent = m_hWnd; cfg.hInstance = _Module.GetResourceInstance(); cfg.pszWindowTitle = L"Window Title"; cfg.pszMainIcon = TD_ERROR_ICON; cfg.pszMainInstruction = L"This is another test\nThere are 3 lines\nof instruction text here."; cfg.pszContent = L"This is the contents of yet another test. Testing the verifaction checkbox below."; cfg.dwCommonButtons = TDCBF_YES_BUTTON | TDCBF_OK_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_CLOSE_BUTTON | TDCBF_RETRY_BUTTON; TASKDIALOG_BUTTON buttons[] = { { 100, L"Button #1" }, { 101, L"Button #2\nText Below" }, }; cfg.pButtons = buttons; cfg.cButtons = 2; cfg.nDefaultButton = IDNO; cfg.pszVerificationText = L"Verifcation text. This is a very long text, so maybe it will wrap."; cfg.dwFlags = TDF_POSITION_RELATIVE_TO_WINDOW; int iRes, iRadio, iVerify; Task98DialogIndirect(&cfg, &iRes, &iRadio, &iVerify); AtlTaskDialogIndirect(&cfg, &iRes, &iRadio, &iVerify); return 0; } LRESULT OnTest6(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { TASKDIALOGCONFIG cfg = { 0 }; cfg.cbSize = sizeof(cfg); cfg.hwndParent = m_hWnd; cfg.hInstance = _Module.GetResourceInstance(); cfg.pszWindowTitle = L"Window Title"; cfg.pszMainIcon = TD_ERROR_ICON; cfg.pszMainInstruction = L"This is another test"; cfg.pszContent = L"This is the contents of yet another test. Testing the verifaction checkbox below."; cfg.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON; TASKDIALOG_BUTTON buttons[] = { { 100, L"Button #1" }, { 101, L"Button #2\nText Below" }, { 102, L"Button #3\nThis is a longer line of text which nothing really interesting in it. Lets see how long it can be.\nLine 2" }, { 103, L"Button #4" }, }; cfg.pButtons = buttons; cfg.cButtons = 4; cfg.nDefaultButton = 101; cfg.pszVerificationText = L"Verifcation text."; cfg.dwFlags = TDF_POSITION_RELATIVE_TO_WINDOW | TDF_USE_COMMAND_LINKS; int iRes, iRadio, iVerify; Task98DialogIndirect(&cfg, &iRes, &iRadio, &iVerify); AtlTaskDialogIndirect(&cfg, &iRes, &iRadio, &iVerify); return 0; } LRESULT OnTest7(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { TASKDIALOGCONFIG cfg = { 0 }; cfg.cbSize = sizeof(cfg); cfg.hwndParent = m_hWnd; cfg.hInstance = _Module.GetResourceInstance(); cfg.pszWindowTitle = L"Window Title"; cfg.pszMainIcon = TD_ERROR_ICON; cfg.pszMainInstruction = L"This is another test. The Main Instruction label can also be rather long and span multiple lines."; cfg.pszContent = L"This is the contents of yet another long label. Testing the verifaction checkbox below. This line is longer than the others."; cfg.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON; TASKDIALOG_BUTTON buttons[] = { { 100, L"Button #1" }, { 101, L"Button #2" }, }; cfg.pButtons = buttons; cfg.cButtons = 2; cfg.nDefaultButton = 101; TASKDIALOG_BUTTON radios[] = { { 200, L"Radio #1" }, { 201, L"Radio #2\nText Below" }, { 202, L"Radio #3. This is a rather long radio button text label which will span multiple lines." }, }; cfg.pRadioButtons = radios; cfg.nDefaultRadioButton = 202; cfg.cRadioButtons = 3; cfg.pszVerificationText = L"Verifcation text."; cfg.pszFooter = L"Footer Text"; cfg.pszFooterIcon = MAKEINTRESOURCEW(IDR_MAINFRAME); cfg.pszExpandedControlText = NULL; cfg.pszCollapsedControlText = NULL; cfg.pszExpandedInformation = L"Expanded information text here..."; cfg.dwFlags = TDF_POSITION_RELATIVE_TO_WINDOW | TDF_USE_COMMAND_LINKS; int iRes, iRadio, iVerify; Task98DialogIndirect(&cfg, &iRes, &iRadio, &iVerify); AtlTaskDialogIndirect(&cfg, &iRes, &iRadio, &iVerify); return 0; } LRESULT OnTest8(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { TASKDIALOGCONFIG cfg = { 0 }; cfg.cbSize = sizeof(cfg); cfg.hwndParent = m_hWnd; cfg.hInstance = _Module.GetResourceInstance(); cfg.pszWindowTitle = L"Window Title"; cfg.pszMainIcon = TD_ERROR_ICON; cfg.pszMainInstruction = L"This is another test"; cfg.pszContent = L"This is the contents of yet another test with a link."; cfg.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON; TASKDIALOG_BUTTON buttons[] = { { 100, L"Button Label for Control #1" }, { 101, L"Button Label for Control #2" }, }; cfg.pButtons = buttons; cfg.cButtons = 2; cfg.nDefaultButton = 101; TASKDIALOG_BUTTON radios[] = { { 200, L"Radio #1\nText Below Radio button #1" }, { 201, L"Radio #2\nText Below Radio button #2" }, { 202, L"Radio #3\nText Below Radio button #3" }, }; cfg.pRadioButtons = radios; cfg.cRadioButtons = 3; cfg.pszVerificationText = L"Verifcation text. This is a long text with\ntwo lines."; cfg.pszFooter = L"Footer Text with a link."; //cfg.hFooterIcon = ::LoadIcon(NULL, MAKEINTRESOURCE(IDI_WINLOGO)); cfg.hFooterIcon = (HICON) ::LoadImage(NULL, MAKEINTRESOURCE(IDI_ASTERISK), IMAGE_ICON, 16, 16, LR_LOADTRANSPARENT | LR_SHARED); cfg.pszExpandedControlText = L"Collapse Control Text\nWith an extra line. Wohoo."; cfg.pszCollapsedControlText = L"Expand Control Text"; cfg.pszExpandedInformation = L"Expanded information text here with a link."; cfg.dwFlags = TDF_POSITION_RELATIVE_TO_WINDOW | TDF_USE_COMMAND_LINKS_NO_ICON | TDF_EXPAND_FOOTER_AREA | TDF_USE_HICON_FOOTER | TDF_ENABLE_HYPERLINKS; int iRes, iRadio, iVerify; Task98DialogIndirect(&cfg, &iRes, &iRadio, &iVerify); AtlTaskDialogIndirect(&cfg, &iRes, &iRadio, &iVerify); return 0; } static HRESULT CALLBACK TaskDialogCallback9(HWND hWnd, UINT msg, WPARAM wParam, LPARAM /*lParam*/, LONG_PTR /*lpRefData*/) { switch( msg ) { case TDN_TIMER: ::SendMessage(hWnd, TDM_SET_PROGRESS_BAR_POS, wParam / 30, 0L); if( wParam / 30 >= 100 ) ::SendMessage(hWnd, TDM_CLICK_BUTTON, IDOK, 0L); break; } return 0; } LRESULT OnTest9(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { TASKDIALOGCONFIG cfg = { 0 }; cfg.cbSize = sizeof(cfg); cfg.hwndParent = m_hWnd; cfg.hInstance = _Module.GetResourceInstance(); cfg.pszWindowTitle = L"Window Title"; cfg.pszMainIcon = TD_ERROR_ICON; cfg.pszMainInstruction = L"This is Progress Bar test"; cfg.pszContent = L"This is the content text above the Progress Bar."; cfg.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON; cfg.nDefaultButton = IDOK; cfg.pfCallback = TaskDialogCallback9; cfg.dwFlags = TDF_POSITION_RELATIVE_TO_WINDOW | TDF_SHOW_PROGRESS_BAR | TDF_CALLBACK_TIMER; int iRes, iRadio, iVerify; Task98DialogIndirect(&cfg, &iRes, &iRadio, &iVerify); AtlTaskDialogIndirect(&cfg, &iRes, &iRadio, &iVerify); return 0; } static HRESULT CALLBACK TaskDialogCallback10(HWND hWnd, UINT msg, WPARAM wParam, LPARAM /*lParam*/, LONG_PTR /*lpRefData*/) { switch( msg ) { case TDN_DIALOG_CONSTRUCTED: ::SendMessage(hWnd, TDM_ENABLE_BUTTON, 101, 0L); ::SendMessage(hWnd, TDM_ENABLE_BUTTON, IDCANCEL, 0L); ::SendMessage(hWnd, TDM_ENABLE_RADIO_BUTTON, 201, 0L); ::SendMessage(hWnd, TDM_SET_MARQUEE_PROGRESS_BAR, 1, 0L); ::SendMessage(hWnd, TDM_SET_PROGRESS_BAR_MARQUEE, 1, 30L); break; case TDN_BUTTON_CLICKED: if( wParam == 100 ) { ::SendMessage(hWnd, TDM_CLICK_RADIO_BUTTON, 202, 0L); return S_FALSE; } return S_OK; } return 0; } LRESULT OnTest10(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { TASKDIALOGCONFIG cfg = { 0 }; cfg.cbSize = sizeof(cfg); cfg.hwndParent = m_hWnd; cfg.hInstance = _Module.GetResourceInstance(); cfg.pszWindowTitle = L"Window Title"; cfg.hMainIcon = ::LoadIcon(NULL, MAKEINTRESOURCE(IDI_WINLOGO)); cfg.pszMainInstruction = L"This is Progress Bar test"; cfg.pszContent = L"This is the content text above the Progress Bar."; cfg.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON; cfg.pfCallback = TaskDialogCallback10; TASKDIALOG_BUTTON buttons[] = { { 100, L"Not clickable" }, { 101, L"Disabled" }, }; cfg.pButtons = buttons; cfg.cButtons = 2; cfg.nDefaultButton = 101; TASKDIALOG_BUTTON radios[] = { { 200, L"Radio #1\nText Below Radio button #1. This button as a very very long text line which should wrap the text to several lines I hope. It will test the sizing of the dialog." }, { 201, L"Radio #2\nText Below Radio button #2" }, { 202, L"Radio #3\nText Below Radio button #3. This is another long line which will wrap to the second line only." }, }; cfg.pRadioButtons = radios; cfg.cRadioButtons = 3; cfg.dwFlags = TDF_POSITION_RELATIVE_TO_WINDOW | TDF_SHOW_PROGRESS_BAR | TDF_USE_HICON_MAIN; int iRes, iRadio, iVerify; Task98DialogIndirect(&cfg, &iRes, &iRadio, &iVerify); AtlTaskDialogIndirect(&cfg, &iRes, &iRadio, &iVerify); return 0; } class CTask98Dialog11 : public CTask98DialogImpl { public: int DoModal(HWND hWnd = NULL) { m_cfg.hwndParent = hWnd; m_cfg.pszWindowTitle = L"Window Title"; m_cfg.hMainIcon = ::LoadIcon(NULL, MAKEINTRESOURCE(IDI_WINLOGO)); m_cfg.pszMainInstruction = L"This is Progress Bar test"; m_cfg.pszContent = L"This is the content text above the Progress Bar."; m_cfg.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON; m_cfg.nDefaultButton = IDOK; TASKDIALOG_BUTTON buttons[] = { { 100, L"Not clickable" }, { 101, L"Disabled" }, }; m_cfg.pButtons = buttons; m_cfg.cButtons = 2; m_cfg.nDefaultButton = 101; TASKDIALOG_BUTTON radios[] = { { 200, L"Radio #1\nText Below Radio button #1." }, { 201, L"Radio #2\nText Below Radio button #2." }, { 202, L"Radio #3\nText Below Radio button #3." }, }; m_cfg.pRadioButtons = radios; m_cfg.cRadioButtons = 3; m_cfg.dwFlags = TDF_POSITION_RELATIVE_TO_WINDOW | TDF_SHOW_PROGRESS_BAR | TDF_USE_HICON_MAIN | TDF_CALLBACK_TIMER; return CTask98DialogImpl::DoModal(hWnd); } void OnCreated() { EnableButton(IDCANCEL, FALSE); EnableButton(101, FALSE); EnableRadioButton(201, FALSE); ClickRadioButton(202); } BOOL OnTimer(UINT wTime) { SetProgressBarPos(wTime / 30); return FALSE; } BOOL OnButtonClicked(int nID) { if( nID == 100 ) return TRUE; return FALSE; } }; LRESULT OnTest11(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { CTask98Dialog11 dlg; dlg.DoModal(); return 0; } LRESULT OnTest12(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { TASKDIALOGCONFIG cfg = { 0 }; cfg.cbSize = sizeof(cfg); cfg.hInstance = _Module.GetResourceInstance(); cfg.pszWindowTitle = L"Window Title"; cfg.pszMainIcon = MAKEINTRESOURCEW(IDR_MAINFRAME); cfg.pszContent = L"This is the contents.\nhttp://www.viksoe.dk/code/testing_a_really.long.url.html?with=argument&that=makes&it&even=1&longer_than_the&screen=no&anditjustkeepgoingandgoing\nLine3\nLine4\nLine5"; cfg.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON; int iRes, iRadio, iVerify; Task98DialogIndirect(&cfg, &iRes, &iRadio, &iVerify); AtlTaskDialogIndirect(&cfg, &iRes, &iRadio, &iVerify); return 0; } }; ///////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_MAINDLG_H__DA2FA2FB_17F8_4507_9DAE_D279641E8337__INCLUDED_) ================================================ FILE: thirdparty/taskdialog98/res/TaskDialogTest.exe.manifest ================================================ TaskDialogTest Application ================================================ FILE: thirdparty/taskdialog98/res/make_icons_include.bat ================================================ xxd -i TaskDlgArrowHot.ico >> icons.h xxd -i TaskDlgArrowNormal.ico >> icons.h xxd -i TaskDlgChevronLess.ico >> icons.h xxd -i TaskDlgChevronMore.ico >> icons.h ================================================ FILE: thirdparty/taskdialog98/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by TaskDialogTest.rc // #define IDD_ABOUTBOX 100 #define IDR_MAINFRAME 128 #define IDD_MAINDLG 129 #define IDS_TASKDLG_CANCEL 133 #define IDC_BUTTON1 1000 #define IDC_BUTTON2 1001 #define IDC_BUTTON3 1002 #define IDC_BUTTON4 1003 #define IDC_BUTTON5 1004 #define IDC_BUTTON6 1005 #define IDC_BUTTON7 1006 #define IDC_BUTTON8 1007 #define IDC_BUTTON9 1008 #define IDC_BUTTON10 1009 #define IDC_BUTTON11 1010 #define IDC_BUTTON12 1011 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 205 #define _APS_NEXT_COMMAND_VALUE 32772 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: thirdparty/taskdialog98/stdafx.cpp ================================================ // stdafx.cpp : source file that includes just the standard includes // TaskDialogTest.pch will be the pre-compiled header // stdafx.obj will contain the pre-compiled type information #include "stdafx.h" #if (_ATL_VER < 0x0700) #include #endif //(_ATL_VER < 0x0700) ================================================ FILE: thirdparty/taskdialog98/stdafx.h ================================================ // stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, but // are changed infrequently // #if !defined(AFX_STDAFX_H__6C883435_4806_4F6B_B9EF_95D748291576__INCLUDED_) #define AFX_STDAFX_H__6C883435_4806_4F6B_B9EF_95D748291576__INCLUDED_ // Change these values to use different versions #define WINVER 0x0400 #define _WIN32_WINNT 0x0400 #define _WIN32_IE 0x0500 #define _RICHEDIT_VER 0x0100 #define _CRT_SECURE_NO_WARNINGS #define _CRT_NON_CONFORMING_SWPRINTFS #include #include extern CAppModule _Module; #include #include #include #include "TaskDialog.h" //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_STDAFX_H__6C883435_4806_4F6B_B9EF_95D748291576__INCLUDED_)