Coverage Report

Created: 2026-06-30 11:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/unotools/source/ucbhelper/ucbhelper.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <sal/config.h>
21
22
#include <cassert>
23
#include <vector>
24
25
#include <com/sun/star/sdbc/XResultSet.hpp>
26
#include <com/sun/star/task/XInteractionHandler.hpp>
27
#include <com/sun/star/task/InteractionHandler.hpp>
28
#include <com/sun/star/ucb/CommandAbortedException.hpp>
29
#include <com/sun/star/ucb/ContentInfo.hpp>
30
#include <com/sun/star/ucb/ContentInfoAttribute.hpp>
31
#include <com/sun/star/ucb/IOErrorCode.hpp>
32
#include <com/sun/star/ucb/InteractiveIOException.hpp>
33
#include <com/sun/star/ucb/NameClashException.hpp>
34
#include <com/sun/star/ucb/UniversalContentBroker.hpp>
35
#include <com/sun/star/ucb/XCommandEnvironment.hpp>
36
#include <com/sun/star/ucb/XContentAccess.hpp>
37
#include <com/sun/star/ucb/XUniversalContentBroker.hpp>
38
#include <com/sun/star/uno/Any.hxx>
39
#include <com/sun/star/uno/Exception.hpp>
40
#include <com/sun/star/uno/Reference.hxx>
41
#include <com/sun/star/uno/RuntimeException.hpp>
42
#include <com/sun/star/uno/Sequence.hxx>
43
#include <comphelper/processfactory.hxx>
44
#include <comphelper/simplefileaccessinteraction.hxx>
45
#include <osl/file.hxx>
46
#include <rtl/ustring.hxx>
47
#include <sal/log.hxx>
48
#include <tools/datetime.hxx>
49
#include <tools/urlobj.hxx>
50
#include <comphelper/diagnose_ex.hxx>
51
#include <ucbhelper/commandenvironment.hxx>
52
#include <ucbhelper/content.hxx>
53
#include <unotools/ucbhelper.hxx>
54
55
namespace com::sun::star::ucb { class XProgressHandler; }
56
namespace com::sun::star::uno { class XComponentContext; }
57
namespace com::sun::star::util { struct DateTime; }
58
59
namespace {
60
61
0
OUString canonic(OUString const & url) {
62
0
    INetURLObject o(url);
63
0
    SAL_WARN_IF(o.HasError(), "unotools.ucbhelper", "Invalid URL \"" << url << '"');
64
0
    return o.GetMainURL(INetURLObject::DecodeMechanism::NONE);
65
0
}
66
67
0
ucbhelper::Content content(OUString const & url) {
68
0
    return ucbhelper::Content(
69
0
        canonic(url),
70
0
        utl::UCBContentHelper::getDefaultCommandEnvironment(),
71
0
        comphelper::getProcessComponentContext());
72
0
}
73
74
0
ucbhelper::Content content(INetURLObject const & url) {
75
0
    return ucbhelper::Content(
76
0
        url.GetMainURL(INetURLObject::DecodeMechanism::NONE),
77
0
        utl::UCBContentHelper::getDefaultCommandEnvironment(),
78
0
        comphelper::getProcessComponentContext());
79
0
}
80
81
0
std::vector<OUString> getContents(OUString const & url) {
82
0
    try {
83
0
        std::vector<OUString> cs;
84
0
        ucbhelper::Content c(content(url));
85
0
        css::uno::Sequence<OUString> args { u"Title"_ustr };
86
0
        css::uno::Reference<css::sdbc::XResultSet> res( c.createCursor(args), css::uno::UNO_SET_THROW);
87
0
        css::uno::Reference<css::ucb::XContentAccess> acc( res, css::uno::UNO_QUERY_THROW);
88
0
        while (res->next()) {
89
0
            cs.push_back(acc->queryContentIdentifierString());
90
0
        }
91
0
        return cs;
92
0
    } catch (css::uno::RuntimeException const &) {
93
0
        throw;
94
0
    } catch (css::ucb::CommandAbortedException const &) {
95
0
        assert(false && "this cannot happen");
96
0
        throw;
97
0
    } catch (css::uno::Exception const &) {
98
0
        TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "getContents(" << url << ")");
99
0
        return std::vector<OUString>();
100
0
    }
101
0
}
102
103
0
OUString getCasePreservingUrl(const INetURLObject& url) {
104
0
    return
105
0
        content(url).executeCommand(
106
0
            u"getCasePreservingURL"_ustr,
107
0
            css::uno::Any()).
108
0
        get<OUString>();
109
0
}
110
111
0
DateTime convert(css::util::DateTime const & dt) {
112
0
    return DateTime(dt);
113
0
}
114
115
}
116
117
css::uno::Reference< css::ucb::XCommandEnvironment > utl::UCBContentHelper::getDefaultCommandEnvironment()
118
8.02k
{
119
8.02k
    css::uno::Reference< css::task::XInteractionHandler > xIH(
120
8.02k
        css::task::InteractionHandler::createWithParent(
121
8.02k
            comphelper::getProcessComponentContext(), nullptr ) );
122
123
8.02k
    css::uno::Reference< css::ucb::XProgressHandler > xProgress;
124
8.02k
    rtl::Reference<ucbhelper::CommandEnvironment> pCommandEnv =
125
8.02k
        new ::ucbhelper::CommandEnvironment(
126
8.02k
            new comphelper::SimpleFileAccessInteraction( xIH ), xProgress );
127
128
8.02k
    return pCommandEnv;
129
8.02k
}
130
131
0
bool utl::UCBContentHelper::IsDocument(OUString const & url) {
132
0
    try {
133
0
        return content(url).isDocument();
134
0
    } catch (css::uno::RuntimeException const &) {
135
0
        throw;
136
0
    } catch (css::ucb::CommandAbortedException const &) {
137
0
        assert(false && "this cannot happen");
138
0
        throw;
139
0
    } catch (css::uno::Exception const &) {
140
0
        TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::IsDocument(" << url << ")");
141
0
        return false;
142
0
    }
143
0
}
144
145
css::uno::Any utl::UCBContentHelper::GetProperty(
146
    OUString const & url, OUString const & property)
