/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */
 
#include <cups/http.h>
#include <cups/ipp.h>
#include <cups/ppd.h>
 
#include <unistd.h>
 
#include <unx/cupsmgr.hxx>
 
#include <o3tl/string_view.hxx>
#include <osl/thread.h>
#include <osl/file.h>
#include <osl/conditn.hxx>
 
#include <rtl/strbuf.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
 
#include <officecfg/Office/Common.hxx>
 
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <vcl/window.hxx>
 
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <string_view>
 
using namespace psp;
using namespace osl;
 
namespace {
 
struct GetPPDAttribs
{
    osl::Condition      m_aCondition;
    OString             m_aParameter;
    OString             m_aResult;
    int                 m_nRefs;
    bool*               m_pResetRunning;
    osl::Mutex*         m_pSyncMutex;
 
    GetPPDAttribs( const char * m_pParameter,
                   bool* pResetRunning, osl::Mutex* pSyncMutex )
            : m_aParameter( m_pParameter ),
              m_pResetRunning( pResetRunning ),
              m_pSyncMutex( pSyncMutex )
    {
        m_nRefs = 2;
        m_aCondition.reset();
    }
 
    ~GetPPDAttribs()
    {
        if( !m_aResult.isEmpty() )
            unlink( m_aResult.getStr() );
    }
 
    void unref()
    {
        if( --m_nRefs == 0 )
        {
            *m_pResetRunning = false;
            delete this;
        }
    }
 
    void executeCall()
    {
        // This CUPS method is not at all thread-safe we need
        // to dup the pointer to a static buffer it returns ASAP
SAL_WNODEPRECATED_DECLARATIONS_PUSH
        const char* pResult = cupsGetPPD(m_aParameter.getStr());
        OString aResult = pResult ? OString(pResult) : OString();
SAL_WNODEPRECATED_DECLARATIONS_POP
        MutexGuard aGuard( *m_pSyncMutex );
        m_aResult = std::move(aResult);
        m_aCondition.set();
        unref();
    }
 
    OString waitResult( TimeValue const *pDelay )
    {
        m_pSyncMutex->release();
 
        if (m_aCondition.wait( pDelay ) != Condition::result_ok
            )
        {
            SAL_WARN("vcl.unx.print",
                    "cupsGetPPD " << m_aParameter << " timed out");
        }
        m_pSyncMutex->acquire();
 
        OString aRetval = m_aResult;
        m_aResult.clear();
        unref();
 
        return aRetval;
    }
};
 
}
 
extern "C" {
    static void getPPDWorker(void* pData)
    {
        osl_setThreadName("CUPSManager getPPDWorker");
        GetPPDAttribs* pAttribs = static_cast<GetPPDAttribs*>(pData);
        pAttribs->executeCall();
    }
}
 
OString CUPSManager::threadedCupsGetPPD( const char* pPrinter )
{
    OString aResult;
 
    m_aGetPPDMutex.acquire();
    // if one thread hangs in cupsGetPPD already, don't start another
    if( ! m_bPPDThreadRunning )
    {
        m_bPPDThreadRunning = true;
        GetPPDAttribs* pAttribs = new GetPPDAttribs( pPrinter,
                                                     &m_bPPDThreadRunning,
                                                     &m_aGetPPDMutex );
 
        oslThread aThread = osl_createThread( getPPDWorker, pAttribs );
 
        TimeValue aValue;
        aValue.Seconds = 5;
        aValue.Nanosec = 0;
 
        // NOTE: waitResult release and acquires the GetPPD mutex
        aResult = pAttribs->waitResult( &aValue );
        osl_destroyThread( aThread );
    }
    m_aGetPPDMutex.release();
 
    return aResult;
}
 
static const char* setPasswordCallback( const char* /*pIn*/ )
{
    const char* pRet = nullptr;
 
    PrinterInfoManager& rMgr = PrinterInfoManager::get();
    if( rMgr.getType() == PrinterInfoManager::Type::CUPS ) // sanity check
        pRet = static_cast<CUPSManager&>(rMgr).authenticateUser();
    return pRet;
}
 
