diff options
-rw-r--r-- | shell/Executable_senddoc.mk | 4 | ||||
-rw-r--r-- | shell/Library_smplmail.mk | 3 | ||||
-rw-r--r-- | shell/source/win32/simplemail/senddoc.cxx | 146 | ||||
-rw-r--r-- | shell/source/win32/simplemail/smplmailclient.cxx | 120 | ||||
-rw-r--r-- | shell/source/win32/simplemail/smplmailclient.hxx | 7 |
5 files changed, 259 insertions, 21 deletions
diff --git a/shell/Executable_senddoc.mk b/shell/Executable_senddoc.mk index aa8f57122636..f14b70e47bec 100644 --- a/shell/Executable_senddoc.mk +++ b/shell/Executable_senddoc.mk @@ -13,8 +13,12 @@ $(eval $(call gb_Executable_use_system_win32_libs,senddoc,\ kernel32 \ )) +$(eval $(call gb_Executable_use_sdk_api,senddoc)) + $(eval $(call gb_Executable_use_libraries,senddoc,\ + i18nlangtag \ sal \ + utl \ )) $(eval $(call gb_Executable_use_static_libraries,senddoc,\ diff --git a/shell/Library_smplmail.mk b/shell/Library_smplmail.mk index a6a82179fd7e..704f9f230915 100644 --- a/shell/Library_smplmail.mk +++ b/shell/Library_smplmail.mk @@ -18,7 +18,10 @@ $(eval $(call gb_Library_use_system_win32_libs,smplmail,\ $(eval $(call gb_Library_use_libraries,smplmail,\ cppu \ cppuhelper \ + i18nlangtag \ sal \ + tl \ + utl \ )) $(eval $(call gb_Library_use_static_libraries,smplmail,\ diff --git a/shell/source/win32/simplemail/senddoc.cxx b/shell/source/win32/simplemail/senddoc.cxx index cb25d7c57e0d..90b06e6953bb 100644 --- a/shell/source/win32/simplemail/senddoc.cxx +++ b/shell/source/win32/simplemail/senddoc.cxx @@ -52,7 +52,8 @@ namespace /* private */ StringList_t gTo; StringList_t gCc; StringList_t gBcc; - StringList_t gAttachments; + // Keep temp filepath and displayed name + std::vector<std::pair<std::string, std::string>> gAttachments; int gMapiFlags = 0; } @@ -120,13 +121,22 @@ void initAttachmentList(MapiAttachmentList_t* pMapiAttachmentList) { OSL_ASSERT(pMapiAttachmentList->empty()); - StringListIterator_t iter = gAttachments.begin(); - StringListIterator_t iter_end = gAttachments.end(); + auto iter = gAttachments.begin(); + auto iter_end = gAttachments.end(); for (/**/; iter != iter_end; ++iter) { MapiFileDesc mfd; ZeroMemory(&mfd, sizeof(mfd)); - mfd.lpszPathName = const_cast<char*>(iter->c_str()); + mfd.lpszPathName = const_cast<char*>(iter->first.c_str()); + // MapiFileDesc documentation (https://msdn.microsoft.com/en-us/library/hh707272) + // allows using here either nullptr, or a pointer to empty string. However, + // for Outlook 2013, we cannot use nullptr here, and must point to a (possibly + // empty) string: otherwise using MAPI_DIALOG_MODELESS results in MAPI_E_FAILURE. + // See http://peach.ease.lsoft.com/scripts/wa-PEACH.exe?A2=MAPI-L;d2bf3060.1604 + // Since C++11, c_str() must return a pointer to single null character when the + // string is empty, so we are OK here in case when there's no explicit file name + // passed + mfd.lpszFileName = const_cast<char*>(iter->second.c_str()); mfd.nPosition = sal::static_int_cast<ULONG>(-1); pMapiAttachmentList->push_back(mfd); } @@ -166,11 +176,12 @@ void initMapiMessage( pMapiMessage->lpOriginator = aMapiOriginator; pMapiMessage->lpRecips = aMapiRecipientList.size() ? &aMapiRecipientList[0] : nullptr; pMapiMessage->nRecipCount = aMapiRecipientList.size(); - pMapiMessage->lpFiles = &aMapiAttachmentList[0]; + if (!aMapiAttachmentList.empty()) + pMapiMessage->lpFiles = &aMapiAttachmentList[0]; pMapiMessage->nFileCount = aMapiAttachmentList.size(); } -const char* KnownParameter[] = +const char* KnownParameters[] = { "--to", "--cc", @@ -183,13 +194,11 @@ const char* KnownParameter[] = "--mapi-logon-ui" }; -const size_t nKnownParameter = SAL_N_ELEMENTS(KnownParameter); - /** @internal */ bool isKnownParameter(const char* aParameterName) { - for (size_t i = 0; i < nKnownParameter; i++) - if (_stricmp(aParameterName, KnownParameter[i]) == 0) + for (const char* KnownParameter : KnownParameters) + if (_stricmp(aParameterName, KnownParameter) == 0) return true; return false; @@ -229,13 +238,109 @@ void initParameter(int argc, char* argv[]) else if (_stricmp(argv[i], "--body") == 0) gBody = argv[i+1]; else if ((_stricmp(argv[i], "--attach") == 0)) - gAttachments.push_back(argv[i+1]); + { + std::string sPath(argv[i + 1]); + // An attachment may optionally be immediately followed by --attach-name and user-visible name + std::string sName; + if ((i+3) < argc && _stricmp(argv[i+2], "--attach-name") == 0) + { + sName = argv[i+3]; + i += 2; + } + gAttachments.emplace_back(sPath, sName); + } i++; } } } +void ShowError(ULONG nMAPIResult) +{ + std::string sMessage = "An error occurred in sending the message. Possible errors could be a missing user account or a defective setup.\n\nError code is "; + std::string sErrorId; + switch (nMAPIResult) + { + case MAPI_E_FAILURE: + sErrorId = "MAPI_E_FAILURE"; + break; + case MAPI_E_LOGON_FAILURE: + sErrorId = "MAPI_E_LOGON_FAILURE"; + break; + case MAPI_E_DISK_FULL: + sErrorId = "MAPI_E_DISK_FULL"; + break; + case MAPI_E_INSUFFICIENT_MEMORY: + sErrorId = "MAPI_E_INSUFFICIENT_MEMORY"; + break; + case MAPI_E_ACCESS_DENIED: + sErrorId = "MAPI_E_ACCESS_DENIED"; + break; + case MAPI_E_TOO_MANY_SESSIONS: + sErrorId = "MAPI_E_ACCESS_DENIED"; + break; + case MAPI_E_TOO_MANY_FILES: + sErrorId = "MAPI_E_TOO_MANY_FILES"; + break; + case MAPI_E_TOO_MANY_RECIPIENTS: + sErrorId = "MAPI_E_TOO_MANY_RECIPIENTS"; + break; + case MAPI_E_ATTACHMENT_NOT_FOUND: + sErrorId = "MAPI_E_ATTACHMENT_NOT_FOUND"; + break; + case MAPI_E_ATTACHMENT_OPEN_FAILURE: + sErrorId = "MAPI_E_ATTACHMENT_OPEN_FAILURE"; + break; + case MAPI_E_ATTACHMENT_WRITE_FAILURE: + sErrorId = "MAPI_E_ATTACHMENT_WRITE_FAILURE"; + break; + case MAPI_E_UNKNOWN_RECIPIENT: + sErrorId = "MAPI_E_UNKNOWN_RECIPIENT"; + break; + case MAPI_E_BAD_RECIPTYPE: + sErrorId = "MAPI_E_BAD_RECIPTYPE"; + break; + case MAPI_E_NO_MESSAGES: + sErrorId = "MAPI_E_NO_MESSAGES"; + break; + case MAPI_E_INVALID_MESSAGE: + sErrorId = "MAPI_E_INVALID_MESSAGE"; + break; + case MAPI_E_TEXT_TOO_LARGE: + sErrorId = "MAPI_E_TEXT_TOO_LARGE"; + break; + case MAPI_E_INVALID_SESSION: + sErrorId = "MAPI_E_INVALID_SESSION"; + break; + case MAPI_E_TYPE_NOT_SUPPORTED: + sErrorId = "MAPI_E_TYPE_NOT_SUPPORTED"; + break; + case MAPI_E_AMBIGUOUS_RECIPIENT: + sErrorId = "MAPI_E_AMBIGUOUS_RECIPIENT"; + break; + case MAPI_E_MESSAGE_IN_USE: + sErrorId = "MAPI_E_MESSAGE_IN_USE"; + break; + case MAPI_E_NETWORK_FAILURE: + sErrorId = "MAPI_E_NETWORK_FAILURE"; + break; + case MAPI_E_INVALID_EDITFIELDS: + sErrorId = "MAPI_E_INVALID_EDITFIELDS"; + break; + case MAPI_E_INVALID_RECIPS: + sErrorId = "MAPI_E_INVALID_RECIPS"; + break; + case MAPI_E_NOT_SUPPORTED: + sErrorId = "MAPI_E_NOT_SUPPORTED"; + break; + default: + sErrorId = std::to_string(nMAPIResult); + } + sMessage += sErrorId; + + MessageBox(nullptr, sMessage.c_str(), "Error sending mail", MB_OK | MB_ICONINFORMATION); +} + /** Main. NOTE: Because this is program only serves implementation @@ -297,6 +402,15 @@ int main(int argc, char* argv[]) { OSL_FAIL(ex.what()); } + + // Now cleanup the temporary attachment files + for (const auto& rAttachment : gAttachments) + DeleteFile(rAttachment.first.c_str()); + + // Only show the error message if UI was requested + if ((ulRet != SUCCESS_SUCCESS) && (gMapiFlags & (MAPI_DIALOG | MAPI_LOGON_UI))) + ShowError(ulRet); + return ulRet; } @@ -329,10 +443,12 @@ int main(int argc, char* argv[]) for (/**/;iter != iter_end; ++iter) oss << "--bcc " << *iter << std::endl; - iter = gAttachments.begin(); - iter_end = gAttachments.end(); - for (/**/;iter != iter_end; ++iter) - oss << "--attach " << *iter << std::endl; + for (const auto& attachment : gAttachments) + { + oss << "--attach " << attachment.first << std::endl; + if (!attachment.second.empty()) + oss << "--attach-name " << attachment.second << std::endl; + } if (gMapiFlags & MAPI_DIALOG) oss << "--mapi-dialog" << std::endl; diff --git a/shell/source/win32/simplemail/smplmailclient.cxx b/shell/source/win32/simplemail/smplmailclient.cxx index 5fcc2fac700f..64163fe4e2da 100644 --- a/shell/source/win32/simplemail/smplmailclient.cxx +++ b/shell/source/win32/simplemail/smplmailclient.cxx @@ -27,6 +27,10 @@ #include <com/sun/star/system/SimpleMailClientFlags.hpp> #include <com/sun/star/system/XSimpleMailMessage2.hpp> #include <osl/file.hxx> +#include <o3tl/make_unique.hxx> +#include <tools/urlobj.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/syslocale.hxx> #define WIN32_LEAN_AND_MEAN #if defined _MSC_VER @@ -34,6 +38,9 @@ #endif #include <windows.h> #include <mapi.h> +#if defined GetTempPath +#undef GetTempPath +#endif #if defined _MSC_VER #pragma warning(pop) #endif @@ -61,8 +68,11 @@ const OUString FROM("--from"); const OUString SUBJECT("--subject"); const OUString BODY("--body"); const OUString ATTACH("--attach"); +const OUString ATTACH_NAME("--attach-name"); const OUString FLAG_MAPI_DIALOG("--mapi-dialog"); const OUString FLAG_MAPI_LOGON_UI("--mapi-logon-ui"); +const OUString FLAG_LANGTAG("--langtag"); +const OUString FLAG_BOOTSTRAP("--bootstrap"); namespace /* private */ { @@ -116,12 +126,14 @@ namespace /* private */ @returns <TRUE/> on success. */ - bool executeSenddoc(const std::vector<OUString>& rCommandArgs) + bool executeSenddoc(const std::vector<OUString>& rCommandArgs, bool bWait) { OUString senddocUrl = getSenddocUrl(); if (senddocUrl.getLength() == 0) return false; + oslProcessOption nProcOption = osl_Process_DETACHED | (bWait ? osl_Process_WAIT : 0); + oslProcess proc; /* for efficiency reasons we are using a 'bad' cast here @@ -131,7 +143,7 @@ namespace /* private */ senddocUrl.pData, const_cast<rtl_uString**>(reinterpret_cast<rtl_uString * const *>(&rCommandArgs[0])), rCommandArgs.size(), - osl_Process_WAIT | osl_Process_DETACHED, + nProcOption, nullptr, nullptr, nullptr, @@ -141,6 +153,9 @@ namespace /* private */ if (err != osl_Process_E_None) return false; + if (!bWait) + return true; + oslProcessInfo procInfo; procInfo.Size = sizeof(oslProcessInfo); osl_getProcessInfo(proc, osl_Process_EXITCODE, &procInfo); @@ -155,6 +170,76 @@ Reference<XSimpleMailMessage> SAL_CALL CSmplMailClient::createSimpleMailMessage( return Reference<XSimpleMailMessage>(new CSmplMailMsg()); } +namespace { +// We cannot use the session-local temporary directory for the attachment, +// because it will get removed upon program exit; and it must be alive for +// senddoc process lifetime. So we use base temppath for the attachments, +// and let the senddoc to do the cleanup if it was started successfully. +// This function works like Desktop::CreateTemporaryDirectory() +OUString InitBaseTempDirURL() +{ + // No need to intercept an exception here, since + // Desktop::CreateTemporaryDirectory() has ensured that path manager is available + SvtPathOptions aOpt; + OUString aRetURL = aOpt.GetTempPath(); + if (aRetURL.isEmpty()) + { + osl::File::getTempDirURL(aRetURL); + } + if (aRetURL.endsWith("/")) + aRetURL = aRetURL.copy(0, aRetURL.getLength() - 1); + + return aRetURL; +} + +const OUString& GetBaseTempDirURL() +{ + static const OUString aRetURL(InitBaseTempDirURL()); + return aRetURL; +} +} + +OUString CSmplMailClient::CopyAttachment(const OUString& sOrigAttachURL, OUString& sUserVisibleName) +{ + // We do two things here: + // 1. Make the attachment temporary filename to not contain any fancy characters possible in + // original filename, that could confuse mailer, and extract the original filename to explicitly + // define it; + // 2. Allow the copied files be outside of the session's temporary directory, and thus not be + // removed in Desktop::RemoveTemporaryDirectory() if soffice process gets closed before the + // mailer finishes using them. + + maAttachmentFiles.emplace_back(o3tl::make_unique<utl::TempFile>(&GetBaseTempDirURL())); + maAttachmentFiles.back()->EnableKillingFile(); + INetURLObject aFilePathObj(maAttachmentFiles.back()->GetURL()); + OUString sNewAttachmentURL = aFilePathObj.GetMainURL(INetURLObject::DecodeMechanism::NONE); + if (osl::File::copy(sOrigAttachURL, sNewAttachmentURL) == osl::FileBase::RC::E_None) + { + INetURLObject url(sOrigAttachURL, INetURLObject::EncodeMechanism::WasEncoded); + sUserVisibleName = url.getName(INetURLObject::LAST_SEGMENT, true, + INetURLObject::DecodeMechanism::WithCharset); + } + else + { + // Failed to copy original; the best effort is to use original file. It is possible that + // the file gets deleted before used in spawned process; but let's hope... the worst thing + // is the absent attachment file anyway. + sNewAttachmentURL = sOrigAttachURL; + maAttachmentFiles.pop_back(); + } + return sNewAttachmentURL; +} + +void CSmplMailClient::ReleaseAttachments() +{ + for (auto& pTempFile : maAttachmentFiles) + { + if (pTempFile) + pTempFile->EnableKillingFile(false); + } + maAttachmentFiles.clear(); +} + /** Assemble a command line for SendDoc.exe out of the members of the supplied SimpleMailMessage. @@ -224,11 +309,12 @@ void CSmplMailClient::assembleCommandLine( rCommandArgs.push_back(subject); } - Sequence<OUString> attachments = xSimpleMailMessage->getAttachement(); - for (int i = 0; i < attachments.getLength(); i++) + for (const auto& attachment : xSimpleMailMessage->getAttachement()) { + OUString sDisplayName; + OUString sTempFileURL(CopyAttachment(attachment, sDisplayName)); OUString sysPath; - osl::FileBase::RC err = osl::FileBase::getSystemPathFromFileURL(attachments[i], sysPath); + osl::FileBase::RC err = osl::FileBase::getSystemPathFromFileURL(sTempFileURL, sysPath); if (err != osl::FileBase::E_None) throw IllegalArgumentException( "Invalid attachment file URL", @@ -237,6 +323,11 @@ void CSmplMailClient::assembleCommandLine( rCommandArgs.push_back(ATTACH); rCommandArgs.push_back(sysPath); + if (!sDisplayName.isEmpty()) + { + rCommandArgs.push_back(ATTACH_NAME); + rCommandArgs.push_back(sDisplayName); + } } if (!(aFlag & NO_USER_INTERFACE)) @@ -244,6 +335,19 @@ void CSmplMailClient::assembleCommandLine( if (!(aFlag & NO_LOGON_DIALOG)) rCommandArgs.push_back(FLAG_MAPI_LOGON_UI); + + rCommandArgs.push_back(FLAG_LANGTAG); + rCommandArgs.push_back(SvtSysLocale().GetUILanguageTag().getBcp47()); + + rtl::Bootstrap aBootstrap; + OUString sBootstrapPath; + aBootstrap.getIniName(sBootstrapPath); + if (!sBootstrapPath.isEmpty()) + { + rCommandArgs.push_back(FLAG_BOOTSTRAP); + rCommandArgs.push_back(sBootstrapPath); + } + } void SAL_CALL CSmplMailClient::sendSimpleMailMessage( @@ -255,10 +359,14 @@ void SAL_CALL CSmplMailClient::sendSimpleMailMessage( std::vector<OUString> senddocParams; assembleCommandLine(xSimpleMailMessage, aFlag, senddocParams); - if (!executeSenddoc(senddocParams)) + const bool bWait = aFlag & NO_USER_INTERFACE; + if (!executeSenddoc(senddocParams, bWait)) throw Exception( "Send email failed", static_cast<XSimpleMailClient*>(this)); + // Let the launched senddoc to cleanup the attachments temporary files + if (!bWait) + ReleaseAttachments(); } void CSmplMailClient::validateParameter( diff --git a/shell/source/win32/simplemail/smplmailclient.hxx b/shell/source/win32/simplemail/smplmailclient.hxx index 2da936a95340..72b79cf0b94c 100644 --- a/shell/source/win32/simplemail/smplmailclient.hxx +++ b/shell/source/win32/simplemail/smplmailclient.hxx @@ -24,7 +24,9 @@ #include <com/sun/star/lang/XServiceInfo.hpp> #include <com/sun/star/system/XSimpleMailClient.hpp> +#include <unotools/tempfile.hxx> #include <vector> +#include <memory> class CSmplMailClient : public cppu::WeakImplHelper<css::system::XSimpleMailClient> { @@ -38,6 +40,11 @@ public: private: void validateParameter(const css::uno::Reference<css::system::XSimpleMailMessage>& xSimpleMailMessage, sal_Int32 aFlag); void assembleCommandLine(const css::uno::Reference<css::system::XSimpleMailMessage>& xSimpleMailMessage, sal_Int32 aFlag, std::vector<OUString>& rCommandArgs); + OUString CopyAttachment(const OUString& sOrigAttachURL, OUString& sUserVisibleName); + // Don't try to delete the copied attachment files; let the spawned process cleanup them + void ReleaseAttachments(); + + std::vector< std::unique_ptr<utl::TempFile> > maAttachmentFiles; }; #endif |