/*************************************************************************** * Copyright (C) 2009 * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) #include #endif #include "RetroShareLink.h" #include "MainWindow.h" #include "gui/gxsforums/GxsForumsDialog.h" #include "gui/gxschannels/GxsChannelDialog.h" #include "gui/Posted/PostedDialog.h" #include "gui/FileTransfer/SearchDialog.h" #include "msgs/MessageComposer.h" #include "util/misc.h" #include "common/PeerDefs.h" #include "common/RsCollectionFile.h" #include #include "gui/connect/ConnectFriendWizard.h" #include "gui/connect/ConfCertDialog.h" #include "gui/connect/PGPKeyDialog.h" #include #include #include #include #include //#define DEBUG_RSLINK 1 #define HOST_FILE "file" #define HOST_EXTRAFILE "extra" #define HOST_PERSON "person" #define HOST_FORUM "forum" #define HOST_CHANNEL "channel" #define HOST_POSTED "posted" #define HOST_MESSAGE "message" #define HOST_SEARCH "search" #define HOST_CERTIFICATE "certificate" #define HOST_PUBLIC_MSG "public_msg" #define HOST_IDENTITY "identity" #define HOST_REGEXP "file|extra|person|forum|channel|posted|search|message|certificate|private_chat|public_msg|identity" #define FILE_NAME "name" #define FILE_SIZE "size" #define FILE_HASH "hash" #define FILE_SOURCE "src" #define PERSON_NAME "name" #define PERSON_HASH "hash" #define FORUM_NAME "name" #define FORUM_ID "id" #define FORUM_MSGID "msgid" #define CHANNEL_NAME "name" #define CHANNEL_ID "id" #define CHANNEL_MSGID "msgid" #define POSTED_NAME "name" #define POSTED_ID "id" #define POSTED_MSGID "msgid" #define IDENTITY_NAME "name" #define IDENTITY_ID "gxsid" #define IDENTITY_GROUP "groupdata" #define MESSAGE_ID "id" #define MESSAGE_SUBJECT "subject" #define SEARCH_KEYWORDS "keywords" #define CERTIFICATE_SSLID "sslid" #define CERTIFICATE_GPG_ID "gpgid" #define CERTIFICATE_GPG_BASE64 "gpgbase64" #define CERTIFICATE_GPG_CHECKSUM "gpgchecksum" #define CERTIFICATE_LOCATION "location" #define CERTIFICATE_NAME "name" #define CERTIFICATE_EXT_IPPORT "extipp" #define CERTIFICATE_LOC_IPPORT "locipp" #define CERTIFICATE_DYNDNS "dyndns" #define CERTIFICATE_RADIX "radix" #define PUBLIC_MSG_TIME_STAMP "time_stamp" #define PUBLIC_MSG_SRC_PGP_ID "gpgid" #define PUBLIC_MSG_HASH "hash" RetroShareLink::RetroShareLink(const QUrl& url) { fromUrl(url); } RetroShareLink::RetroShareLink(const QString& url) { fromString(url); } void RetroShareLink::fromString(const QString& url) { clear(); // parse #ifdef DEBUG_RSLINK std::cerr << "got new RS link \"" << url.toStdString() << "\"" << std::endl ; #endif if ((url.startsWith(QString(RSLINK_SCHEME) + "://" + QString(HOST_FILE)) && url.count("|") == 3) || (url.startsWith(QString(RSLINK_SCHEME) + "://" + QString(HOST_PERSON)) && url.count("|") == 2)) { /* Old link, we try it */ QStringList list = url.split ("|"); if (list.size() >= 1) { if (list.size() == 4 && list[0] == QString(RSLINK_SCHEME) + "://" + QString(HOST_FILE)) { bool ok ; _type = TYPE_FILE; _name = list[1] ; _size = list[2].toULongLong(&ok) ; _hash = list[3].left(40) ; // normally not necessary, but it's a security. if (ok) { #ifdef DEBUG_RSLINK std::cerr << "New RetroShareLink forged:" << std::endl ; std::cerr << " name = \"" << _name.toStdString() << "\"" << std::endl ; std::cerr << " hash = \"" << _hash.toStdString() << "\"" << std::endl ; std::cerr << " size = " << _size << std::endl ; #endif check(); return; } } else if (list.size() == 3 && list[0] == QString(RSLINK_SCHEME) + "://" + QString(HOST_PERSON)) { _type = TYPE_PERSON; _name = list[1] ; _hash = list[2].left(40) ; // normally not necessary, but it's a security. _size = 0; check(); return; } // bad link } } /* Now try QUrl */ fromUrl(QUrl::fromEncoded(url.toUtf8().constData())); } // Qt 4 and Qt5 use different classes // to make it work with Qt4 and Qt5, use a template template QString decodedQueryItemValue(const QUrl_or_QUrlQuery& urlQuery, const QString& key) { #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) // Qt5 needs a additional flag to properly decode everything // we have to decode, becaue we want only decoded stuff in our QString return urlQuery.queryItemValue(key, QUrl::FullyDecoded); #else return urlQuery.queryItemValue(key); #endif } void RetroShareLink::fromUrl(const QUrl& url) { #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) const QUrlQuery urlQuery(url); #else const QUrl &urlQuery(url); #endif clear(); // parse #ifdef DEBUG_RSLINK std::cerr << "got new RS link \"" << url.toString().toStdString() << "\"" << std::endl ; #endif if (url.scheme() != RSLINK_SCHEME) { /* No RetroShare-Link */ #ifdef DEBUG_RSLINK std::cerr << "Not a RS link: scheme=" << url.scheme().toStdString() << std::endl; #endif return; } if (url.host() == HOST_FILE) { bool ok ; _type = TYPE_FILE; _name = decodedQueryItemValue(urlQuery, FILE_NAME); _size = urlQuery.queryItemValue(FILE_SIZE).toULongLong(&ok); _hash = urlQuery.queryItemValue(FILE_HASH).left(40); // normally not necessary, but it's a security. if (ok) { #ifdef DEBUG_RSLINK std::cerr << "New RetroShareLink forged:" << std::endl ; std::cerr << " name = \"" << _name.toStdString() << "\"" << std::endl ; std::cerr << " hash = \"" << _hash.toStdString() << "\"" << std::endl ; std::cerr << " size = " << _size << std::endl ; #endif check(); return; } } if(url.host() == HOST_PUBLIC_MSG) { bool ok ; _type = TYPE_PUBLIC_MSG ; _hash = urlQuery.queryItemValue(PUBLIC_MSG_HASH) ; _time_stamp = urlQuery.queryItemValue(PUBLIC_MSG_TIME_STAMP).toUInt(&ok) ; _GPGid = urlQuery.queryItemValue(PUBLIC_MSG_SRC_PGP_ID) ; check() ; return; } if (url.host() == HOST_EXTRAFILE) { bool ok ; _type = TYPE_EXTRAFILE; _name = decodedQueryItemValue(urlQuery, FILE_NAME); _size = urlQuery.queryItemValue(FILE_SIZE).toULongLong(&ok); _hash = urlQuery.queryItemValue(FILE_HASH).left(40); // normally not necessary, but it's a security. _SSLid = urlQuery.queryItemValue(FILE_SOURCE); if (ok) { #ifdef DEBUG_RSLINK std::cerr << "New RetroShareLink forged:" << std::endl ; std::cerr << " name = \"" << _name.toStdString() << "\"" << std::endl ; std::cerr << " hash = \"" << _hash.toStdString() << "\"" << std::endl ; std::cerr << " size = " << _size << std::endl ; std::cerr << " src = " << _SSLid.toStdString() << std::endl ; #endif check(); return; } } if (url.host() == HOST_PERSON) { _type = TYPE_PERSON; _name = decodedQueryItemValue(urlQuery, PERSON_NAME); _hash = urlQuery.queryItemValue(PERSON_HASH).left(40); // normally not necessary, but it's a security. check(); return; } if (url.host() == HOST_FORUM) { _type = TYPE_FORUM; _name = decodedQueryItemValue(urlQuery, FORUM_NAME); _hash = urlQuery.queryItemValue(FORUM_ID); _msgId = urlQuery.queryItemValue(FORUM_MSGID); check(); return; } if (url.host() == HOST_CHANNEL) { _type = TYPE_CHANNEL; _name = decodedQueryItemValue(urlQuery, CHANNEL_NAME); _hash = urlQuery.queryItemValue(CHANNEL_ID); _msgId = urlQuery.queryItemValue(CHANNEL_MSGID); check(); return; } if (url.host() == HOST_POSTED) { _type = TYPE_POSTED; _name = decodedQueryItemValue(urlQuery, POSTED_NAME); _hash = urlQuery.queryItemValue(POSTED_ID); _msgId = urlQuery.queryItemValue(POSTED_MSGID); check(); return; } if (url.host() == HOST_SEARCH) { _type = TYPE_SEARCH; _name = decodedQueryItemValue(urlQuery, SEARCH_KEYWORDS); check(); return; } if(url.host() == HOST_IDENTITY) { _type = TYPE_IDENTITY ; QString name = decodedQueryItemValue(urlQuery, IDENTITY_NAME) ; QString radix= decodedQueryItemValue(urlQuery, IDENTITY_GROUP) ; QString gxsid= urlQuery.queryItemValue(IDENTITY_ID) ; RsGxsId id(gxsid.toStdString()) ; if(!id.isNull()) createIdentity(id,name,radix) ; else std::cerr << "(EE) identity link is not valid." << std::endl; return ; } if (url.host() == HOST_MESSAGE) { _type = TYPE_MESSAGE; std::string id = urlQuery.queryItemValue(MESSAGE_ID).toStdString(); createMessage(RsPeerId(id), urlQuery.queryItemValue(MESSAGE_SUBJECT)); return; } if (url.host() == HOST_CERTIFICATE) { _type = TYPE_CERTIFICATE; _radix = decodedQueryItemValue(urlQuery, CERTIFICATE_RADIX); #ifdef DEBUG_RSLINK std::cerr << "Got a certificate link!!" << std::endl; #endif check() ; return; } // bad link #ifdef DEBUG_RSLINK std::cerr << "Wrongly formed RS link. Can't process." << std::endl ; #endif clear(); } RetroShareLink::RetroShareLink() { clear(); } RetroShareLink RetroShareLink::createIdentity(const RsGxsId& id, const QString& name, const QString& radix_data) { RetroShareLink link; link.clear(); link._name = name; link._hash = QString::fromStdString(id.toStdString()); link._radix_group_data = radix_data ; link._type = TYPE_IDENTITY; link.check(); return link; } RetroShareLink RetroShareLink::createExtraFile(const QString& name, uint64_t size, const QString& hash,const QString& ssl_id) { RetroShareLink link; link.clear(); link._name = name; link._size = size; link._hash = hash; link._SSLid = ssl_id; link._type = TYPE_EXTRAFILE; link.check(); return link; } RetroShareLink RetroShareLink::createFile(const QString& name, uint64_t size, const QString& hash) { RetroShareLink link; link.clear(); link._name = name; link._size = size; link._hash = hash; link._type = TYPE_FILE; link.check(); return link; } RetroShareLink RetroShareLink::createPublicMsgInvite(time_t time_stamp,const QString& issuer_pgp_id,const QString& hash) { RetroShareLink link; link.clear() ; link._type = TYPE_PUBLIC_MSG ; link._time_stamp = time_stamp ; link._hash = hash ; link._GPGid = issuer_pgp_id ; link.check() ; return link; } RetroShareLink RetroShareLink::createPerson(const RsPgpId& id) { RetroShareLink link; link.clear(); RsPeerDetails detail; if (rsPeers->getGPGDetails(id, detail) == false) { std::cerr << "RetroShareLink::createPerson() Couldn't find peer id " << id << std::endl; } else { link._hash = QString::fromStdString(id.toStdString()); link._name = QString::fromUtf8(detail.name.c_str()); link._type = TYPE_PERSON; } link.check(); return link; } RetroShareLink RetroShareLink::createCertificate(const RsPeerId& ssl_id) { RetroShareLink link; link.clear(); #warning csoler 2012-08-14: This is baaaaaad code: // - we should not need to parse and re-read a cert in old format. // RsPeerDetails detail; if (rsPeers->getPeerDetails(ssl_id, detail) == false) { std::cerr << "RetroShareLink::createPerson() Couldn't find peer id " << ssl_id << std::endl; } else { link._type = TYPE_CERTIFICATE; link._radix = QString::fromUtf8(rsPeers->GetRetroshareInvite(ssl_id,false).c_str()); link._name = QString::fromUtf8(detail.name.c_str()); link._location = QString::fromUtf8(detail.location.c_str()); link._radix.replace("\n",""); std::cerr << "Found radix = " << link._radix.toStdString() << std::endl; } link.check(); return link; } RetroShareLink RetroShareLink::createUnknwonSslCertificate(const RsPeerId& sslId, const RsPgpId& gpgId) { RetroShareLink link; link.clear(); // first try ssl id link = createCertificate(sslId); if (link.valid()) { if (gpgId.isNull() || link._GPGid.toStdString() == gpgId.toStdString()) { return link; } // wrong gpg id link.clear(); return link; } // then gpg id link = createPerson(gpgId); if (link.valid()) { if (!link._SSLid.isEmpty()) { link.clear(); return link; } if (sslId.isNull()) { link.check(); return link; } link._SSLid = QString::fromStdString(sslId.toStdString()); if (link._location.isEmpty()) { link._location = link._name; } link.check(); return link; } link.clear(); return link; } RetroShareLink RetroShareLink::createGxsGroupLink(const RetroShareLink::enumType &linkType, const RsGxsGroupId &groupId, const QString &groupName) { RetroShareLink link; link.clear(); if (!groupId.isNull()) { link._hash = QString::fromStdString(groupId.toStdString()); link._type = linkType; link._name = groupName; } link.check(); return link; } RetroShareLink RetroShareLink::createGxsMessageLink(const RetroShareLink::enumType &linkType, const RsGxsGroupId &groupId, const RsGxsMessageId &msgId, const QString &msgName) { RetroShareLink link; link.clear(); if (!groupId.isNull() && !msgId.isNull()) { link._hash = QString::fromStdString(groupId.toStdString()); link._msgId = QString::fromStdString(msgId.toStdString()); link._type = linkType; link._name = msgName; } link.check(); return link; } RetroShareLink RetroShareLink::createSearch(const QString& keywords) { RetroShareLink link; link.clear(); link._name = keywords; link._type = TYPE_SEARCH; link.check(); return link; } RetroShareLink RetroShareLink::createMessage(const RsPeerId& peerId, const QString& subject) { RetroShareLink link; link.clear(); link._hash = QString::fromStdString(peerId.toStdString()); PeerDefs::rsidFromId(peerId, &link._name); link._subject = subject; link._type = TYPE_MESSAGE; link.check(); return link; } RetroShareLink RetroShareLink::createMessage(const RsGxsId& peerId, const QString& subject) { RetroShareLink link; link.clear(); link._hash = QString::fromStdString(peerId.toStdString()); PeerDefs::rsidFromId(peerId, &link._name); link._subject = subject; link._type = TYPE_MESSAGE; link.check(); return link; } void RetroShareLink::clear() { _valid = false; _type = TYPE_UNKNOWN; _subType = 0; _hash = "" ; _size = 0 ; _name = "" ; _GPGid = "" ; _time_stamp = 0 ; _encrypted_chat_info = "" ; _radix_group_data = "" ; } void RetroShareLink::check() { _valid = true; switch (_type) { case TYPE_UNKNOWN: case TYPE_PRIVATE_CHAT: _valid = false; break; case TYPE_EXTRAFILE: if(!checkSSLId(_SSLid)) _valid = false; // no break! We also test file stuff below. case TYPE_FILE: if(_size > (((uint64_t)1)<<40)) // 1TB. Who has such large files? _valid = false; if(!checkName(_name)) _valid = false; if(!checkHash(_hash)) _valid = false; break; case TYPE_PUBLIC_MSG: if(!checkHash(_hash)) _valid = false ; if(!checkPGPId(_GPGid)) _valid = false ; break ; case TYPE_IDENTITY: if(_name.isNull()) _valid = false ; if(_radix_group_data.isNull()) _valid = false ; if(_hash.isNull()) _valid = false ; break ; case TYPE_PERSON: if(_size != 0) _valid = false; if(_name.isEmpty()) _valid = false; if(_hash.isEmpty()) _valid = false; break; case TYPE_FORUM: if(_size != 0) _valid = false; if(_name.isEmpty()) _valid = false; if(_hash.isEmpty()) _valid = false; break; case TYPE_CHANNEL: if(_size != 0) _valid = false; if(_name.isEmpty()) _valid = false; if(_hash.isEmpty()) _valid = false; break; case TYPE_POSTED: if(_size != 0) _valid = false; if(_name.isEmpty()) _valid = false; if(_hash.isEmpty()) _valid = false; break; case TYPE_SEARCH: if(_size != 0) _valid = false; if(_name.isEmpty()) _valid = false; if(!_hash.isEmpty()) _valid = false; break; case TYPE_MESSAGE: if(_size != 0) _valid = false; if(_hash.isEmpty()) _valid = false; break; case TYPE_CERTIFICATE: break; } if (!_valid) { clear(); } } QString RetroShareLink::title() const { if (!valid()) { return ""; } switch (_type) { case TYPE_UNKNOWN: case TYPE_PRIVATE_CHAT: break; case TYPE_PUBLIC_MSG: { RsPeerDetails detail; rsPeers->getGPGDetails(RsPgpId(_GPGid.toStdString()), detail) ; return QObject::tr("Click to send a private message to %1 (%2).").arg(QString::fromUtf8(detail.name.c_str())).arg(_GPGid) ; } case TYPE_EXTRAFILE: return QObject::tr("%1 (%2, Extra - Source included)").arg(hash()).arg(misc::friendlyUnit(size())); case TYPE_FILE: return QString("%1 (%2)").arg(hash()).arg(misc::friendlyUnit(size())); case TYPE_IDENTITY: return _name ; case TYPE_PERSON: return PeerDefs::rsidFromId(RsPgpId(hash().toStdString())); case TYPE_FORUM: case TYPE_CHANNEL: case TYPE_POSTED: case TYPE_SEARCH: break; case TYPE_MESSAGE: return PeerDefs::rsidFromId(RsPeerId(hash().toStdString())); case TYPE_CERTIFICATE: RsPeerDetails details ; uint32_t error_code ; if(!rsPeers->loadDetailsFromStringCert(_radix.toStdString(),details,error_code)) return QObject::tr("This cert is malformed. Error code:")+" "+QString::number(error_code) ; else return QObject::tr("Click to add this RetroShare cert to your PGP keyring\nand open the Make Friend Wizard.\n") + QString("PGP Id =")+" " + QString::fromStdString(details.gpg_id.toStdString()) + QString("\nSSLId =")+" "+QString::fromStdString(details.id.toStdString()); } return ""; } static QString encodeItem(QString item) { return QUrl::toPercentEncoding(item); } QString RetroShareLink::toString() const { QUrl url; #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) QUrlQuery urlQuery; #else QUrl &urlQuery(url); #endif switch (_type) { case TYPE_UNKNOWN: case TYPE_PRIVATE_CHAT: return ""; case TYPE_FILE: url.setScheme(RSLINK_SCHEME); url.setHost(HOST_FILE); urlQuery.addQueryItem(FILE_NAME, encodeItem(_name)); urlQuery.addQueryItem(FILE_SIZE, QString::number(_size)); urlQuery.addQueryItem(FILE_HASH, _hash); break; case TYPE_PUBLIC_MSG: url.setScheme(RSLINK_SCHEME) ; url.setHost(HOST_PUBLIC_MSG) ; urlQuery.addQueryItem(PUBLIC_MSG_TIME_STAMP,QString::number(_time_stamp)) ; urlQuery.addQueryItem(PUBLIC_MSG_HASH,_hash) ; urlQuery.addQueryItem(PUBLIC_MSG_SRC_PGP_ID,_GPGid) ; break; case TYPE_IDENTITY: url.setScheme(RSLINK_SCHEME) ; url.setHost(HOST_IDENTITY) ; urlQuery.addQueryItem(IDENTITY_ID,_hash) ; urlQuery.addQueryItem(IDENTITY_NAME,encodeItem(_name)) ; urlQuery.addQueryItem(IDENTITY_GROUP,encodeItem(_radix_group_data)) ; break ; case TYPE_EXTRAFILE: url.setScheme(RSLINK_SCHEME); url.setHost(HOST_EXTRAFILE); urlQuery.addQueryItem(FILE_NAME, encodeItem(_name)); urlQuery.addQueryItem(FILE_SIZE, QString::number(_size)); urlQuery.addQueryItem(FILE_HASH, _hash); urlQuery.addQueryItem(FILE_SOURCE, _SSLid); break; case TYPE_PERSON: url.setScheme(RSLINK_SCHEME); url.setHost(HOST_PERSON); urlQuery.addQueryItem(PERSON_NAME, encodeItem(_name)); urlQuery.addQueryItem(PERSON_HASH, _hash); break; case TYPE_FORUM: url.setScheme(RSLINK_SCHEME); url.setHost(HOST_FORUM); urlQuery.addQueryItem(FORUM_NAME, encodeItem(_name)); urlQuery.addQueryItem(FORUM_ID, _hash); if (!_msgId.isEmpty()) { urlQuery.addQueryItem(FORUM_MSGID, _msgId); } break; case TYPE_CHANNEL: url.setScheme(RSLINK_SCHEME); url.setHost(HOST_CHANNEL); urlQuery.addQueryItem(CHANNEL_NAME, encodeItem(_name)); urlQuery.addQueryItem(CHANNEL_ID, _hash); if (!_msgId.isEmpty()) { urlQuery.addQueryItem(CHANNEL_MSGID, _msgId); } break; case TYPE_POSTED: url.setScheme(RSLINK_SCHEME); url.setHost(HOST_POSTED); urlQuery.addQueryItem(POSTED_NAME, encodeItem(_name)); urlQuery.addQueryItem(POSTED_ID, _hash); if (!_msgId.isEmpty()) { urlQuery.addQueryItem(POSTED_MSGID, _msgId); } break; case TYPE_SEARCH: url.setScheme(RSLINK_SCHEME); url.setHost(HOST_SEARCH); urlQuery.addQueryItem(SEARCH_KEYWORDS, encodeItem(_name)); break; case TYPE_MESSAGE: url.setScheme(RSLINK_SCHEME); url.setHost(HOST_MESSAGE); urlQuery.addQueryItem(MESSAGE_ID, _hash); if (_subject.isEmpty() == false) { urlQuery.addQueryItem(MESSAGE_SUBJECT, encodeItem(_subject)); } break; case TYPE_CERTIFICATE: url.setScheme(RSLINK_SCHEME); url.setHost(HOST_CERTIFICATE) ; urlQuery.addQueryItem(CERTIFICATE_RADIX, encodeItem(_radix)); urlQuery.addQueryItem(CERTIFICATE_NAME, encodeItem(_name)); urlQuery.addQueryItem(CERTIFICATE_LOCATION, encodeItem(_location)); break; } #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) url.setQuery(urlQuery); return url.toString(QUrl::EncodeSpaces | QUrl::EncodeUnicode); #else return url.toString().replace(" ","%20");//Seems to be already done with Qt4 but to be sure. #endif } QString RetroShareLink::niceName() const { if (type() == TYPE_PERSON) return PeerDefs::rsid(name().toUtf8().constData(), RsPgpId(hash().toStdString())); if(type() == TYPE_IDENTITY) return QObject::tr("Identity link (name=%1, ID=%2)").arg(_name).arg(_hash) ; if(type() == TYPE_PUBLIC_MSG) { RsPeerDetails detail; rsPeers->getGPGDetails(RsPgpId(_GPGid.toStdString()), detail) ; return QObject::tr("Click this link to send a private message to %1 (%2)").arg(QString::fromUtf8(detail.name.c_str())).arg(_GPGid) ; } if(type() == TYPE_CERTIFICATE) { RsPeerDetails details ; uint32_t error_code ; if(!rsPeers->loadDetailsFromStringCert(_radix.toStdString(),details,error_code)) return QObject::tr("This cert is malformed. Error code:")+" "+QString::number(error_code) ; else return QObject::tr("RetroShare Certificate (%1, @%2)").arg(QString::fromUtf8(details.name.c_str()), QString::fromUtf8(details.location.c_str())); // should add SSL id there } return name(); } QString RetroShareLink::toHtml() const { QString html = "" ; return html; } QString RetroShareLink::toHtmlFull() const { return QString("" + toString() + "" ; } QString RetroShareLink::toHtmlSize() const { QString size = QString("(%1)").arg(misc::friendlyUnit(_size)); if (type() == TYPE_FILE && RsCollectionFile::isCollectionFile(name())) { FileInfo finfo; if (rsFiles->FileDetails(RsFileHash(hash().toStdString()), RS_FILE_HINTS_EXTRA | RS_FILE_HINTS_LOCAL, finfo)) { RsCollectionFile collection; if (collection.load(QString::fromUtf8(finfo.path.c_str()), false)) { size += QString(" [%1]").arg(misc::friendlyUnit(collection.size())); } } } QString link = QString("%2 %3").arg(toString()).arg(name()).arg(size); return link; } bool RetroShareLink::checkName(const QString& name) { if(name == "") return false ; for(int i=0;i47 && b<58) || (b>96 && b<103))) return false ; } return true ; } bool RetroShareLink::checkPGPId(const QString& pgp_id) { if(pgp_id.length() != 16) return false ; QByteArray qb(pgp_id.toLatin1()) ; for(int i=0;i47 && b<58) || (b>64 && b<71))) return false ; } return true ; } bool RetroShareLink::checkRadix64(const QString& s) { QByteArray qb(s.toLatin1()) ; for(int i=0;i 46 && b < 58) || (b > 64 && b < 91) || (b > 96 && b < 123) || b=='+' || b=='=')) { std::cerr << "Character not allowed in radix: " << b << std::endl; return false; } } std::cerr << "Radix check: passed" << std::endl; return true ; } bool RetroShareLink::checkHash(const QString& hash) { if(hash.length() != 40) return false ; QByteArray qb(hash.toLatin1()) ; for(int i=0;i47 && b<58) || (b>96 && b<103))) return false ; } return true ; } static void processList(const QStringList &list, const QString &textSingular, const QString &textPlural, QString &result) { if (list.isEmpty()) { return; } if (list.size() == 1) { result += "" + textSingular + ":"; } else { result += "" + textPlural + ":"; } result += "

"; QStringList::const_iterator it; for (it = list.begin(); it != list.end(); ++it) { if (it != list.begin()) { result += ", "; } result += *it; } result += "

"; } /*static*/ int RetroShareLink::process(const QList &linksIn, uint flag /* = RSLINK_PROCESS_NOTIFY_ALL*/) { QList::const_iterator linkIt; flag &= ~RSLINK_PROCESS_NOTIFY_BAD_CHARS ; // this will be set below when needed /* filter dublicate links */ QList links; for (linkIt = linksIn.begin(); linkIt != linksIn.end(); ++linkIt) { if (links.contains(*linkIt)) { continue; } links.append(*linkIt); } if (flag & RSLINK_PROCESS_NOTIFY_ASK) { /* ask for some types of link */ QStringList fileAdd; QStringList personAdd; for (linkIt = links.begin(); linkIt != links.end(); ++linkIt) { const RetroShareLink &link = *linkIt; if (link.valid() == false) { continue; } switch (link.type()) { case TYPE_UNKNOWN: case TYPE_FORUM: case TYPE_CHANNEL: case TYPE_POSTED: case TYPE_SEARCH: case TYPE_MESSAGE: case TYPE_IDENTITY: case TYPE_CERTIFICATE: case TYPE_PUBLIC_MSG: case TYPE_PRIVATE_CHAT: // no need to ask break; case TYPE_FILE: case TYPE_EXTRAFILE: fileAdd.append(link.name()); break; case TYPE_PERSON: personAdd.append(PeerDefs::rsid(link.name().toUtf8().constData(), RsPgpId(link.hash().toStdString()))); break; } } QString content; if (!fileAdd.isEmpty()) { processList(fileAdd, QObject::tr("Add file"), QObject::tr("Add files"), content); } //if (personAdd.size()) { // processList(personAdd, QObject::tr("Add friend"), QObject::tr("Add friends"), content); //} if (content.isEmpty() == false) { QString question = ""; if (links.size() == 1) { question += QObject::tr("Do you want to process the link ?"); } else { question += QObject::tr("Do you want to process %1 links ?").arg(links.size()); } question += "

" + content + ""; QMessageBox mb(QObject::tr("Confirmation"), question, QMessageBox::Question, QMessageBox::Yes,QMessageBox::No, 0); if (mb.exec() == QMessageBox::No) { return 0; } } } int countInvalid = 0; int countUnknown = 0; int countFileOpened = 0; bool needNotifySuccess = false; // file QStringList fileAdded; QStringList fileExist; // person QStringList personAdded; QStringList personExist; QStringList personFailed; QStringList personNotFound; // forum QStringList forumFound; QStringList forumMsgFound; QStringList forumUnknown; QStringList forumMsgUnknown; // forum QStringList channelFound; QStringList channelMsgFound; QStringList channelUnknown; QStringList channelMsgUnknown; // forum QStringList postedFound; QStringList postedMsgFound; QStringList postedUnknown; QStringList postedMsgUnknown; // search QStringList searchStarted; // message QStringList messageStarted; QStringList messageReceipientNotAccepted; QStringList messageReceipientUnknown; // Certificate //QStringList GPGBase64Strings ; //QStringList SSLIds ; // summary QList processedList; QList errorList; processedList << &fileAdded << &personAdded << &forumFound << &channelFound << &postedFound << &searchStarted << &messageStarted; errorList << &fileExist << &personExist << &personFailed << &personNotFound << &forumUnknown << &forumMsgUnknown << &channelUnknown << &channelMsgUnknown << &postedUnknown << &postedMsgUnknown << &messageReceipientNotAccepted << &messageReceipientUnknown; // not needed: forumFound, channelFound, messageStarted for (linkIt = links.begin(); linkIt != links.end(); ++linkIt) { const RetroShareLink &link = *linkIt; if (link.valid() == false) { std::cerr << " RetroShareLink::process invalid request" << std::endl; ++countInvalid; continue; } switch (link.type()) { case TYPE_UNKNOWN: ++countUnknown; break; case TYPE_CERTIFICATE: { #ifdef DEBUG_RSLINK std::cerr << " RetroShareLink::process certificate." << std::endl; #endif needNotifySuccess = true; std::cerr << "Usign this certificate:" << std::endl; std::cerr << link.radix().toStdString() << std::endl; ConnectFriendWizard connectFriendWizard; connectFriendWizard.setCertificate(link.radix(), (link.subType() == RSLINK_SUBTYPE_CERTIFICATE_USER_REQUEST) ? true : false); connectFriendWizard.exec(); needNotifySuccess = false; } break ; case TYPE_PUBLIC_MSG: { std::cerr << "(!!) Distant messages from links is disabled for now" << std::endl; // std::cerr << "Opening a public msg window " << std::endl; // std::cerr << " time_stamp = " << link._time_stamp << std::endl; // std::cerr << " hash = " << link._hash.toStdString() << std::endl; // std::cerr << " Issuer Id = " << link._GPGid.toStdString() << std::endl; // // if(link._time_stamp < time(NULL)) // { // QMessageBox::information(NULL,QObject::tr("Messaging link is expired"),QObject::tr("This Messaging link is expired. The destination peer will not receive it.")) ; // break ; // } // // MessageComposer::msgDistantPeer(link._hash.toStdString(),link._GPGid.toStdString()) ; } break ; case TYPE_IDENTITY: { if(rsIdentity->deserialiseIdentityFromMemory(link.radixGroupData().toStdString())) QMessageBox::information(NULL,QObject::tr("Identity added to People"),QObject::tr("The identity was added to people. You can now chat with it, send messages to it, etc.")) ; else QMessageBox::warning(NULL,QObject::tr("Identity cannot be added to People"),QObject::tr("The identity was not added to people. Some error occured. The link is probably corrupted.")) ; } break; case TYPE_FILE: case TYPE_EXTRAFILE: { #ifdef DEBUG_RSLINK std::cerr << " RetroShareLink::process FileRequest : fileName : " << link.name().toUtf8().constData() << ". fileHash : " << link.hash().toStdString() << ". fileSize : " << link.size() << std::endl; #endif needNotifySuccess = true; std::list srcIds; // Add the link built-in source. This is needed for EXTRA files, where the source is specified in the link. if(link.type() == TYPE_EXTRAFILE) { #ifdef DEBUG_RSLINK std::cerr << " RetroShareLink::process Adding built-in source " << link.SSLId().toStdString() << std::endl; #endif srcIds.push_back(RsPeerId(link.SSLId().toStdString())) ; } // Get a list of available direct sources, in case the file is browsable only. // FileInfo finfo ; rsFiles->FileDetails(RsFileHash(link.hash().toStdString()), RS_FILE_HINTS_REMOTE, finfo) ; for(std::list::const_iterator it(finfo.peers.begin());it!=finfo.peers.end();++it) { #ifdef DEBUG_RSLINK std::cerr << " adding peerid " << (*it).peerId << std::endl ; #endif srcIds.push_back((*it).peerId) ; } QString cleanname = link.name() ; static const QString bad_chars_str = "/\\\"*:?<>|" ; for(int i=0;ialreadyHaveFile(RsFileHash(link.hash().toStdString()), fi)) { /* make path for downloaded file */ std::string path; path = fi.path;//Shared files has path with filename included //Seems that all FileInfo get .path==filepath+filename //if (fi.downloadStatus == FT_STATE_COMPLETE) // path = fi.path + "/" + fi.fname; QFileInfo qinfo; qinfo.setFile(QString::fromUtf8(path.c_str())); if (qinfo.exists() && qinfo.isFile()) { QString question = ""; question += QObject::tr("Warning: Retroshare is about to ask your system to open this file. "); question += QObject::tr("Before you do so, please make sure that this file does not contain malicious executable code."); question += "

" + cleanname + ""; QMessageBox mb(QObject::tr("Confirmation"), question, QMessageBox::Warning, QMessageBox::Yes,QMessageBox::No, 0); if (mb.exec() == QMessageBox::Yes) { ++countFileOpened; bFileOpened = true; /* open file with a suitable application */ if (!RsUrlHandler::openUrl(QUrl::fromLocalFile(qinfo.absoluteFilePath()))) { std::cerr << "RetroShareLink::process(): can't open file " << path << std::endl; } } } } if (rsFiles->FileRequest(cleanname.toUtf8().constData(), RsFileHash(link.hash().toStdString()), link.size(), "", RS_FILE_REQ_ANONYMOUS_ROUTING, srcIds)) { fileAdded.append(link.name()); } else { if (!bFileOpened) fileExist.append(link.name()); } } break; case TYPE_PERSON: { #ifdef DEBUG_RSLINK std::cerr << " RetroShareLink::process FriendRequest : name : " << link.name().toStdString() << ". id : " << link.hash().toStdString() << std::endl; #endif RsPeerDetails detail; if (rsPeers->getGPGDetails(RsPgpId(link.hash().toStdString()), detail)) PGPKeyDialog::showIt(detail.gpg_id,PGPKeyDialog::PageDetails) ; else personNotFound.append(PeerDefs::rsid(link.name().toUtf8().constData(), RsPgpId(link.hash().toStdString()))); // needNotifySuccess = true; // RsPeerDetails detail; // if (rsPeers->getGPGDetails(RsPgpId(link.hash().toStdString()), detail)) // { // if (RsPgpId(detail.gpg_id) == rsPeers->getGPGOwnId()) { // // it's me, do nothing // break; // } // // if (detail.accept_connection) { // // peer connection is already accepted // personExist.append(PeerDefs::rsid(detail)); // break; // } // // if (rsPeers->addFriend(RsPeerId(), RsPgpId(link.hash().toStdString()))) { // ConfCertDialog::loadAll(); // personAdded.append(PeerDefs::rsid(detail)); // break; // } // // personFailed.append(PeerDefs::rsid(link.name().toUtf8().constData(), RsPgpId(link.hash().toStdString()))); // break; // } // // personNotFound.append(PeerDefs::rsid(link.name().toUtf8().constData(), RsPgpId(link.hash().toStdString()))); } break; case TYPE_FORUM: { #ifdef DEBUG_RSLINK std::cerr << " RetroShareLink::process ForumRequest : name : " << link.name().toStdString() << ". id : " << link.hash().toStdString() << ". msgId : " << link.msgId().toStdString() << std::endl; #endif MainWindow::showWindow(MainWindow::Forums); GxsForumsDialog *forumsDialog = dynamic_cast(MainWindow::getPage(MainWindow::Forums)); if (!forumsDialog) { return false; } if (forumsDialog->navigate(RsGxsGroupId(link.id().toStdString()), RsGxsMessageId(link.msgId().toStdString()))) { if (link.msgId().isEmpty()) { forumFound.append(link.name()); } else { forumMsgFound.append(link.name()); } } else { if (link.msgId().isEmpty()) { forumUnknown.append(link.name()); } else { forumMsgUnknown.append(link.name()); } } } break; case TYPE_CHANNEL: { #ifdef DEBUG_RSLINK std::cerr << " RetroShareLink::process ChannelRequest : name : " << link.name().toStdString() << ". id : " << link.hash().toStdString() << ". msgId : " << link.msgId().toStdString() << std::endl; #endif MainWindow::showWindow(MainWindow::Channels); GxsChannelDialog *channelDialog = dynamic_cast(MainWindow::getPage(MainWindow::Channels)); if (!channelDialog) { return false; } if (channelDialog->navigate(RsGxsGroupId(link.id().toStdString()), RsGxsMessageId(link.msgId().toStdString()))) { if (link.msgId().isEmpty()) { channelFound.append(link.name()); } else { channelMsgFound.append(link.name()); } } else { if (link.msgId().isEmpty()) { channelUnknown.append(link.name()); } else { channelMsgUnknown.append(link.name()); } } } break; case TYPE_POSTED: { #ifdef DEBUG_RSLINK std::cerr << " RetroShareLink::process PostedRequest : name : " << link.name().toStdString() << ". id : " << link.hash().toStdString() << ". msgId : " << link.msgId().toStdString() << std::endl; #endif MainWindow::showWindow(MainWindow::Posted); PostedDialog *postedDialog = dynamic_cast(MainWindow::getPage(MainWindow::Posted)); if (!postedDialog) { return false; } if (postedDialog->navigate(RsGxsGroupId(link.id().toStdString()), RsGxsMessageId(link.msgId().toStdString()))) { if (link.msgId().isEmpty()) { postedFound.append(link.name()); } else { postedMsgFound.append(link.name()); } } else { if (link.msgId().isEmpty()) { postedUnknown.append(link.name()); } else { postedMsgUnknown.append(link.name()); } } } break; case TYPE_SEARCH: { #ifdef DEBUG_RSLINK std::cerr << " RetroShareLink::process SearchRequest : string : " << link.name().toStdString() << std::endl; #endif SearchDialog *searchDialog = dynamic_cast(MainWindow::getPage(MainWindow::Search)); if (!searchDialog) { std::cerr << "Retrieve of search dialog failed. Please debug!" << std::endl; break; } MainWindow::showWindow(MainWindow::Search); searchDialog->searchKeywords(link.name()); searchStarted.append(link.name()); } break; case TYPE_MESSAGE: { #ifdef DEBUG_RSLINK std::cerr << " RetroShareLink::process MessageRequest : id : " << link.hash().toStdString() << ", subject : " << link.name().toStdString() << std::endl; #endif RsPeerDetails detail; // This is awful, but apparently the hash can be multiple different types. Let's check! RsPeerId ssl_id(link.hash().toStdString()) ; if(!ssl_id.isNull() && rsPeers->getPeerDetails(ssl_id,detail) && detail.accept_connection) { MessageComposer *msg = MessageComposer::newMsg(); msg->addRecipient(MessageComposer::TO, detail.id); if (link.subject().isEmpty() == false) { msg->setTitleText(link.subject()); } msg->show(); messageStarted.append(PeerDefs::nameWithLocation(detail)); break ; } RsIdentityDetails gxs_details ; RsGxsId gxs_id(link.hash().toStdString()) ; if(!gxs_id.isNull() && rsIdentity->getIdDetails(gxs_id,gxs_details)) { if(gxs_details.mFlags & RS_IDENTITY_FLAGS_IS_OWN_ID) { QMessageBox::warning(NULL,QString("Cannot send message to yourself"),QString("This identity is owned by you. You wouldn't want to send yourself a message right?")); break ; } MessageComposer *msg = MessageComposer::newMsg(); msg->addRecipient(MessageComposer::TO, gxs_id) ; if (link.subject().isEmpty() == false) msg->setTitleText(link.subject()); msg->show(); messageStarted.append(PeerDefs::nameWithLocation(gxs_details)); break ; } messageReceipientUnknown.append(PeerDefs::rsidFromId(RsPeerId(link.hash().toStdString()))); } break; default: std::cerr << " RetroShareLink::process unknown type: " << link.type() << std::endl; ++countUnknown; } } int countProcessed = 0; int countError = 0; QList::iterator listIt; for (listIt = processedList.begin(); listIt != processedList.end(); ++listIt) { countProcessed += (*listIt)->size(); } for (listIt = errorList.begin(); listIt != errorList.end(); ++listIt) { countError += (*listIt)->size(); } // success notify needed ? if (needNotifySuccess == false) { flag &= ~RSLINK_PROCESS_NOTIFY_SUCCESS; } // error notify needed ? if (countError == 0) { flag &= ~RSLINK_PROCESS_NOTIFY_ERROR; } QString result; if (flag & (RSLINK_PROCESS_NOTIFY_SUCCESS | RSLINK_PROCESS_NOTIFY_ERROR)) { result += (links.count() == 1 ? QObject::tr("%1 of %2 RetroShare link processed.") : QObject::tr("%1 of %2 RetroShare links processed.")).arg(countProcessed).arg(links.count()) + "

"; } // file if (flag & RSLINK_PROCESS_NOTIFY_SUCCESS) { if (!fileAdded.isEmpty()) { processList(fileAdded, QObject::tr("File added"), QObject::tr("Files added"), result); } } if (flag & RSLINK_PROCESS_NOTIFY_ERROR) { if (!fileExist.isEmpty()) { processList(fileExist, QObject::tr("File exist"), QObject::tr("Files exist"), result); } } // person if (flag & RSLINK_PROCESS_NOTIFY_SUCCESS) { if (!personAdded.isEmpty()) { processList(personAdded, QObject::tr("Friend added"), QObject::tr("Friends added"), result); } } if (flag & RSLINK_PROCESS_NOTIFY_ERROR) { if (!personExist.isEmpty()) { processList(personExist, QObject::tr("Friend exist"), QObject::tr("Friends exist"), result); } if (!personFailed.isEmpty()) { processList(personFailed, QObject::tr("Friend not added"), QObject::tr("Friends not added"), result); } if (!personNotFound.isEmpty()) { processList(personNotFound, QObject::tr("Friend not found"), QObject::tr("Friends not found"), result); } } // forum if (flag & RSLINK_PROCESS_NOTIFY_ERROR) { if (!forumUnknown.isEmpty()) { processList(forumUnknown, QObject::tr("Forum not found"), QObject::tr("Forums not found"), result); } if (!forumMsgUnknown.isEmpty()) { processList(forumMsgUnknown, QObject::tr("Forum message not found"), QObject::tr("Forum messages not found"), result); } } // channel if (flag & RSLINK_PROCESS_NOTIFY_ERROR) { if (!channelUnknown.isEmpty()) { processList(channelUnknown, QObject::tr("Channel not found"), QObject::tr("Channels not found"), result); } if (!channelMsgUnknown.isEmpty()) { processList(channelMsgUnknown, QObject::tr("Channel message not found"), QObject::tr("Channel messages not found"), result); } } // posted if (flag & RSLINK_PROCESS_NOTIFY_ERROR) { if (!postedUnknown.isEmpty()) { processList(postedUnknown, QObject::tr("Posted not found"), QObject::tr("Posted not found"), result); } if (!postedMsgUnknown.isEmpty()) { processList(postedMsgUnknown, QObject::tr("Posted message not found"), QObject::tr("Posted messages not found"), result); } } // message if (flag & RSLINK_PROCESS_NOTIFY_ERROR) { if (!messageReceipientNotAccepted.isEmpty()) { processList(messageReceipientNotAccepted, QObject::tr("Recipient not accepted"), QObject::tr("Recipients not accepted"), result); } if (!messageReceipientUnknown.isEmpty()) { processList(messageReceipientUnknown, QObject::tr("Unkown recipient"), QObject::tr("Unkown recipients"), result); } } if (flag & RSLINK_PROCESS_NOTIFY_ERROR) { if (countUnknown) { result += QString("
%1: %2").arg(QObject::tr("Malformed links")).arg(countUnknown); } if (countInvalid) { result += QString("
%1: %2").arg(QObject::tr("Invalid links")).arg(countInvalid); } } if(flag & RSLINK_PROCESS_NOTIFY_BAD_CHARS) result += QString("
%1").arg(QObject::tr("Warning: forbidden characters found in filenames. \nCharacters \",|,/,\\,<,>,*,? will be replaced by '_'.")) ; if ((result.isEmpty() == false) && (links.count() > countFileOpened)) { //Don't count files opened QMessageBox mb(QObject::tr("Result"), "" + result + "", QMessageBox::Information, QMessageBox::Ok, 0, 0); mb.exec(); } return 0; } /*static*/ int RetroShareLink::process(const QStringList &urls, RetroShareLink::enumType type /* = RetroShareLink::TYPE_UNKNOWN*/, uint flag /* = RSLINK_PROCESS_NOTIFY_ALL*/) { QList links; for (QStringList::const_iterator it = urls.begin(); it != urls.end(); ++it) { RetroShareLink link(*it); if (link.valid() && (type == RetroShareLink::TYPE_UNKNOWN || link.type() == type)) { links.append(link); } } return process(links, flag); } /**** RSLinkClipboard ********************************************/ void RSLinkClipboard::copyLinks(const QList& links) { QString res ; for (int i = 0; i < links.size(); ++i) res += links[i].toString() + "\n" ; QApplication::clipboard()->setText(res) ; } void RSLinkClipboard::pasteLinks(QList &links) { return parseClipboard(links); } void RSLinkClipboard::parseClipboard(QList &links) { // parse clipboard for links. // QString text = QApplication::clipboard()->text() ; parseText(text, links); } QString RSLinkClipboard::toString() { QList links; parseClipboard(links); QString res ; for(int i = 0; i < links.size(); ++i) res += links[i].toString() + "\n" ; return res ; } QString RSLinkClipboard::toHtml() { QList links; parseClipboard(links); QString res ; for(int i = 0; i < links.size(); ++i) res += links[i].toHtml() + "
" ; return res ; } bool RSLinkClipboard::empty(RetroShareLink::enumType type /* = RetroShareLink::TYPE_UNKNOWN*/) { QList links; parseClipboard(links); if (type == RetroShareLink::TYPE_UNKNOWN) { return links.empty(); } for (QList::iterator link = links.begin(); link != links.end(); ++link) { if (link->type() == type) { return false; } } return true; } /*static*/ int RSLinkClipboard::process(RetroShareLink::enumType type /* = RetroShareLink::TYPE_UNKNOWN*/, uint flag /* = RSLINK_PROCESS_NOTIFY_ALL*/) { QList links; pasteLinks(links); QList linksToProcess; for (int i = 0; i < links.size(); ++i) { if (links[i].valid() && (type == RetroShareLink::TYPE_UNKNOWN || links[i].type() == type)) { linksToProcess.append(links[i]); } } if (linksToProcess.isEmpty()) { return 0; } return RetroShareLink::process(linksToProcess, flag); } void RSLinkClipboard::parseText(QString text, QList &links) { links.clear(); std::cerr << "Parsing text:" << text.toStdString() << std::endl ; QRegExp rx(QString("retroshare://(%1)[^\r\n]+").arg(HOST_REGEXP)); int pos = 0; while((pos = rx.indexIn(text, pos)) != -1) { QString url(text.mid(pos, rx.matchedLength())); RetroShareLink link(url); if(link.valid()) { // check that the link is not already in the list: bool already = false ; for (int i = 0; i