/* This file is part of cpp-ethereum. cpp-ethereum 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 3 of the License, or (at your option) any later version. cpp-ethereum 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 cpp-ethereum. If not, see . */ /** @file DappLoader.cpp * @author Arkadiy Paronyan * @date 2015 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DappLoader.h" using namespace dev; using namespace dev::eth; using namespace dev::crypto; QString contentsOfQResource(std::string const& res); DappLoader::DappLoader(QObject* _parent, WebThreeDirect* _web3, Address _nameReg): QObject(_parent), m_web3(_web3), m_nameReg(_nameReg) { connect(&m_net, &QNetworkAccessManager::finished, this, &DappLoader::downloadComplete); } DappLocation DappLoader::resolveAppUri(QString const& _uri) { QUrl url(_uri); if (!url.scheme().isEmpty() && url.scheme() != "eth") throw dev::Exception(); //TODO: QStringList parts = url.host().split('.', QString::SkipEmptyParts); QStringList domainParts; std::reverse(parts.begin(), parts.end()); parts.append(url.path().split('/', QString::SkipEmptyParts)); Address address = m_nameReg; Address lastAddress; int partIndex = 0; h256 contentHash; while (address && partIndex < parts.length()) { lastAddress = address; string32 name = ZeroString32; QByteArray utf8 = parts[partIndex].toUtf8(); std::copy(utf8.data(), utf8.data() + utf8.size(), name.data()); if (address != m_nameReg) address = abiOut
(web3()->ethereum()->call(address, abiIn("subRegistrar(bytes32)", name)).output); else address = abiOut
(web3()->ethereum()->call(address, abiIn("register(bytes32)", name)).output); domainParts.append(parts[partIndex]); if (!address) { //we have the address of the last part, try to get content hash contentHash = abiOut(web3()->ethereum()->call(lastAddress, abiIn("content(bytes32)", name)).output); if (!contentHash) throw dev::Exception() << errinfo_comment("Can't resolve address"); } ++partIndex; } string32 urlHintName = ZeroString32; QByteArray utf8 = QString("urlhint").toUtf8(); std::copy(utf8.data(), utf8.data() + utf8.size(), urlHintName.data()); Address urlHint = abiOut
(web3()->ethereum()->call(m_nameReg, abiIn("addr(bytes32)", urlHintName)).output); string32 contentUrl = abiOut(web3()->ethereum()->call(urlHint, abiIn("url(bytes32)", contentHash)).output); QString domain = domainParts.join('/'); parts.erase(parts.begin(), parts.begin() + partIndex); QString path = parts.join('/'); QString contentUrlString = QString::fromUtf8(std::string(contentUrl.data(), contentUrl.size()).c_str()); if (!contentUrlString.startsWith("http://") || !contentUrlString.startsWith("https://")) contentUrlString = "http://" + contentUrlString; return DappLocation { domain, path, contentUrlString, contentHash }; } void DappLoader::downloadComplete(QNetworkReply* _reply) { QUrl requestUrl = _reply->request().url(); if (m_pageUrls.count(requestUrl) != 0) { //inject web3 js QByteArray content = "\n"); content.append(_reply->readAll()); QString contentType = _reply->header(QNetworkRequest::ContentTypeHeader).toString(); if (contentType.isEmpty()) { QMimeDatabase db; contentType = db.mimeTypeForUrl(requestUrl).name(); } pageReady(content, contentType, requestUrl); return; } try { //try to interpret as rlp QByteArray data = _reply->readAll(); _reply->deleteLater(); h256 expected = m_uriHashes[requestUrl]; bytes package(reinterpret_cast(data.constData()), reinterpret_cast(data.constData() + data.size())); Secp256k1 dec; dec.decrypt(expected, package); h256 got = sha3(package); if (got != expected) { //try base64 data = QByteArray::fromBase64(data); package = bytes(reinterpret_cast(data.constData()), reinterpret_cast(data.constData() + data.size())); dec.decrypt(expected, package); got = sha3(package); if (got != expected) throw dev::Exception() << errinfo_comment("Dapp content hash does not match"); } RLP rlp(package); loadDapp(rlp); } catch (...) { qWarning() << tr("Error downloading DApp: ") << boost::current_exception_diagnostic_information().c_str(); emit dappError(); } } void DappLoader::loadDapp(RLP const& _rlp) { Dapp dapp; unsigned len = _rlp.itemCountStrict(); dapp.manifest = loadManifest(_rlp[0].toString()); for (unsigned c = 1; c < len; ++c) { bytesConstRef content = _rlp[c].toBytesConstRef(); h256 hash = sha3(content); auto entry = std::find_if(dapp.manifest.entries.cbegin(), dapp.manifest.entries.cend(), [=](ManifestEntry const& _e) { return _e.hash == hash; }); if (entry != dapp.manifest.entries.cend()) { if (entry->path == "/deployment.js") { //inject web3 code bytes b(web3Content().data(), web3Content().data() + web3Content().size()); b.insert(b.end(), content.begin(), content.end()); dapp.content[hash] = b; } else dapp.content[hash] = content.toBytes(); } else throw dev::Exception() << errinfo_comment("Dapp content hash does not match"); } emit dappReady(dapp); } QByteArray const& DappLoader::web3Content() { if (m_web3Js.isEmpty()) { QString code; code += contentsOfQResource(":/js/bignumber.min.js"); code += "\n"; code += contentsOfQResource(":/js/webthree.js"); code += "\n"; code += contentsOfQResource(":/js/setup.js"); code += "\n"; m_web3Js = code.toLatin1(); } return m_web3Js; } Manifest DappLoader::loadManifest(std::string const& _manifest) { /// https://github.com/ethereum/go-ethereum/wiki/URL-Scheme Manifest manifest; Json::Reader jsonReader; Json::Value root; jsonReader.parse(_manifest, root, false); Json::Value entries = root["entries"]; for (Json::ValueIterator it = entries.begin(); it != entries.end(); ++it) { Json::Value const& entryValue = *it; std::string path = entryValue["path"].asString(); if (path.size() == 0 || path[0] != '/') path = "/" + path; std::string contentType = entryValue["contentType"].asString(); std::string strHash = entryValue["hash"].asString(); if (strHash.length() == 64) strHash = "0x" + strHash; h256 hash = jsToFixed<32>(strHash); unsigned httpStatus = entryValue["status"].asInt(); manifest.entries.push_back(ManifestEntry{ path, hash, contentType, httpStatus }); } return manifest; } void DappLoader::loadDapp(QString const& _uri) { QUrl uri(_uri); QUrl contentUri; h256 hash; if (uri.path().endsWith(".dapp") && uri.query().startsWith("hash=")) { contentUri = uri; QString query = uri.query(); query.remove("hash="); if (!query.startsWith("0x")) query.insert(0, "0x"); hash = jsToFixed<32>(query.toStdString()); } else { DappLocation location = resolveAppUri(_uri); contentUri = location.contentUri; hash = location.contentHash; uri = contentUri; } QNetworkRequest request(contentUri); m_uriHashes[uri] = hash; m_net.get(request); } void DappLoader::loadPage(QString const& _uri) { QUrl uri(_uri); QNetworkRequest request(uri); m_pageUrls.insert(uri); m_net.get(request); }