/*
 *  CUPSManager class
 */
 
CUPSManager* CUPSManager::tryLoadCUPS()
{
    CUPSManager* pManager = nullptr;
    static const char* pEnv = getenv("SAL_DISABLE_CUPS");
 
    if (!pEnv || !*pEnv)
        pManager = new CUPSManager();
    return pManager;
}
 
extern "C"
{
static void run_dest_thread_stub( void* pThis )
{
    osl_setThreadName("CUPSManager cupsGetDests");
    CUPSManager::runDestThread( pThis );
}
}
 
CUPSManager::CUPSManager() :
        PrinterInfoManager( PrinterInfoManager::Type::CUPS ),
        m_nDests( 0 ),
        m_pDests( nullptr ),
        m_bNewDests( false ),
        m_bPPDThreadRunning( false )
{
    m_aDestThread = osl_createThread( run_dest_thread_stub, this );
}
 
CUPSManager::~CUPSManager()
{
    if( m_aDestThread )
    {
        osl_joinWithThread( m_aDestThread );
        osl_destroyThread( m_aDestThread );
    }
 
    if (m_nDests && m_pDests)
        cupsFreeDests( m_nDests, m_pDests );
}
 
void CUPSManager::runDestThread( void* pThis )
{
    static_cast<CUPSManager*>(pThis)->runDests();
}
 
void CUPSManager::runDests()
{
    SAL_INFO("vcl.unx.print", "starting cupsGetDests");
    cups_dest_t* pDests = nullptr;
 
    // n#722902 - do a fast-failing check for cups working *at all* first
    http_t* p_http;
SAL_WNODEPRECATED_DECLARATIONS_PUSH
    if( (p_http=httpConnectEncrypt(
             cupsServer(),
             ippPort(),
             cupsEncryption())) == nullptr )
        return;
 
    int nDests = cupsGetDests2(p_http,  &pDests);
    SAL_INFO("vcl.unx.print", "came out of cupsGetDests");
 
    osl::MutexGuard aGuard( m_aCUPSMutex );
    m_nDests = nDests;
    m_pDests = pDests;
    m_bNewDests = true;
    SAL_INFO("vcl.unx.print", "finished cupsGetDests");
 
    httpClose(p_http);
SAL_WNODEPRECATED_DECLARATIONS_POP
}
 
static void SetIfCustomOption(PPDContext& rContext, const cups_option_t& rOption, rtl_TextEncoding aEncoding)
{
    if (strncmp(rOption.value, RTL_CONSTASCII_STRINGPARAM("Custom.")) == 0)
    {
        const PPDParser* pParser = rContext.getParser();
        if (!pParser)
        {
            // normal for first sight of this printer
            return;
        }
 
        const PPDKey* pKey = pParser->getKey(OStringToOUString(rOption.name, aEncoding));
        if (!pKey)
        {
            SAL_WARN("vcl.unx.print", "Custom key " << rOption.name << " not found");
            return;
        }
 
        const PPDValue* pCustomValue = rContext.getValue(pKey);
        if (!pCustomValue)
        {
            SAL_WARN("vcl.unx.print", "Value for " << rOption.name << " not found");
            return;
        }
 
        if (!pCustomValue->m_bCustomOption)
        {
            SAL_WARN("vcl.unx.print", "Value for " << rOption.name << " not set to custom option");
            return;
        }
 
        // seems sensible to keep a value the user explicitly set even if lpoptions was used to set
        // another default
        if (pCustomValue->m_bCustomOptionSetViaApp)
            return;
        pCustomValue->m_aCustomOption = OStringToOUString(rOption.value, aEncoding);
    }
}
 
