summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qurl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io/qurl.cpp')
-rw-r--r--src/corelib/io/qurl.cpp248
1 files changed, 135 insertions, 113 deletions
diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp
index 14d89a9ea8..4360b5b076 100644
--- a/src/corelib/io/qurl.cpp
+++ b/src/corelib/io/qurl.cpp
@@ -14,14 +14,18 @@
\ingroup network
\ingroup shared
+ \compares weak
It can parse and construct URLs in both encoded and unencoded
form. QUrl also has support for internationalized domain names
(IDNs).
- The most common way to use QUrl is to initialize it via the
- constructor by passing a QString. Otherwise, setUrl() can also
- be used.
+ The most common way to use QUrl is to initialize it via the constructor by
+ passing a QString containing a full URL. QUrl objects can also be created
+ from a QByteArray containing a full URL using QUrl::fromEncoded(), or
+ heuristically from incomplete URLs using QUrl::fromUserInput(). The URL
+ representation can be obtained from a QUrl using either QUrl::toString() or
+ QUrl::toEncoded().
URLs can be represented in two forms: encoded or unencoded. The
unencoded representation is suitable for showing to users, but
@@ -400,18 +404,17 @@
#include "private/qipaddress_p.h"
#include "qurlquery.h"
#include "private/qdir_p.h"
+#include <private/qtools_p.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-
-// in qstring.cpp:
-void qt_from_latin1(char16_t *dst, const char *str, size_t size) noexcept;
+using namespace QtMiscUtils;
inline static bool isHex(char c)
{
c |= 0x20;
- return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
+ return isAsciiDigit(c) || (c >= 'a' && c <= 'f');
}
static inline QString ftpScheme()
@@ -510,7 +513,7 @@ public:
ErrorCode validityError(QString *source = nullptr, qsizetype *position = nullptr) const;
bool validateComponent(Section section, const QString &input, qsizetype begin, qsizetype end);
bool validateComponent(Section section, const QString &input)
- { return validateComponent(section, input, 0, input.length()); }
+ { return validateComponent(section, input, 0, input.size()); }
// no QString scheme() const;
void appendAuthority(QString &appendTo, QUrl::FormattingOptions options, Section appendingTo) const;
@@ -819,14 +822,16 @@ recodeFromUser(const QString &input, const ushort *actions, qsizetype from, qsiz
static inline void appendToUser(QString &appendTo, QStringView value, QUrl::FormattingOptions options,
const ushort *actions)
{
- // Test ComponentFormattingOptions, ignore FormattingOptions.
- if ((options & 0xFFFF0000) == QUrl::PrettyDecoded) {
+ // The stored value is already QUrl::PrettyDecoded, so there's nothing to
+ // do if that's what the user asked for (test only
+ // ComponentFormattingOptions, ignore FormattingOptions).
+ if ((options & 0xFFFF0000) == QUrl::PrettyDecoded ||
+ !qt_urlRecode(appendTo, value, options, actions))
appendTo += value;
- return;
- }
- if (!qt_urlRecode(appendTo, value, options, actions))
- appendTo += value;
+ // copy nullness, if necessary, because QString::operator+=(QStringView) doesn't
+ if (appendTo.isNull() && !value.isNull())
+ appendTo.detach();
}
inline void QUrlPrivate::appendAuthority(QString &appendTo, QUrl::FormattingOptions options, Section appendingTo) const
@@ -918,7 +923,7 @@ inline void QUrlPrivate::appendPath(QString &appendTo, QUrl::FormattingOptions o
}
// check if we need to remove trailing slashes
if (options & QUrl::StripTrailingSlash) {
- while (thePathView.length() > 1 && thePathView.endsWith(u'/'))
+ while (thePathView.size() > 1 && thePathView.endsWith(u'/'))
thePathView.chop(1);
}
@@ -960,14 +965,14 @@ inline bool QUrlPrivate::setScheme(const QString &value, qsizetype len, bool doS
qsizetype needsLowercasing = -1;
const ushort *p = reinterpret_cast<const ushort *>(value.data());
for (qsizetype i = 0; i < len; ++i) {
- if (p[i] >= 'a' && p[i] <= 'z')
+ if (isAsciiLower(p[i]))
continue;
- if (p[i] >= 'A' && p[i] <= 'Z') {
+ if (isAsciiUpper(p[i])) {
needsLowercasing = i;
continue;
}
if (i) {
- if (p[i] >= '0' && p[i] <= '9')
+ if (isAsciiDigit(p[i]))
continue;
if (p[i] == '+' || p[i] == '-' || p[i] == '.')
continue;
@@ -988,7 +993,7 @@ inline bool QUrlPrivate::setScheme(const QString &value, qsizetype len, bool doS
QChar *schemeData = scheme.data(); // force detaching here
for (qsizetype i = needsLowercasing; i >= 0; --i) {
ushort c = schemeData[i].unicode();
- if (c >= 'A' && c <= 'Z')
+ if (isAsciiUpper(c))
schemeData[i] = QChar(c + 0x20);
}
}
@@ -1040,7 +1045,7 @@ inline void QUrlPrivate::setAuthority(const QString &auth, qsizetype from, qsize
unsigned long x = 0;
for (qsizetype i = colonIndex + 1; i < end; ++i) {
ushort c = auth.at(i).unicode();
- if (c >= '0' && c <= '9') {
+ if (isAsciiDigit(c)) {
x *= 10;
x += c - '0';
} else {
@@ -1182,16 +1187,14 @@ static const QChar *parseIpFuture(QString &host, const QChar *begin, const QChar
const QChar *const origBegin = begin;
if (begin[3].unicode() != '.')
return &begin[3];
- if ((begin[2].unicode() >= 'A' && begin[2].unicode() <= 'F') ||
- (begin[2].unicode() >= 'a' && begin[2].unicode() <= 'f') ||
- (begin[2].unicode() >= '0' && begin[2].unicode() <= '9')) {
+ if (isHexDigit(begin[2].unicode())) {
// this is so unlikely that we'll just go down the slow path
// decode the whole string, skipping the "[vH." and "]" which we already know to be there
host += QStringView(begin, 4);
// uppercase the version, if necessary
if (begin[2].unicode() >= 'a')
- host[host.length() - 2] = QChar{begin[2].unicode() - 0x20};
+ host[host.size() - 2] = QChar{begin[2].unicode() - 0x20};
begin += 4;
--end;
@@ -1203,11 +1206,7 @@ static const QChar *parseIpFuture(QString &host, const QChar *begin, const QChar
}
for ( ; begin != end; ++begin) {
- if (begin->unicode() >= 'A' && begin->unicode() <= 'Z')
- host += *begin;
- else if (begin->unicode() >= 'a' && begin->unicode() <= 'z')
- host += *begin;
- else if (begin->unicode() >= '0' && begin->unicode() <= '9')
+ if (isAsciiLetterOrNumber(begin->unicode()))
host += *begin;
else if (begin->unicode() < 0x80 && strchr(acceptable, begin->unicode()) != nullptr)
host += *begin;
@@ -1341,7 +1340,7 @@ QUrlPrivate::setHost(const QString &value, qsizetype from, qsizetype iend, QUrl:
}
// recurse
- return setHost(s, 0, s.length(), QUrl::StrictMode);
+ return setHost(s, 0, s.size(), QUrl::StrictMode);
}
s = qt_ACE_do(value.mid(from, iend - from), NormalizeAce, ForbidLeadingDot, {});
@@ -1377,7 +1376,7 @@ inline void QUrlPrivate::parse(const QString &url, QUrl::ParsingMode parsingMode
qsizetype colon = -1;
qsizetype question = -1;
qsizetype hash = -1;
- const qsizetype len = url.length();
+ const qsizetype len = url.size();
const QChar *const begin = url.constData();
const ushort *const data = reinterpret_cast<const ushort *>(begin);
@@ -1628,7 +1627,7 @@ inline QUrlPrivate::ErrorCode QUrlPrivate::validityError(QString *source, qsizet
if (path.isEmpty())
return NoError;
if (path.at(0) == u'/') {
- if (hasAuthority() || path.length() == 1 || path.at(1) != u'/')
+ if (hasAuthority() || path.size() == 1 || path.at(1) != u'/')
return NoError;
if (source) {
*source = path;
@@ -1648,7 +1647,7 @@ inline QUrlPrivate::ErrorCode QUrlPrivate::validityError(QString *source, qsizet
return NoError;
// check for a path of "text:text/"
- for (qsizetype i = 0; i < path.length(); ++i) {
+ for (qsizetype i = 0; i < path.size(); ++i) {
ushort c = path.at(i).unicode();
if (c == '/') {
// found the slash before the colon
@@ -1792,7 +1791,20 @@ inline void QUrlPrivate::validate() const
/*!
- Constructs a URL by parsing \a url. QUrl will automatically percent encode
+ Constructs a URL by parsing \a url. Note this constructor expects a proper
+ URL or URL-Reference and will not attempt to guess intent. For example, the
+ following declaration:
+
+ \snippet code/src_corelib_io_qurl.cpp constructor-url-reference
+
+ Will construct a valid URL but it may not be what one expects, as the
+ scheme() part of the input is missing. For a string like the above,
+ applications may want to use fromUserInput(). For this constructor or
+ setUrl(), the following is probably what was intended:
+
+ \snippet code/src_corelib_io_qurl.cpp constructor-url
+
+ QUrl will automatically percent encode
all characters that are not allowed in a URL and decode the percent-encoded
sequences that represent an unreserved character (letters, digits, hyphens,
underscores, dots and tildes). All other characters are left in their
@@ -1956,7 +1968,7 @@ void QUrl::setScheme(const QString &scheme)
d->flags &= ~QUrlPrivate::IsLocalFile;
d->scheme.clear();
} else {
- d->setScheme(scheme, scheme.length(), /* do set error */ true);
+ d->setScheme(scheme, scheme.size(), /* do set error */ true);
}
}
@@ -2016,7 +2028,7 @@ void QUrl::setAuthority(const QString &authority, ParsingMode mode)
return;
}
- d->setAuthority(authority, 0, authority.length(), mode);
+ d->setAuthority(authority, 0, authority.size(), mode);
if (authority.isNull()) {
// QUrlPrivate::setAuthority cleared almost everything
// but it leaves the Host bit set
@@ -2087,7 +2099,7 @@ void QUrl::setUserInfo(const QString &userInfo, ParsingMode mode)
return;
}
- d->setUserInfo(trimmed, 0, trimmed.length());
+ d->setUserInfo(trimmed, 0, trimmed.size());
if (userInfo.isNull()) {
// QUrlPrivate::setUserInfo cleared almost everything
// but it leaves the UserName bit set
@@ -2159,7 +2171,7 @@ void QUrl::setUserName(const QString &userName, ParsingMode mode)
mode = TolerantMode;
}
- d->setUserName(data, 0, data.length());
+ d->setUserName(data, 0, data.size());
if (userName.isNull())
d->sectionIsPresent &= ~QUrlPrivate::UserName;
else if (mode == StrictMode && !d->validateComponent(QUrlPrivate::UserName, userName))
@@ -2222,7 +2234,7 @@ void QUrl::setPassword(const QString &password, ParsingMode mode)
mode = TolerantMode;
}
- d->setPassword(data, 0, data.length());
+ d->setPassword(data, 0, data.size());
if (password.isNull())
d->sectionIsPresent &= ~QUrlPrivate::Password;
else if (mode == StrictMode && !d->validateComponent(QUrlPrivate::Password, password))
@@ -2284,7 +2296,7 @@ void QUrl::setHost(const QString &host, ParsingMode mode)
mode = TolerantMode;
}
- if (d->setHost(data, 0, data.length(), mode)) {
+ if (d->setHost(data, 0, data.size(), mode)) {
if (host.isNull())
d->sectionIsPresent &= ~QUrlPrivate::Host;
} else if (!data.startsWith(u'[')) {
@@ -2293,7 +2305,7 @@ void QUrl::setHost(const QString &host, ParsingMode mode)
data.prepend(u'[');
data.append(u']');
- if (!d->setHost(data, 0, data.length(), mode)) {
+ if (!d->setHost(data, 0, data.size(), mode)) {
// failed again
if (data.contains(u':')) {
// source data contains ':', so it's an IPv6 error
@@ -2330,7 +2342,7 @@ QString QUrl::host(ComponentFormattingOptions options) const
if (d) {
d->appendHost(result, options);
if (result.startsWith(u'['))
- result = result.mid(1, result.length() - 2);
+ result = result.mid(1, result.size() - 2);
}
return result;
}
@@ -2409,7 +2421,7 @@ void QUrl::setPath(const QString &path, ParsingMode mode)
mode = TolerantMode;
}
- d->setPath(data, 0, data.length());
+ d->setPath(data, 0, data.size());
// optimized out, since there is no path delimiter
// if (path.isNull())
@@ -2545,7 +2557,7 @@ void QUrl::setQuery(const QString &query, ParsingMode mode)
mode = TolerantMode;
}
- d->setQuery(data, 0, data.length());
+ d->setQuery(data, 0, data.size());
if (query.isNull())
d->sectionIsPresent &= ~QUrlPrivate::Query;
else if (mode == StrictMode && !d->validateComponent(QUrlPrivate::Query, query))
@@ -2643,7 +2655,7 @@ void QUrl::setFragment(const QString &fragment, ParsingMode mode)
mode = TolerantMode;
}
- d->setFragment(data, 0, data.length());
+ d->setFragment(data, 0, data.size());
if (fragment.isNull())
d->sectionIsPresent &= ~QUrlPrivate::Fragment;
else if (mode == StrictMode && !d->validateComponent(QUrlPrivate::Fragment, fragment))
@@ -2937,7 +2949,7 @@ QUrl QUrl::adjusted(QUrl::FormattingOptions options) const
that.detach();
QString path;
d->appendPath(path, options | FullyEncoded, QUrlPrivate::Path);
- that.d->setPath(path, 0, path.length());
+ that.d->setPath(path, 0, path.size());
}
return that;
}
@@ -2958,19 +2970,23 @@ QByteArray QUrl::toEncoded(FormattingOptions options) const
}
/*!
- \fn QUrl QUrl::fromEncoded(const QByteArray &input, ParsingMode parsingMode)
-
Parses \a input and returns the corresponding QUrl. \a input is
assumed to be in encoded form, containing only ASCII characters.
- Parses the URL using \a parsingMode. See setUrl() for more information on
+ Parses the URL using \a mode. See setUrl() for more information on
this parameter. QUrl::DecodedMode is not permitted in this context.
+ \note In Qt versions prior to 6.7, this function took a QByteArray, not
+ QByteArrayView. If you experience compile errors, it's because your code
+ is passing objects that are implicitly convertible to QByteArray, but not
+ QByteArrayView. Wrap the corresponding argument in \c{QByteArray{~~~}} to
+ make the cast explicit. This is backwards-compatible with old Qt versions.
+
\sa toEncoded(), setUrl()
*/
-QUrl QUrl::fromEncoded(const QByteArray &input, ParsingMode mode)
+QUrl QUrl::fromEncoded(QByteArrayView input, ParsingMode mode)
{
- return QUrl(QString::fromUtf8(input.constData(), input.size()), mode);
+ return QUrl(QString::fromUtf8(input), mode);
}
/*!
@@ -3053,88 +3069,101 @@ QByteArray QUrl::toAce(const QString &domain, AceProcessingOptions options)
/*!
\internal
- Returns \c true if this URL is "less than" the given \a url. This
+ \fn bool QUrl::operator<(const QUrl &lhs, const QUrl &rhs)
+
+ Returns \c true if URL \a lhs is "less than" URL \a rhs. This
provides a means of ordering URLs.
*/
-bool QUrl::operator <(const QUrl &url) const
+
+Qt::weak_ordering compareThreeWay(const QUrl &lhs, const QUrl &rhs)
{
- if (!d || !url.d) {
- bool thisIsEmpty = !d || d->isEmpty();
- bool thatIsEmpty = !url.d || url.d->isEmpty();
+ if (!lhs.d || !rhs.d) {
+ bool thisIsEmpty = !lhs.d || lhs.d->isEmpty();
+ bool thatIsEmpty = !rhs.d || rhs.d->isEmpty();
// sort an empty URL first
- return thisIsEmpty && !thatIsEmpty;
+ if (thisIsEmpty) {
+ if (!thatIsEmpty)
+ return Qt::weak_ordering::less;
+ else
+ return Qt::weak_ordering::equivalent;
+ } else {
+ return Qt::weak_ordering::greater;
+ }
}
int cmp;
- cmp = d->scheme.compare(url.d->scheme);
+ cmp = lhs.d->scheme.compare(rhs.d->scheme);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- cmp = d->userName.compare(url.d->userName);
+ cmp = lhs.d->userName.compare(rhs.d->userName);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- cmp = d->password.compare(url.d->password);
+ cmp = lhs.d->password.compare(rhs.d->password);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- cmp = d->host.compare(url.d->host);
+ cmp = lhs.d->host.compare(rhs.d->host);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- if (d->port != url.d->port)
- return d->port < url.d->port;
+ if (lhs.d->port != rhs.d->port)
+ return Qt::compareThreeWay(lhs.d->port, rhs.d->port);
- cmp = d->path.compare(url.d->path);
+ cmp = lhs.d->path.compare(rhs.d->path);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- if (d->hasQuery() != url.d->hasQuery())
- return url.d->hasQuery();
+ if (lhs.d->hasQuery() != rhs.d->hasQuery())
+ return rhs.d->hasQuery() ? Qt::weak_ordering::less : Qt::weak_ordering::greater;
- cmp = d->query.compare(url.d->query);
+ cmp = lhs.d->query.compare(rhs.d->query);
if (cmp != 0)
- return cmp < 0;
+ return Qt::compareThreeWay(cmp, 0);
- if (d->hasFragment() != url.d->hasFragment())
- return url.d->hasFragment();
+ if (lhs.d->hasFragment() != rhs.d->hasFragment())
+ return rhs.d->hasFragment() ? Qt::weak_ordering::less : Qt::weak_ordering::greater;
- cmp = d->fragment.compare(url.d->fragment);
- return cmp < 0;
+ cmp = lhs.d->fragment.compare(rhs.d->fragment);
+ return Qt::compareThreeWay(cmp, 0);
}
/*!
- Returns \c true if this URL and the given \a url are equal;
+ \fn bool QUrl::operator==(const QUrl &lhs, const QUrl &rhs)
+
+ Returns \c true if \a lhs and \a rhs URLs are equivalent;
otherwise returns \c false.
\sa matches()
*/
-bool QUrl::operator ==(const QUrl &url) const
+
+bool comparesEqual(const QUrl &lhs, const QUrl &rhs)
{
- if (!d && !url.d)
+ if (!lhs.d && !rhs.d)
return true;
- if (!d)
- return url.d->isEmpty();
- if (!url.d)
- return d->isEmpty();
+ if (!lhs.d)
+ return rhs.d->isEmpty();
+ if (!rhs.d)
+ return lhs.d->isEmpty();
// First, compare which sections are present, since it speeds up the
// processing considerably. We just have to ignore the host-is-present flag
// for local files (the "file" protocol), due to the requirements of the
// XDG file URI specification.
int mask = QUrlPrivate::FullUrl;
- if (isLocalFile())
+ if (lhs.isLocalFile())
mask &= ~QUrlPrivate::Host;
- return (d->sectionIsPresent & mask) == (url.d->sectionIsPresent & mask) &&
- d->scheme == url.d->scheme &&
- d->userName == url.d->userName &&
- d->password == url.d->password &&
- d->host == url.d->host &&
- d->port == url.d->port &&
- d->path == url.d->path &&
- d->query == url.d->query &&
- d->fragment == url.d->fragment;
+ return (lhs.d->sectionIsPresent & mask) == (rhs.d->sectionIsPresent & mask) &&
+ lhs.d->scheme == rhs.d->scheme &&
+ lhs.d->userName == rhs.d->userName &&
+ lhs.d->password == rhs.d->password &&
+ lhs.d->host == rhs.d->host &&
+ lhs.d->port == rhs.d->port &&
+ lhs.d->path == rhs.d->path &&
+ lhs.d->query == rhs.d->query &&
+ lhs.d->fragment == rhs.d->fragment;
}
/*!
@@ -3214,15 +3243,13 @@ bool QUrl::matches(const QUrl &url, FormattingOptions options) const
}
/*!
- Returns \c true if this URL and the given \a url are not equal;
+ \fn bool QUrl::operator !=(const QUrl &lhs, const QUrl &rhs)
+
+ Returns \c true if \a lhs and \a rhs URLs are not equal;
otherwise returns \c false.
\sa matches()
*/
-bool QUrl::operator !=(const QUrl &url) const
-{
- return !(*this == url);
-}
/*!
Assigns the specified \a url to this object.
@@ -3346,7 +3373,7 @@ QUrl QUrl::fromLocalFile(const QString &localFile)
QString deslashified = fromNativeSeparators(localFile);
// magic for drives on windows
- if (deslashified.length() > 1 && deslashified.at(1) == u':' && deslashified.at(0) != u'/') {
+ if (deslashified.size() > 1 && deslashified.at(1) == u':' && deslashified.at(0) != u'/') {
deslashified.prepend(u'/');
} else if (deslashified.startsWith("//"_L1)) {
// magic for shared drive on windows
@@ -3367,7 +3394,7 @@ QUrl QUrl::fromLocalFile(const QString &localFile)
// Path hostname is not a valid URL host, so set it entirely in the path
// (by leaving deslashified unchanged)
} else if (indexOfPath > 2) {
- deslashified = deslashified.right(deslashified.length() - indexOfPath);
+ deslashified = deslashified.right(deslashified.size() - indexOfPath);
} else {
deslashified.clear();
}
@@ -3431,16 +3458,16 @@ bool QUrl::isParentOf(const QUrl &childUrl) const
if (!d)
return ((childUrl.scheme().isEmpty())
&& (childUrl.authority().isEmpty())
- && childPath.length() > 0 && childPath.at(0) == u'/');
+ && childPath.size() > 0 && childPath.at(0) == u'/');
QString ourPath = path();
return ((childUrl.scheme().isEmpty() || d->scheme == childUrl.scheme())
&& (childUrl.authority().isEmpty() || authority() == childUrl.authority())
&& childPath.startsWith(ourPath)
- && ((ourPath.endsWith(u'/') && childPath.length() > ourPath.length())
- || (!ourPath.endsWith(u'/') && childPath.length() > ourPath.length()
- && childPath.at(ourPath.length()) == u'/')));
+ && ((ourPath.endsWith(u'/') && childPath.size() > ourPath.size())
+ || (!ourPath.endsWith(u'/') && childPath.size() > ourPath.size()
+ && childPath.at(ourPath.size()) == u'/')));
}
@@ -3488,15 +3515,12 @@ QDebug operator<<(QDebug d, const QUrl &url)
static QString errorMessage(QUrlPrivate::ErrorCode errorCode, const QString &errorSource, qsizetype errorPosition)
{
- QChar c = size_t(errorPosition) < size_t(errorSource.length()) ?
+ QChar c = size_t(errorPosition) < size_t(errorSource.size()) ?
errorSource.at(errorPosition) : QChar(QChar::Null);
switch (errorCode) {
case QUrlPrivate::NoError:
- Q_ASSERT_X(false, "QUrl::errorString",
- "Impossible: QUrl::errorString should have treated this condition");
- Q_UNREACHABLE();
- return QString();
+ Q_UNREACHABLE_RETURN(QString()); // QUrl::errorString should have treated this condition
case QUrlPrivate::InvalidSchemeError: {
auto msg = "Invalid scheme (character '%1' not permitted)"_L1;
@@ -3553,9 +3577,7 @@ static QString errorMessage(QUrlPrivate::ErrorCode errorCode, const QString &err
return QStringLiteral("Relative URL's path component contains ':' before any '/'");
}
- Q_ASSERT_X(false, "QUrl::errorString", "Cannot happen, unknown error");
- Q_UNREACHABLE();
- return QString();
+ Q_UNREACHABLE_RETURN(QString());
}
static inline void appendComponentIfPresent(QString &msg, bool present, const char *componentName,