| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_desktop.hxx" |
| |
| |
| #include "dp_misc.h" |
| #include "dp_version.hxx" |
| #include "dp_interact.h" |
| #include "rtl/uri.hxx" |
| #include "rtl/digest.h" |
| #include "rtl/random.h" |
| #include "rtl/bootstrap.hxx" |
| #include "unotools/bootstrap.hxx" |
| #include "osl/file.hxx" |
| #include "osl/pipe.hxx" |
| #include "osl/security.hxx" |
| #include "osl/thread.hxx" |
| #include "osl/mutex.hxx" |
| #include "com/sun/star/ucb/CommandAbortedException.hpp" |
| #include "com/sun/star/task/XInteractionHandler.hpp" |
| #include "com/sun/star/bridge/UnoUrlResolver.hpp" |
| #include "com/sun/star/bridge/XUnoUrlResolver.hpp" |
| #include "com/sun/star/deployment/ExtensionManager.hpp" |
| #include "com/sun/star/task/XRestartManager.hpp" |
| #include "boost/scoped_array.hpp" |
| #include "boost/shared_ptr.hpp" |
| #include <comphelper/processfactory.hxx> |
| |
| #ifdef WNT |
| //#include "tools/prewin.h" |
| #define UNICODE |
| #define _UNICODE |
| #define WIN32_LEAN_AND_MEAN |
| #include <Windows.h> |
| //#include "tools/postwin.h" |
| #endif |
| |
| using namespace ::com::sun::star; |
| using namespace ::com::sun::star::uno; |
| using ::rtl::OUString; |
| using ::rtl::OString; |
| |
| |
| #define SOFFICE1 "soffice.exe" |
| #define SOFFICE2 "soffice.bin" |
| #define SBASE "sbase.exe" |
| #define SCALC "scalc.exe" |
| #define SDRAW "sdraw.exe" |
| #define SIMPRESS "simpress.exe" |
| #define SWRITER "swriter.exe" |
| |
| namespace dp_misc { |
| namespace { |
| |
| struct UnoRc : public rtl::StaticWithInit< |
| const boost::shared_ptr<rtl::Bootstrap>, UnoRc> { |
| const boost::shared_ptr<rtl::Bootstrap> operator () () { |
| OUString unorc( RTL_CONSTASCII_USTRINGPARAM( |
| "$OOO_BASE_DIR/program/" SAL_CONFIGFILE("uno")) ); |
| ::rtl::Bootstrap::expandMacros( unorc ); |
| ::boost::shared_ptr< ::rtl::Bootstrap > ret( |
| new ::rtl::Bootstrap( unorc ) ); |
| OSL_ASSERT( ret->getHandle() != 0 ); |
| return ret; |
| } |
| }; |
| |
| struct OfficePipeId : public rtl::StaticWithInit<const OUString, OfficePipeId> { |
| const OUString operator () (); |
| }; |
| |
| const OUString OfficePipeId::operator () () |
| { |
| OUString userPath; |
| ::utl::Bootstrap::PathStatus aLocateResult = |
| ::utl::Bootstrap::locateUserInstallation( userPath ); |
| if (!(aLocateResult == ::utl::Bootstrap::PATH_EXISTS || |
| aLocateResult == ::utl::Bootstrap::PATH_VALID)) |
| { |
| throw Exception(OUSTR("Extension Manager: Could not obtain path for UserInstallation."), 0); |
| } |
| |
| rtlDigest digest = rtl_digest_create( rtl_Digest_AlgorithmMD5 ); |
| if (digest <= 0) { |
| throw RuntimeException( |
| OUSTR("cannot get digest rtl_Digest_AlgorithmMD5!"), 0 ); |
| } |
| |
| sal_uInt8 const * data = |
| reinterpret_cast<sal_uInt8 const *>(userPath.getStr()); |
| sal_Size size = (userPath.getLength() * sizeof (sal_Unicode)); |
| sal_uInt32 md5_key_len = rtl_digest_queryLength( digest ); |
| ::boost::scoped_array<sal_uInt8> md5_buf( new sal_uInt8 [ md5_key_len ] ); |
| |
| rtl_digest_init( digest, data, static_cast<sal_uInt32>(size) ); |
| rtl_digest_update( digest, data, static_cast<sal_uInt32>(size) ); |
| rtl_digest_get( digest, md5_buf.get(), md5_key_len ); |
| rtl_digest_destroy( digest ); |
| |
| // create hex-value string from the MD5 value to keep |
| // the string size minimal |
| ::rtl::OUStringBuffer buf; |
| buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("SingleOfficeIPC_") ); |
| for ( sal_uInt32 i = 0; i < md5_key_len; ++i ) { |
| buf.append( static_cast<sal_Int32>(md5_buf[ i ]), 0x10 ); |
| } |
| return buf.makeStringAndClear(); |
| } |
| |
| bool existsOfficePipe() |
| { |
| OUString const & pipeId = OfficePipeId::get(); |
| if (pipeId.getLength() == 0) |
| return false; |
| ::osl::Security sec; |
| ::osl::Pipe pipe( pipeId, osl_Pipe_OPEN, sec ); |
| return pipe.is(); |
| } |
| |
| |
| //Returns true if the Folder was more recently modified then |
| //the lastsynchronized file. That is the repository needs to |
| //be synchronized. |
| bool compareExtensionFolderWithLastSynchronizedFile( |
| OUString const & folderURL, OUString const & fileURL) |
| { |
| bool bNeedsSync = false; |
| ::osl::DirectoryItem itemExtFolder; |
| ::osl::File::RC err1 = |
| ::osl::DirectoryItem::get(folderURL, itemExtFolder); |
| //If it does not exist, then there is nothing to be done |
| if (err1 == ::osl::File::E_NOENT) |
| { |
| return false; |
| } |
| else if (err1 != ::osl::File::E_None) |
| { |
| OSL_ENSURE(0, "Cannot access extension folder"); |
| return true; //sync just in case |
| } |
| |
| //If last synchronized does not exist, then OOo is started for the first time |
| ::osl::DirectoryItem itemFile; |
| ::osl::File::RC err2 = ::osl::DirectoryItem::get(fileURL, itemFile); |
| if (err2 == ::osl::File::E_NOENT) |
| { |
| return true; |
| |
| } |
| else if (err2 != ::osl::File::E_None) |
| { |
| OSL_ENSURE(0, "Cannot access file lastsynchronized"); |
| return true; //sync just in case |
| } |
| |
| //compare the modification time of the extension folder and the last |
| //modified file |
| ::osl::FileStatus statFolder(FileStatusMask_ModifyTime); |
| ::osl::FileStatus statFile(FileStatusMask_ModifyTime); |
| if (itemExtFolder.getFileStatus(statFolder) == ::osl::File::E_None) |
| { |
| if (itemFile.getFileStatus(statFile) == ::osl::File::E_None) |
| { |
| TimeValue timeFolder = statFolder.getModifyTime(); |
| TimeValue timeFile = statFile.getModifyTime(); |
| |
| if (timeFile.Seconds < timeFolder.Seconds) |
| bNeedsSync = true; |
| } |
| else |
| { |
| OSL_ASSERT(0); |
| bNeedsSync = true; |
| } |
| } |
| else |
| { |
| OSL_ASSERT(0); |
| bNeedsSync = true; |
| } |
| return bNeedsSync; |
| } |
| |
| bool needToSyncRepostitory(OUString const & name) |
| { |
| OUString folder; |
| OUString file; |
| if (name.equals(OUString(RTL_CONSTASCII_USTRINGPARAM("bundled")))) |
| { |
| folder = OUString( |
| RTL_CONSTASCII_USTRINGPARAM("$BUNDLED_EXTENSIONS")); |
| file = OUString ( |
| RTL_CONSTASCII_USTRINGPARAM( |
| "$BUNDLED_EXTENSIONS_USER/lastsynchronized")); |
| } |
| else if (name.equals(OUString(RTL_CONSTASCII_USTRINGPARAM("shared")))) |
| { |
| folder = OUString( |
| RTL_CONSTASCII_USTRINGPARAM( |
| "$UNO_SHARED_PACKAGES_CACHE/uno_packages")); |
| file = OUString ( |
| RTL_CONSTASCII_USTRINGPARAM( |
| "$SHARED_EXTENSIONS_USER/lastsynchronized")); |
| } |
| else |
| { |
| OSL_ASSERT(0); |
| return true; |
| } |
| ::rtl::Bootstrap::expandMacros(folder); |
| ::rtl::Bootstrap::expandMacros(file); |
| return compareExtensionFolderWithLastSynchronizedFile( |
| folder, file); |
| } |
| |
| |
| } // anon namespace |
| |
| //============================================================================== |
| |
| namespace { |
| inline OUString encodeForRcFile( OUString const & str ) |
| { |
| // escape $\{} (=> rtl bootstrap files) |
| ::rtl::OUStringBuffer buf; |
| sal_Int32 pos = 0; |
| const sal_Int32 len = str.getLength(); |
| for ( ; pos < len; ++pos ) { |
| sal_Unicode c = str[ pos ]; |
| switch (c) { |
| case '$': |
| case '\\': |
| case '{': |
| case '}': |
| buf.append( static_cast<sal_Unicode>('\\') ); |
| break; |
| } |
| buf.append( c ); |
| } |
| return buf.makeStringAndClear(); |
| } |
| } |
| |
| //============================================================================== |
| OUString makeURL( OUString const & baseURL, OUString const & relPath_ ) |
| { |
| ::rtl::OUStringBuffer buf; |
| if (baseURL.getLength() > 1 && baseURL[ baseURL.getLength() - 1 ] == '/') |
| buf.append( baseURL.copy( 0, baseURL.getLength() - 1 ) ); |
| else |
| buf.append( baseURL ); |
| OUString relPath(relPath_); |
| if (relPath.getLength() > 0 && relPath[ 0 ] == '/') |
| relPath = relPath.copy( 1 ); |
| if (relPath.getLength() > 0) |
| { |
| buf.append( static_cast<sal_Unicode>('/') ); |
| if (baseURL.matchAsciiL( |
| RTL_CONSTASCII_STRINGPARAM("vnd.sun.star.expand:") )) { |
| // encode for macro expansion: relPath is supposed to have no |
| // macros, so encode $, {} \ (bootstrap mimic) |
| relPath = encodeForRcFile(relPath); |
| |
| // encode once more for vnd.sun.star.expand schema: |
| // vnd.sun.star.expand:$UNO_... |
| // will expand to file-url |
| relPath = ::rtl::Uri::encode( relPath, rtl_UriCharClassUric, |
| rtl_UriEncodeIgnoreEscapes, |
| RTL_TEXTENCODING_UTF8 ); |
| } |
| buf.append( relPath ); |
| } |
| return buf.makeStringAndClear(); |
| } |
| |
| OUString makeURLAppendSysPathSegment( OUString const & baseURL, OUString const & relPath_ ) |
| { |
| OUString segment = relPath_; |
| OSL_ASSERT(segment.indexOf(static_cast<sal_Unicode>('/')) == -1); |
| |
| ::rtl::Uri::encode( |
| segment, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes, |
| RTL_TEXTENCODING_UTF8); |
| return makeURL(baseURL, segment); |
| } |
| |
| |
| |
| //============================================================================== |
| OUString expandUnoRcTerm( OUString const & term_ ) |
| { |
| OUString term(term_); |
| UnoRc::get()->expandMacrosFrom( term ); |
| return term; |
| } |
| |
| OUString makeRcTerm( OUString const & url ) |
| { |
| OSL_ASSERT( url.matchAsciiL( RTL_CONSTASCII_STRINGPARAM( |
| "vnd.sun.star.expand:") ) ); |
| if (url.matchAsciiL( RTL_CONSTASCII_STRINGPARAM("vnd.sun.star.expand:") )) { |
| // cut protocol: |
| OUString rcterm( url.copy( sizeof ("vnd.sun.star.expand:") - 1 ) ); |
| // decode uric class chars: |
| rcterm = ::rtl::Uri::decode( |
| rcterm, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 ); |
| return rcterm; |
| } |
| else |
| return url; |
| } |
| |
| //============================================================================== |
| OUString expandUnoRcUrl( OUString const & url ) |
| { |
| if (url.matchAsciiL( RTL_CONSTASCII_STRINGPARAM("vnd.sun.star.expand:") )) { |
| // cut protocol: |
| OUString rcurl( url.copy( sizeof ("vnd.sun.star.expand:") - 1 ) ); |
| // decode uric class chars: |
| rcurl = ::rtl::Uri::decode( |
| rcurl, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 ); |
| // expand macro string: |
| UnoRc::get()->expandMacrosFrom( rcurl ); |
| return rcurl; |
| } |
| else { |
| return url; |
| } |
| } |
| |
| //============================================================================== |
| bool office_is_running() |
| { |
| //We need to check if we run within the office process. Then we must not use the pipe, because |
| //this could cause a deadlock. This is actually a workaround for i82778 |
| OUString sFile; |
| oslProcessError err = osl_getExecutableFile(& sFile.pData); |
| bool ret = false; |
| if (osl_Process_E_None == err) |
| { |
| sFile = sFile.copy(sFile.lastIndexOf('/') + 1); |
| if ( |
| #if defined UNIX |
| sFile.equals(OUString(RTL_CONSTASCII_USTRINGPARAM(SOFFICE2))) |
| #elif defined WNT || defined OS2 |
| //osl_getExecutableFile should deliver "soffice.bin" on windows |
| //even if swriter.exe, scalc.exe etc. was started. This is a bug |
| //in osl_getExecutableFile |
| sFile.equals(OUString(RTL_CONSTASCII_USTRINGPARAM(SOFFICE1))) |
| || sFile.equals(OUString(RTL_CONSTASCII_USTRINGPARAM(SOFFICE2))) |
| || sFile.equals(OUString(RTL_CONSTASCII_USTRINGPARAM(SBASE))) |
| || sFile.equals(OUString(RTL_CONSTASCII_USTRINGPARAM(SCALC))) |
| || sFile.equals(OUString(RTL_CONSTASCII_USTRINGPARAM(SDRAW))) |
| || sFile.equals(OUString(RTL_CONSTASCII_USTRINGPARAM(SIMPRESS))) |
| || sFile.equals(OUString(RTL_CONSTASCII_USTRINGPARAM(SWRITER))) |
| #else |
| #error "Unsupported platform" |
| #endif |
| |
| ) |
| ret = true; |
| else |
| ret = existsOfficePipe(); |
| } |
| else |
| { |
| OSL_ENSURE(0, "NOT osl_Process_E_None "); |
| //if osl_getExecutable file than we take the risk of creating a pipe |
| ret = existsOfficePipe(); |
| } |
| return ret; |
| } |
| |
| //============================================================================== |
| oslProcess raiseProcess( |
| OUString const & appURL, Sequence<OUString> const & args ) |
| { |
| ::osl::Security sec; |
| oslProcess hProcess = 0; |
| oslProcessError rc = osl_executeProcess( |
| appURL.pData, |
| reinterpret_cast<rtl_uString **>( |
| const_cast<OUString *>(args.getConstArray()) ), |
| args.getLength(), |
| osl_Process_DETACHED, |
| sec.getHandle(), |
| 0, // => current working dir |
| 0, 0, // => no env vars |
| &hProcess ); |
| |
| switch (rc) { |
| case osl_Process_E_None: |
| break; |
| case osl_Process_E_NotFound: |
| throw RuntimeException( OUSTR("image not found!"), 0 ); |
| case osl_Process_E_TimedOut: |
| throw RuntimeException( OUSTR("timout occured!"), 0 ); |
| case osl_Process_E_NoPermission: |
| throw RuntimeException( OUSTR("permission denied!"), 0 ); |
| case osl_Process_E_Unknown: |
| throw RuntimeException( OUSTR("unknown error!"), 0 ); |
| case osl_Process_E_InvalidError: |
| default: |
| throw RuntimeException( OUSTR("unmapped error!"), 0 ); |
| } |
| |
| return hProcess; |
| } |
| |
| //============================================================================== |
| OUString generateRandomPipeId() |
| { |
| // compute some good pipe id: |
| static rtlRandomPool s_hPool = rtl_random_createPool(); |
| if (s_hPool == 0) |
| throw RuntimeException( OUSTR("cannot create random pool!?"), 0 ); |
| sal_uInt8 bytes[ 32 ]; |
| if (rtl_random_getBytes( |
| s_hPool, bytes, ARLEN(bytes) ) != rtl_Random_E_None) { |
| throw RuntimeException( OUSTR("random pool error!?"), 0 ); |
| } |
| ::rtl::OUStringBuffer buf; |
| for ( sal_uInt32 i = 0; i < ARLEN(bytes); ++i ) { |
| buf.append( static_cast<sal_Int32>(bytes[ i ]), 0x10 ); |
| } |
| return buf.makeStringAndClear(); |
| } |
| |
| //============================================================================== |
| Reference<XInterface> resolveUnoURL( |
| OUString const & connectString, |
| Reference<XComponentContext> const & xLocalContext, |
| AbortChannel * abortChannel ) |
| { |
| Reference<bridge::XUnoUrlResolver> xUnoUrlResolver( |
| bridge::UnoUrlResolver::create( xLocalContext ) ); |
| |
| for (;;) |
| { |
| if (abortChannel != 0 && abortChannel->isAborted()) { |
| throw ucb::CommandAbortedException( |
| OUSTR("abort!"), Reference<XInterface>() ); |
| } |
| try { |
| return xUnoUrlResolver->resolve( connectString ); |
| } |
| catch (connection::NoConnectException &) { |
| TimeValue tv = { 0 /* secs */, 500000000 /* nanosecs */ }; |
| ::osl::Thread::wait( tv ); |
| } |
| } |
| } |
| |
| #ifdef WNT |
| void writeConsoleWithStream(::rtl::OUString const & sText, HANDLE stream) |
| { |
| DWORD nWrittenChars = 0; |
| WriteFile(stream, sText.getStr(), |
| sText.getLength() * 2, &nWrittenChars, NULL); |
| } |
| #else |
| void writeConsoleWithStream(::rtl::OUString const & sText, FILE * stream) |
| { |
| OString s = OUStringToOString(sText, osl_getThreadTextEncoding()); |
| fprintf(stream, "%s", s.getStr()); |
| fflush(stream); |
| } |
| #endif |
| |
| #ifdef WNT |
| void writeConsoleWithStream(::rtl::OString const & sText, HANDLE stream) |
| { |
| writeConsoleWithStream(OStringToOUString( |
| sText, RTL_TEXTENCODING_UTF8), stream); |
| } |
| #else |
| void writeConsoleWithStream(::rtl::OString const & sText, FILE * stream) |
| { |
| fprintf(stream, "%s", sText.getStr()); |
| fflush(stream); |
| } |
| #endif |
| |
| void writeConsole(::rtl::OUString const & sText) |
| { |
| #ifdef WNT |
| writeConsoleWithStream(sText, GetStdHandle(STD_OUTPUT_HANDLE)); |
| #else |
| writeConsoleWithStream(sText, stdout); |
| #endif |
| } |
| |
| void writeConsole(::rtl::OString const & sText) |
| { |
| #ifdef WNT |
| writeConsoleWithStream(sText, GetStdHandle(STD_OUTPUT_HANDLE)); |
| #else |
| writeConsoleWithStream(sText, stdout); |
| #endif |
| } |
| |
| void writeConsoleError(::rtl::OUString const & sText) |
| { |
| #ifdef WNT |
| writeConsoleWithStream(sText, GetStdHandle(STD_ERROR_HANDLE)); |
| #else |
| writeConsoleWithStream(sText, stderr); |
| #endif |
| } |
| |
| |
| void writeConsoleError(::rtl::OString const & sText) |
| { |
| #ifdef WNT |
| writeConsoleWithStream(sText, GetStdHandle(STD_ERROR_HANDLE)); |
| #else |
| writeConsoleWithStream(sText, stderr); |
| #endif |
| } |
| |
| |
| |
| OUString readConsole() |
| { |
| #ifdef WNT |
| sal_Unicode aBuffer[1024]; |
| DWORD dwRead = 0; |
| //unopkg.com feeds unopkg.exe with wchar_t|s |
| if (ReadFile( GetStdHandle(STD_INPUT_HANDLE), &aBuffer, sizeof(aBuffer), &dwRead, NULL ) ) |
| { |
| OSL_ASSERT((dwRead % 2) == 0); |
| OUString value( aBuffer, dwRead / 2); |
| return value.trim(); |
| } |
| #else |
| char buf[1024]; |
| rtl_zeroMemory(buf, 1024); |
| // read one char less so that the last char in buf is always zero |
| if (fgets(buf, 1024, stdin) != NULL) |
| { |
| OUString value = ::rtl::OStringToOUString(::rtl::OString(buf), osl_getThreadTextEncoding()); |
| return value.trim(); |
| } |
| #endif |
| return OUString(); |
| } |
| |
| void TRACE(::rtl::OUString const & sText) |
| { |
| (void) sText; |
| #if OSL_DEBUG_LEVEL > 1 |
| writeConsole(sText); |
| #endif |
| } |
| |
| void TRACE(::rtl::OString const & sText) |
| { |
| (void) sText; |
| #if OSL_DEBUG_LEVEL > 1 |
| writeConsole(sText); |
| #endif |
| } |
| |
| void syncRepositories(Reference<ucb::XCommandEnvironment> const & xCmdEnv) |
| { |
| OUString sDisable; |
| ::rtl::Bootstrap::get( OUSTR( "DISABLE_EXTENSION_SYNCHRONIZATION" ), sDisable, OUString() ); |
| if (sDisable.getLength() > 0) |
| return; |
| |
| Reference<deployment::XExtensionManager> xExtensionManager; |
| //synchronize shared before bundled otherewise there are |
| //more revoke and registration calls. |
| sal_Bool bModified = false; |
| if (needToSyncRepostitory(OUString(RTL_CONSTASCII_USTRINGPARAM("shared"))) |
| || needToSyncRepostitory(OUString(RTL_CONSTASCII_USTRINGPARAM("bundled")))) |
| { |
| xExtensionManager = |
| deployment::ExtensionManager::get( |
| comphelper_getProcessComponentContext()); |
| |
| if (xExtensionManager.is()) |
| { |
| bModified = xExtensionManager->synchronize( |
| Reference<task::XAbortChannel>(), xCmdEnv); |
| } |
| } |
| |
| if (bModified) |
| { |
| Reference<task::XRestartManager> restarter( |
| comphelper_getProcessComponentContext()->getValueByName( |
| OUSTR( "/singletons/com.sun.star.task.OfficeRestartManager") ), UNO_QUERY ); |
| if (restarter.is()) |
| { |
| restarter->requestRestart(xCmdEnv.is() == sal_True ? xCmdEnv->getInteractionHandler() : |
| Reference<task::XInteractionHandler>()); |
| } |
| } |
| } |
| |
| |
| |
| } |