void CUPSManager::initialize()
{
    // get normal printers, clear printer list
    PrinterInfoManager::initialize();
 
    // check whether thread has completed
    // if not behave like old printing system
    osl::MutexGuard aGuard( m_aCUPSMutex );
 
    if( ! m_bNewDests )
        return;
 
    // dest thread has run, clean up
    if( m_aDestThread )
    {
        osl_joinWithThread( m_aDestThread );
        osl_destroyThread( m_aDestThread );
        m_aDestThread = nullptr;
    }
    m_bNewDests = false;
 
    // clear old stuff
    m_aCUPSDestMap.clear();
 
    if( ! (m_nDests && m_pDests ) )
        return;
 
    // check for CUPS server(?) > 1.2
    // since there is no API to query, check for options that were
    // introduced in dests with 1.2
    // this is needed to check for %%IncludeFeature support
    // (#i65684#, #i65491#)
    cups_dest_t* pDest = m_pDests;
 
    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
    int nPrinter = m_nDests;
 
    // reset global default PPD options; these are queried on demand from CUPS
    m_aGlobalDefaults.m_pParser = nullptr;
    m_aGlobalDefaults.m_aContext = PPDContext();
 
    // add CUPS printers, should there be a printer
    // with the same name as a CUPS printer, overwrite it
    while( nPrinter-- )
    {
        pDest = m_pDests+nPrinter;
        OUString aPrinterName = OStringToOUString( pDest->name, aEncoding );
        if( pDest->instance && *pDest->instance )
        {
            aPrinterName += "/" +
                OStringToOUString( pDest->instance, aEncoding );
        }
 
        // initialize printer with possible configuration from psprint.conf
        bool bSetToGlobalDefaults = m_aPrinters.find( aPrinterName ) == m_aPrinters.end();
        Printer aPrinter = m_aPrinters[ aPrinterName ];
        if( bSetToGlobalDefaults )
            aPrinter.m_aInfo = m_aGlobalDefaults;
        aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
        if( pDest->is_default )
            m_aDefaultPrinter = aPrinterName;
 
        // note: the parser that goes with the PrinterInfo
        // is created implicitly by the JobData::operator=()
        // when it detects the NULL ptr m_pParser.
        // if we wanted to fill in the parser here this
        // would mean we'd have to download PPDs for each and
        // every printer - which would be really bad runtime
        // behaviour
        aPrinter.m_aInfo.m_pParser = nullptr;
        aPrinter.m_aInfo.m_aContext.setParser( nullptr );
        std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aPrinterName );
        if( c_it != m_aDefaultContexts.end() )
        {
            aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
            aPrinter.m_aInfo.m_aContext = c_it->second;
        }
        aPrinter.m_aInfo.m_aDriverName = "CUPS:" + aPrinterName;
 
        for( int k = 0; k < pDest->num_options; k++ )
        {
            if(!strcmp(pDest->options[k].name, "printer-info"))
                aPrinter.m_aInfo.m_aComment=OStringToOUString(pDest->options[k].value, aEncoding);
            if(!strcmp(pDest->options[k].name, "printer-location"))
                aPrinter.m_aInfo.m_aLocation=OStringToOUString(pDest->options[k].value, aEncoding);
            if(!strcmp(pDest->options[k].name, "auth-info-required"))
                aPrinter.m_aInfo.m_aAuthInfoRequired=OStringToOUString(pDest->options[k].value, aEncoding);
            // tdf#149439 Update Custom values that may have changed if this is not a newly discovered printer
            SetIfCustomOption(aPrinter.m_aInfo.m_aContext, pDest->options[k], aEncoding);
        }
 
        m_aPrinters[ aPrinter.m_aInfo.m_aPrinterName ] = aPrinter;
        m_aCUPSDestMap[ aPrinter.m_aInfo.m_aPrinterName ] = nPrinter;
    }
 
    // remove everything that is not a CUPS printer and not
    // a special purpose printer (PDF, Fax)
    std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
    while(it != m_aPrinters.end())
    {
        if( m_aCUPSDestMap.find( it->first ) != m_aCUPSDestMap.end() )
        {
            ++it;
            continue;
        }
 
        if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
        {
            ++it;
            continue;
        }
        it = m_aPrinters.erase(it);
    }
 
    cupsSetPasswordCB( setPasswordCallback );
}
 
