This is for a web server assignment and this is an extra feature that I would like to implement. I would like to ensure that a client cannot specify a file above the servers root directory.
For example, lets say I have a folder "above", and within "above", I have www. I have my root directory set to /above/www in the server, so the server should be able to access anything within /above/www. Lets also say I have some a1.txt in above, so /above/a1.txt, I don't want a client to be able to perform GET /../a1.txt HTTP/1.0 and get access to that file.
I have 90% of the server implemented, I just need to determine if a file is above a given directory in the filesystem tree or if it is below. I can't think of a super easy way of doing that except for counting the ../ strings and if there are more of those than there are folders, I am above my root directory.
I am running linux, and c++11 is acceptable.
Use boost::filesystem::path(relativePath).absolute().parent_path().string() to obtain the absolute path of the directory that the file represented by relativePath lies in. Now, you can simply check that "/above/www" is a prefix of this directory.
My method for completing the task is to use two for loops and strtok.
This may not be the most c++ answer, but it works.
bool within_dir(const char* path, const char* root_dir)
{
assert(path != NULL);
assert(root_dir != NULL);
char *fname_dup, *root_dup, *token;
int root_value, path_value;
// Duplicate char arrays so strtok doesn't damage the originals
fname_dup = (char*)malloc(sizeof(char) * strlen(path) + 1);
assert(fname_dup != NULL);
bzero(fname_dup, sizeof(char) * strlen(path) +1 );
strncpy (fname_dup, path, sizeof(char) * strlen(path));
root_dup = (char*)malloc(sizeof(char) * strlen(root_dir) + 1);
assert(root_dup != NULL);
bzero(root_dup, sizeof(char) * strlen(root_dir)+ 1);
strncpy(root_dup, root_dir, sizeof(char) * strlen(root_dir) );
// Count root directory
for (root_value = 0, token = strtok(root_dup, "/");
token != NULL;
token = strtok(NULL, "/"))
{
if (strcmp(token, "..") == 0)
root_value--;
else
root_value++;
}
// Count and compare path value to root value
for (path_value = 0, token = strtok(fname_dup, "/");
token != NULL;
token = strtok(NULL, "/"))
{
if (strcmp(token, "..") == 0)
{
path_value--;
if (path_value < root_value)
{
free(root_dup);
free(fname_dup);
return false;
}
}
else
path_value++;
}
free(root_dup);
free(fname_dup);
return true;
}
Basically the code runs through and counts up how "positive" or "negative" the root is. Going up a level is negative, and going down a level is positive. Once it knows the value of the root, if the value of the file path ever goes below the value of the root, we know that the filepath must go up at least one layer above the root directory, and therefore is invalid. Since we don't actually care about the actual value, only if it goes up a layer from the root directory, we immediately stop and return false. If the filepath always remains more positive than the root, then the filepath will always be lower on the filetree. No guarantees are made for links.
Related
I am writing a small HTTP web server in C++ as part of a hobby project, and I need to serve static files. However, one problem I want to avoid is a user typing in, for example, http://example.com/../passwd. To ensure that users don't enter in a malicious path, I want to check if a path entered is in the current parent directory.
My current approach is to use std::filesystem::directory_iterator, checking if the provided one is a file and if its the same as the one provided. However, this is very slow and clunky, and I believe that there is a better solution.
A better solution would be to simply append the user's specified path to your desired root path, canonicalize the result, and then check if the result is still within the root path.
For example, when the user requests http://example.com/../passwd, your server will see a request like:
GET /../passwd HTTP/1.1
So, append just "../passwd" to your root folder and compare the result, for example:
#include <string>
#include <filesystem>
namespace fs = std::filesystem;
bool isSubDir(fs::path p, fs::path root)
{
static const fs::path emptyPath;
while (p != emptyPath) {
if (fs::equivalent(p, root)) {
return true;
}
p = p.parent_path();
}
return false;
}
...
fs::path requestedPath = fs::path("../passwd").make_preferred();
fs::path parentPath = fs::path("C:\\webroot\\");
fs::path actualPath = fs::canonical(parentPath / requestedPath);
if (isSubDir(actualPath, parentPath))
{
// serve file at actualPath as needed...
}
else
{
// send error reply...
}
I am trying to copy a folder to another folder using std::filesystem::copy(), so far it only copies the files and folders within the folder I'm trying to move over, instead of the folder itself. Any ideas why?
I know this could be done manually by creating a new directory using std::filesystem::create_directory(), but it won't carry over the security info and permissions from the original folder.
EDIT:
path = C:\Users\Test\Folder
nPath = C:\Users\Test\Movehere
boolean CopyToPath(std::string path, std::string nPath) {
if (fs::exists(path) && fs::exists(nPath)) {
try {
//path = Folder To Copy, nPath = Destination
fs::copy(path, nPath, fs::copy_options::overwrite_existing | fs::copy_options::recursive);
return true;
}
catch (fs::filesystem_error e) {
std::cout << e.what() << "\n";
return false;
}
}
else
return false;
}
This is expected behavior, as documented at https://en.cppreference.com/w/cpp/filesystem/copy:
Otherwise, if from is a directory and either options has copy_options::recursive or is copy_options::none,
If to does not exist, first executes create_directory(to, from) (creates the new directory with a copy of the old directory's attributes)
Then, whether to already existed or was just created, iterates over the files contained in from as if by for (const std::filesystem::directory_entry& x : std::filesystem::directory_iterator(from)) and for each directory entry, recursively calls copy(x.path(), to/x.path().filename(), options | in-recursive-copy), where in-recursive-copy is a special bit that has no other effect when set in options. (The sole purpose of setting this bit is to prevent recursive copying subdirectories if options is copy_options::none.)
In other words, copy() does not copy the source directory itself, only the contents of the directory.
If your goal is to make a copy of Folder itself inside of Movehere, ie C:\Users\Test\Movehere\Folder, then you will have to extract Folder from the source directory and append it to the target path, eg:
fs::path src = "C:\\Users\\Test\\Folder";
fs::path dest = "C:\\Users\\Test\\Movehere";
dest /= src.filename();
fs::create_directory(dest, src);
// only because CopyToPath() requires this due
// to its use of fs::exists(), instead of letting
// fs::copy() create it...
CopyToPath(src, dest);
I am trying to fix this issue:
https://github.com/gitahead/gitahead/issues/380
The problem is that the tree used in the model does not contain any untracked files and therefore the view has nothing to show. When I stage on file it is shown.
Is there a way to track in the tree also the untracked files?
I created a small test application to find the problem. When one file is staged, count is unequal to zero, otherwise it is always zero.
Testsetup
new git repository (TestRepository) with the following untracked files:
testfile.txt
testfolder/testfile2.txt
d
#include <git2.h>
#include <stdio.h>
int main() {
git_libgit2_init();
git_repository *repo = NULL;
int error = git_repository_open(&repo, "/TestRepository");
if (error < 0) {
const git_error *e = git_error_last();
printf("Error %d/%d: %s\n", error, e->klass, e->message);
exit(error);
}
git_tree *tree = nullptr;
git_index* idx = nullptr;
git_repository_index(&idx, repo);
git_oid id;
if (git_index_write_tree(&id, idx)) {
const git_error *e = git_error_last();
printf("Error %d/%d: %s\n", error, e->klass, e->message);
exit(error);
}
git_tree_lookup(&tree, repo, &id);
int count = git_tree_entrycount(tree);
printf("%d", count);
git_repository_free(repo);
printf("SUCCESS");
return 0;
}
If I understood correctly, what you're seeing is normal: as the file is untracked/new, the index has no knowledge of it, so if you ask the index, it has no "staged" changes to compare with, hence no diff.
If you want a diff for a yet-to-be tracked file, you'll have to provide it another way, usually by asking git_diff to do the work of comparing the worktree version with /dev/null, the empty blob, etc.
Since you're after a libgit2 solution, the way I'm trying to do that in GitX is via the git_status_list_new API, which gives a somewhat filesystem-independent way of generating both viewable diffs (staged & unstaged) on-the-fly, using git_patch_from_blobs/git_patch_from_blobs_and_buffer. In retrospect, maybe that should live in the library as git_status_entry_generate_patch or something…
SCENARIO
PROCEDURE A gathers files from a webservice, and copies them to a root folder. Sometimes files are copied in a subfolder of the root, for example:
c:\root\file1
c:\root\file2
c:\root\filea
c:\root\<unique random name>\fileA
I suspect (but I'm not sure) that the webservice runs on linux-system and file names are case-sensitive. So, files are copied on a Windows file-system, and when uppercase/lowercase conflicts occure, files are copied in a subfolder. The subfolder has an unique randomly generated name.
PROCEDURE B scans files in the root and sub-folders in order to archive them. Files correctly archived are deleted. PROCEDURE A and PROCEDURE B don't run simultaneously.
And now my task ... I've to delete empty subfolder of the root.
FIRST SOLUTION (the easiest one)
When procedure B ends, I can scan empty subfolders of the root, end then delete them. Well ...
DWORD DeleteEmptySubFolder(LPCSTR szRootFolder)
{
DWORD dwError = 0;
CString sFolder(szRootFolder);
sFolder += "*.*";
CFileFind find_folder;
BOOL bWorking = find_folder.FindFile(sFolder);
while (bWorking)
{
bWorking = find_folder.FindNextFile();
if(find_folder.IsDots())
continue;
if(find_folder.IsDirectory())
{
if(PathIsDirectoryEmpty(find_folder.GetFilePath()))
if(!RemoveDirectory(find_folder.GetFilePath()))
dwError = GetLastError();
}
}
return dwError;
}
and now here are the problems: I haven't got any control on PROCEDURE B and I don't know when it ends. PROCEDURE B can call a user function after archiving each individual file.
SECOND SOLUTION (adequate but not too efficient)
I can still call the above function
DWORD DeleteEmptySubFolder(LPCSTR szRootFolder)
It's not efficient for sure, it will scan all subfolders of the root for each archived file, but it will delete only empty subfolders.
THIRD SOLUTION (it should work)
When procedure B call user function, I know the root folder and the full path of the archived file. So I can check if the folder of the file is a sub-folder of the root:
#define EQUAL_FOLDER 0
#define A_SUBFOLDER_OF_B 1
#define B_SUBFOLDER_OF_A 2
#define UNRELATED_FOLDER 3
int CompareFolderHiearachy(LPCSTR szFolderA, LPCSTR szFolderB)
{
if(_stricmp(szFolderA, szFolderB))
{
// StrStrI - Windows function (from shlwapi.dll) which finds the first occurrence of a substring within a string (the comparison is not case-sensitive).
if(StrStrI(szFolderA, szFolderB) == szFolderA)
return A_SUBFOLDER_OF_B;
else if(StrStrI(szFolderB, szFolderA) == szFolderB)
return B_SUBFOLDER_OF_A;
else
return UNRELATED_FOLDER;
}
else
return EQUAL_FOLDER;
}
Maybe this solution could work fine in my scenario, but it can only handle cases where folder/file names are consistent. For example:
local disk:
root: C:\folder\
filename: c:\folder\subfolder\fileA
mapped disk:
root: Z:\folder\
filename: Z:\folder\subfolder\fileA
UNC:
root: \\SERVER\folder\
filename: \\SERVER\folder\subfolder\fileA
and now my too generic and abstract question, can I check the hierarchy/realtionship of two folders in the worst scenario ?
\\server\folder1\folder2 (UNC)
z:\folder2 (network drive).
or even worst ....
\\MYPC\folder1\folder2
c:\folder2
Maybe I'm asking a bit perverse question ... but it's quite challenging and intriguing, isn't it ?
Thank you very much.
I improved the thrid solution; it can solve severale situations, but nfortunately it can't handle all the possible cases.
#define ERROR_OCCURED -1
#define EQUAL_FOLDER 0
#define A_SUBFOLDER_OF_B 1
#define B_SUBFOLDER_OF_A 2
#define UNRELATED_FOLDER 3
int CompareFolderHiearachy(LPCSTR szFolderA, LPCSTR szFolderB)
{
char pBuffer[32767];
DWORD dwBufferLength = 32767;
UNIVERSAL_NAME_INFO * unameinfo = reinterpret_cast< UNIVERSAL_NAME_INFO *>(pBuffer);
DWORD dwRetVal = WNetGetUniversalName(szFolderA, UNIVERSAL_NAME_INFO_LEVEL, reinterpret_cast<LPVOID>(pBuffer), &dwBufferLength);
if(dwRetVal != NO_ERROR && dwRetVal != ERROR_NOT_CONNECTED && dwRetVal != ERROR_BAD_DEVICE)
return ERROR_OCCURED;
CString sFolderA(unameinfo->lpUniversalName ? unameinfo->lpUniversalName : szFolderA);
ZeroMemory(pBuffer, dwBufferLength);
dwRetVal = WNetGetUniversalName(szFolderB, UNIVERSAL_NAME_INFO_LEVEL, reinterpret_cast<LPVOID>(pBuffer), &dwBufferLength);
if(dwRetVal != NO_ERROR && dwRetVal != ERROR_NOT_CONNECTED && dwRetVal != ERROR_BAD_DEVICE)
return ERROR_OCCURED;
CString sFolderB(unameinfo->lpUniversalName ? unameinfo->lpUniversalName : szFolderB);
if(_stricmp(sFolderA, sFolderB))
{
// StrStrI - Windows function (from shlwapi.dll) which finds the first occurrence of a substring within a string (the comparison is not case-sensitive).
if(StrStrI(sFolderA, sFolderB) == static_cast<LPCSTR>(sFolderA))
return A_SUBFOLDER_OF_B;
else if(StrStrI(szFolderB, sFolderA) == static_cast<LPCSTR>(sFolderB))
return B_SUBFOLDER_OF_A;
else
return UNRELATED_FOLDER;
}
else
return EQUAL_FOLDER;
}
It can't solve the following:
folder A: \\MY_PC\shared_folder\folderA
folder B: C:\shared_folder
(\\MY_PC\shared_folder and C:\shared_folder are the sane folder)
and:
folder A: \\SERVER\shared_folderA\shared_folderB
folder B: \\SERVER\shared_folderB
TCHAR* pszBackupPath;
m_Edt_ExportPath.GetWindowText(pszBackupPath, dwcchBackupPath);
StrTrim(pszBackupPath, L" ");
StrTrim(pszBackupPath, L"\\"); //this line has issue
iRet = _tcslen(pszBackupPath);
boRet = PathIsNetworkPath(pszBackupPath);
if (FALSE == boRet)
{
// MessageBox with string "Entered path is network path.
}
boRet = PathIsDirectory(pszBackupPath);
if (FALSE == boRet)
{
// MessageBox with string "Entered path is not a valid directory.
}
This is a part of my code in MFC.
I am passing a network path from UI. But because of StrTrim(pszBackupPath, L"\\") "\\" get trimmed from start and end. But I want it to be trimmed from end only.
I don't know any direct API. Please suggest.
There is a simple function to do that: PathRemoveBackslash (or PathCchRemoveBackslash for Windows 8 and later).