# # # patch "src/monotone/MonotoneHandle.cpp" # from [86f796134ef0dacf87dcd9ab5b661c4e21f32bce] # to [64577f6c8702eb62dffdb572226138e6b4fdc399] # # patch "src/monotone/MonotoneHandle.h" # from [77be272ea7291102c6d439e36f2aef180de5e2b5] # to [b4216f23afadeb16b99423faaeddafa99d365de1] # # patch "src/monotone/MonotoneThread.cpp" # from [873624b25c7e21f540123a7391fceab9321c97f8] # to [3e57addf8d783dfee4b0d23b7623d251a1863216] # ============================================================ --- src/monotone/MonotoneHandle.cpp 86f796134ef0dacf87dcd9ab5b661c4e21f32bce +++ src/monotone/MonotoneHandle.cpp 64577f6c8702eb62dffdb572226138e6b4fdc399 @@ -25,8 +25,15 @@ QStringList MonotoneHandle::defaultDatab QStringList MonotoneHandle::defaultDatabaseLocations; -MonotoneHandle::MonotoneHandle(const QString & d, Type t) : data(d), type(t) {} +MonotoneHandle::MonotoneHandle(Type t, const QString & d) + : type(t), data(d) +{} +QString MonotoneHandle::identify() const +{ + return QString("%1:%2").arg((int)type).arg(data); +} + void MonotoneHandle::validateDatabaseFile(const QString & database) { QFile dbfile(database); @@ -129,6 +136,99 @@ void MonotoneHandle::validateWorkspacePa } } +void MonotoneHandle::validatePrivateKey(const QString & keyFragment, QString & keyId) +{ + if (keyFragment.isEmpty()) + { + throw GuitoneException( + QObject::tr("key fragment in URI is empty") + ); + } + + QString output; + QStringList args; + args << "au" << "keys"; + + if (!MonotoneProcess::singleRun(args, MonotoneHandlePtr(), output)) + { + throw GuitoneException( + QObject::tr("could not query private keys") + ); + } + + BasicIOParser parser(output); + if (!parser.parse()) + { + throw GuitoneException( + QObject::tr("could not parse basic_io from key output") + ); + } + + QStringList possibleKeys; + + StanzaList stanzas = parser.getStanzas(); + foreach (const Stanza & st, stanzas) + { + QString keyHash; + bool matched = false; + bool isPrivate = false; + + foreach (const StanzaEntry & sten, st) + { + if (sten.sym == "hash") + { + keyHash = sten.hash; + if (sten.hash.indexOf(keyFragment) != -1) + { + matched = true; + } + continue; + } + + if (sten.sym == "given_name" || sten.sym == "local_name") + { + I(sten.vals.size() == 1); + if (sten.vals.at(0).indexOf(keyFragment) != -1) + { + matched = true; + } + } + + // sanity, basically + if (sten.sym == "private_location") + { + I(sten.vals.size() == 1); + if (sten.vals.at(0) == "keystore") + { + isPrivate = true; + } + } + } + + if (matched && isPrivate) + { + possibleKeys.push_back(keyHash); + } + } + + if (possibleKeys.size() == 0) + { + throw GuitoneException( + QObject::tr("no private key for fragment '%1' found").arg(keyFragment) + ); + } + + if (possibleKeys.size() > 1) + { + throw GuitoneException( + QObject::tr("multiple private keys for fragment '%1' found:\n\t%2") + .arg(keyFragment).arg(possibleKeys.join("\n\t")) + ); + } + + keyId = possibleKeys.at(0); +} + void MonotoneHandle::validateServerConnection(const QString & host) { QStringList hostAndPort = host.split(":", QString::SkipEmptyParts); @@ -199,48 +299,58 @@ MonotoneHandlePtr MonotoneHandle::create MonotoneHandlePtr MonotoneHandle::create(const QString & pathOrURI) { + Type type; QString cleanedPathOrURI(pathOrURI.trimmed()); - Type type; - if (cleanedPathOrURI.isEmpty()) { type = empty_handle; } else { - QRegExp rx("^mtn://([\\w\\-]+(?:\\.[\\w\\-]+)*(?::\\d+)?)$"); + QRegExp rx("^mtn://(?:(\\w*)@)?([\\w\\-]+(?:\\.[\\w\\-]+)*(?::\\d+)?)$"); if (rx.indexIn(cleanedPathOrURI) != -1) { - validateServerConnection(rx.cap(1)); type = server_handle; + validateServerConnection(rx.cap(2)); + + if (!rx.cap(1).isEmpty()) + { + QString key; + validatePrivateKey(rx.cap(1), key); + + // FIXME: this might not be so readable, but since most keys + // have email syntax, mtn://address@hidden@server.com isn't better + // either without escaping + cleanedPathOrURI = "mtn://" + key + "@" + rx.cap(2); + } } else { QFileInfo finfo(cleanedPathOrURI); if (finfo.isDir()) { + type = workspace_handle; + cleanedPathOrURI = finfo.canonicalFilePath(); QString database; validateWorkspacePath(cleanedPathOrURI, database); resolveDatabaseAlias(database); validateDatabaseFile(database); - - type = workspace_handle; } else { + type = database_handle; + resolveDatabaseAlias(cleanedPathOrURI); validateDatabaseFile(cleanedPathOrURI); - - type = database_handle; } } I(!cleanedPathOrURI.isEmpty()); } - return MonotoneHandlePtr(new MonotoneHandle(cleanedPathOrURI, type)); + return MonotoneHandlePtr(new MonotoneHandle(type, cleanedPathOrURI)); } ============================================================ --- src/monotone/MonotoneHandle.h 77be272ea7291102c6d439e36f2aef180de5e2b5 +++ src/monotone/MonotoneHandle.h b4216f23afadeb16b99423faaeddafa99d365de1 @@ -30,19 +30,22 @@ public: server_handle } Type; Type getType() const { return type; } + QString getData() const { return data; } - QString identify() const { return QString("%1:%2").arg((int)type).arg(data); } + QString identify() const; + static MonotoneHandlePtr create(const QString & handle = QString()); private: + Type type; QString data; - Type type; - MonotoneHandle(const QString &, Type); + MonotoneHandle(Type, const QString &); static void validateWorkspacePath(QString &, QString &); static void validateDatabaseFile(const QString &); + static void validatePrivateKey(const QString &, QString &); static void validateServerConnection(const QString &); static void resolveDatabaseAlias(QString &); ============================================================ --- src/monotone/MonotoneThread.cpp 873624b25c7e21f540123a7391fceab9321c97f8 +++ src/monotone/MonotoneThread.cpp 3e57addf8d783dfee4b0d23b7623d251a1863216 @@ -109,7 +109,6 @@ void MonotoneThread::run() } MonotoneResourceFile rcFile; args << "--rcfile" << rcFile.fileName(); - args << "--quiet"; if (monotoneHandle->getType() == MonotoneHandle::database_handle) { @@ -128,9 +127,31 @@ void MonotoneThread::run() if (monotoneHandle->getType() == MonotoneHandle::server_handle) { - // remove the mtn:// prefix - unfortunately monotone does not - // yet understand its URIs everywhere + // remote_stdio outputs a lot of junk we don't want to listen to + args << "--reallyquiet"; + + // FIXME: remove the mtn:// prefix - monotone up until 0.48 doesn't + // understand the mtn:// syntax everywhere QString host = monotoneHandle->getData().mid(6); + + // FIXME: monotone unfortunately also does not understand our + // custom '@' syntax - so we need to give the key / auth explicitely + // with --key + int pos = host.indexOf("@"); + QString key; + if (pos != -1) + { + key = host.left(pos); + host = host.mid(pos + 1); + } + + // FIXME: monotone 0.48 crashes on explicit anonymous connections + // see https://savannah.nongnu.org/bugs/?30237 + if (!key.isEmpty()) + { + args << "--key" << key; + } + args << "remote_stdio" << host; } else