static void updatePrinterContextInfo( ppd_group_t* pPPDGroup, PPDContext& rContext )
{
    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
    for( int i = 0; i < pPPDGroup->num_options; i++ )
    {
        ppd_option_t* pOption = pPPDGroup->options + i;
        for( int n = 0; n < pOption->num_choices; n++ )
        {
            ppd_choice_t* pChoice = pOption->choices + n;
            if( pChoice->marked )
            {
                const PPDKey* pKey = rContext.getParser()->getKey( OStringToOUString( pOption->keyword, aEncoding ) );
                if( pKey )
                {
                    const PPDValue* pValue = pKey->getValue( OStringToOUString( pChoice->choice, aEncoding ) );
                    if( pValue )
                    {
                        if( pValue != pKey->getDefaultValue() )
                        {
                            rContext.setValue( pKey, pValue, true );
                            SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is set to " << pChoice->choice);
 
                        }
                        else
                            SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is defaulted to " << pChoice->choice);
                    }
                    else
                        SAL_INFO("vcl.unx.print", "caution: value " << pChoice->choice << " not found in key " << pOption->keyword);
                }
                else
                    SAL_INFO("vcl.unx.print", "caution: key " << pOption->keyword << " not found in parser");
            }
        }
    }
 
    // recurse through subgroups
    for( int g = 0; g < pPPDGroup->num_subgroups; g++ )
    {
        updatePrinterContextInfo( pPPDGroup->subgroups + g, rContext );
    }
}
 
const PPDParser* CUPSManager::createCUPSParser( const OUString& rPrinter )
{
    const PPDParser* pNewParser = nullptr;
    OUString aPrinter;
 
    if( rPrinter.startsWith("CUPS:") )
        aPrinter = rPrinter.copy( 5 );
    else
        aPrinter = rPrinter;
 
    if( m_aCUPSMutex.tryToAcquire() )
    {
        if (m_nDests && m_pDests)
        {
            std::unordered_map< OUString, int >::iterator dest_it =
            m_aCUPSDestMap.find( aPrinter );
            if( dest_it != m_aCUPSDestMap.end() )
            {
                cups_dest_t* pDest = m_pDests + dest_it->second;
                OString aPPDFile = threadedCupsGetPPD( pDest->name );
                SAL_INFO("vcl.unx.print",
                        "PPD for " << aPrinter << " is " << aPPDFile);
                if( !aPPDFile.isEmpty() )
                {
                    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
                    OUString aFileName( OStringToOUString( aPPDFile, aEncoding ) );
                    // update the printer info with context information
SAL_WNODEPRECATED_DECLARATIONS_PUSH
                    ppd_file_t* pPPD = ppdOpenFile( aPPDFile.getStr() );
SAL_WNODEPRECATED_DECLARATIONS_POP
                    if( pPPD )
                    {
                        // create the new parser
                        PPDParser* pCUPSParser = new PPDParser( aFileName );
                        pCUPSParser->m_aFile = rPrinter;
                        pNewParser = pCUPSParser;
 
SAL_WNODEPRECATED_DECLARATIONS_PUSH
                        /*int nConflicts =*/ cupsMarkOptions( pPPD, pDest->num_options, pDest->options );
SAL_WNODEPRECATED_DECLARATIONS_POP
                        SAL_INFO("vcl.unx.print", "processing the following options for printer " << pDest->name << " (instance " << (pDest->instance == nullptr ? "null" : pDest->instance) << "):");
                        for( int k = 0; k < pDest->num_options; k++ )
                            SAL_INFO("vcl.unx.print",
                                "   \"" << pDest->options[k].name <<
                                "\" = \"" << pDest->options[k].value << "\"");
                        PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
 
                        // remember the default context for later use
                        PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
                        rContext.setParser( pNewParser );
                        // set system default paper; printer CUPS PPD options
                        // may overwrite it
                        setDefaultPaper( rContext );
                        for( int i = 0; i < pPPD->num_groups; i++ )
                            updatePrinterContextInfo( pPPD->groups + i, rContext );
 
                        // tdf#149439 Set Custom values.
                        for (int k = 0; k < pDest->num_options; ++k)
                            SetIfCustomOption(rContext, pDest->options[k], aEncoding);
 
                        rInfo.m_pParser = pNewParser;
                        rInfo.m_aContext = rContext;
 
                        // clean up the mess
SAL_WNODEPRECATED_DECLARATIONS_PUSH
                        ppdClose( pPPD );
SAL_WNODEPRECATED_DECLARATIONS_POP
 
                    }
                    else
                        SAL_INFO("vcl.unx.print", "ppdOpenFile failed, falling back to generic driver");
 
                    // remove temporary PPD file
                    if (!getenv("SAL_CUPS_PPD_RETAIN_TMP"))
                        unlink(aPPDFile.getStr());
                    else
                        std::cout << "Saved PPD file as: " << aPPDFile << std::endl;
                }
                else
                    SAL_INFO("vcl.unx.print", "cupsGetPPD failed, falling back to generic driver");
            }
            else
                SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
        }
        m_aCUPSMutex.release();
    }
    else
        SAL_WARN("vcl.unx.print", "could not acquire CUPS mutex !!!" );
 
    if( ! pNewParser )
    {
        // get the default PPD
        pNewParser = PPDParser::getParser( u"SGENPRT"_ustr );
        SAL_INFO("vcl.unx.print", "Parsing default SGENPRT PPD" );
 
        PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
 
        rInfo.m_pParser = pNewParser;
        rInfo.m_aContext.setParser( pNewParser );
    }
 
    return pNewParser;
}
 
