Handle permission denied errors.

This commit is contained in:
Robin E. R. Davies
2025-06-24 13:59:57 -04:00
parent 30938d967b
commit 98ee16bb82
10 changed files with 258 additions and 329 deletions
+17 -2
View File
@@ -207,7 +207,13 @@ std::vector<DbFileInfo> AudioDirectoryInfoImpl::QueryTracks()
std::vector<AudioFileMetadata> AudioDirectoryInfoImpl::GetFiles()
{
OpenAudioDb();
std::vector<DbFileInfo> dbFiles = UpdateDbFiles();
std::vector<DbFileInfo> dbFiles;
try {
dbFiles = UpdateDbFiles();
} catch (const std::exception &e) {
Lv2Log::error("Error updating audio file info: %s - %s",
path.string().c_str(), e.what());
}
std::vector<AudioFileMetadata> metadataResults;
for (const auto &dbFile : dbFiles)
{
@@ -298,7 +304,16 @@ std::vector<DbFileInfo> AudioDirectoryInfoImpl::UpdateDbFiles()
for (auto dirEntry : fs::directory_iterator(path))
{
if (!dirEntry.is_directory())
bool isDirectory;
try {
isDirectory = dirEntry.is_directory();
} catch (
const std::filesystem::filesystem_error &e) {
Lv2Log::error("Error accessing directory entry: %s - %s", dirEntry.path().string().c_str(), e.what());
continue; // skip this entry if we cannot access it.
}
if (!isDirectory)
{
auto path = dirEntry.path();
std::string name = path.filename();
+51 -44
View File
@@ -142,25 +142,27 @@ uint32_t FileMetadataFeature::getFileMetadata(
json_variant jsonData;
std::string result;
if (fs::exists(path))
try
{
try
if (fs::exists(path))
{
std::ifstream f(path);
json_reader reader(f);
reader.read(&jsonData);
result = jsonData.as_object()->at(std::string(key)).as_string();
}
catch (const std::exception &e)
else
{
Lv2Log::debug(SS("No metadata file found for " << absolute_path << " key: " << key));
return 0;
}
}
else
catch (const std::exception &e)
{
Lv2Log::debug(SS("No metadata file found for " << absolute_path << " key: " << key));
Lv2Log::debug(SS("Permission denied for " << absolute_path << " key: " << key));
return 0;
}
size_t len = result.length() + 1; // +1 for null terminator
if (len > fileMetadataSize || fileMetadata == nullptr)
{
@@ -168,7 +170,7 @@ uint32_t FileMetadataFeature::getFileMetadata(
return len;
}
memcpy(fileMetadata, result.c_str(), len);
Lv2Log::debug(SS("Get file metadata for " << absolute_path << " key: " << key << " value: " << result));
Lv2Log::debug(SS("Get file metadata for " << absolute_path << " key: " << key << " value: " << result));
return len;
}
PIPEDAL_FileMetadata_Status FileMetadataFeature::deleteFileMetadata(
@@ -181,52 +183,57 @@ PIPEDAL_FileMetadata_Status FileMetadataFeature::deleteFileMetadata(
}
Lv2Log::debug(SS("Delete file metadata for " << absolute_path << " key: " << key));
std::filesystem::path path{SS(absolute_path << ".mdata")};
try {
std::filesystem::path path{SS(absolute_path << ".mdata")};
if (!fs::exists(path))
{
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Metadata file does not exist
}
if (!fs::exists(path))
{
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Metadata file does not exist
}
json_variant jsonData;
try
{
std::ifstream f(path);
json_reader reader(f);
reader.read(&jsonData);
}
catch (const std::exception &e)
{
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Failed to read existing metadata
}
json_variant jsonData;
try
{
std::ifstream f(path);
json_reader reader(f);
reader.read(&jsonData);
}
catch (const std::exception &e)
{
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Failed to read existing metadata
}
auto obj = jsonData.as_object();
auto it = obj->find(std::string(key));
if (it == obj->end())
{
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Key not found
}
auto obj = jsonData.as_object();
auto it = obj->find(std::string(key));
if (it == obj->end())
{
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Key not found
}
obj->erase(it);
obj->erase(it);
if (obj->begin() == obj->end())
{
// If the object is empty, delete the metadata file
fs::remove(path);
return PIPEDAL_FILE_METADATA_SUCCESS; // Metadata deleted successfully
}
if (obj->begin() == obj->end())
{
// If the object is empty, delete the metadata file
fs::remove(path);
return PIPEDAL_FILE_METADATA_SUCCESS; // Metadata deleted successfully
}
std::ofstream f(path);
if (!f.is_open())
{
Lv2Log::error(SS("Failed to write metadata file " << path));
std::ofstream f(path);
if (!f.is_open())
{
Lv2Log::error(SS("Failed to write metadata file " << path));
return PIPEDAL_FILE_METADATA_PERMISSION_DENIED; // Failed to open existing metadata
}
json_writer writer(f);
writer.write(jsonData);
return PIPEDAL_FILE_METADATA_SUCCESS;
} catch (const std::exception &e) {
Lv2Log::error(SS("Exception while deleting metadata: " << e.what()));
return PIPEDAL_FILE_METADATA_PERMISSION_DENIED; // Failed to open existing metadata
}
json_writer writer(f);
writer.write(jsonData);
return PIPEDAL_FILE_METADATA_SUCCESS;
}
PIPEDAL_FileMetadata_Status FileMetadataFeature::S_setFileMetadata(
+76 -50
View File
@@ -1690,7 +1690,8 @@ static void AddFilesToResult(
}
catch (const std::exception &error)
{
throw std::logic_error("GetFileList failed. Directory not found: " + rootPath.string());
throw std::logic_error(
SS("GetFileList failed. " << rootPath.string() << " - " << error.what()));
}
// sort lexicographically
@@ -1723,7 +1724,7 @@ static void AddTracksToResult(
std::set<std::string> validExtensions = fileProperty.GetPermittedFileExtensions(
modDirectoryInfo ? modDirectoryInfo->modType : "");
if (validExtensions.size() == 0)
if (validExtensions.size() == 0)
{
const auto &audioExtensions = MimeTypes::instance().AudioExtensions();
validExtensions.insert(audioExtensions.begin(), audioExtensions.end());
@@ -1733,54 +1734,76 @@ static void AddTracksToResult(
try
{
// Add directories first.
for (auto const &dir_entry : std::filesystem::directory_iterator(rootPath))
try
{
if (!IsValidUtf8(dir_entry.path().string()))
for (auto const &dir_entry : std::filesystem::directory_iterator(rootPath))
{
Lv2Log::warning("Invalid UTF-8 name in directory: " + dir_entry.path().string());
continue; // skip invalid UTF-8 names.
}
const auto &path = dir_entry.path();
auto name = path.filename().string();
if (dir_entry.is_directory())
{
resultFiles.push_back(FileEntry{path, name, true, fs::is_symlink(path)});
if (!IsValidUtf8(dir_entry.path().string()))
{
Lv2Log::warning("Invalid UTF-8 name in directory: " + dir_entry.path().string());
continue; // skip invalid UTF-8 names.
}
const auto &path = dir_entry.path();
auto name = path.filename().string();
try {
if (dir_entry.is_directory())
{
resultFiles.push_back(FileEntry{path, name, true, dir_entry.is_symlink()});
}
} catch (const std::exception &e)
{
Lv2Log::warning(SS("Failed to add directory entry: " << path.string() << " - " << e.what()));
}
}
}
catch (const std::exception &error)
{
throw std::logic_error(
SS("AddTracksToResult failed to enumerate directories. " << rootPath.string() << " - " << error.what()));
}
auto collator = Locale::GetInstance()->GetCollator();
std::sort(
resultFiles.begin(),
resultFiles.begin(),
resultFiles.end(),
[collator](const FileEntry &l, const FileEntry &r)
{
if (l.isDirectory_ != r.isDirectory_)
{
return l.isDirectory_ > r.isDirectory_;
}
return collator->Compare(l.displayName_ ,r.displayName_) < 0;
});
if (l.isDirectory_ != r.isDirectory_)
{
return l.isDirectory_ > r.isDirectory_;
}
return collator->Compare(l.displayName_, r.displayName_) < 0;
});
// Add audio files.
auto audioFiles = AudioDirectoryInfo::Create(rootPath,
GetShadowIndexDirectory(audioRootDirectory,rootPath)
);
for (const auto &audioFile : audioFiles->GetFiles())
GetShadowIndexDirectory(audioRootDirectory, rootPath));
try
{
fs::path audioFilePath = rootPath / audioFile.fileName();
std::string extension = UiFileProperty::GetFileExtension(audioFilePath);
if (validExtensions.size() == 0 || validExtensions.contains(extension) || validExtensions.contains(".*"))
for (const auto &audioFile : audioFiles->GetFiles())
{
resultFiles.push_back(
FileEntry(
audioFilePath,
audioFile.title(),
false,
std::make_shared<AudioFileMetadata>(audioFile)));
fs::path audioFilePath = rootPath / audioFile.fileName();
std::string extension = UiFileProperty::GetFileExtension(audioFilePath);
if (validExtensions.size() == 0 || validExtensions.contains(extension) || validExtensions.contains(".*"))
{
resultFiles.push_back(
FileEntry(
audioFilePath,
audioFile.title(),
false,
std::make_shared<AudioFileMetadata>(audioFile)));
}
}
}
catch (const std::exception &error)
{
throw std::logic_error(
SS("AddTracksToResult failed to enumerate audio files. " << rootPath.string() << " - " << error.what()));
}
}
catch (const std::exception &error)
{
throw std::logic_error("GetFileList failed. Directory not found: " + rootPath.string());
throw std::logic_error(SS("GetFileList failed. " << error.what() << "(" << rootPath.string() << ")"));
}
}
@@ -1869,7 +1892,7 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath, cons
if (IsInAudioTracksDirectory(relativePath))
{
AddTracksToResult(this->GetPluginUploadDirectory(),result, rootModDirectory, fileProperty, relativePath);
AddTracksToResult(this->GetPluginUploadDirectory(), result, rootModDirectory, fileProperty, relativePath);
}
else
{
@@ -2046,7 +2069,7 @@ void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
else
{
std::filesystem::remove(fileName);
if (IsInAudioTracksDirectory(fileName))
if (IsInAudioTracksDirectory(fileName))
{
// remove the metadata file as well.
std::filesystem::remove(fileName.string() + ".mdata");
@@ -2074,13 +2097,12 @@ std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, co
return result;
}
bool Storage::IsValidArtworkFile(const std::filesystem::path& fullPath)
bool Storage::IsValidArtworkFile(const std::filesystem::path &fullPath)
{
if (IsInAudioTracksDirectory(fullPath) && isArtworkFileName(fullPath.filename().string()))
{
// allow artwork files.
return true;
return true;
}
return false;
}
@@ -2208,9 +2230,10 @@ std::string Storage::RenameFilePropertyFile(
}
std::filesystem::rename(oldPath, newPath);
if (fs::exists(oldPath.string()+".mdata")) {
if (fs::exists(oldPath.string() + ".mdata"))
{
// rename the metadata file as well.
std::filesystem::rename(oldPath.string()+".mdata", newPath.string()+".mdata");
std::filesystem::rename(oldPath.string() + ".mdata", newPath.string() + ".mdata");
}
return newPath;
}
@@ -2222,12 +2245,13 @@ fs::path MakeVersionedPath(const fs::path &path)
return path; // no need to version a non-existing file.
}
fs::path newPath = path;
auto stem = newPath.stem().string();;
auto stem = newPath.stem().string();
;
if (stem.ends_with(")"))
{
// remove the trailing (n) from the file name.
size_t pos = stem.find_last_of('(');
std::string stemVersion = stem.substr(pos+1, stem.length()-1-(pos+1));
std::string stemVersion = stem.substr(pos + 1, stem.length() - 1 - (pos + 1));
// check if it is a number.
if (stemVersion.find_first_not_of("0123456789") == std::string::npos)
{
@@ -2237,7 +2261,7 @@ fs::path MakeVersionedPath(const fs::path &path)
{
// remove trailing space.
stem = stem.substr(0, stem.length() - 1);
}
}
}
}
@@ -2246,12 +2270,15 @@ fs::path MakeVersionedPath(const fs::path &path)
{
// append (n) to the file name.
std::string newFileName;
if (version == 0) {
if (version == 0)
{
newFileName = SS(stem << newPath.extension().string());
} else {
}
else
{
// append (n) to the file name.
newFileName = SS(stem << " (" << std::to_string(version) << ")" << newPath.extension().string());
}
}
newPath = newPath.parent_path() / newFileName;
++version;
}
@@ -2280,7 +2307,6 @@ std::string Storage::CopyFilePropertyFile(
std::filesystem::path newPath = this->GetPluginUploadDirectory() / uiFileProperty.directory() / newRelativePath;
if (!this->IsValidSampleFileName(newPath))
{
throw std::runtime_error("Invalid file name.");
@@ -2299,14 +2325,14 @@ std::string Storage::CopyFilePropertyFile(
std::filesystem::create_hard_link(oldPath, newPath);
if (IsInAudioTracksDirectory(oldPath)
&& IsInAudioTracksDirectory(newPath))
if (IsInAudioTracksDirectory(oldPath) && IsInAudioTracksDirectory(newPath))
{
std::filesystem::path metadataPath = SS(oldPath.string() << ".mdata");
if (fs::exists(metadataPath)) {
if (fs::exists(metadataPath))
{
// copy the metadata file as well.
std::filesystem::copy_file(
metadataPath,
metadataPath,
SS(newPath.string() << ".mdata"),
fs::copy_options::overwrite_existing);
}
+81 -76
View File
@@ -52,15 +52,13 @@ static const std::string PLUGIN_PRESETS_MIME_TYPE = "application/vnd.pipedal.plu
static const std::string PRESET_MIME_TYPE = "application/vnd.pipedal.preset";
static const std::string BANK_MIME_TYPE = "application/vnd.pipedal.bank";
static const std::string CACHE_CONTROL_INDEFINITELY = "max-age=31536000,public,immutable"; // 1 year
static const std::string CACHE_CONTROL_SHORT = "max-age=300,public"; // 5 minutes
static const std::string CACHE_CONTROL_INDEFINITELY = "max-age=31536000,public,immutable"; // 1 year
static const std::string CACHE_CONTROL_SHORT = "max-age=300,public"; // 5 minutes
using namespace pipedal;
using namespace boost::system;
namespace fs = std::filesystem;
class UserUploadResponse
{
public:
@@ -87,7 +85,7 @@ int32_t ConvertThumbnailSize(const std::string &param)
{
return 0;
}
return static_cast<int32_t > (std::stoi(param));
return static_cast<int32_t>(std::stoi(param));
}
static bool IsZipFile(const std::filesystem::path &path)
@@ -128,7 +126,6 @@ private:
std::vector<std::string> extensions;
};
class DownloadIntercept : public RequestHandler
{
PiPedalModel *model;
@@ -392,16 +389,15 @@ public:
{
auto lastModified = std::filesystem::last_write_time(path);
res.set(HttpField::LastModified, HtmlHelper::timeToHttpDate(lastModified));
}
AudioDirectoryInfo::Ptr CreateDirectoryInfo(const fs::path &path) {
}
AudioDirectoryInfo::Ptr CreateDirectoryInfo(const fs::path &path)
{
return AudioDirectoryInfo::Create(path,
GetShadowIndexDirectory(
this->model->GetPluginUploadDirectory(),
path));
GetShadowIndexDirectory(
this->model->GetPluginUploadDirectory(),
path));
}
virtual void get_response(
const uri &request_uri,
HttpRequest &req,
@@ -500,89 +496,98 @@ public:
throw PiPedalException("File not found.");
}
AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo(path.parent_path());
auto files = audioDirectory->GetFiles();
for (const auto &file : files)
try
{
std::string fileNameOnly = path.filename();
if (file.fileName() == fileNameOnly)
auto files = audioDirectory->GetFiles();
for (const auto &file : files)
{
std::stringstream ss;
json_writer writer(ss);
writer.write(file);
res.setBody(ss.str());
return;
std::string fileNameOnly = path.filename();
if (file.fileName() == fileNameOnly)
{
std::stringstream ss;
json_writer writer(ss);
writer.write(file);
res.setBody(ss.str());
return;
}
}
}
catch (const std::exception &e)
{
Lv2Log::error("Error getting audio directory info: %s - (%s)", path.c_str(), e.what());
throw e;
}
// If we get here, the file was not found in the directory.
throw PiPedalException("File not found in directory.");
}
else if (segment == "Thumbnail")
{
ThumbnailTemporaryFile thumbnailTemporaryFile;
try
{
fs::path path = request_uri.query("path");
if (path.empty() ||
!fs::exists(path) ||
!this->model->IsInUploadsDirectory(path) ||
HasDotDot(path))
std::shared_ptr<TemporaryFile> thumbnail;
try
{
// path for folder thumbnails.
path = request_uri.query("ffile");
std::shared_ptr<TemporaryFile> thumbnail;
if (!path.empty() && fs::exists(path) &&
this->model->IsInUploadsDirectory(path) &&
!HasDotDot(path))
fs::path path = request_uri.query("path");
if (path.empty() ||
!fs::exists(path) ||
!this->model->IsInUploadsDirectory(path) ||
HasDotDot(path))
{
// path is a folder.
thumbnail = std::make_shared<TemporaryFile>();
thumbnail->SetNonDeletedPath(path);
}
else
{
auto t = AudioDirectoryInfo::DefaultThumbnailTemporaryFile();
thumbnail = std::make_shared<TemporaryFile>();
thumbnail->SetNonDeletedPath(t.Path());
std::shared_ptr<TemporaryFile> thumbnail;
// path for folder thumbnails.
path = request_uri.query("ffile");
if (!path.empty() && fs::exists(path) &&
this->model->IsInUploadsDirectory(path) &&
!HasDotDot(path))
{
// path is a folder.
thumbnail = std::make_shared<TemporaryFile>();
thumbnail->SetNonDeletedPath(path);
}
else
{
auto t = AudioDirectoryInfo::DefaultThumbnailTemporaryFile();
thumbnail = std::make_shared<TemporaryFile>();
thumbnail->SetNonDeletedPath(t.Path());
}
res.set(HttpField::content_type, MimeTypes::instance().MimeTypeFromExtension(path.extension()));
res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted, and will change if the file ismodified.
setLastModifiedFromFile(res, thumbnail->Path());
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail->Path())));
res.setBodyFile(thumbnail);
return;
}
res.set(HttpField::content_type, MimeTypes::instance().MimeTypeFromExtension(path.extension()));
res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted, and will change if the file ismodified.
setLastModifiedFromFile(res,thumbnail->Path());
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail->Path())));
res.setBodyFile(thumbnail);
return;
int32_t width = ConvertThumbnailSize(request_uri.query("w"));
int32_t height = ConvertThumbnailSize(request_uri.query("h"));
AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo(
path.parent_path());
audioDirectory->GetFiles(); // ensure that the .index file is up to date.
thumbnailTemporaryFile = audioDirectory->GetThumbnail(path.filename(), width, height);
}
int32_t width = ConvertThumbnailSize(request_uri.query("w"));
int32_t height = ConvertThumbnailSize(request_uri.query("h"));
AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo(
path.parent_path());
audioDirectory->GetFiles(); // ensure that the .index file is up to date.
ThumbnailTemporaryFile thumbnail;
try {
thumbnail = audioDirectory->GetThumbnail(path.filename(), width, height);
} catch (const std::exception &e) {
thumbnail = audioDirectory->DefaultThumbnailTemporaryFile();
catch (const std::exception &e)
{
fs::path defaultThumbnail = model->GetWebRoot() / "img/missing_thumbnail.jpg";
thumbnailTemporaryFile.SetNonDeletedPath(defaultThumbnail, "image/jpeg");
}
res.set(HttpField::content_type, thumbnail.GetMimeType());
res.set(HttpField::content_type, thumbnailTemporaryFile.GetMimeType());
res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted with time-stamp.
setLastModifiedFromFile(res, path);
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail.Path())));
setLastModifiedFromFile(res, thumbnailTemporaryFile.Path());
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnailTemporaryFile.Path())));
std::filesystem::path t = thumbnail.Path();
res.setBodyFile(t,thumbnail.DeleteFile());
thumbnail.Detach();
std::filesystem::path t = thumbnailTemporaryFile.Path();
res.setBodyFile(t, thumbnailTemporaryFile.DeleteFile());
thumbnailTemporaryFile.Detach();
}
catch (const std::exception &e)
{
Lv2Log::error("Error getting thumbnail: %s - (%s)", request_uri.str().c_str(), e.what());
Lv2Log::error("Error getting thumbnail: %s - (%s)", request_uri.str().c_str(), e.what());
throw e;
}
}
@@ -605,11 +610,11 @@ public:
{
result = (directoryPath / result).string();
}
// json-encode the result.
std::stringstream ss;
json_writer writer(ss);
writer.write(result);
writer.write(result);
res.setBody(ss.str());
}
@@ -637,11 +642,11 @@ public:
{
result = (directoryPath / result).string();
}
// json-encode the result.
std::stringstream ss;
json_writer writer(ss);
writer.write(result);
writer.write(result);
res.setBody(ss.str());
}