147
0
{
148
0
    try {
149
0
        return content(url).getPropertyValue(property);
150
0
    } catch (css::uno::RuntimeException const &) {
151
0
        throw;
152
0
    } catch (css::ucb::CommandAbortedException const &) {
153
0
        assert(false && "this cannot happen");
154
0
        throw;
155
0
    } catch (css::uno::Exception const &) {
156
0
        TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::GetProperty(" << url << ", " << property << ")");
157
0
        return css::uno::Any();
158
0
    }
159
0
}
160
161
0
bool utl::UCBContentHelper::IsFolder(OUString const & url) {
162
0
    try {
163
0
        return content(url).isFolder();
164
0
    } catch (css::uno::RuntimeException const &) {
165
0
        throw;
166
0
    } catch (css::ucb::CommandAbortedException const &) {
167
0
        assert(false && "this cannot happen");
168
0
        throw;
169
0
    } catch (css::uno::Exception const &) {
170
0
        TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::IsFolder(" << url << ")");
171
0
        return false;
172
0
    }
173
0
}
174
175
bool utl::UCBContentHelper::GetTitle(
176
    OUString const & url, OUString * title)
177
0
{
178
0
    assert(title != nullptr);
179
0
    try {
180
0
        return content(url).getPropertyValue(u"Title"_ustr) >>= *title;
181
0
    } catch (css::uno::RuntimeException const &) {
182
0
        throw;
183
0
    } catch (css::ucb::CommandAbortedException const &) {
184
0
        assert(false && "this cannot happen");
185
0
        throw;
186
0
    } catch (css::uno::Exception const &) {
187
0
        TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::GetTitle(" << url << ")");
188
0
        return false;
189
0
    }
190
0
}
191
192
0
bool utl::UCBContentHelper::Kill(OUString const & url) {
193
0
    try {
194
0
        content(url).executeCommand(
195
0
            u"delete"_ustr,
196
0
            css::uno::Any(true));
197
0
        return true;
198
0
    } catch (css::uno::RuntimeException const &) {
199
0
        throw;
200
0
    } catch (css::ucb::CommandAbortedException const &) {
201
0
        assert(false && "this cannot happen");
202
0
        throw;
203
0
    } catch (css::uno::Exception const &) {
204
0
        TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::Kill(" << url << ")");
205
0
        return false;
206
0
    }
207
0
}
208
209
bool utl::UCBContentHelper::MakeFolder(
210
    ucbhelper::Content & parent, OUString const & title,
211
    ucbhelper::Content & result)