void CUPSManager::setupJobContextData( JobData& rData )
{
    std::unordered_map< OUString, int >::iterator dest_it =
        m_aCUPSDestMap.find( rData.m_aPrinterName );
 
    if( dest_it == m_aCUPSDestMap.end() )
        return PrinterInfoManager::setupJobContextData( rData );
 
    std::unordered_map< OUString, Printer >::iterator p_it =
        m_aPrinters.find( rData.m_aPrinterName );
    if( p_it == m_aPrinters.end() ) // huh ?
    {
        SAL_WARN("vcl.unx.print", "CUPS printer list in disorder, "
                "no dest for printer " << rData.m_aPrinterName);
        return;
    }
 
    if( p_it->second.m_aInfo.m_pParser == nullptr )
    {
        // in turn calls createCUPSParser
        // which updates the printer info
        p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
    }
    if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
    {
        OUString aPrinter;
        if( p_it->second.m_aInfo.m_aDriverName.startsWith("CUPS:") )
            aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 5 );
        else
            aPrinter = p_it->second.m_aInfo.m_aDriverName;
 
        p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
    }
 
    rData.m_pParser     = p_it->second.m_aInfo.m_pParser;
    rData.m_aContext    = p_it->second.m_aInfo.m_aContext;
}
 
FILE* CUPSManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
{
    SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
 
    if( m_aCUPSDestMap.find( rPrintername ) == m_aCUPSDestMap.end() )
    {
        SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
        return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
    }
 
    OUString aTmpURL, aTmpFile;
    osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
    osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
    OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
    FILE* fp = fopen( aSysFile.getStr(), "w" );
    if( fp )
        m_aSpoolFiles[fp] = aSysFile;
 
    return fp;
}
 
namespace {
 
struct less_ppd_key
{
    bool operator()(const PPDKey* left, const PPDKey* right)
    { return left->getOrderDependency() < right->getOrderDependency(); }
};
 
}
 
