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