arkpar
10 years ago
committed by
arkpar
12 changed files with 636 additions and 38 deletions
@ -0,0 +1,141 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappHost.cpp
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include <microhttpd.h> |
|||
#include <boost/algorithm/string.hpp> |
|||
#include <libdevcore/Common.h> |
|||
#include <QUrl> |
|||
#include "DappHost.h" |
|||
|
|||
using namespace dev; |
|||
|
|||
DappHost::DappHost(int _port, int _threads): |
|||
m_port(_port), m_threads(_threads), m_running(false), m_daemon(nullptr) |
|||
{ |
|||
startListening(); |
|||
} |
|||
|
|||
DappHost::~DappHost() |
|||
{ |
|||
stopListening(); |
|||
} |
|||
|
|||
void DappHost::startListening() |
|||
{ |
|||
if(!this->m_running) |
|||
{ |
|||
this->m_daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, this->m_port, nullptr, nullptr, &DappHost::callback, this, MHD_OPTION_THREAD_POOL_SIZE, this->m_threads, MHD_OPTION_END); |
|||
if (this->m_daemon != nullptr) |
|||
this->m_running = true; |
|||
} |
|||
} |
|||
|
|||
void DappHost::stopListening() |
|||
{ |
|||
if(this->m_running) |
|||
{ |
|||
MHD_stop_daemon(this->m_daemon); |
|||
this->m_running = false; |
|||
} |
|||
} |
|||
|
|||
void DappHost::sendOptionsResponse(MHD_Connection* _connection) |
|||
{ |
|||
MHD_Response *result = MHD_create_response_from_data(0, NULL, 0, 1); |
|||
MHD_add_response_header(result, "Allow", "GET, OPTIONS"); |
|||
//MHD_add_response_header(result, "Access-Control-Allow-Origin", "*");
|
|||
MHD_add_response_header(result, "Access-Control-Allow-Headers", "origin, content-type, accept"); |
|||
MHD_add_response_header(result, "DAV", "1"); |
|||
MHD_queue_response(_connection, MHD_HTTP_OK, result); |
|||
MHD_destroy_response(result); |
|||
} |
|||
|
|||
void DappHost::sendNotAllowedResponse(MHD_Connection* _connection) |
|||
{ |
|||
MHD_Response *result = MHD_create_response_from_data(0, NULL, 0, 1); |
|||
MHD_add_response_header(result, "Allow", "GET, OPTIONS"); |
|||
MHD_queue_response(_connection, MHD_HTTP_METHOD_NOT_ALLOWED, result); |
|||
MHD_destroy_response(result); |
|||
} |
|||
|
|||
void DappHost::sendResponse(std::string const& _url, MHD_Connection* _connection) |
|||
{ |
|||
QUrl requestUrl(QString::fromStdString(_url)); |
|||
QString path = requestUrl.path().toLower(); |
|||
if (path.isEmpty()) |
|||
path = "/"; |
|||
|
|||
bytesConstRef response; |
|||
unsigned code = MHD_HTTP_NOT_FOUND; |
|||
std::string contentType; |
|||
|
|||
while (!path.isEmpty()) |
|||
{ |
|||
auto iter = m_entriesByPath.find(path); |
|||
if (iter != m_entriesByPath.end()) |
|||
{ |
|||
ManifestEntry const* entry = iter->second; |
|||
auto contentIter = m_dapp.content.find(entry->hash); |
|||
if (contentIter == m_dapp.content.end()) |
|||
break; |
|||
|
|||
response = bytesConstRef(contentIter->second.data(), contentIter->second.size()); |
|||
code = entry->httpStatus != 0 ? entry->httpStatus : MHD_HTTP_OK; |
|||
contentType = entry->contentType; |
|||
break; |
|||
} |
|||
path.truncate(path.length() - 1); |
|||
path = path.mid(0, path.lastIndexOf('/')); |
|||
} |
|||
|
|||
MHD_Response *result = MHD_create_response_from_data(response.size(), const_cast<byte*>(response.data()), 0, 1); |
|||
if (!contentType.empty()) |
|||
MHD_add_response_header(result, "Content-Type", contentType.c_str()); |
|||
//MHD_add_response_header(result, "Access-Control-Allow-Origin", "*");
|
|||
MHD_queue_response(_connection, code, result); |
|||
MHD_destroy_response(result); |
|||
} |
|||
|
|||
int DappHost::callback(void* _cls, MHD_Connection* _connection, char const* _url, char const* _method, char const* _version, char const* _uploadData, size_t* _uploadDataSize, void** _conCls) |
|||
{ |
|||
(void)_version; |
|||
(void)_uploadData; |
|||
(void)_uploadDataSize; |
|||
(void)_conCls; |
|||
DappHost* host = static_cast<DappHost*>(_cls); |
|||
if (std::string("GET") == _method) |
|||
host->sendResponse(std::string(_url), _connection); |
|||
else if (std::string("OPTIONS") == _method) |
|||
host->sendOptionsResponse(_connection); |
|||
else |
|||
host->sendNotAllowedResponse(_connection); |
|||
return MHD_YES; |
|||
} |
|||
|
|||
QUrl DappHost::hostDapp(Dapp&& _dapp) |
|||
{ |
|||
m_dapp = std::move(_dapp); |
|||
m_entriesByPath.clear(); |
|||
for (ManifestEntry const& entry: m_dapp.manifest.entries) |
|||
m_entriesByPath[QString::fromStdString(entry.path)] = &entry; |
|||
|
|||
return QUrl(QString("http://localhost:%1/").arg(m_port)); |
|||
} |
@ -0,0 +1,58 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappHost.h
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <QUrl> |
|||
#include <QString> |
|||
#include "DappLoader.h" |
|||
|
|||
struct MHD_Daemon; |
|||
struct MHD_Connection; |
|||
|
|||
/// DApp web server. Servers web content, resolves paths by hashes
|
|||
class DappHost |
|||
{ |
|||
public: |
|||
/// @param _port Network pork to listen for incoming connections
|
|||
/// @param _threads Max number of threads to process requests
|
|||
DappHost(int _port, int _threads = 10); |
|||
virtual ~DappHost(); |
|||
/// Load and host a dapp. Previsous dapp in discarded. Synchronous
|
|||
QUrl hostDapp(Dapp&& _dapp); |
|||
|
|||
private: |
|||
void startListening(); |
|||
void stopListening(); |
|||
void sendOptionsResponse(MHD_Connection* _connection); |
|||
void sendNotAllowedResponse(MHD_Connection* _connection); |
|||
void sendResponse(std::string const& _url, MHD_Connection* _connection); |
|||
static int callback(void* _cls, MHD_Connection* _connection, char const* _url, char const* _method, char const* _version, char const* _uploadData, size_t* _uploadDataSize, void** _conCls); |
|||
|
|||
int m_port; |
|||
int m_threads; |
|||
bool m_running; |
|||
MHD_Daemon* m_daemon; |
|||
Dapp m_dapp; |
|||
std::map<QString, ManifestEntry const*> m_entriesByPath; |
|||
}; |
|||
|
@ -0,0 +1,203 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappLoader.cpp
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include <algorithm> |
|||
#include <json/json.h> |
|||
#include <QUrl> |
|||
#include <QStringList> |
|||
#include <QNetworkAccessManager> |
|||
#include <QNetworkReply> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/RLP.h> |
|||
#include <libdevcrypto/CryptoPP.h> |
|||
#include <libdevcrypto/SHA3.h> |
|||
#include <libethcore/CommonJS.h> |
|||
#include <libethereum/Client.h> |
|||
#include <libwebthree/WebThree.h> |
|||
#include "DappLoader.h" |
|||
|
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::crypto; |
|||
|
|||
Address c_registrar = Address("0000000000000000000000000000000000000a28"); |
|||
Address c_urlHint = Address("0000000000000000000000000000000000000a29"); |
|||
|
|||
QString contentsOfQResource(std::string const& res); |
|||
|
|||
DappLoader::DappLoader(QObject* _parent, WebThreeDirect* _web3): |
|||
QObject(_parent), m_web3(_web3) |
|||
{ |
|||
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 = c_registrar; |
|||
Address lastAddress; |
|||
int partIndex = 0; |
|||
|
|||
h256 contentHash; |
|||
|
|||
while (address && partIndex < parts.length()) |
|||
{ |
|||
lastAddress = address; |
|||
string32 name = { 0 }; |
|||
QByteArray utf8 = parts[partIndex].toUtf8(); |
|||
std::copy(utf8.data(), utf8.data() + utf8.size(), name.data()); |
|||
address = abiOut<Address>(web3()->ethereum()->call(address, abiIn("addr(string32)", name))); |
|||
domainParts.append(parts[partIndex]); |
|||
if (!address) |
|||
{ |
|||
//we have the address of the last part, try to get content hash
|
|||
contentHash = abiOut<h256>(web3()->ethereum()->call(lastAddress, abiIn("content(string32)", name))); |
|||
if (!contentHash) |
|||
throw dev::Exception() << errinfo_comment("Can't resolve address"); |
|||
} |
|||
++partIndex; |
|||
} |
|||
|
|||
|
|||
string32 contentUrl = abiOut<string32>(web3()->ethereum()->call(c_urlHint, abiIn("url(hash256)", contentHash))); |
|||
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) |
|||
{ |
|||
try |
|||
{ |
|||
//try to interpret as rlp
|
|||
QByteArray data = _reply->readAll(); |
|||
_reply->deleteLater(); |
|||
|
|||
h256 expected = m_uriHashes[_reply->request().url()]; |
|||
bytes package(reinterpret_cast<unsigned char const*>(data.constData()), reinterpret_cast<unsigned char const*>(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<unsigned char const*>(data.constData()), reinterpret_cast<unsigned char const*>(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
|
|||
QString code; |
|||
code += contentsOfQResource(":/js/bignumber.min.js"); |
|||
code += "\n"; |
|||
code += contentsOfQResource(":/js/webthree.js"); |
|||
code += "\n"; |
|||
code += contentsOfQResource(":/js/setup.js"); |
|||
code += "\n"; |
|||
QByteArray res = code.toLatin1(); |
|||
bytes b(res.data(), res.data() + res.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); |
|||
} |
|||
|
|||
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) |
|||
{ |
|||
DappLocation location = resolveAppUri(_uri); |
|||
QUrl uri(location.contentUri); |
|||
QNetworkRequest request(uri); |
|||
m_uriHashes[uri] = location.contentHash; |
|||
m_net.get(request); |
|||
} |
|||
|
@ -0,0 +1,94 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappLoader.h
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <string> |
|||
#include <vector> |
|||
#include <QObject> |
|||
#include <QString> |
|||
#include <QUrl> |
|||
#include <QNetworkAccessManager> |
|||
#include <libdevcore/FixedHash.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
class WebThreeDirect; |
|||
class RLP; |
|||
} |
|||
|
|||
struct ManifestEntry |
|||
{ |
|||
std::string path; |
|||
dev::h256 hash; |
|||
std::string contentType; |
|||
unsigned httpStatus; |
|||
}; |
|||
|
|||
struct Manifest |
|||
{ |
|||
std::vector<ManifestEntry> entries; |
|||
}; |
|||
|
|||
struct Dapp |
|||
{ |
|||
Manifest manifest; |
|||
std::map<dev::h256, dev::bytes> content; |
|||
}; |
|||
|
|||
|
|||
struct DappLocation |
|||
{ |
|||
QString canonDomain; |
|||
QString path; |
|||
QString contentUri; |
|||
dev::h256 contentHash; |
|||
}; |
|||
|
|||
///Downloads, unpacks and prepares DApps for hosting
|
|||
class DappLoader: public QObject |
|||
{ |
|||
Q_OBJECT |
|||
public: |
|||
DappLoader(QObject* _parent, dev::WebThreeDirect* _web3); |
|||
///Load a new DApp. Resolves a name with a name reg contract. Asynchronous. dappReady is emitted once everything is read, dappError othervise
|
|||
///@param _uri Eth name path
|
|||
void loadDapp(QString const& _uri); |
|||
|
|||
signals: |
|||
void dappReady(Dapp& _dapp); |
|||
void dappError(); |
|||
|
|||
private slots: |
|||
void downloadComplete(QNetworkReply* _reply); |
|||
|
|||
private: |
|||
dev::WebThreeDirect* web3() const { return m_web3; } |
|||
DappLocation resolveAppUri(QString const& _uri); |
|||
void loadDapp(dev::RLP const& _rlp); |
|||
Manifest loadManifest(std::string const& _manifest); |
|||
|
|||
dev::WebThreeDirect* m_web3; |
|||
QNetworkAccessManager m_net; |
|||
std::map<QUrl, dev::h256> m_uriHashes; |
|||
}; |
|||
|
@ -0,0 +1,29 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file WebPage.cpp
|
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma GCC diagnostic ignored "-Wpedantic" //QtWebEngineWidgets/qwebenginecertificateerror.h:78:348: error: extra ';'
|
|||
#include "WebPage.h" |
|||
|
|||
void WebPage::javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel _level, const QString& _message, int _lineNumber, const QString& _sourceID) |
|||
{ |
|||
QString prefix = _level == QWebEnginePage::ErrorMessageLevel ? "error" : _level == QWebEnginePage::WarningMessageLevel ? "warning" : ""; |
|||
emit consoleMessage(QString("%1(%2:%3):%4").arg(prefix).arg(_sourceID).arg(_lineNumber).arg(_message)); |
|||
} |
@ -0,0 +1,37 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file WebPage.h
|
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <QString> |
|||
#include <QtWebEngineWidgets/QWebEnginePage> |
|||
|
|||
class WebPage: public QWebEnginePage |
|||
{ |
|||
Q_OBJECT |
|||
public: |
|||
WebPage(QObject* _parent): QWebEnginePage(_parent) { } |
|||
signals: |
|||
void consoleMessage(QString const& _msg); |
|||
|
|||
protected: |
|||
void javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel _level, const QString& _message, int _lineNumber, const QString& _sourceID) override; |
|||
}; |
Loading…
Reference in new issue