void CUPSManager::getOptionsFromDocumentSetup(const JobData& rJob, bool bBanner, int& rNumOptions,
                                              cups_option_t** ppOptions)
{
    rNumOptions = 0;
    *ppOptions = nullptr;
 
    // emit features ordered to OrderDependency
    // ignore features that are set to default
 
    // sanity check
    if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser )
    {
        std::size_t i;
        std::size_t nKeys = rJob.m_aContext.countValuesModified();
        ::std::vector< const PPDKey* > aKeys( nKeys );
        for(  i = 0; i < nKeys; i++ )
            aKeys[i] = rJob.m_aContext.getModifiedKey( i );
        ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() );
 
        for( i = 0; i < nKeys; i++ )
        {
            const PPDKey* pKey = aKeys[i];
            const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
            OUString sPayLoad;
            if (pValue && pValue->m_eType == eInvocation)
            {
                sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
            }
 
            if (!sPayLoad.isEmpty())
            {
                OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
                OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
                rNumOptions = cupsAddOption(aKey.getStr(), aValue.getStr(), rNumOptions, ppOptions);
 
                // for duplex, also set the corresponding CUPS "sides" option, see section
                // "Printing On Both Sides of the Paper" at https://www.cups.org/doc/options.html
                if (aKey == "Duplex")
                {
                    if (aValue == "None")
                        rNumOptions = cupsAddOption("sides", "one-sided", rNumOptions, ppOptions);
                    else if (aValue == "DuplexTumble")
                        rNumOptions = cupsAddOption("sides", "two-sided-short-edge", rNumOptions, ppOptions);
                    else if (aValue == "DuplexNoTumble")
                        rNumOptions = cupsAddOption("sides", "two-sided-long-edge", rNumOptions, ppOptions);
                }
            }
        }
    }
 
    if( rJob.m_nCopies > 1 )
    {
        OString aVal( OString::number( rJob.m_nCopies ) );
        rNumOptions = cupsAddOption("copies", aVal.getStr(), rNumOptions, ppOptions);
        aVal = OString::boolean(rJob.m_bCollate);
        rNumOptions = cupsAddOption("collate", aVal.getStr(), rNumOptions, ppOptions);
    }
    if( ! bBanner )
    {
        rNumOptions = cupsAddOption("job-sheets", "none", rNumOptions, ppOptions);
    }
}
 
namespace
{
    class RTSPWDialog : public weld::GenericDialogController
    {
        std::unique_ptr<weld::Label> m_xText;
        std::unique_ptr<weld::Label> m_xDomainLabel;
        std::unique_ptr<weld::Entry> m_xDomainEdit;
        std::unique_ptr<weld::Label> m_xUserLabel;
        std::unique_ptr<weld::Entry> m_xUserEdit;
        std::unique_ptr<weld::Label> m_xPassLabel;
        std::unique_ptr<weld::Entry> m_xPassEdit;
 
    public:
        RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName);
 
        OString getDomain() const
        {
            return OUStringToOString( m_xDomainEdit->get_text(), osl_getThreadTextEncoding() );
        }
 
        OString getUserName() const
        {
            return OUStringToOString( m_xUserEdit->get_text(), osl_getThreadTextEncoding() );
        }
 
        OString getPassword() const
        {
            return OUStringToOString( m_xPassEdit->get_text(), osl_getThreadTextEncoding() );
        }
 
        void SetDomainVisible(bool bShow)
        {
            m_xDomainLabel->set_visible(bShow);
            m_xDomainEdit->set_visible(bShow);
        }
 
        void SetUserVisible(bool bShow)
        {
            m_xUserLabel->set_visible(bShow);
            m_xUserEdit->set_visible(bShow);
        }
 