212
0
{
213
0
    bool exists = false;
214
0
    try {
215
0
        const css::uno::Sequence<css::ucb::ContentInfo> info(
216
0
            parent.queryCreatableContentsInfo());
217
0
        for (const auto& rInfo : info) {
218
            // Simply look for the first KIND_FOLDER:
219
0
            if ((rInfo.Attributes
220
0
                 & css::ucb::ContentInfoAttribute::KIND_FOLDER)
221
0
                != 0)
222
0
            {
223
                // Make sure the only required bootstrap property is "Title":
224
0
                if ( rInfo.Properties.getLength() != 1 || rInfo.Properties[0].Name != "Title" )
225
0
                {
226
0
                    continue;
227
0
                }
228
0
                if (parent.insertNewContent(rInfo.Type, { u"Title"_ustr }, { css::uno::Any(title) }, result))
229
0
                {
230
0
                    return true;
231
0
                }
232
0
            }
233
0
        }
234
0
    } catch (css::ucb::InteractiveIOException const & e) {
235
0
        if (e.Code == css::ucb::IOErrorCode_ALREADY_EXISTING) {
236
0
            exists = true;
237
0
        } else {
238
0
            TOOLS_INFO_EXCEPTION(
239
0
                "unotools.ucbhelper",
240
0
                "UCBContentHelper::MakeFolder(" << title << ")");
241
0
        }
242
0
    } catch (css::ucb::NameClashException const &) {
243
0
        exists = true;
244
0
    } catch (css::uno::RuntimeException const &) {
245
0
        throw;
246
0
    } catch (css::ucb::CommandAbortedException const &) {
247
0
        assert(false && "this cannot happen");
248
0
        throw;
249
0
    } catch (css::uno::Exception const &) {
250
0
        TOOLS_INFO_EXCEPTION(
251
0
            "unotools.ucbhelper",
252
0
            "UCBContentHelper::MakeFolder(" << title << ") ");
253
0
    }
254
0
    if (exists) {
255
0
        INetURLObject o(parent.getURL());
256
0
        o.Append(title);
257
0
        result = content(o);
258
0
        return true;
259
0
    } else {
260
0
        return false;
261
0
    }
262
0
}
263
264
bool utl::UCBContentHelper::IsYounger(
265
    OUString const & younger, OUString const & older)
266
0
{
267
0
    try {
268
0
        return
269
0
            convert(
270
0
                content(younger).getPropertyValue(
271
0
                    u"DateModified"_ustr).
272
0
                get<css::util::DateTime>())
273
0
            > convert(
274
0
                content(older).getPropertyValue(
275
0
                    u"DateModified"_ustr).
276
0
                get<css::util::DateTime>());
277
0
    } catch (css::uno::RuntimeException const &) {
278
0
        throw;
279
0
    } catch (css::ucb::CommandAbortedException const &) {
280
0
        assert(false && "this cannot happen");
281
0
        throw;
282
0
    } catch (css::uno::Exception const &) {
283
0
        TOOLS_INFO_EXCEPTION(
284
0
            "unotools.ucbhelper",
285
0
            "UCBContentHelper::IsYounger(" << younger << ", " << older << ")");
286
0
        return false;
287
0
    }
288
0
}
289
290
0
bool utl::UCBContentHelper::Exists(OUString const & url) {
291
0
    OUString pathname;
292
0
    if (osl::FileBase::getSystemPathFromFileURL(url, pathname)
293
0
        == osl::FileBase::E_None)
294
0
    {
295
        // Try to create a directory entry for the given URL:
296
0
        OUString url2;
297
0
        if (osl::FileBase::getFileURLFromSystemPath(pathname, url2)
298
0
            == osl::FileBase::E_None)
299
0
        {
300
            // #106526 osl_getDirectoryItem is an existence check, no further
301
            // osl_getFileStatus call necessary:
302
0
            osl::DirectoryItem item;
303
0
            return osl::DirectoryItem::get(url2, item) == osl::FileBase::E_None;
304
0
        } else {
305
0
            return false;
306
0
        }
307
0
    } else {
308
        // Divide URL into folder and name part:
309
0
        INetURLObject o(url);
310
0
        OUString name(
311
0
            o.getName(
312
0
                INetURLObject::LAST_SEGMENT, true,
313
0
                INetURLObject::DecodeMechanism::WithCharset));
314
0
        o.removeSegment();
315
0
        o.removeFinalSlash();
316
0
        std::vector<OUString> cs(
317
0
            getContents(o.GetMainURL(INetURLObject::DecodeMechanism::NONE)));
318
0
        return std::any_of(cs.begin(), cs.end(),
319
0
            [&name](const OUString& rItem) {
320
0
                return INetURLObject(rItem).
321
0
                    getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset).
322
0
                        equalsIgnoreAsciiCase(name); });
323
0
    }
