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