        void SetPassVisible(bool bShow)
        {
            m_xPassLabel->set_visible(bShow);
            m_xPassEdit->set_visible(bShow);
        }
    };
 
    RTSPWDialog::RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName)
        : GenericDialogController(pParent, u"vcl/ui/cupspassworddialog.ui"_ustr, u"CUPSPasswordDialog"_ustr)
        , m_xText(m_xBuilder->weld_label(u"text"_ustr))
        , m_xDomainLabel(m_xBuilder->weld_label(u"label3"_ustr))
        , m_xDomainEdit(m_xBuilder->weld_entry(u"domain"_ustr))
        , m_xUserLabel(m_xBuilder->weld_label(u"label1"_ustr))
        , m_xUserEdit(m_xBuilder->weld_entry(u"user"_ustr))
        , m_xPassLabel(m_xBuilder->weld_label(u"label2"_ustr))
        , m_xPassEdit(m_xBuilder->weld_entry(u"pass"_ustr))
    {
        OUString aText(m_xText->get_label());
        aText = aText.replaceFirst("%s", OStringToOUString(rServer, osl_getThreadTextEncoding()));
        m_xText->set_label(aText);
        m_xDomainEdit->set_text(u"WORKGROUP"_ustr);
        if (rUserName.empty())
            m_xUserEdit->grab_focus();
        else
        {
            m_xUserEdit->set_text(OStringToOUString(rUserName, osl_getThreadTextEncoding()));
            m_xPassEdit->grab_focus();
        }
    }
 
    bool AuthenticateQuery(std::string_view rServer, OString& rUserName, OString& rPassword)
    {
        bool bRet = false;
 
        RTSPWDialog aDialog(Application::GetDefDialogParent(), rServer, rUserName);
        if (aDialog.run() == RET_OK)
        {
            rUserName = aDialog.getUserName();
            rPassword = aDialog.getPassword();
            bRet = true;
        }
 
        return bRet;
    }
}
 
namespace
{
    OString EscapeCupsOption(const OString& rIn)
    {
        OStringBuffer sRet;
        sal_Int32 nLen = rIn.getLength();
        for (sal_Int32 i = 0; i < nLen; ++i)
        {
            switch(rIn[i])
            {
                case '\\':
                case '\'':
                case '\"':
                case ',':
                case ' ':
                case '\f':
                case '\n':
                case '\r':
                case '\t':
                case '\v':
                    sRet.append('\\');
                    break;
            }
            sRet.append(rIn[i]);
        }
        return sRet.makeStringAndClear();
    }
}
 
bool CUPSManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
{
    SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
 
    int nJobID = 0;
 
    osl::MutexGuard aGuard( m_aCUPSMutex );
 
    std::unordered_map< OUString, int >::iterator dest_it =
        m_aCUPSDestMap.find( rPrintername );
    if( dest_it == m_aCUPSDestMap.end() )
    {
        SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
        return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
    }
 
    std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
    if( it != m_aSpoolFiles.end() )
    {
        fclose( pFile );
        rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
 
        // setup cups options
        int nNumOptions = 0;
        cups_option_t* pOptions = nullptr;
        getOptionsFromDocumentSetup(rDocumentJobData, bBanner, nNumOptions, &pOptions);
 
        PrinterInfo aInfo(getPrinterInfo(rPrintername));
        if (!aInfo.m_aAuthInfoRequired.isEmpty())
        {
            bool bDomain(false), bUser(false), bPass(false);
            sal_Int32 nIndex = 0;
            do
            {
                std::u16string_view aToken = o3tl::getToken(aInfo.m_aAuthInfoRequired, 0, ',', nIndex);
                if (aToken == u"domain")
                    bDomain = true;
                else if (aToken == u"username")
                    bUser = true;
                else if (aToken == u"password")
                    bPass = true;
            }
            while (nIndex >= 0);
 
            if (bDomain || bUser || bPass)
            {
                OString sPrinterName(OUStringToOString(rPrintername, RTL_TEXTENCODING_UTF8));
                OString sUser = cupsUser();
                RTSPWDialog aDialog(Application::GetDefDialogParent(), sPrinterName, sUser);
                aDialog.SetDomainVisible(bDomain);
                aDialog.SetUserVisible(bUser);
                aDialog.SetPassVisible(bPass);
 
                if (aDialog.run() == RET_OK)
                {
                    OString sAuth;
                    if (bDomain)
                        sAuth = EscapeCupsOption(aDialog.getDomain());
                    if (bUser)
                    {
                        if (bDomain)
                            sAuth += ",";
                        sAuth += EscapeCupsOption(aDialog.getUserName());
                    }
                    if (bPass)
                    {
                        if (bUser || bDomain)
                            sAuth += ",";
                        sAuth += EscapeCupsOption(aDialog.getPassword());
                    }
                    nNumOptions = cupsAddOption("auth-info", sAuth.getStr(), nNumOptions, &pOptions);
                }
            }
        }
 
        OString sJobName(OUStringToOString(rJobTitle, aEnc));
 
        //fax4CUPS, "the job name will be dialled for you"
        //so override the jobname with the desired number
        if (!rFaxNumber.isEmpty())
        {
            sJobName = OUStringToOString(rFaxNumber, aEnc);
        }
 
        cups_dest_t* pDest = m_pDests + dest_it->second;
        nJobID = cupsPrintFile(pDest->name,
            it->second.getStr(),
            sJobName.getStr(),
            nNumOptions, pOptions);
        SAL_INFO("vcl.unx.print", "cupsPrintFile( " << pDest->name << ", "
                << it->second << ", " << rJobTitle << ", " << nNumOptions
                << ", " << pOptions << " ) returns " << nJobID);
        for( int n = 0; n < nNumOptions; n++ )
            SAL_INFO("vcl.unx.print",
                "    option " << pOptions[n].name << "=" << pOptions[n].value);
#if OSL_DEBUG_LEVEL > 1
        OString aCmd( "cp " );
        aCmd += it->second.getStr();
        aCmd += OString( " $HOME/cupsprint.ps" );
        system( aCmd.getStr() );
#endif
 
        unlink( it->second.getStr() );
        m_aSpoolFiles.erase(it);
        if( pOptions )
            cupsFreeOptions( nNumOptions, pOptions );
    }
 
    return nJobID != 0;
}
 
