diff --git a/Makefile b/Makefile index 42f169fa..db4fe127 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ ifeq ($(ARCH), lin) LDFLAGS += -rdynamic \ -lpthread -lGL -ldl \ $(shell pkg-config --libs gtk+-2.0) \ - -Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi + -Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl TARGET = Rack endif @@ -23,7 +23,7 @@ ifeq ($(ARCH), mac) CXXFLAGS += -DAPPLE -stdlib=libc++ LDFLAGS += -stdlib=libc++ -lpthread -ldl \ -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo \ - -Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi + -Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl TARGET = Rack BUNDLE = dist/$(TARGET).app endif @@ -33,7 +33,7 @@ ifeq ($(ARCH), win) LDFLAGS += -static-libgcc -static-libstdc++ -lpthread \ -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows \ -lgdi32 -lopengl32 -lcomdlg32 -lole32 \ - -Ldep/lib -lglew32 -lglfw3dll -lcurl -lzip -lrtaudio -lrtmidi \ + -Ldep/lib -lglew32 -lglfw3dll -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl \ -Wl,-Bstatic -ljansson -lspeexdsp TARGET = Rack.exe OBJECTS = Rack.res @@ -112,8 +112,14 @@ ifeq ($(ARCH), mac) cp dep/lib/libspeexdsp.1.dylib $(BUNDLE)/Contents/MacOS/ cp dep/lib/libcurl.4.dylib $(BUNDLE)/Contents/MacOS/ cp dep/lib/libzip.5.dylib $(BUNDLE)/Contents/MacOS/ +<<<<<<< HEAD cp dep/lib/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/ cp dep/lib/librtaudio.dylib $(BUNDLE)/Contents/MacOS/ +======= + cp dep/lib/librtaudio.dylib $(BUNDLE)/Contents/MacOS/ + cp dep/lib/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/ + cp dep/lib/libcrypto.1.1.dylib $(BUNDLE)/Contents/MacOS/ +>>>>>>> v0.5 install_name_tool -change /usr/local/lib/libGLEW.2.1.0.dylib @executable_path/libGLEW.2.1.0.dylib $(BUNDLE)/Contents/MacOS/Rack install_name_tool -change lib/libglfw.3.dylib @executable_path/libglfw.3.dylib $(BUNDLE)/Contents/MacOS/Rack @@ -121,8 +127,14 @@ ifeq ($(ARCH), mac) install_name_tool -change $(PWD)/dep/lib/libspeexdsp.1.dylib @executable_path/libspeexdsp.1.dylib $(BUNDLE)/Contents/MacOS/Rack install_name_tool -change $(PWD)/dep/lib/libcurl.4.dylib @executable_path/libcurl.4.dylib $(BUNDLE)/Contents/MacOS/Rack install_name_tool -change $(PWD)/dep/lib/libzip.5.dylib @executable_path/libzip.5.dylib $(BUNDLE)/Contents/MacOS/Rack +<<<<<<< HEAD install_name_tool -change $(PWD)/dep/lib/librtmidi.4.dylib @executable_path/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/Rack install_name_tool -change librtaudio.dylib @executable_path/librtaudio.dylib $(BUNDLE)/Contents/MacOS/Rack +======= + install_name_tool -change librtaudio.dylib @executable_path/librtaudio.dylib $(BUNDLE)/Contents/MacOS/Rack + install_name_tool -change $(PWD)/dep/lib/librtmidi.4.dylib @executable_path/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/Rack + install_name_tool -change $(PWD)/dep/lib/libcrypto.1.1.dylib @executable_path/libcrypto.1.1.dylib $(BUNDLE)/Contents/MacOS/Rack +>>>>>>> v0.5 otool -L $(BUNDLE)/Contents/MacOS/Rack @@ -149,6 +161,8 @@ ifeq ($(ARCH), win) cp dep/bin/libspeexdsp-1.dll dist/Rack/ cp dep/bin/libzip-5.dll dist/Rack/ cp dep/bin/librtaudio.dll dist/Rack/ + cp dep/bin/libcrypto-1_1-x64.dll dist/Rack/ + cp dep/bin/libssl-1_1-x64.dll dist/Rack/ mkdir -p dist/Rack/plugins cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/ # Make ZIP @@ -169,6 +183,8 @@ ifeq ($(ARCH), lin) cp dep/lib/libzip.so.5 dist/Rack/ cp dep/lib/librtaudio.so dist/Rack/ cp dep/lib/librtmidi.so.4 dist/Rack/ + cp dep/lib/libssl.so.1.1 dist/Rack/ + cp dep/lib/libcrypto.so.1.1 dist/Rack/ mkdir -p dist/Rack/plugins cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/ # Make ZIP diff --git a/dep/Makefile b/dep/Makefile index eff4442e..2066846d 100755 --- a/dep/Makefile +++ b/dep/Makefile @@ -31,6 +31,7 @@ ifeq ($(ARCH),lin) libzip = lib/libzip.so rtmidi = lib/librtmidi.so rtaudio = lib/librtaudio.so + openssl = lib/libssl.so endif ifeq ($(ARCH),mac) @@ -42,6 +43,7 @@ ifeq ($(ARCH),mac) libzip = lib/libzip.dylib rtmidi = lib/librtmidi.dylib rtaudio = lib/librtaudio.dylib + openssl = lib/libssl.dylib endif ifeq ($(ARCH),win) @@ -52,7 +54,8 @@ ifeq ($(ARCH),win) libcurl = bin/libcurl-4.dll libzip = bin/libzip-5.dll rtmidi = bin/librtmidi-4.dll - rtaudio = bin/librtaudio-6.dll + rtaudio = bin/librtaudio.dll + openssl = bin/libssl-1_1-x64.dll endif # Library configuration @@ -106,12 +109,20 @@ $(libspeexdsp): $(MAKE) -C speexdsp-SpeexDSP-1.2rc3 $(MAKE) -C speexdsp-SpeexDSP-1.2rc3 install -$(libcurl): +$(openssl): + $(WGET) https://www.openssl.org/source/openssl-1.1.0g.tar.gz + $(UNTAR) openssl-1.1.0g.tar.gz + cd openssl-1.1.0g && ./config --prefix="$(LOCAL)" + $(MAKE) -C openssl-1.1.0g + $(MAKE) -C openssl-1.1.0g install + +$(libcurl): $(openssl) $(WGET) https://github.com/curl/curl/releases/download/curl-7_56_0/curl-7.56.0.tar.gz $(UNTAR) curl-7.56.0.tar.gz cd curl-7.56.0 && ./configure --prefix="$(LOCAL)" \ --disable-ftp --disable-file --disable-ldap --disable-ldaps --disable-rtsp --disable-proxy --disable-dict --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smb --disable-smtp --disable-gopher --disable-manual \ - --without-zlib --without-ssl --without-ca-bundle --without-ca-path --without-ca-fallback --without-libpsl --without-libmetalink --without-libssh2 --without-librtmp --without-winidn --without-libidn2 --without-nghttp2 + --without-zlib --without-libpsl --without-libmetalink --without-libssh2 --without-librtmp --without-winidn --without-libidn2 --without-nghttp2 \ + --without-ca-bundle --with-ca-fallback --with-ssl="$(LOCAL)" $(MAKE) -C curl-7.56.0 $(MAKE) -C curl-7.56.0 install diff --git a/include/components.hpp b/include/components.hpp index 03854062..cac1cf91 100644 --- a/include/components.hpp +++ b/include/components.hpp @@ -461,6 +461,14 @@ struct CKSS : SVGSwitch, ToggleSwitch { } }; +struct CKSSThree : SVGSwitch, ToggleSwitch { + CKSSThree() { + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_0.svg"))); + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_1.svg"))); + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_2.svg"))); + } +}; + struct CKD6 : SVGSwitch, MomentarySwitch { CKD6() { addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKD6_0.svg"))); diff --git a/include/plugin.hpp b/include/plugin.hpp index 8e9f612b..9f258352 100644 --- a/include/plugin.hpp +++ b/include/plugin.hpp @@ -60,7 +60,8 @@ void pluginInit(); void pluginDestroy(); void pluginLogIn(std::string email, std::string password); void pluginLogOut(); -void pluginRefresh(); +/** Returns whether a new plugin is available, and downloads it unless doing a dry run */ +bool pluginSync(bool dryRun); void pluginCancelDownload(); bool pluginIsLoggedIn(); bool pluginIsDownloading(); diff --git a/include/util.hpp b/include/util.hpp index f7e4732a..3e7b53dc 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -30,6 +30,16 @@ will expand to #define LENGTHOF(arr) (sizeof(arr) / sizeof((arr)[0])) +/** Reserve space for _count enums starting with _name. +Example: + enum Foo { + ENUMS(BAR, 14) + }; + + BAR + 0 to BAR + 11 is reserved +*/ +#define ENUMS(_name, _count) _name, _name ## _LAST = _name + (_count) - 1 + /** Deprecation notice for GCC */ #define DEPRECATED __attribute__ ((deprecated)) diff --git a/include/util/request.hpp b/include/util/request.hpp index d0405155..7ae510c7 100644 --- a/include/util/request.hpp +++ b/include/util/request.hpp @@ -19,5 +19,6 @@ json_t *requestJson(RequestMethod method, std::string url, json_t *dataJ); /** Returns the filename, blank if unsuccessful */ bool requestDownload(std::string url, std::string filename, float *progress); std::string requestEscape(std::string s); +std::string requestSHA256File(std::string filename); } // namespace rack diff --git a/res/ComponentLibrary/CKSSThree_0.svg b/res/ComponentLibrary/CKSSThree_0.svg new file mode 100644 index 00000000..a8283517 --- /dev/null +++ b/res/ComponentLibrary/CKSSThree_0.svg @@ -0,0 +1,321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/res/ComponentLibrary/CKSSThree_1.svg b/res/ComponentLibrary/CKSSThree_1.svg new file mode 100644 index 00000000..46656fcf --- /dev/null +++ b/res/ComponentLibrary/CKSSThree_1.svg @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/res/ComponentLibrary/CKSSThree_2.svg b/res/ComponentLibrary/CKSSThree_2.svg new file mode 100644 index 00000000..cbfd986b --- /dev/null +++ b/res/ComponentLibrary/CKSSThree_2.svg @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/src/app.cpp b/src/app.cpp index 8b8bade6..84795945 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -10,7 +10,8 @@ std::string gApplicationVersion = #else ""; #endif -std::string gApiHost = "http://api.vcvrack.com"; +std::string gApiHost = "https://api.vcvrack.com"; +// std::string gApiHost = "http://localhost:8081"; RackWidget *gRackWidget = NULL; Toolbar *gToolbar = NULL; diff --git a/src/app/AddModuleWindow.cpp b/src/app/AddModuleWindow.cpp index 48f2d91d..ac99389e 100644 --- a/src/app/AddModuleWindow.cpp +++ b/src/app/AddModuleWindow.cpp @@ -70,7 +70,7 @@ struct MetadataMenu : ListMenu { // Plugin metadata if (!model->plugin->website.empty()) { - addChild(construct(&MenuEntry::text, "Website", &UrlItem::url, model->plugin->path)); + addChild(construct(&MenuEntry::text, "Website", &UrlItem::url, model->plugin->website)); } if (!model->plugin->manual.empty()) { addChild(construct(&MenuEntry::text, "Manual", &UrlItem::url, model->plugin->manual)); @@ -120,10 +120,6 @@ struct ModelItem : MenuItem { sModel = model; MenuItem::onMouseEnter(e); } - void onMouseLeave(EventMouseLeave &e) override { - sModel = NULL; - MenuItem::onMouseLeave(e); - } }; diff --git a/src/app/PluginManagerWidget.cpp b/src/app/PluginManagerWidget.cpp index c246c325..096eb071 100644 --- a/src/app/PluginManagerWidget.cpp +++ b/src/app/PluginManagerWidget.cpp @@ -1,11 +1,57 @@ #include #include "app.hpp" #include "plugin.hpp" +#include "gui.hpp" +#include "../ext/osdialog/osdialog.h" namespace rack { +struct SyncButton : Button { + bool checked = false; + bool available = false; + bool completed = false; + + void step() override { + if (!checked) { + std::thread t([this]() { + if (pluginSync(true)) + available = true; + }); + t.detach(); + checked = true; + } + if (completed) { + if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been updated. Close Rack and re-launch it to load new updates.")) { + guiClose(); + } + completed = false; + } + } + void draw(NVGcontext *vg) override { + Button::draw(vg); + if (available) { + // Notification circle + nvgBeginPath(vg); + nvgCircle(vg, 3, 3, 4.0); + nvgFillColor(vg, nvgRGBf(1.0, 0.0, 0.0)); + nvgFill(vg); + nvgStrokeColor(vg, nvgRGBf(0.5, 0.0, 0.0)); + nvgStroke(vg); + } + } + void onAction(EventAction &e) override { + available = false; + std::thread t([this]() { + if (pluginSync(false)) + completed = true; + }); + t.detach(); + } +}; + + PluginManagerWidget::PluginManagerWidget() { box.size.y = BND_WIDGET_HEIGHT; float margin = 5; @@ -91,19 +137,13 @@ PluginManagerWidget::PluginManagerWidget() { manageWidget->addChild(manageButton); pos.x += manageButton->box.size.x; - struct RefreshButton : Button { - void onAction(EventAction &e) override { - std::thread t(pluginRefresh); - t.detach(); - } - }; pos.x += margin; - Button *refreshButton = new RefreshButton(); - refreshButton->box.pos = pos; - refreshButton->box.size.x = 125; - refreshButton->text = "Refresh plugins"; - manageWidget->addChild(refreshButton); - pos.x += refreshButton->box.size.x; + Button *syncButton = new SyncButton(); + syncButton->box.pos = pos; + syncButton->box.size.x = 125; + syncButton->text = "Update plugins"; + manageWidget->addChild(syncButton); + pos.x += syncButton->box.size.x; struct LogOutButton : Button { void onAction(EventAction &e) override { diff --git a/src/plugin.cpp b/src/plugin.cpp index 3b0b1b51..8c5d0b41 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -7,11 +7,12 @@ #include #include // for MAXPATHLEN #include +#include #include #include -#if ARCH_WIN +#if defined(ARCH_WIN) #include #include #define mkdir(_dir, _perms) _mkdir(_dir) @@ -189,35 +190,55 @@ static int extractZip(const char *filename, const char *dir) { return err; } -static void refreshPurchase(json_t *pluginJ) { +static void syncPlugin(json_t *pluginJ) { json_t *slugJ = json_object_get(pluginJ, "slug"); if (!slugJ) return; std::string slug = json_string_value(slugJ); + info("Syncing plugin %s", slug.c_str()); json_t *nameJ = json_object_get(pluginJ, "name"); if (!nameJ) return; std::string name = json_string_value(nameJ); - json_t *versionJ = json_object_get(pluginJ, "version"); - if (!versionJ) return; - std::string version = json_string_value(versionJ); - - // Check whether the plugin is already loaded - for (Plugin *plugin : gPlugins) { - if (plugin->slug == slug && plugin->version == version) { - return; + std::string download; + std::string sha256; + + json_t *downloadsJ = json_object_get(pluginJ, "downloads"); + if (downloadsJ) { +#if defined(ARCH_WIN) + #define DOWNLOADS_ARCH "win" +#elif defined(ARCH_MAC) + #define DOWNLOADS_ARCH "mac" +#elif defined(ARCH_LIN) + #define DOWNLOADS_ARCH "lin" +#endif + json_t *archJ = json_object_get(downloadsJ, DOWNLOADS_ARCH); + if (archJ) { + // Get download URL + json_t *downloadJ = json_object_get(archJ, "download"); + if (downloadJ) + download = json_string_value(downloadJ); + // Get SHA256 hash + json_t *sha256J = json_object_get(archJ, "sha256"); + if (sha256J) + sha256 = json_string_value(sha256J); } } - // Append token and version to download URL - std::string url = gApiHost; - url += "/download"; - url += "?product="; - url += slug; - url += "&version="; - url += requestEscape(gApplicationVersion); - url += "&token="; - url += requestEscape(gToken); + json_t *productIdJ = json_object_get(pluginJ, "productId"); + if (productIdJ) { + download = gApiHost; + download += "/download"; + download += "?slug="; + download += slug; + download += "&token="; + download += requestEscape(gToken); + } + + if (download.empty()) { + warn("Could not get download URL for plugin %s", slug.c_str()); + return; + } // If plugin is not loaded, download the zip file to /plugins downloadName = name; @@ -227,21 +248,127 @@ static void refreshPurchase(json_t *pluginJ) { std::string pluginsDir = assetLocal("plugins"); std::string pluginPath = pluginsDir + "/" + slug; std::string zipPath = pluginPath + ".zip"; - bool success = requestDownload(url, zipPath, &downloadProgress); + bool success = requestDownload(download, zipPath, &downloadProgress); if (success) { + if (!sha256.empty()) { + // Check SHA256 hash + std::string actualSha256 = requestSHA256File(zipPath); + if (actualSha256 != sha256) { + warn("Plugin %s does not match expected SHA256 checksum", slug.c_str()); + return; + } + } + // Unzip file int err = extractZip(zipPath.c_str(), pluginsDir.c_str()); if (!err) { // Delete zip remove(zipPath.c_str()); // Load plugin - loadPlugin(pluginPath); + // loadPlugin(pluginPath); } } downloadName = ""; } +static bool trySyncPlugin(json_t *pluginJ, json_t *communityPluginsJ, bool dryRun) { + std::string slug = json_string_value(pluginJ); + + // Find community plugin + size_t communityIndex; + json_t *communityPluginJ = NULL; + json_array_foreach(communityPluginsJ, communityIndex, communityPluginJ) { + json_t *communitySlugJ = json_object_get(communityPluginJ, "slug"); + if (communitySlugJ) { + std::string communitySlug = json_string_value(communitySlugJ); + if (slug == communitySlug) + break; + } + } + if (communityIndex == json_array_size(communityPluginsJ)) { + warn("Plugin sync error: %s not found in community", slug.c_str()); + return false; + } + + // Get community version + std::string version; + json_t *versionJ = json_object_get(communityPluginJ, "version"); + if (versionJ) { + version = json_string_value(versionJ); + } + + // Check whether we already have a plugin with the same slug and version + for (Plugin *plugin : gPlugins) { + if (plugin->slug == slug) { + // plugin->version might be blank, so adding a version of the manifest will update the plugin + if (plugin->version == version) + return false; + } + } + + if (!dryRun) + syncPlugin(communityPluginJ); + return true; +} + +bool pluginSync(bool dryRun) { + if (gToken.empty()) + return false; + + bool available = false; + + // Download my plugins + json_t *reqJ = json_object(); + json_object_set(reqJ, "version", json_string(gApplicationVersion.c_str())); + json_object_set(reqJ, "token", json_string(gToken.c_str())); + json_t *resJ = requestJson(METHOD_GET, gApiHost + "/plugins", reqJ); + json_decref(reqJ); + + // Download community plugins + json_t *communityResJ = requestJson(METHOD_GET, gApiHost + "/community/plugins", NULL); + + if (!dryRun) { + isDownloading = true; + downloadProgress = 0.0; + downloadName = ""; + } + + if (resJ && communityResJ) { + json_t *errorJ = json_object_get(resJ, "error"); + json_t *communityErrorJ = json_object_get(resJ, "error"); + if (errorJ) { + warn("Plugin sync error: %s", json_string_value(errorJ)); + } + else if (communityErrorJ) { + warn("Plugin sync error: %s", json_string_value(communityErrorJ)); + } + else { + // Check each plugin in list of my plugins + json_t *pluginsJ = json_object_get(resJ, "plugins"); + json_t *communityPluginsJ = json_object_get(communityResJ, "plugins"); + size_t index; + json_t *pluginJ; + json_array_foreach(pluginsJ, index, pluginJ) { + if (trySyncPlugin(pluginJ, communityPluginsJ, dryRun)) + available = true; + } + } + } + + if (resJ) + json_decref(resJ); + + if (communityResJ) + json_decref(communityResJ); + + if (!dryRun) { + isDownloading = false; + } + + return available; +} + //////////////////// // plugin API //////////////////// @@ -271,10 +398,10 @@ void pluginInit() { void pluginDestroy() { for (Plugin *plugin : gPlugins) { // Free library handle -#if ARCH_WIN +#if defined(ARCH_WIN) if (plugin->handle) FreeLibrary((HINSTANCE)plugin->handle); -#elif ARCH_LIN || ARCH_MAC +#elif defined(ARCH_LIN) || defined(ARCH_MAC) if (plugin->handle) dlclose(plugin->handle); #endif @@ -315,39 +442,6 @@ void pluginLogOut() { gToken = ""; } -void pluginRefresh() { - if (gToken.empty()) - return; - - isDownloading = true; - downloadProgress = 0.0; - downloadName = ""; - - json_t *reqJ = json_object(); - json_object_set(reqJ, "token", json_string(gToken.c_str())); - json_t *resJ = requestJson(METHOD_GET, gApiHost + "/purchases", reqJ); - json_decref(reqJ); - - if (resJ) { - json_t *errorJ = json_object_get(resJ, "error"); - if (errorJ) { - const char *errorStr = json_string_value(errorJ); - warn("Plugin refresh error: %s", errorStr); - } - else { - json_t *purchasesJ = json_object_get(resJ, "purchases"); - size_t index; - json_t *purchaseJ; - json_array_foreach(purchasesJ, index, purchaseJ) { - refreshPurchase(purchaseJ); - } - } - json_decref(resJ); - } - - isDownloading = false; -} - void pluginCancelDownload() { // TODO } diff --git a/src/util/request.cpp b/src/util/request.cpp index c046bbb7..72702c04 100644 --- a/src/util/request.cpp +++ b/src/util/request.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace rack { @@ -79,6 +80,7 @@ json_t *requestJson(RequestMethod method, std::string url, json_t *dataJ) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, reqStr); std::string resText; + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText); @@ -130,6 +132,8 @@ bool requestDownload(std::string url, std::string filename, float *progress) { curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferInfoCallback); curl_easy_setopt(curl, CURLOPT_XFERINFODATA, progress); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); info("Downloading %s", url.c_str()); CURLcode res = curl_easy_perform(curl); @@ -153,5 +157,36 @@ std::string requestEscape(std::string s) { return ret; } +std::string requestSHA256File(std::string filename) { + FILE *f = fopen(filename.c_str(), "rb"); + if (!f) + return ""; + + uint8_t hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX sha256; + SHA256_Init(&sha256); + const int bufferLen = 1 << 15; + uint8_t *buffer = new uint8_t[bufferLen]; + int len = 0; + while ((len = fread(buffer, 1, bufferLen, f))) { + SHA256_Update(&sha256, buffer, len); + } + SHA256_Final(hash, &sha256); + delete[] buffer; + fclose(f); + + // Convert binary hash to hex + char hashHex[64]; + const char hexTable[] = "0123456789abcdef"; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + uint8_t h = hash[i]; + hashHex[2*i + 0] = hexTable[h >> 4]; + hashHex[2*i + 1] = hexTable[h & 0x0f]; + } + + std::string str(hashHex, sizeof(hashHex)); + return str; +} + } // namespace rack