mirror of
https://github.com/RetroShare/RetroShare.git
synced 2025-07-23 22:51:12 -04:00
447 lines
15 KiB
C++
447 lines
15 KiB
C++
/*******************************************************************************
|
|
* gui/common/RsCollection.cpp *
|
|
* *
|
|
* Copyright (C) 2011, Retroshare Team <retroshare.project@gmail.com> *
|
|
* *
|
|
* This program is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the GNU Affero General Public License as *
|
|
* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Affero General Public License *
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
|
* *
|
|
*******************************************************************************/
|
|
|
|
#include <stdexcept>
|
|
#include <retroshare/rsfiles.h>
|
|
|
|
#include "RsCollection.h"
|
|
#include "RsCollectionDialog.h"
|
|
#include "util/misc.h"
|
|
|
|
#include <QDir>
|
|
#include <QObject>
|
|
#include <QTextStream>
|
|
#include <QDomElement>
|
|
#include <QDomDocument>
|
|
#include <QMessageBox>
|
|
#include <QIcon>
|
|
|
|
// #define COLLECTION_DEBUG 1
|
|
|
|
const QString RsCollection::ExtensionString = QString("rscollection") ;
|
|
|
|
RsCollection::RsCollection()
|
|
{
|
|
mFileTree = std::unique_ptr<RsFileTree>(new RsFileTree());
|
|
}
|
|
RsCollection::RsCollection(const RsFileTree& ft)
|
|
{
|
|
mFileTree = std::unique_ptr<RsFileTree>(new RsFileTree(ft));
|
|
|
|
for(uint64_t i=0;i<mFileTree->numFiles();++i)
|
|
mHashes.insert(std::make_pair(mFileTree->fileData(i).hash,i ));
|
|
}
|
|
|
|
RsCollection::RsCollection(const std::vector<DirDetails>& file_infos,FileSearchFlags flags)
|
|
: mFileTree(new RsFileTree)
|
|
{
|
|
if(! ( (flags & RS_FILE_HINTS_LOCAL) || (flags & RS_FILE_HINTS_REMOTE)))
|
|
{
|
|
std::cerr << "(EE) Wrong flags passed to RsCollection constructor. Please fix the code!" << std::endl;
|
|
return ;
|
|
}
|
|
|
|
for(uint32_t i = 0;i<file_infos.size();++i)
|
|
recursAddElements(mFileTree->root(),file_infos[i],flags) ;
|
|
|
|
for(uint64_t i=0;i<mFileTree->numFiles();++i)
|
|
mHashes.insert(std::make_pair(mFileTree->fileData(i).hash,i ));
|
|
}
|
|
|
|
RsCollection::~RsCollection()
|
|
{
|
|
}
|
|
|
|
void RsCollection::autoDownloadFiles(ColFileInfo colFileInfo, QString dlDir) const
|
|
{
|
|
if (!colFileInfo.filename_has_wrong_characters)
|
|
{
|
|
QString cleanPath = dlDir + colFileInfo.path ;
|
|
std::cout << "making directory " << cleanPath.toStdString() << std::endl;
|
|
|
|
if(!QDir(QApplication::applicationDirPath()).mkpath(cleanPath))
|
|
std::cerr << "Unable to make path: " + cleanPath.toStdString() << std::endl;
|
|
|
|
if (colFileInfo.type==DIR_TYPE_FILE)
|
|
rsFiles->FileRequest(colFileInfo.name.toUtf8().constData(),
|
|
RsFileHash(colFileInfo.hash.toStdString()),
|
|
colFileInfo.size,
|
|
cleanPath.toUtf8().constData(),
|
|
RS_FILE_REQ_ANONYMOUS_ROUTING,
|
|
std::list<RsPeerId>());
|
|
}
|
|
foreach(ColFileInfo colFileInfoChild, colFileInfo.children)
|
|
{
|
|
autoDownloadFiles(colFileInfoChild, dlDir);
|
|
}
|
|
}
|
|
|
|
static QString purifyFileName(const QString& input,bool& bad)
|
|
{
|
|
static const QString bad_chars = "/\\\"*:?<>|" ;
|
|
bad = false ;
|
|
QString output = input ;
|
|
|
|
for(int i=0;i<output.length();++i)
|
|
for(int j=0;j<bad_chars.length();++j)
|
|
if(output[i] == bad_chars[j])
|
|
{
|
|
output[i] = '_' ;
|
|
bad = true ;
|
|
}
|
|
|
|
return output ;
|
|
}
|
|
|
|
void RsCollection::merge_in(const QString& fname,uint64_t size,const RsFileHash& hash,RsFileTree::DirIndex parent_index)
|
|
{
|
|
mHashes[hash]= mFileTree->addFile(parent_index,fname.toStdString(),hash,size);
|
|
}
|
|
void RsCollection::merge_in(const RsFileTree& tree, RsFileTree::DirIndex parent_index)
|
|
{
|
|
recursMergeTree(mFileTree->root(),tree,tree.directoryData(parent_index)) ;
|
|
}
|
|
|
|
void RsCollection::recursMergeTree(RsFileTree::DirIndex parent,const RsFileTree& tree,const RsFileTree::DirData& dd)
|
|
{
|
|
for(uint32_t i=0;i<dd.subfiles.size();++i)
|
|
{
|
|
const RsFileTree::FileData& fd(tree.fileData(dd.subfiles[i]));
|
|
mHashes[fd.hash] = mFileTree->addFile(parent,fd.name,fd.hash,fd.size);
|
|
}
|
|
for(uint32_t i=0;i<dd.subdirs.size();++i)
|
|
{
|
|
const RsFileTree::DirData& ld(tree.directoryData(dd.subdirs[i]));
|
|
|
|
auto new_dir_index = mFileTree->addDirectory(parent,ld.name);
|
|
recursMergeTree(new_dir_index,tree,ld);
|
|
}
|
|
}
|
|
|
|
void RsCollection::recursAddElements(RsFileTree::DirIndex parent, const DirDetails& dd, FileSearchFlags flags)
|
|
{
|
|
if (dd.type == DIR_TYPE_FILE)
|
|
mHashes[dd.hash] = mFileTree->addFile(parent,dd.name,dd.hash,dd.size);
|
|
else if (dd.type == DIR_TYPE_DIR)
|
|
{
|
|
RsFileTree::DirIndex new_dir_index = mFileTree->addDirectory(parent,dd.name);
|
|
|
|
for(uint32_t i=0;i<dd.children.size();++i)
|
|
{
|
|
if (!dd.children[i].ref)
|
|
continue;
|
|
|
|
DirDetails subDirDetails;
|
|
|
|
if (!rsFiles->RequestDirDetails(dd.children[i].ref, subDirDetails, flags))
|
|
continue;
|
|
|
|
recursAddElements(new_dir_index,subDirDetails,flags) ;
|
|
}
|
|
}
|
|
}
|
|
|
|
QString RsCollection::errorString(RsCollectionErrorCode code)
|
|
{
|
|
switch(code)
|
|
{
|
|
default: [[fallthrough]] ;
|
|
case RsCollectionErrorCode::UNKNOWN_ERROR: return QObject::tr("Unknown error");
|
|
case RsCollectionErrorCode::COLLECTION_NO_ERROR: return QObject::tr("No error");
|
|
case RsCollectionErrorCode::FILE_READ_ERROR: return QObject::tr("Error while openning file");
|
|
case RsCollectionErrorCode::FILE_CONTAINS_HARMFUL_STRINGS: return QObject::tr("Collection file contains potentially harmful code");
|
|
case RsCollectionErrorCode::INVALID_ROOT_NODE: return QObject::tr("Invalid root node. RsCollection node was expected.");
|
|
case RsCollectionErrorCode::XML_PARSING_ERROR: return QObject::tr("XML parsing error in collection file");
|
|
}
|
|
}
|
|
|
|
RsCollection::RsCollection(const RsCollection& col)
|
|
: mFileTree(new RsFileTree(*col.mFileTree)),mHashes(col.mHashes)
|
|
{
|
|
}
|
|
|
|
RsCollection::RsCollection(const QString& fileName, RsCollectionErrorCode& error)
|
|
: mFileTree(new RsFileTree)
|
|
{
|
|
if (!checkFile(fileName,error))
|
|
return ;
|
|
|
|
QFile file(fileName);
|
|
|
|
if (!file.open(QIODevice::ReadOnly))
|
|
{
|
|
std::cerr << "Cannot open file " << fileName.toStdString() << " !!" << std::endl;
|
|
error = RsCollectionErrorCode::FILE_READ_ERROR;
|
|
//showErrorBox(fileName, QApplication::translate("RsCollectionFile", "Cannot open file %1").arg(fileName));
|
|
return ;
|
|
}
|
|
|
|
QDomDocument xml_doc;
|
|
bool ok = xml_doc.setContent(&file) ;
|
|
|
|
if(!ok)
|
|
{
|
|
error = RsCollectionErrorCode::XML_PARSING_ERROR;
|
|
return;
|
|
}
|
|
file.close();
|
|
|
|
QDomNode root = xml_doc.elementsByTagName("RsCollection").at(0).toElement();
|
|
|
|
if(root.isNull())
|
|
{
|
|
error = RsCollectionErrorCode::INVALID_ROOT_NODE;
|
|
return;
|
|
}
|
|
|
|
if(!recursParseXml(xml_doc,root,0))
|
|
error = RsCollectionErrorCode::XML_PARSING_ERROR;
|
|
else
|
|
error = RsCollectionErrorCode::COLLECTION_NO_ERROR;
|
|
}
|
|
|
|
// check that the file is a valid rscollection file, and not a lol bomb or some shit like this
|
|
|
|
bool RsCollection::checkFile(const QString& fileName, RsCollectionErrorCode& error)
|
|
{
|
|
QFile file(fileName);
|
|
error = RsCollectionErrorCode::COLLECTION_NO_ERROR;
|
|
|
|
if (!file.open(QIODevice::ReadOnly))
|
|
{
|
|
std::cerr << "Cannot open file " << fileName.toStdString() << " !!" << std::endl;
|
|
error = RsCollectionErrorCode::FILE_READ_ERROR;
|
|
|
|
//showErrorBox(fileName, QApplication::translate("RsCollectionFile", "Cannot open file %1").arg(fileName));
|
|
return false;
|
|
}
|
|
if (file.reset()){
|
|
std::cerr << "Checking this file for bomb elements and various wrong stuff" << std::endl;
|
|
char c ;
|
|
|
|
std::vector<std::string> bad_strings ;
|
|
bad_strings.push_back(std::string("<!entity ")) ;
|
|
static const int max_size = 12 ; // should be as large as the largest element in bad_strings
|
|
char current[max_size] = { 0,0,0,0,0,0,0,0,0,0,0,0 } ;
|
|
int n=0 ;
|
|
|
|
while( !file.atEnd() || n >= 0)
|
|
{
|
|
if (!file.atEnd())
|
|
file.getChar(&c);
|
|
else
|
|
c=0;
|
|
|
|
if(c == '\t' || c == '\n' || c == '\b' || c == '\r')
|
|
continue ;
|
|
|
|
if (n == max_size || file.atEnd())
|
|
for(int i=0;i<n-1;++i)
|
|
current[i] = current[i+1] ;
|
|
|
|
if(n == max_size)
|
|
--n ;
|
|
|
|
if(c >= 'A' && c <= 'Z') c += 'a' - 'A' ;
|
|
|
|
if(!file.atEnd())
|
|
current[n] = c ;
|
|
else
|
|
current[n] = 0 ;
|
|
|
|
//std::cerr << "n==" << n <<" Checking string " << std::string(current,n+1) << " c = " << std::hex << (int)c << std::dec << std::endl;
|
|
|
|
for(uint i=0;i<bad_strings.size();++i)
|
|
if(std::string(current,bad_strings[i].length()) == bad_strings[i])
|
|
{
|
|
//showErrorBox(file.fileName(), QApplication::translate("RsCollectionFile", "This file contains the string \"%1\" and is therefore an invalid collection file. \n\nIf you believe it is correct, remove the corresponding line from the file and re-open it with Retroshare.").arg(bad_strings[i].c_str()));
|
|
error = RsCollectionErrorCode::FILE_CONTAINS_HARMFUL_STRINGS;
|
|
file.close();
|
|
return false ;
|
|
//std::cerr << "Bad string detected" << std::endl;
|
|
}
|
|
|
|
if(file.atEnd())
|
|
n-- ;
|
|
else if(n < max_size)
|
|
++n ;
|
|
}
|
|
file.close();
|
|
return true;
|
|
}
|
|
error = RsCollectionErrorCode::UNKNOWN_ERROR;
|
|
return false;
|
|
}
|
|
|
|
bool RsCollection::save(const QString& fileName) const
|
|
{
|
|
QFile file(fileName);
|
|
|
|
if (!file.open(QIODevice::WriteOnly))
|
|
{
|
|
std::cerr << "Cannot write to file " << fileName.toStdString() << " !!" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
QDomDocument xml_doc ;
|
|
QDomElement root = xml_doc.createElement("RsCollection");
|
|
|
|
const RsFileTree::DirData& root_data(mFileTree->directoryData(mFileTree->root()));
|
|
|
|
if(!recursExportToXml(xml_doc,root,root_data))
|
|
return false;
|
|
|
|
xml_doc.appendChild(root);
|
|
|
|
QTextStream stream(&file) ;
|
|
stream.setCodec("UTF-8") ;
|
|
|
|
stream << xml_doc.toString() ;
|
|
file.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RsCollection::recursParseXml(QDomDocument& doc,const QDomNode& e,const RsFileTree::DirIndex parent)
|
|
{
|
|
mHashes.clear();
|
|
QDomNode n = e.firstChild() ;
|
|
#ifdef COLLECTION_DEBUG
|
|
std::cerr << "Parsing element " << e.tagName().toStdString() << std::endl;
|
|
#endif
|
|
|
|
while(!n.isNull())
|
|
{
|
|
QDomElement ee = n.toElement(); // try to convert the node to an element.
|
|
|
|
#ifdef COLLECTION_DEBUG
|
|
std::cerr << " Seeing child " << ee.tagName().toStdString() << std::endl;
|
|
#endif
|
|
|
|
if(ee.tagName() == QString("File"))
|
|
{
|
|
RsFileHash hash(ee.attribute(QString("sha1")).toStdString()) ;
|
|
|
|
bool bad_chars_detected = false;
|
|
std::string name = purifyFileName(ee.attribute(QString("name")), bad_chars_detected).toUtf8().constData() ;
|
|
uint64_t size = ee.attribute(QString("size")).toULongLong() ;
|
|
|
|
mHashes[hash] = mFileTree->addFile(parent,name,hash,size);
|
|
}
|
|
else if(ee.tagName() == QString("Directory"))
|
|
{
|
|
bool bad_chars_detected = false ;
|
|
std::string cleanDirName = purifyFileName(ee.attribute(QString("name")),bad_chars_detected).toUtf8().constData() ;
|
|
|
|
RsFileTree::DirIndex new_dir_index = mFileTree->addDirectory(parent,cleanDirName);
|
|
|
|
recursParseXml(doc,ee,new_dir_index);
|
|
}
|
|
|
|
n = n.nextSibling() ;
|
|
}
|
|
return true;
|
|
}
|
|
bool RsCollection::recursExportToXml(QDomDocument& doc,QDomElement& e,const RsFileTree::DirData& dd) const
|
|
{
|
|
for(uint32_t i=0;i<dd.subfiles.size();++i)
|
|
{
|
|
QDomElement f = doc.createElement("File") ;
|
|
|
|
const RsFileTree::FileData& fd(mFileTree->fileData(dd.subfiles[i]));
|
|
|
|
f.setAttribute(QString("name"),QString::fromUtf8(fd.name.c_str())) ;
|
|
f.setAttribute(QString("sha1"),QString::fromStdString(fd.hash.toStdString())) ;
|
|
f.setAttribute(QString("size"),QString::number(fd.size)) ;
|
|
|
|
e.appendChild(f) ;
|
|
}
|
|
|
|
for(uint32_t i=0;i<dd.subdirs.size();++i)
|
|
{
|
|
const RsFileTree::DirData& di(mFileTree->directoryData(dd.subdirs[i]));
|
|
|
|
QDomElement d = doc.createElement("Directory") ;
|
|
d.setAttribute(QString("name"),QString::fromUtf8(di.name.c_str())) ;
|
|
|
|
if(!recursExportToXml(doc,d,di))
|
|
return false;
|
|
|
|
e.appendChild(d) ;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
qulonglong RsCollection::count() const
|
|
{
|
|
return mFileTree->numFiles();
|
|
}
|
|
qulonglong RsCollection::size()
|
|
{
|
|
return mFileTree->totalFileSize();
|
|
}
|
|
|
|
bool RsCollection::isCollectionFile(const QString &fileName)
|
|
{
|
|
QString ext = QFileInfo(fileName).suffix().toLower();
|
|
|
|
return (ext == RsCollection::ExtensionString);
|
|
}
|
|
|
|
void RsCollection::updateHashes(const std::map<RsFileHash,RsFileHash>& old_to_new_hashes)
|
|
{
|
|
for(auto it:old_to_new_hashes)
|
|
{
|
|
auto fit = mHashes.find(it.first);
|
|
|
|
if(fit == mHashes.end())
|
|
{
|
|
RsErr() << "Could not find hash " << it.first << " in RsCollection list of hashes. This is a bug." ;
|
|
return ;
|
|
}
|
|
const auto& fd(mFileTree->fileData(fit->second));
|
|
|
|
if(fd.hash != it.first)
|
|
{
|
|
RsErr() << "Mismatch hash for file " << fd.name << " (found " << fd.hash << " instead of " << it.first << ") in RsCollection list of hashes. This is a bug." ;
|
|
return ;
|
|
}
|
|
mFileTree->updateFile(fit->second,fd.name,it.second,fd.size);
|
|
}
|
|
}
|
|
|
|
bool RsCollection::removeFile(RsFileTree::FileIndex index_to_remove,RsFileTree::DirIndex parent_index)
|
|
{
|
|
return mFileTree->removeFile(index_to_remove,parent_index);
|
|
}
|
|
bool RsCollection::removeDirectory(RsFileTree::DirIndex index_to_remove,RsFileTree::DirIndex parent_index)
|
|
{
|
|
return mFileTree->removeDirectory(index_to_remove,parent_index);
|
|
}
|
|
|
|
void RsCollection::cleanup()
|
|
{
|
|
RsDbg() << "Cleaning up RsCollection with " << mFileTree->numDirs() << " dirs and " << mFileTree->numFiles() << " files." ;
|
|
|
|
mFileTree = RsFileTree::fromTreeCleaned(*mFileTree);
|
|
|
|
RsDbg() << "Simplified to " << mFileTree->numDirs() << " dirs and " << mFileTree->numFiles() << " files.";
|
|
}
|