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     ucbhelper::CommandEnvironment* pCommandEnv =
127         new ::ucbhelper::CommandEnvironment(
128             new comphelper::SimpleFileAccessInteraction( xIH ), xProgress );
129 
130     css::uno::Reference < css::ucb::XCommandEnvironment > xEnv(
131         static_cast< css::ucb::XCommandEnvironment* >(pCommandEnv), css::uno::UNO_QUERY );
132     return xEnv;
133 }
134 
IsDocument(OUString const & url)135 bool utl::UCBContentHelper::IsDocument(OUString const & url) {
136     try {
137         return content(url).isDocument();
138     } catch (css::uno::RuntimeException const &) {
139         throw;
140     } catch (css::ucb::CommandAbortedException const &) {
141         assert(false && "this cannot happen");
142         throw;
143     } catch (css::uno::Exception const &) {
144         TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::IsDocument(" << url << ")");
145         return false;
146     }
147 }
148 
GetProperty(OUString const & url,OUString const & property)149 css::uno::Any utl::UCBContentHelper::GetProperty(
150     OUString const & url, OUString const & property)
151 {
152     try {
153         return content(url).getPropertyValue(property);
154     } catch (css::uno::RuntimeException const &) {
155         throw;
156     } catch (css::ucb::CommandAbortedException const &) {
157         assert(false && "this cannot happen");
158         throw;
159     } catch (css::uno::Exception const &) {
160         TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::GetProperty(" << url << ", " << property << ")");
161         return css::uno::Any();
162     }
163 }
164 
IsFolder(OUString const & url)165 bool utl::UCBContentHelper::IsFolder(OUString const & url) {
166     try {
167         return content(url).isFolder();
168     } catch (css::uno::RuntimeException const &) {
169         throw;
170     } catch (css::ucb::CommandAbortedException const &) {
171         assert(false && "this cannot happen");
172         throw;
173     } catch (css::uno::Exception const &) {
174         TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::IsFolder(" << url << ")");
175         return false;
176     }
177 }
178 
GetTitle(OUString const & url,OUString * title)179 bool utl::UCBContentHelper::GetTitle(
180     OUString const & url, OUString * title)
181 {
182     assert(title != nullptr);
183     try {
184         return content(url).getPropertyValue("Title") >>= *title;
185     } catch (css::uno::RuntimeException const &) {
186         throw;
187     } catch (css::ucb::CommandAbortedException const &) {
188         assert(false && "this cannot happen");
189         throw;
190     } catch (css::uno::Exception const &) {
191         TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::GetTitle(" << url << ")");
192         return false;
193     }
194 }
195 
Kill(OUString const & url)196 bool utl::UCBContentHelper::Kill(OUString const & url) {
197     try {
198         content(url).executeCommand(
199             "delete",
200             css::uno::makeAny(true));
201         return true;
202     } catch (css::uno::RuntimeException const &) {
203         throw;
204     } catch (css::ucb::CommandAbortedException const &) {
205         assert(false && "this cannot happen");
206         throw;
207     } catch (css::uno::Exception const &) {
208         TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::Kill(" << url << ")");
209         return false;
210     }
211 }
212 
MakeFolder(ucbhelper::Content & parent,OUString const & title,ucbhelper::Content & result)213 bool utl::UCBContentHelper::MakeFolder(
214     ucbhelper::Content & parent, OUString const & title,
215     ucbhelper::Content & result)
216 {
217     bool exists = false;
218     try {
219         const css::uno::Sequence<css::ucb::ContentInfo> info(
220             parent.queryCreatableContentsInfo());
221         for (const auto& rInfo : info) {
222             // Simply look for the first KIND_FOLDER:
223             if ((rInfo.Attributes
224                  & css::ucb::ContentInfoAttribute::KIND_FOLDER)
225                 != 0)
226             {
227                 // Make sure the only required bootstrap property is "Title":
228                 if ( rInfo.Properties.getLength() != 1 || rInfo.Properties[0].Name != "Title" )
229                 {
230                     continue;
231                 }
232                 css::uno::Sequence<OUString> keys { "Title" };
233                 css::uno::Sequence<css::uno::Any> values(1);
234                 values[0] <<= title;
235                 if (parent.insertNewContent(rInfo.Type, keys, values, result))
236                 {
237                     return true;
238                 }
239             }
240         }
241     } catch (css::ucb::InteractiveIOException const & e) {
242         if (e.Code == css::ucb::IOErrorCode_ALREADY_EXISTING) {
243             exists = true;
244         } else {
245             TOOLS_INFO_EXCEPTION(
246                 "unotools.ucbhelper",
247                 "UCBContentHelper::MakeFolder(" << title << ")");
248         }
249     } catch (css::ucb::NameClashException const &) {
250         exists = true;
251     } catch (css::uno::RuntimeException const &) {
252         throw;
253     } catch (css::ucb::CommandAbortedException const &) {
254         assert(false && "this cannot happen");
255         throw;
256     } catch (css::uno::Exception const &) {
257         TOOLS_INFO_EXCEPTION(
258             "unotools.ucbhelper",
259             "UCBContentHelper::MakeFolder(" << title << ") ");
260     }
261     if (exists) {
262         INetURLObject o(parent.getURL());
263         o.Append(title);
264         result = content(o);
265         return true;
266     } else {
267         return false;
268     }
269 }
270 
IsYounger(OUString const & younger,OUString const & older)271 bool utl::UCBContentHelper::IsYounger(
272     OUString const & younger, OUString const & older)
273 {
274     try {
275         return
276             convert(
277                 content(younger).getPropertyValue(
278                     "DateModified").
279                 get<css::util::DateTime>())
280             > convert(
281                 content(older).getPropertyValue(
282                     "DateModified").
283                 get<css::util::DateTime>());
284     } catch (css::uno::RuntimeException const &) {
285         throw;
286     } catch (css::ucb::CommandAbortedException const &) {
287         assert(false && "this cannot happen");
288         throw;
289     } catch (css::uno::Exception const &) {
290         TOOLS_INFO_EXCEPTION(
291             "unotools.ucbhelper",
292             "UCBContentHelper::IsYounger(" << younger << ", " << older << ")");
293         return false;
294     }
295 }
296 
Exists(OUString const & url)297 bool utl::UCBContentHelper::Exists(OUString const & url) {
298     OUString pathname;
299     if (osl::FileBase::getSystemPathFromFileURL(url, pathname)
300         == osl::FileBase::E_None)
301     {
302         // Try to create a directory entry for the given URL:
303         OUString url2;
304         if (osl::FileBase::getFileURLFromSystemPath(pathname, url2)
305             == osl::FileBase::E_None)
306         {
307             // #106526 osl_getDirectoryItem is an existence check, no further
308             // osl_getFileStatus call necessary:
309             osl::DirectoryItem item;
310             return osl::DirectoryItem::get(url2, item) == osl::FileBase::E_None;
311         } else {
312             return false;
313         }
314     } else {
315         // Divide URL into folder and name part:
316         INetURLObject o(url);
317         OUString name(
318             o.getName(
319                 INetURLObject::LAST_SEGMENT, true,
320                 INetURLObject::DecodeMechanism::WithCharset));
321         o.removeSegment();
322         o.removeFinalSlash();
323         std::vector<OUString> cs(
324             getContents(o.GetMainURL(INetURLObject::DecodeMechanism::NONE)));
325         return std::any_of(cs.begin(), cs.end(),
326             [&name](const OUString& rItem) {
327                 return INetURLObject(rItem).
328                     getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset).
329                         equalsIgnoreAsciiCase(name); });
330     }
331 }
332 
IsSubPath(OUString const & parent,OUString const & child)333 bool utl::UCBContentHelper::IsSubPath(
334     OUString const & parent, OUString const & child)
335 {
336     // The comparison is done in the following way:
337     // - First, compare case sensitively
338     // - If names are different, try a fallback comparing case insensitively
339     // - If the last comparison succeeded, get case preserving normalized names
340     //   for the files and compare them
341     // (The second step is required because retrieving the normalized names
342     // might be very expensive in some cases.)
343     INetURLObject candidate(child);
344     INetURLObject folder(parent);
345     if (candidate.GetProtocol() != folder.GetProtocol()) {
346         return false;
347     }
348     INetURLObject candidateLower(child.toAsciiLowerCase());
349     INetURLObject folderLower(parent.toAsciiLowerCase());
350     try {
351         INetURLObject tmp;
352         do {
353             if (candidate == folder
354                 || (candidate.GetProtocol() == INetProtocol::File
355                     && candidateLower == folderLower
356                     && (getCasePreservingUrl(candidate)
357                         == getCasePreservingUrl(folder))))
358             {
359                 return true;
360             }
361             tmp = candidate;
362         } while (candidate.removeSegment() && candidateLower.removeSegment()
363                  && candidate != tmp);
364             // INetURLObject::removeSegment sometimes returns true without
365             // modifying the URL, e.g., in case of "file:///"
366     } catch (css::uno::RuntimeException const &) {
367         throw;
368     } catch (css::ucb::CommandAbortedException const &) {
369         assert(false && "this cannot happen");
370         throw;
371     } catch (css::uno::Exception const &) {
372         TOOLS_INFO_EXCEPTION(
373             "unotools.ucbhelper",
374             "UCBContentHelper::IsSubPath(" << parent << ", " << child << ")");
375     }
376     return false;
377 }
378 
EqualURLs(OUString const & url1,OUString const & url2)379 bool utl::UCBContentHelper::EqualURLs(
380     OUString const & url1, OUString const & url2)
381 {
382     if (url1.isEmpty() || url2.isEmpty()) {
383         return false;
384     }
385     css::uno::Reference< css::ucb::XUniversalContentBroker > ucb(
386         css::ucb::UniversalContentBroker::create(
387             comphelper::getProcessComponentContext()));
388     return
389         ucb->compareContentIds(
390             ucb->createContentIdentifier(canonic(url1)),
391             ucb->createContentIdentifier(canonic(url2)))
392         == 0;
393 }
394 
ensureFolder(const css::uno::Reference<css::uno::XComponentContext> & xCtx,const css::uno::Reference<css::ucb::XCommandEnvironment> & xEnv,const OUString & rFolder,ucbhelper::Content & result)395 bool utl::UCBContentHelper::ensureFolder(
396     const css::uno::Reference< css::uno::XComponentContext >& xCtx,
397     const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
398     const OUString& rFolder, ucbhelper::Content & result) throw()
399 {
400     try
401     {
402         INetURLObject aURL( rFolder );
403         OUString aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
404         aURL.removeSegment();
405         ::ucbhelper::Content aParent;
406 
407         if ( ::ucbhelper::Content::create( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
408                                   xEnv, xCtx, aParent ) )
409         {
410             return ::utl::UCBContentHelper::MakeFolder(aParent, aTitle, result);
411         }
412     }
413     catch (...)
414     {
415     }
416 
417     return false;
418 }
419 
420 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
421