kdelibs/kio/bookmarks/kbookmark.cc
Ivailo Monev b0d0a4ee18 kio: do not convert the element attribute to latin1 in KBookmark::url()
that will only convert it back to QString in the KUrl constructor but it
will not be from UTF-8 bytes - the attribute string is converted to latin1,
duh

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2023-07-05 05:47:03 +03:00

684 lines
19 KiB
C++

// -*- c-basic-offset:4; indent-tabs-mode:nil -*-
// vim: set ts=4 sts=4 sw=4 et:
/* This file is part of the KDE libraries
Copyright (C) 2000 David Faure <faure@kde.org>
Copyright (C) 2003 Alexander Kellett <lypanov@kde.org>
Copyright (C) 2008 Norbert Frese <nf2@scheinwelt.at>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kbookmark.h"
#include <QStack>
#include <QDateTime>
#include <kdebug.h>
#include <kmimetype.h>
#include <kstringhandler.h>
#include <kglobal.h>
#include <klocale.h>
#include <kbookmarkmanager.h>
#include <assert.h>
#define METADATA_KDE_OWNER "http://www.kde.org"
#define METADATA_FREEDESKTOP_OWNER "http://freedesktop.org"
#define METADATA_MIME_OWNER "http://www.freedesktop.org/standards/shared-mime-info"
////// utility functions
static QDomNode cd(QDomNode node, const QString &name, bool create)
{
QDomNode subnode = node.namedItem(name);
if (create && subnode.isNull()) {
subnode = node.ownerDocument().createElement(name);
node.appendChild(subnode);
}
return subnode;
}
static QDomNode cd_or_create(QDomNode node, const QString &name)
{
return cd(node, name, true);
}
static QDomText get_or_create_text(QDomNode node)
{
QDomNode subnode = node.firstChild();
if (subnode.isNull()) {
subnode = node.ownerDocument().createTextNode("");
node.appendChild(subnode);
}
return subnode.toText();
}
static QDomNode findMetadata(const QString &forOwner, QDomNode &parent, bool create)
{
bool forOwnerIsKDE = forOwner == METADATA_KDE_OWNER;
QDomElement metadataElement;
for (QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling()) {
QDomElement elem = _node.toElement();
if (!elem.isNull() && elem.tagName() == "metadata") {
const QString owner = elem.attribute("owner");
if (owner == forOwner) {
return elem;
}
if (owner.isEmpty() && forOwnerIsKDE) {
metadataElement = elem;
}
}
}
if (create && metadataElement.isNull()) {
metadataElement = parent.ownerDocument().createElement("metadata");
parent.appendChild(metadataElement);
metadataElement.setAttribute("owner", forOwner);
} else if (!metadataElement.isNull() && forOwnerIsKDE) {
// i'm not sure if this is good, we shouln't take over foreign metatdata
metadataElement.setAttribute("owner", METADATA_KDE_OWNER);
}
return metadataElement;
}
//////
KBookmarkGroup::KBookmarkGroup()
: KBookmark(QDomElement())
{
}
KBookmarkGroup::KBookmarkGroup(const QDomElement &elem)
: KBookmark(elem)
{
}
bool KBookmarkGroup::isOpen() const
{
return element.attribute("folded") == "no"; // default is: folded
}
KBookmark KBookmarkGroup::first() const
{
return KBookmark(nextKnownTag(element.firstChildElement(), true));
}
KBookmark KBookmarkGroup::previous(const KBookmark &current) const
{
return KBookmark(nextKnownTag(current.element.previousSiblingElement(), false));
}
KBookmark KBookmarkGroup::next(const KBookmark &current) const
{
return KBookmark(nextKnownTag(current.element.nextSiblingElement(), true));
}
int KBookmarkGroup::indexOf(const KBookmark &child) const
{
uint counter = 0;
for (KBookmark bk = first(); !bk.isNull(); bk = next(bk), ++counter) {
if (bk.element == child.element) {
return counter;
}
}
return -1;
}
QDomElement KBookmarkGroup::nextKnownTag(const QDomElement &start, bool goNext) const
{
static const QString bookmark = QString::fromLatin1("bookmark");
static const QString folder = QString::fromLatin1("folder");
static const QString separator = QString::fromLatin1("separator");
for(QDomElement elem = start; !elem.isNull();) {
const QString tag = elem.tagName();
if (tag == folder || tag == bookmark || tag == separator) {
return elem;
}
if (goNext) {
elem = elem.nextSiblingElement();
} else {
elem = elem.previousSiblingElement();
}
}
return QDomElement();
}
KBookmarkGroup KBookmarkGroup::createNewFolder(const QString &text)
{
if (isNull()) {
return KBookmarkGroup();
}
QDomDocument doc = element.ownerDocument();
QDomElement groupElem = doc.createElement("folder");
element.appendChild(groupElem);
QDomElement textElem = doc.createElement("title");
groupElem.appendChild(textElem);
textElem.appendChild(doc.createTextNode(text));
return KBookmarkGroup(groupElem);
}
KBookmark KBookmarkGroup::createNewSeparator()
{
if (isNull()) {
return KBookmark();
}
QDomDocument doc = element.ownerDocument();
Q_ASSERT(!doc.isNull());
QDomElement sepElem = doc.createElement("separator");
element.appendChild( sepElem );
return KBookmark(sepElem);
}
bool KBookmarkGroup::moveBookmark(const KBookmark &item, const KBookmark &after)
{
QDomNode n;
if (!after.isNull()) {
n = element.insertAfter(item.element, after.element);
} else {
// first child
if (element.firstChild().isNull()) {
// Empty element -> set as real first child
n = element.insertBefore( item.element, QDomElement() );
}
// we have to skip everything up to the first valid child
QDomElement firstChild = nextKnownTag(element.firstChild().toElement(), true);
if (!firstChild.isNull()) {
n = element.insertBefore(item.element, firstChild);
} else {
// No real first child -> append after the <title> etc.
n = element.appendChild(item.element);
}
}
return (!n.isNull());
}
KBookmark KBookmarkGroup::addBookmark(const KBookmark &bm)
{
element.appendChild(bm.internalElement());
return bm;
}
KBookmark KBookmarkGroup::addBookmark(const QString &text, const KUrl &url, const QString &icon)
{
if (isNull()) {
return KBookmark();
}
QDomDocument doc = element.ownerDocument();
QDomElement elem = doc.createElement("bookmark");
elem.setAttribute("href", url.url()); // gives us utf8
QDomElement textElem = doc.createElement("title");
elem.appendChild(textElem);
textElem.appendChild(doc.createTextNode(text));
KBookmark newBookmark = addBookmark(KBookmark(elem));
// as icons are moved to metadata, we have to use the KBookmark API for this
newBookmark.setIcon(icon.isEmpty() ? KMimeType::iconNameForUrl(url) : icon);
return newBookmark;
}
void KBookmarkGroup::deleteBookmark(const KBookmark &bk)
{
element.removeChild(bk.element);
}
QList<KUrl> KBookmarkGroup::groupUrlList() const
{
QList<KUrl> urlList;
for (KBookmark bm = first(); !bm.isNull(); bm = next(bm)) {
if (bm.isSeparator() || bm.isGroup()) {
continue;
}
urlList << bm.url();
}
return urlList;
}
//////
KBookmark::KBookmark()
{
}
KBookmark::KBookmark(const QDomElement &elem)
: element(elem)
{
}
bool KBookmark::isGroup() const
{
QString tag = element.tagName();
// don't forget the toplevel group
return (tag == "folder" || tag == "xbel" );
}
bool KBookmark::isSeparator() const
{
return (element.tagName() == "separator");
}
bool KBookmark::isNull() const
{
return element.isNull();
}
bool KBookmark::hasParent() const
{
QDomElement parent = element.parentNode().toElement();
return !parent.isNull();
}
QString KBookmark::text() const
{
return KStringHandler::csqueeze(fullText());
}
QString KBookmark::fullText() const
{
if (isSeparator()) {
return i18n("--- separator ---");
}
QString text = element.namedItem("title").toElement().text();
text.replace('\n', ' '); // #140673
return text;
}
void KBookmark::setFullText(const QString &fullText)
{
QDomNode titleNode = element.namedItem("title");
if (titleNode.isNull()) {
titleNode = element.ownerDocument().createElement("title");
element.appendChild(titleNode);
}
if (titleNode.firstChild().isNull()) {
QDomText domtext = titleNode.ownerDocument().createTextNode("");
titleNode.appendChild(domtext);
}
QDomText domtext = titleNode.firstChild().toText();
domtext.setData(fullText);
}
KUrl KBookmark::url() const
{
return KUrl(element.attribute("href")); // Decodes it from utf8
}
void KBookmark::setUrl(const KUrl &url)
{
element.setAttribute("href", url.url());
}
QString KBookmark::icon() const
{
QDomNode metaDataNode = metaData(METADATA_FREEDESKTOP_OWNER, false);
QDomElement iconElement = cd(metaDataNode, "bookmark:icon", false).toElement();
QString icon = iconElement.attribute("name");
if (icon == "bookmark_folder") {
return "folder-bookmarks";
}
if (icon.isEmpty()) {
// Default icon depends on URL for bookmarks, and is default directory
// icon for groups.
if (isGroup()) {
icon = "folder-bookmarks";
} else {
if (isSeparator()) {
icon = "edit-clear"; // whatever
} else {
// get icon from mimeType
QString _mimeType = mimeType();
if (!_mimeType.isEmpty()) {
KMimeType::Ptr mime = KMimeType::mimeType(_mimeType, KMimeType::ResolveAliases);
if (mime) {
return mime->iconName();
}
}
// get icon from URL
icon = KMimeType::iconNameForUrl(url());
}
}
}
return icon;
}
void KBookmark::setIcon(const QString &icon)
{
QDomNode metaDataNode = metaData(METADATA_FREEDESKTOP_OWNER, true);
QDomElement iconElement = cd_or_create(metaDataNode, "bookmark:icon").toElement();
iconElement.setAttribute("name", icon);
// migration code
if (!element.attribute("icon").isEmpty()) {
element.removeAttribute("icon");
}
}
QString KBookmark::description() const
{
if (isSeparator()) {
return QString();
}
QString description = element.namedItem("desc").toElement().text();
description.replace('\n', ' '); // #140673
return description;
}
void KBookmark::setDescription(const QString &description)
{
QDomNode descNode = element.namedItem("desc");
if (descNode.isNull()) {
descNode = element.ownerDocument().createElement("desc");
element.appendChild(descNode);
}
if (descNode.firstChild().isNull()) {
QDomText domtext = descNode.ownerDocument().createTextNode(QString());
descNode.appendChild(domtext);
}
QDomText domtext = descNode.firstChild().toText();
domtext.setData(description);
}
QString KBookmark::mimeType() const
{
QDomNode metaDataNode = metaData(METADATA_MIME_OWNER, false);
QDomElement mimeTypeElement = cd(metaDataNode, "mime:mime-type", false).toElement();
return mimeTypeElement.attribute("type");
}
void KBookmark::setMimeType(const QString &mimeType)
{
QDomNode metaDataNode = metaData(METADATA_MIME_OWNER, true);
QDomElement iconElement = cd_or_create(metaDataNode, "mime:mime-type").toElement();
iconElement.setAttribute("type", mimeType);
}
KBookmarkGroup KBookmark::parentGroup() const
{
return KBookmarkGroup(element.parentNode().toElement());
}
KBookmarkGroup KBookmark::toGroup() const
{
Q_ASSERT(isGroup());
return KBookmarkGroup(element);
}
QString KBookmark::address() const
{
if (element.tagName() == "xbel") {
return ""; // not QString() !
}
// Use keditbookmarks's DEBUG_ADDRESSES flag to debug this code :)
if (element.parentNode().isNull()) {
Q_ASSERT(false);
return "ERROR"; // Avoid an infinite loop
}
KBookmarkGroup group = parentGroup();
QString parentAddress = group.address();
int pos = group.indexOf(*this);
Q_ASSERT(pos != -1);
return parentAddress + '/' + QString::number(pos);
}
int KBookmark::positionInParent() const
{
return parentGroup().indexOf(*this);
}
QDomElement KBookmark::internalElement() const
{
return element;
}
KBookmark KBookmark::standaloneBookmark( const QString &text, const KUrl &url, const QString &icon)
{
QDomDocument doc("xbel");
QDomElement elem = doc.createElement("xbel");
doc.appendChild(elem);
KBookmarkGroup grp(elem);
grp.addBookmark(text, url, icon);
return grp.first();
}
QString KBookmark::commonParent(const QString &first, const QString &second)
{
QString A = first;
QString B = second;
QString error("ERROR");
if (A == error || B == error) {
return error;
}
A += '/';
B += '/';
uint lastCommonSlash = 0;
uint lastPos = A.length() < B.length() ? A.length() : B.length();
for(uint i = 0; i < lastPos; ++i) {
if (A[i] != B[i]) {
return A.left(lastCommonSlash);
}
if (A[i] == '/') {
lastCommonSlash = i;
}
}
return A.left(lastCommonSlash);
}
void KBookmark::updateAccessMetadata()
{
kDebug(7043) << "updateAccessMetadata" << address() << url().prettyUrl();
const uint timet = QDateTime::currentDateTime().toTime_t();
setMetaDataItem("time_added", QString::number(timet), DontOverwriteMetaData);
setMetaDataItem("time_visited", QString::number(timet));
QString countStr = metaDataItem("visit_count"); // TODO: use spec'ed name
bool ok = false;
int currentCount = countStr.toInt(&ok);
if (!ok) {
currentCount = 0;
}
currentCount++;
setMetaDataItem("visit_count", QString::number(currentCount));
// TODO: time_modified
}
QString KBookmark::parentAddress(const QString &address)
{
return address.left(address.lastIndexOf(QLatin1Char('/')));
}
uint KBookmark::positionInParent(const QString &address)
{
return address.mid(address.lastIndexOf(QLatin1Char('/')) + 1).toInt();
}
QString KBookmark::previousAddress( const QString & address )
{
uint pp = positionInParent(address);
if (pp > 0) {
return parentAddress(address) + QLatin1Char('/') + QString::number(pp - 1);
}
return QString();
}
QString KBookmark::nextAddress(const QString &address)
{
return parentAddress(address) + QLatin1Char('/') + QString::number(positionInParent(address) + 1);
}
QDomNode KBookmark::metaData(const QString &owner, bool create) const
{
QDomNode infoNode = cd(internalElement(), "info", create);
if (infoNode.isNull()) {
return QDomNode();
}
return findMetadata(owner, infoNode , create);
}
QString KBookmark::metaDataItem(const QString &key) const
{
QDomNode metaDataNode = metaData(METADATA_KDE_OWNER, false);
for ( QDomElement e = metaDataNode.firstChildElement(); !e.isNull(); e = e.nextSiblingElement() )
{
if (e.tagName() == key) {
return e.text();
}
}
return QString();
}
void KBookmark::setMetaDataItem(const QString &key, const QString &value, MetaDataOverwriteMode mode)
{
QDomNode metaDataNode = metaData(METADATA_KDE_OWNER, true);
QDomNode item = cd_or_create( metaDataNode, key );
QDomText text = get_or_create_text( item );
if (mode == DontOverwriteMetaData && !text.data().isEmpty()) {
return;
}
text.setData(value);
}
bool KBookmark::operator==(const KBookmark &rhs) const
{
return element == rhs.element;
}
////
KBookmarkGroupTraverser::~KBookmarkGroupTraverser()
{
}
void KBookmarkGroupTraverser::traverse(const KBookmarkGroup &root)
{
QStack<KBookmarkGroup> stack;
stack.push(root);
KBookmark bk = root.first();
for(;;) {
if (bk.isNull()) {
if (stack.count() == 1) {
// only root is on the stack
return;
}
if (stack.count() > 0) {
visitLeave(stack.top());
bk = stack.pop();
}
bk = stack.top().next(bk);
} else if (bk.isGroup()) {
KBookmarkGroup gp = bk.toGroup();
visitEnter(gp);
bk = gp.first();
stack.push(gp);
} else {
visit(bk);
bk = stack.top().next(bk);
}
}
}
void KBookmarkGroupTraverser::visit(const KBookmark &)
{
}
void KBookmarkGroupTraverser::visitEnter(const KBookmarkGroup &)
{
}
void KBookmarkGroupTraverser::visitLeave(const KBookmarkGroup &)
{
}
void KBookmark::populateMimeData(QMimeData *mimeData) const
{
KBookmark::List bookmarkList;
bookmarkList.append(*this);
bookmarkList.populateMimeData(mimeData);
}
KBookmark::List::List()
: QList<KBookmark>()
{
}
void KBookmark::List::populateMimeData(QMimeData *mimeData) const
{
KUrl::List urls;
QDomDocument doc("xbel");
QDomElement elem = doc.createElement("xbel");
doc.appendChild(elem);
for ( const_iterator it = begin(), end = this->end() ; it != end ; ++it ) {
urls.append((*it).url());
elem.appendChild((*it).internalElement().cloneNode(true /* deep */));
}
// This sets text/uri-list and text/plain into the mimedata
urls.populateMimeData(mimeData);
mimeData->setData("application/x-xbel", doc.toByteArray());
}
bool KBookmark::List::canDecode(const QMimeData *mimeData)
{
return mimeData->hasFormat("application/x-xbel") || KUrl::List::canDecode(mimeData);
}
QStringList KBookmark::List::mimeDataTypes()
{
return QStringList() << QString::fromLatin1("application/x-xbel") << KUrl::List::mimeDataTypes();
}
KBookmark::List KBookmark::List::fromMimeData(const QMimeData *mimeData, QDomDocument &doc)
{
KBookmark::List bookmarks;
QByteArray payload = mimeData->data("application/x-xbel");
if (!payload.isEmpty()) {
doc.setContent(payload);
QDomElement elem = doc.documentElement();
const QDomNodeList children = elem.childNodes();
for (int childno = 0; childno < children.count(); childno++) {
bookmarks.append(KBookmark(children.item(childno).toElement()));
}
return bookmarks;
}
const KUrl::List urls = KUrl::List::fromMimeData(mimeData);
if (!urls.isEmpty()) {
KUrl::List::ConstIterator uit = urls.begin();
KUrl::List::ConstIterator uEnd = urls.end();
for ( ; uit != uEnd ; ++uit ) {
// kDebug(7043) << "url=" << (*uit);
bookmarks.append(KBookmark::standaloneBookmark((*uit).prettyUrl(), (*uit)));
}
}
return bookmarks;
}