324
0
}
325
326
bool utl::UCBContentHelper::IsSubPath(
327
    OUString const & parent, OUString const & child)
328
0
{
329
    // The comparison is done in the following way:
330
    // - First, compare case sensitively
331
    // - If names are different, try a fallback comparing case insensitively
332
    // - If the last comparison succeeded, get case preserving normalized names
333
    //   for the files and compare them
334
    // (The second step is required because retrieving the normalized names
335
    // might be very expensive in some cases.)
336
0
    INetURLObject candidate(child);
337
0
    INetURLObject folder(parent);
338
0
    if (candidate.GetProtocol() != folder.GetProtocol()) {
339
0
        return false;
340
0
    }
341
0
    INetURLObject candidateLower(child.toAsciiLowerCase());
342
0
    INetURLObject folderLower(parent.toAsciiLowerCase());
343
0
    try {
344
0
        INetURLObject tmp;
345
0
        do {
346
0
            if (candidate == folder
347
0
                || (candidate.GetProtocol() == INetProtocol::File
348
0
                    && candidateLower == folderLower
349
0
                    && (getCasePreservingUrl(candidate)
350
0
                        == getCasePreservingUrl(folder))))
351
0
            {
352
0
                return true;
353
0
            }
354
0
            tmp = candidate;
355
0
        } while (candidate.removeSegment() && candidateLower.removeSegment()
356
0
                 && candidate != tmp);
357
            // INetURLObject::removeSegment sometimes returns true without
358
            // modifying the URL, e.g., in case of "file:///"
359
0
    } catch (css::uno::RuntimeException const &) {
360
0
        throw;
361
0
    } catch (css::ucb::CommandAbortedException const &) {
362
0
        assert(false && "this cannot happen");
363
0
        throw;
364
0
    } catch (css::uno::Exception const &) {
365
0
        TOOLS_INFO_EXCEPTION(
366
0
            "unotools.ucbhelper",
367
0
            "UCBContentHelper::IsSubPath(" << parent << ", " << child << ")");
368
0
    }
369
0
    return false;
370
0
}
371
372
bool utl::UCBContentHelper::EqualURLs(
373
    OUString const & url1, OUString const & url2)
374
0
{
375
0
    if (url1.isEmpty() || url2.isEmpty()) {
376
0
        return false;
377
0
    }
378
0
    css::uno::Reference< css::ucb::XUniversalContentBroker > ucb(
379
0
        css::ucb::UniversalContentBroker::create(
380
0
            comphelper::getProcessComponentContext()));
381
0
    return
382
0
        ucb->compareContentIds(
383
0
            ucb->createContentIdentifier(canonic(url1)),
384
0
            ucb->createContentIdentifier(canonic(url2)))
385
0
        == 0;
386
0
}
387
388
bool utl::UCBContentHelper::ensureFolder(
389
    const css::uno::Reference< css::uno::XComponentContext >& xCtx,
390
    const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
391
    std::u16string_view rFolder, ucbhelper::Content & result) noexcept
392
0
{
393
0
    try
394
0
    {
395
0
        INetURLObject aURL( rFolder );
396
0
        OUString aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
397
0
        aURL.removeSegment();
398
0
        ::ucbhelper::Content aParent;
399
400
0
        if ( ::ucbhelper::Content::create( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
401
0
                                  xEnv, xCtx, aParent ) )
402
0
        {
403
0
            return ::utl::UCBContentHelper::MakeFolder(aParent, aTitle, result);
404
0
        }
405
0
    }
406
0
    catch (...)
407
0
    {
408
0
    }
409
410
0
    return false;
411
0
}
412
413
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */