From 4b713f405a81b067611244d54f80017e6686e3a9 Mon Sep 17 00:00:00 2001 From: Robin Davies Date: Mon, 28 Oct 2024 08:45:52 -0400 Subject: [PATCH] Convert all file ops to use absolute paths --- react/src/DialogEx.tsx | 1 + react/src/FilePropertyDialog.tsx | 6 +- .../src/FilePropertyDirectorySelectDialog.tsx | 43 +-- react/src/FilePropertyDirectoryTree.tsx | 4 + react/src/PiPedalModel.tsx | 16 +- react/src/UploadFileDialog.tsx | 2 +- src/FilePropertyDirectoryTree.cpp | 12 +- src/FilePropertyDirectoryTree.hpp | 3 + src/Storage.cpp | 108 ++++--- src/Storage.hpp | 2 +- src/WebServerConfig.cpp | 271 ++++++++++-------- 11 files changed, 286 insertions(+), 182 deletions(-) diff --git a/react/src/DialogEx.tsx b/react/src/DialogEx.tsx index 0fb78d9..70c765a 100644 --- a/react/src/DialogEx.tsx +++ b/react/src/DialogEx.tsx @@ -198,6 +198,7 @@ class DialogEx extends React.Component implements I { this.onEnterKey(); } + evt.stopPropagation(); } render() { let { tag,onClose,...extra} = this.props; diff --git a/react/src/FilePropertyDialog.tsx b/react/src/FilePropertyDialog.tsx index d36112d..5d79c9a 100644 --- a/react/src/FilePropertyDialog.tsx +++ b/react/src/FilePropertyDialog.tsx @@ -741,9 +741,7 @@ export default withStyles(styles, { withTheme: true })( } } uploadPage={ - "uploadUserFile?directory=" + encodeURIComponent( - pathConcat(this.props.fileProperty.directory, this.state.navDirectory) - ) + "uploadUserFile?directory=" + encodeURIComponent(this.state.navDirectory) + "&ext=" + encodeURIComponent(this.getFileExtensionList(this.props.fileProperty)) } @@ -792,7 +790,7 @@ export default withStyles(styles, { withTheme: true })( defaultPath={this.getDefaultPath()} excludeDirectory={ this.isDirectory(this.state.selectedFile) - ? pathConcat(this.state.navDirectory, pathFileName(this.state.selectedFile)) : ""} + ? this.state.selectedFile : ""} onClose={() => { this.setState({ moveDialogOpen: false }); }} onOk={ (path) => { diff --git a/react/src/FilePropertyDirectorySelectDialog.tsx b/react/src/FilePropertyDirectorySelectDialog.tsx index a9b341f..0c51423 100644 --- a/react/src/FilePropertyDirectorySelectDialog.tsx +++ b/react/src/FilePropertyDirectorySelectDialog.tsx @@ -38,28 +38,18 @@ import { isDarkMode } from './DarkMode'; import IconButton from '@mui/material/IconButton'; -function pathConcat(l: string, r: string): string -{ - if (l.length === 0) return r; - if (r.length === 0) return l; - return l +'/' + r; -} - class DirectoryTree { name: string = ""; path: string = ""; expanded: boolean = false; + isProtected: boolean = true; children: DirectoryTree[] = []; constructor(tree: FilePropertyDirectoryTree, parentTree: (DirectoryTree | null) = null) { this.name = tree.directoryName; - if (parentTree) { - this.path = pathConcat(parentTree.path,tree.directoryName); - } else { - this.name = "Home"; - this.path = tree.directoryName; - this.expanded = true; - } + this.path = tree.directoryName; + this.name = tree.displayName; + this.isProtected = tree.isProtected; for (var treeChild of tree.children) { this.children.push(new DirectoryTree(treeChild, this)); } @@ -143,6 +133,7 @@ export interface FilePropertyDirectorySelectDialogState { selectedPath: string; directoryTree?: DirectoryTree; hasSelection: boolean; + isProtected: boolean; directoryTreeInvalidatecount: number; }; @@ -160,6 +151,7 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC selectedPath: props.defaultPath, directoryTree: undefined, hasSelection: false, + isProtected: true, directoryTreeInvalidatecount: 0 }; @@ -192,14 +184,19 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC this.model.getFilePropertyDirectoryTree(this.props.uiFileProperty) .then((filePropertyDirectoryTree) => { let myTree = new DirectoryTree(filePropertyDirectoryTree); - if (this.props.excludeDirectory) + if (this.props.excludeDirectory.length !== 0) { myTree.remove(this.props.excludeDirectory); } myTree.expand(this.state.selectedPath); - let hasSelection = myTree.find(this.state.selectedPath) != null; - this.setState({ directoryTree: myTree, hasSelection: hasSelection }); + let selection = myTree.find(this.state.selectedPath); + let hasSelection = !!selection; + this.setState({ + directoryTree: myTree, + hasSelection: hasSelection, + isProtected: selection ? selection.isProtected : false + }); this.requestScroll = true; }) .catch((e) => { @@ -223,6 +220,9 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC onOK() { + if (this.state.isProtected) { + return; + } if (this.state.hasSelection) { this.props.onOk(this.state.selectedPath); } @@ -238,7 +238,11 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC private onTreeClick(directoryTree: DirectoryTree) { if (!this.state.directoryTree) return; this.state.directoryTree.expand(directoryTree.path); - this.setState({ selectedPath: directoryTree.path, hasSelection: true, directoryTreeInvalidatecount: this.state.directoryTreeInvalidatecount + 1 }); + this.setState({ + selectedPath: directoryTree.path, + hasSelection: true, + isProtected: directoryTree.isProtected, + directoryTreeInvalidatecount: this.state.directoryTreeInvalidatecount + 1 }); } private renderTree_(directoryTree: DirectoryTree) { let ref: ((element: HTMLButtonElement | null) => void) | undefined = undefined; @@ -338,7 +342,8 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC - diff --git a/react/src/FilePropertyDirectoryTree.tsx b/react/src/FilePropertyDirectoryTree.tsx index 4a9191b..8275a73 100644 --- a/react/src/FilePropertyDirectoryTree.tsx +++ b/react/src/FilePropertyDirectoryTree.tsx @@ -21,6 +21,8 @@ export default class FilePropertyDirectoryTree { deserialize(input: any) : FilePropertyDirectoryTree { this.directoryName = input.directoryName; + this.displayName = input.displayName; + this.isProtected = input.isProtected; this.children = FilePropertyDirectoryTree.deserialize_array(input.children); return this; } @@ -35,5 +37,7 @@ export default class FilePropertyDirectoryTree { directoryName: string = ""; + displayName: string = ""; + isProtected: boolean = false; children: FilePropertyDirectoryTree[] = []; } \ No newline at end of file diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index fa3ed30..5b188c8 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -2510,7 +2510,7 @@ export class PiPedalModel //implements PiPedalModel link.click(); } - uploadFile(uploadPage: string, file: File, contentType: string = "application/octet-stream", abortController?: AbortController): Promise { + uploadUserFile(uploadPage: string, file: File, contentType: string = "application/octet-stream", abortController?: AbortController): Promise { let result = new Promise((resolve, reject) => { try { if (file.size > this.maxFileUploadSize) { @@ -2551,10 +2551,20 @@ export class PiPedalModel //implements PiPedalModel } }) .then((json) => { - resolve(json as string); + let response = json as {errorMessage: string, path: string}; + if (response.errorMessage !== "") + { + throw new Error(response.errorMessage); + } + resolve(response.path); }) .catch((error) => { - reject("Upload failed. " + error); + if (error instanceof Error) + { + reject("Upload failed. " + (error as Error).message); + } else { + reject("Upload failed. " + error); + } }) ; } catch (error) { diff --git a/react/src/UploadFileDialog.tsx b/react/src/UploadFileDialog.tsx index 689ff5d..7a55e3f 100644 --- a/react/src/UploadFileDialog.tsx +++ b/react/src/UploadFileDialog.tsx @@ -208,7 +208,7 @@ export default class UploadFileDialog extends ResizeResponsiveComponent> children_; DECLARE_JSON_MAP(FilePropertyDirectoryTree); diff --git a/src/Storage.cpp b/src/Storage.cpp index d780d86..9f1a96c 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -71,6 +71,12 @@ static bool isSubdirectory(const fs::path&path, const fs::path&basePath) return true; } +static bool hasSyntheticModRoot(const UiFileProperty &fileProperty) +{ + return (fileProperty.modDirectories().size() > 1 || (fileProperty.modDirectories().size() == 1 && fileProperty.useLegacyModDirectory())); + +} + Storage::Storage() { SetConfigRoot("~/var/Config"); @@ -1819,7 +1825,7 @@ FileRequestResult Storage::GetFileList2(const std::string &relativePath_,const U { ThrowPermissionDeniedError(); } - if (fileProperty.modDirectories().size() > 1 || (fileProperty.modDirectories().size() == 1 && fileProperty.useLegacyModDirectory())) + if (hasSyntheticModRoot(fileProperty)) { return Storage::GetModFileList2(absolutePath,fileProperty); } @@ -1873,29 +1879,32 @@ bool Storage::IsValidSampleFileName(const std::filesystem::path &fileName) { return false; } - if (!ensureNoDotDot(fileName)) - { - return false; - } std::filesystem::path audioFilePath = this->GetPluginUploadDirectory(); std::filesystem::path parentDirectory = fileName.parent_path(); - while (true) + + auto iTarget = parentDirectory.begin(); + + for (auto i = audioFilePath.begin(); i != audioFilePath.end(); ++i) { - if (!parentDirectory.has_parent_path()) - { + if (iTarget == parentDirectory.end()) { return false; } - std::string name = parentDirectory.filename().string(); - if (parentDirectory == audioFilePath) - return true; - parentDirectory = parentDirectory.parent_path(); - if (parentDirectory.string().length() < audioFilePath.string().length()) - { + if (*i != *iTarget) { return false; } + ++iTarget; } + while (iTarget != parentDirectory.end()) + { + if (iTarget->string() == "..") + { + return false; + } + ++iTarget; + } + return true; } void Storage::DeleteSampleFile(const std::filesystem::path &fileName) { @@ -1936,13 +1945,13 @@ void Storage::DeleteSampleFile(const std::filesystem::path &fileName) } std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, const std::string &filename) { - if (!ensureNoDotDot(directory)) - { - throw std::logic_error(SS("Invalide filename: " << filename)); - } std::filesystem::path filePath{filename}; - std::filesystem::path result = this->GetPluginUploadDirectory() / directory / filename; + std::filesystem::path result = fs::path(directory) / filename; + if (!result.is_absolute()) + { + result = this->GetPluginUploadDirectory() / result; + } if (!this->IsValidSampleFileName(result)) { throw std::logic_error(SS("Invalid upload path: " << result)); @@ -2065,8 +2074,8 @@ void Storage::FillSampleDirectoryTree(FilePropertyDirectoryTree*node, const std: { if (child.is_directory()) { - const auto& childPath = child.path(); - FilePropertyDirectoryTree::ptr childTree = std::make_unique(childPath.filename()); + const auto& childPath = child.path(); + FilePropertyDirectoryTree::ptr childTree = std::make_unique(childPath); FillSampleDirectoryTree(childTree.get(),childPath); node->children_.push_back(std::move(childTree)); } @@ -2078,22 +2087,53 @@ void Storage::FillSampleDirectoryTree(FilePropertyDirectoryTree*node, const std: return collator->Compare(left->directoryName_,right->directoryName_) < 0; }); } -FilePropertyDirectoryTree::ptr Storage::GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty) const +FilePropertyDirectoryTree::ptr Storage::GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty) { - FilePropertyDirectoryTree::ptr result = std::make_unique(""); - if (uiFileProperty.directory().empty()) - { - throw std::runtime_error("Invalid uiFileProperty"); - } - if (!ensureNoDotDot(uiFileProperty.directory())) - { - throw std::runtime_error("Invalid uiFileProperty"); - } - std::filesystem::path rootDirectory = this->GetPluginUploadDirectory() / uiFileProperty.directory(); + fs::path uploadDirectory = this->GetPluginUploadDirectory(); - FillSampleDirectoryTree(result.get(),rootDirectory); + if (hasSyntheticModRoot(uiFileProperty)) + { + FilePropertyDirectoryTree::ptr result = std::make_unique("","Home"); + result->isProtected_ = true; + for (const auto& modDirectory: uiFileProperty.modDirectories()) + { + auto modDirectoryInfo = ModFileTypes::GetModDirectory(modDirectory); + if (modDirectoryInfo) + { + auto childPath = uploadDirectory / modDirectoryInfo->pipedalPath; + FilePropertyDirectoryTree::ptr child = std::make_unique( + childPath,modDirectoryInfo->displayName + ); + FillSampleDirectoryTree(child.get(),childPath); + result->children_.push_back(std::move(child)); + } + } + if (uiFileProperty.useLegacyModDirectory()) { + auto childPath = uploadDirectory / uiFileProperty.directory(); + FilePropertyDirectoryTree::ptr child = std::make_unique( + childPath + ); + FillSampleDirectoryTree(child.get(),childPath); + result->children_.push_back(std::move(child)); + } + return result; + + } else { + if (uiFileProperty.directory().empty()) + { + throw std::runtime_error("Invalid uiFileProperty"); + } + if (!ensureNoDotDot(uiFileProperty.directory())) + { + throw std::runtime_error("Invalid uiFileProperty"); + } + std::filesystem::path rootDirectory = uploadDirectory / uiFileProperty.directory(); + FilePropertyDirectoryTree::ptr result = std::make_unique(rootDirectory.string(),"Home"); + + FillSampleDirectoryTree(result.get(),rootDirectory); + return result; + } - return result; } diff --git a/src/Storage.hpp b/src/Storage.hpp index cdcfcd3..15df9e9 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -226,7 +226,7 @@ public: const std::string&oldRelativePath, const std::string&newRelativePath, const UiFileProperty&uiFileProperty); - FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty) const; + FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty); }; diff --git a/src/WebServerConfig.cpp b/src/WebServerConfig.cpp index e516139..4097268 100644 --- a/src/WebServerConfig.cpp +++ b/src/WebServerConfig.cpp @@ -1,4 +1,4 @@ - // Copyright (c) 2024 Robin Davies +// Copyright (c) 2024 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in @@ -32,7 +32,7 @@ #include "UpdaterSecurity.hpp" #include "TemporaryFile.hpp" #include "PresetBundle.hpp" - +#include "json.hpp" #define OLD_PRESET_EXTENSION ".piPreset" #define PRESET_EXTENSION ".piPreset" @@ -50,40 +50,52 @@ using namespace pipedal; using namespace boost::system; namespace fs = std::filesystem; +class UserUploadResponse +{ +public: + std::string errorMessage_; + std::string path_; + DECLARE_JSON_MAP(UserUploadResponse); +}; +JSON_MAP_BEGIN(UserUploadResponse) +JSON_MAP_REFERENCE(UserUploadResponse, errorMessage) +JSON_MAP_REFERENCE(UserUploadResponse, path) +JSON_MAP_END() static bool IsZipFile(const std::filesystem::path &path) { std::ifstream f(path); - if (!f.is_open()) return false; + if (!f.is_open()) + return false; char c[4]; - memset(c,0,sizeof(c)); + memset(c, 0, sizeof(c)); f >> c[0] >> c[1] >> c[2] >> c[3]; // official file header according to PKware documetnation. return c[0] == 0x50 && c[1] == 0x4B && c[2] == 0x03 && c[3] == 0x04; - - } -class ExtensionChecker { +class ExtensionChecker +{ public: - ExtensionChecker(const std::string&extensionList) - :extensions(split(extensionList,',')) + ExtensionChecker(const std::string &extensionList) + : extensions(split(extensionList, ',')) { } - bool IsValidExtension(const std::string&extension) + bool IsValidExtension(const std::string &extension) { - if (extensions.size() == 0) + if (extensions.size() == 0) return true; - for (const auto&ext: extensions) + for (const auto &ext : extensions) { - if (ext == extension) + if (ext == extension) return true; } return false; } + private: std::vector extensions; }; @@ -133,7 +145,8 @@ public: else if (segment == "uploadBank") { return true; - } else if (segment == "uploadUserFile") + } + else if (segment == "uploadUserFile") { return true; } @@ -159,7 +172,7 @@ public: PluginPresets pluginPresets = model->GetPluginPresets(pluginUri); std::stringstream s; - json_writer writer(s,false); + json_writer writer(s, false); writer.write(pluginPresets); *pContent = s.str(); } @@ -176,7 +189,7 @@ public: file.selectedPreset(newInstanceId); std::stringstream s; - json_writer writer(s,false); + json_writer writer(s, false); writer.write(file); *pContent = s.str(); *pName = pedalboard.name(); @@ -210,12 +223,11 @@ public: std::string content; GetPluginPresets(request_uri, &name, &content); - TemporaryFile tmpFile { WEB_TEMP_DIR}; - PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model),content); + TemporaryFile tmpFile{WEB_TEMP_DIR}; + PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model), content); presetbundleWriter->WriteToFile(tmpFile.Path()); size_t contentLength = std::filesystem::file_size(tmpFile.Path()); - res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE); res.set(HttpField::cache_control, "no-cache"); res.setContentLength(content.length()); @@ -228,8 +240,8 @@ public: std::string content; GetPreset(request_uri, &name, &content); - TemporaryFile tmpFile { WEB_TEMP_DIR}; - PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content); + TemporaryFile tmpFile{WEB_TEMP_DIR}; + PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content); presetbundleWriter->WriteToFile(tmpFile.Path()); size_t contentLength = std::filesystem::file_size(tmpFile.Path()); @@ -245,14 +257,11 @@ public: std::string content; GetBank(request_uri, &name, &content); - TemporaryFile tmpFile { WEB_TEMP_DIR}; - PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content); + TemporaryFile tmpFile{WEB_TEMP_DIR}; + PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content); presetbundleWriter->WriteToFile(tmpFile.Path()); size_t contentLength = std::filesystem::file_size(tmpFile.Path()); - - - res.set(HttpField::content_type, BANK_MIME_TYPE); res.set(HttpField::cache_control, "no-cache"); res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION)); @@ -290,12 +299,11 @@ public: std::string content; GetPluginPresets(request_uri, &name, &content); - std::shared_ptr tmpFile =std::make_shared(WEB_TEMP_DIR); - PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model),content); + std::shared_ptr tmpFile = std::make_shared(WEB_TEMP_DIR); + PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model), content); presetbundleWriter->WriteToFile(tmpFile->Path()); size_t contentLength = std::filesystem::file_size(tmpFile->Path()); - res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE); res.set(HttpField::cache_control, "no-cache"); res.setContentLength(contentLength); @@ -308,12 +316,11 @@ public: std::string content; GetPreset(request_uri, &name, &content); - std::shared_ptr tmpFile =std::make_shared(WEB_TEMP_DIR); - PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content); + std::shared_ptr tmpFile = std::make_shared(WEB_TEMP_DIR); + PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content); presetbundleWriter->WriteToFile(tmpFile->Path()); size_t contentLength = std::filesystem::file_size(tmpFile->Path()); - res.set(HttpField::content_type, PRESET_MIME_TYPE); res.set(HttpField::cache_control, "no-cache"); res.setContentLength(contentLength); @@ -326,13 +333,11 @@ public: std::string content; GetBank(request_uri, &name, &content); - std::shared_ptr tmpFile =std::make_shared(WEB_TEMP_DIR); - PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content); + std::shared_ptr tmpFile = std::make_shared(WEB_TEMP_DIR); + PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content); presetbundleWriter->WriteToFile(tmpFile->Path()); size_t contentLength = std::filesystem::file_size(tmpFile->Path()); - - res.set(HttpField::content_type, BANK_MIME_TYPE); res.set(HttpField::cache_control, "no-cache"); res.setContentLength(contentLength); @@ -356,40 +361,49 @@ public: } } } - static std::string GetFirstFolderOrFile(const std::vector&fileNames) + static std::string GetFirstFolderOrFile(const std::vector &fileNames) { - for (const auto &fileName: fileNames) + for (const auto &fileName : fileNames) { size_t nPos = fileName.find('/'); - if (nPos != std::string::npos) { - return fileName.substr(0,nPos); + if (nPos != std::string::npos) + { + return fileName.substr(0, nPos); } } if (fileNames.size() == 0) { return 0; - } else { + } + else + { return fileNames[0]; } } - static bool HasSingleRootDirectory(ZipFileReader&zipFile) { + static bool HasSingleRootDirectory(ZipFileReader &zipFile) + { bool hasDirectory = false; std::string previousDirectory; - for (auto& file: zipFile.GetFiles()) + for (auto &file : zipFile.GetFiles()) { auto pos = file.find('/'); - if (pos == std::string::npos) { + if (pos == std::string::npos) + { // a file in the root. return false; - } else + } + else { - std::string currentRootDirectory = file.substr(0,pos); - if (hasDirectory) { + std::string currentRootDirectory = file.substr(0, pos); + if (hasDirectory) + { if (currentRootDirectory != previousDirectory) { return false; } - } else { + } + else + { hasDirectory = true; previousDirectory = currentRootDirectory; } @@ -412,26 +426,26 @@ public: { PluginPresets presets; fs::path filePath = req.get_body_temporary_file(); - if (filePath.empty()) + if (filePath.empty()) { throw std::runtime_error("Unexpected."); } if (IsZipFile(filePath)) { - auto presetReader = PresetBundleReader::LoadPluginPresetsFile(*(this->model),filePath); + auto presetReader = PresetBundleReader::LoadPluginPresetsFile(*(this->model), filePath); presetReader->ExtractMediaFiles(); std::stringstream ss(presetReader->GetPluginPresetsJson()); json_reader reader(ss); reader.read(&presets); - - } else { + } + else + { json_reader reader(req.get_body_input_stream()); reader.read(&presets); } model->UploadPluginPresets(presets); - res.set(HttpField::content_type, "application/json"); res.set(HttpField::cache_control, "no-cache"); std::stringstream sResult; @@ -447,19 +461,21 @@ public: BankFile bankFile; fs::path filePath = req.get_body_temporary_file(); - if (filePath.empty()) + if (filePath.empty()) { throw std::runtime_error("Unexpected."); } if (IsZipFile(filePath)) - { - auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model),filePath); + { + auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model), filePath); presetReader->ExtractMediaFiles(); std::stringstream ss(presetReader->GetPresetJson()); json_reader reader(ss); reader.read(&bankFile); - } else { + } + else + { // legacy json format, no zip, no media files. json_reader reader(req.get_body_input_stream()); reader.read(&bankFile); @@ -487,19 +503,21 @@ public: BankFile bankFile; fs::path filePath = req.get_body_temporary_file(); - if (filePath.empty()) + if (filePath.empty()) { throw std::runtime_error("Unexpected."); } if (IsZipFile(filePath)) - { - auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model),filePath); + { + auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model), filePath); presetReader->ExtractMediaFiles(); std::stringstream ss(presetReader->GetPresetJson()); json_reader reader(ss); reader.read(&bankFile); - } else { + } + else + { // legacy json format, no zip, no media files. json_reader reader(req.get_body_input_stream()); reader.read(&bankFile); @@ -522,70 +540,88 @@ public: res.setContentLength(result.length()); res.setBody(result); - } else if (segment == "uploadUserFile") + } + else if (segment == "uploadUserFile") { - res.set(HttpField::content_type, "application/json"); - res.set(HttpField::cache_control, "no-cache"); - std::string instanceId = request_uri.query("id"); - std::string directory = request_uri.query("directory"); - std::string filename = request_uri.query("filename"); - std::string patchProperty = request_uri.query("property"); - - - if (patchProperty.length() == 0 && directory.length() == 0) + UserUploadResponse result; + try { - throw PiPedalException("Malformed request."); - } + res.set(HttpField::content_type, "application/json"); + res.set(HttpField::cache_control, "no-cache"); + std::string instanceId = request_uri.query("id"); + std::string directory = request_uri.query("directory"); + std::string filename = request_uri.query("filename"); + std::string patchProperty = request_uri.query("property"); - res.set(HttpField::content_type, "application/json"); - res.set(HttpField::cache_control, "no-cache"); + if (patchProperty.length() == 0 && directory.length() == 0) + { + throw PiPedalException("Malformed request."); + } - std::string outputFileName = std::filesystem::path(directory) / filename; + res.set(HttpField::content_type, "application/json"); + res.set(HttpField::cache_control, "no-cache"); - if (filename.ends_with(".zip")) - { - ExtensionChecker extensionChecker { request_uri.query("ext") }; - namespace fs = std::filesystem; + fs::path outputFileName = std::filesystem::path(directory) / filename; - try { - auto zipFile = ZipFileReader::Create(req.get_body_temporary_file()); - std::vector files = zipFile->GetFiles(); - bool hasSingleRootDirectory = HasSingleRootDirectory(*zipFile); - if (!hasSingleRootDirectory) { - directory = (fs::path(directory) / fs::path(filename).filename().replace_extension("")).string(); - } - for (const auto&inputFile : files) + if (filename.ends_with(".zip")) + { + ExtensionChecker extensionChecker{request_uri.query("ext")}; + namespace fs = std::filesystem; + + try { - if (!inputFile.ends_with("/")) // don't process directory entries. + auto zipFile = ZipFileReader::Create(req.get_body_temporary_file()); + std::vector files = zipFile->GetFiles(); + bool hasSingleRootDirectory = HasSingleRootDirectory(*zipFile); + if (!hasSingleRootDirectory) { - fs::path inputPath { inputFile}; - std::string extension = inputPath.extension(); - if (extensionChecker.IsValidExtension(extension)) + directory = (fs::path(directory) / fs::path(filename).filename().replace_extension("")).string(); + } + for (const auto &inputFile : files) + { + if (!inputFile.ends_with("/")) // don't process directory entries. { - auto si = zipFile->GetFileInputStream(inputFile); - std::string path = this->model->UploadUserFile(directory,patchProperty,inputFile, si,zipFile->GetFileSize(inputFile)); + fs::path inputPath{inputFile}; + std::string extension = inputPath.extension(); + if (extensionChecker.IsValidExtension(extension)) + { + auto si = zipFile->GetFileInputStream(inputFile); + std::string path = this->model->UploadUserFile(directory, patchProperty, inputFile, si, zipFile->GetFileSize(inputFile)); + } } } + // set outputPath to the file or folder we would like focus to go to. + // almost always a single folder in the root. + std::string returnPath = GetFirstFolderOrFile(files); + outputFileName = fs::path(directory) / returnPath; } - // set outputPath to the file or folder we would like focus to go to. - // almost always a single folder in the root. - std::string returnPath = GetFirstFolderOrFile(files); - outputFileName = this->model->GetPluginUploadDirectory() / directory / returnPath; - } catch (const std::exception &e) - { - Lv2Log::error(SS("Unzip failed. " << e.what())); - throw; + catch (const std::exception &e) + { + Lv2Log::error(SS("Unzip failed. " << e.what())); + throw; + } + FileSystemSync(); + } + else + { + outputFileName = this->model->UploadUserFile(directory, patchProperty, filename, req.get_body_input_stream(), req.content_length()); } - FileSystemSync(); - } else { - outputFileName = this->model->UploadUserFile(directory,patchProperty,filename,req.get_body_input_stream(), req.content_length()); + if (outputFileName.is_relative()) + { + outputFileName = this->model->GetPluginUploadDirectory() / outputFileName; + } + result.path_ = outputFileName.string(); + } + catch (const std::exception &e) + { + result.errorMessage_ = e.what(); } std::stringstream ss; json_writer writer(ss); - writer.write(outputFileName); + writer.write(result); std::string response = ss.str(); res.setContentLength(response.length()); @@ -598,7 +634,7 @@ public: } catch (const std::exception &e) { - Lv2Log::error(SS("Error uploading file: " << e.what()) ); + Lv2Log::error(SS("Error uploading file: " << e.what())); if (strcmp(e.what(), "Not found") == 0) { ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory); @@ -611,7 +647,6 @@ public: } }; - static std::string StripPortNumber(const std::string &fromAddress) { std::string address = fromAddress; @@ -663,10 +698,10 @@ public: std::stringstream s; s << "{ \"socket_server_port\": " << portNumber - << ", \"socket_server_address\": \"" << webSocketAddress + << ", \"socket_server_address\": \"" << webSocketAddress << "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize - << ", \"enable_auto_update\": " << (ENABLE_AUTO_UPDATE ? " true": "false") - << " }"; + << ", \"enable_auto_update\": " << (ENABLE_AUTO_UPDATE ? " true" : "false") + << " }"; return s.str(); } @@ -720,16 +755,14 @@ public: }; void pipedal::ConfigureWebServer( - WebServer&server, - PiPedalModel&model, + WebServer &server, + PiPedalModel &model, int port, size_t maxUploadSize) { - std::shared_ptr interceptConfig{new InterceptConfig(port, maxUploadSize)}; - server.AddRequestHandler(interceptConfig); - - std::shared_ptr downloadIntercept = std::make_shared(&model); - server.AddRequestHandler(downloadIntercept); - + std::shared_ptr interceptConfig{new InterceptConfig(port, maxUploadSize)}; + server.AddRequestHandler(interceptConfig); + std::shared_ptr downloadIntercept = std::make_shared(&model); + server.AddRequestHandler(downloadIntercept); }