bool CUPSManager::checkPrintersChanged( bool bWait )
{
    bool bChanged = false;
    if( bWait )
    {
        if(  m_aDestThread )
        {
            // initial asynchronous detection still running
            SAL_INFO("vcl.unx.print", "syncing cups discovery thread");
            osl_joinWithThread( m_aDestThread );
            osl_destroyThread( m_aDestThread );
            m_aDestThread = nullptr;
            SAL_INFO("vcl.unx.print", "done: syncing cups discovery thread");
        }
        else
        {
            // #i82321# check for cups printer updates
            // with this change the whole asynchronous detection in a thread is
            // almost useless. The only relevance left is for some stalled systems
            // where the user can set SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION
            // (see vcl/unx/source/gdi/salprnpsp.cxx)
            // so that checkPrintersChanged( true ) will never be called
 
            // there is no way to query CUPS whether the printer list has changed
            // so get the dest list anew
            if( m_nDests && m_pDests )
                cupsFreeDests( m_nDests, m_pDests );
            m_nDests = 0;
            m_pDests = nullptr;
            runDests();
        }
    }
    if( m_aCUPSMutex.tryToAcquire() )
    {
        bChanged = m_bNewDests;
        m_aCUPSMutex.release();
    }
 
    if( ! bChanged )
    {
        bChanged = PrinterInfoManager::checkPrintersChanged( bWait );
        // #i54375# ensure new merging with CUPS list in :initialize
        if( bChanged )
            m_bNewDests = true;
    }
 
    if( bChanged )
        initialize();
 
    return bChanged;
}
 
const char* CUPSManager::authenticateUser()
{
    const char* pRet = nullptr;
 
    osl::MutexGuard aGuard( m_aCUPSMutex );
 
    OString aUser = cupsUser();
    OString aServer = cupsServer();
    OString aPassword;
    if (AuthenticateQuery(aServer, aUser, aPassword))
    {
        m_aPassword = aPassword;
        m_aUser = aUser;
        cupsSetUser( m_aUser.getStr() );
        pRet = m_aPassword.getStr();
    }
 
    return pRet;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V595 The 'pOptions' pointer was utilized before it was verified against nullptr. Check lines: 891, 902.