I am uploading image to a folder and saving its path in db.Here is my code.
[HttpPost]
public ActionResult UploadPic(FileManagement fmanage, HttpPostedFileBase file)
{
string email = User.Identity.Name;
if (file != null && file.ContentLength > 0)
{
var FileName = string.Format("{0}.{1}", Guid.NewGuid(), Path.GetFileName(file.FileName));
var path = Path.Combine(Server.MapPath("~/Content/Uploads"), FileName);
file.SaveAs(path);
using (var session = DocumentStore.OpenSession("RavenMemberShip"))
{
var query = from q in Session.Query<Registration>() where q.Email == email select q;
if (query.Count() > 0)
{
foreach (var updated in query)
{
updated.FileName = FileName;
updated.Path = path;
session.SaveChanges();
}
}
}
}
else
ModelState.AddModelError("", "Remove the errors and try again");
return View();
}
But the path is stored as double forward slash,which is wrong.How can i save the path as single slash.
Thanks in advance for help.
You said the path is stored with a "double forward slash", which would be //. I think you meant a "double backslash", which would be \\. Please provide an example if I am mistaken.
The double backslash is appropriate, because it escapes the backslash character. A value such as \n would be a newline, so an actual backslash has to be escaped as \\. This is part of how JSON stores strings.
However, you might want to consider only storing the file name into your document. The full path to the uploads folder is just going to be redundant. If you ever want to change it, then you should just be able to edit a setting instead of having to modify all of your documents.
Other issues with your code:
Remove this line:
if (query.Count() > 0)
It is unnecessary, and is causing the query to be executed twice.
This line:
session.SaveChanges();
Needs to be moved after your foreach loop. You only want a single batch of changes sent to RavenDB.
Use consistent naming conventions. The FileName local variable should be cased as fileName.
Related
When searching a directory for files of a specific name driven by the _fileToSearch parameter, I then create a custom list of DrawingFound and store the files path in a string called FileDirectory.
I then require on a button click OpenDrawing() for the file stored within FileDirectory to open to the user. This works in most cases, however, if the path has a , for example then the explorer defaults to opening the users documents directory. How can I handle commas within a file path to achieve the desired outcome?
public partial class DrawingFound
{
public string DrawingName;
public string FileType;
public string FileDirectory;
public string Revision;
public void OpenDrawing()
{
Process.Start("Explorer.exe", FileDirectory);
}
}
public void GetDrawings()
{
string _searchFolder = #"C:\Users\ThisUser\Documents";
string _fileToSearch = "Example of file, where a comma is used.txt";
ObservableCollection<DrawingFound> _drawings = new();
DirectoryInfo dirInfo = new(_searchFolder);
FileInfo[] files = dirInfo.GetFiles($"*{_fileToSearch}*", SearchOption.AllDirectories);
foreach (FileInfo file in files)
{
if (!_drawings.Any(item => $"{item.DrawingName}{item.FileType}" == file.Name))
{
_drawings.Add(new DrawingFound
{
DrawingName = Path.GetFileNameWithoutExtension(file.Name),
FileType = file.Extension,
FileDirectory = file.FullName,
Revision = "- Ignore -"
});
}
}
}
depending on your OS, you may need to use "escaping"
For example, to store a string one "two" three in a literal delimited with quotation marks, you need to escape the quotation marks. Depending on the language and environment, the "escape character" can be e.g. a \
in this example:
foo = "one \"two\" three"
I hope this helps; otherwise, please be more specific about your language, OS, e.t.c.
Thank you to everyone for your assistance with this matter. I managed to fix the issue and the operation now works as expected. #George Rey following your example I added the escape characters to achieve this:
Process.Start("explorer.exe", $"\"{FileDirectory}\"");
After you did your edits, the problem is more clear:
I guess that your OS is windows.
The problem is not with the comma but rather with the space.
The system treats the characters before the space as "file path" and the rest as "parameters." This is for historical reasons.
wrap the entire path in "embedded quotes" so that it is clear to the OS that the entire string is a path. This should prevent it from trying to elide the command parameters out of that string
I want to make sure this is enough to prevent directory traversal and also any suggestions or tips would be appreciated. The directory "/wwwroot/Posts/" is the only directory which is allowed.
[HttpGet("/[controller]/[action]/{name}")]
public IActionResult Post(string name)
{
if(string.IsNullOrEmpty(name))
{
return View("Post", new BlogPostViewModel(true)); //error page
}
char[] InvalidFilenameChars = Path.GetInvalidFileNameChars();
if (name.IndexOfAny(InvalidFilenameChars) >= 0)
{
return View("Post", new BlogPostViewModel(true));
}
DirectoryInfo dir = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/Posts"));
var userpath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/Posts", name));
if (Path.GetDirectoryName(userpath) != dir.FullName)
{
return View("Post", new BlogPostViewModel(true));
}
var temp = Path.Combine(dir.FullName, name + ".html");
if (!System.IO.File.Exists(temp))
{
return View("Post", new BlogPostViewModel(true));
}
BlogPostViewModel model = new BlogPostViewModel(Directory.GetCurrentDirectory(), name);
return View("Post", model);
}
Probably, but I wouldn't consider it bulletproof. Let's break this down:
First you are black-listing known invalid characters:
char[] InvalidFilenameChars = Path.GetInvalidFileNameChars();
if (name.IndexOfAny(InvalidFilenameChars) >= 0)
{
return View("Post", new BlogPostViewModel(true));
}
This is a good first step, but blacklisting input is rarely enough. It will prevent certain control characters, but the documentation does not explicitly state that directory separators ( e.g. / and \) are included. The documentation states:
The array returned from this method is not guaranteed to contain the
complete set of characters that are invalid in file and directory
names. The full set of invalid characters can vary by file system.
Next, you attempt to make sure that after path.combine you have the expected parent folder for your file:
DirectoryInfo dir = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/Posts"));
var userpath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/Posts", name));
if (Path.GetDirectoryName(userpath) != dir.FullName)
{
return View("Post", new BlogPostViewModel(true));
}
In theory, if the attacker passed in ../foo (and perhaps that gets past the blacklisting attempt above if / isn't in the list of invalid characters), then Path.Combine should combine the paths and return /somerootpath/wwwroot/foo. GetParentFolder would return /somerootpath/wwwroot which would be a non-match and it would get rejected. However, suppose Path.Combine concatenates and returns /somerootpath/wwwroot/Posts/../foo. In this case GetParentFolder will return /somerootpath/wwwRoot/Posts which is a match and it proceeds. Seems unlikely, but there may be control characters which get past GetInvalidFileNameChars() based on the documentation stating that it is not exhaustive which trick Path.Combine into something along these lines.
Your approach will probably work. However, if it is at all possible, I would strongly recommend you whitelist the expected input rather than attempt to blacklist all possible invalid inputs. For example, if you can be certain that all valid filenames will be made up of letters, numbers, and underscores, build a regular expression that asserts that and check before continuing. Testing for ^[A-Za-z0-0_]+$ would assert that and be 100% bulletproof.
I need to process some file paths in C# that potentially contain illegal characters, for example:
C:\path\something\output_at_13:26:43.txt
in that path, the :s in the timestamp make the filename invalid, and I want to replace them with another safe character.
I've searched for solutions here on SO, but they seem to be all based around something like:
path = string.Join("_", path.Split(Path.GetInvalidFileNameChars()));
or similar solutions. These solutions however are not good, because they screw up the drive letter, and I obtain an output of:
C_\path\something\output_at_13_26_43.txt
I tried using Path.GetInvalidPathChars() but it still doesn't work, because it doesn't include the : in the illegal characters, so it doesn't replace the ones in the filename.
So, after figuring that out, I tried doing this:
string dir = Path.GetDirectoryName(path);
string file = Path.GetFileName(path);
file = string.Join(replacement, file.Split(Path.GetInvalidFileNameChars()));
dir = string.Join(replacement, dir.Split(Path.GetInvalidPathChars()));
path = Path.Combine(dir, file);
but this is not good either, because the :s in the filename seem to interfere with the Path.GetFilename() logic, and it only returns the last piece after the last :, so I'm losing pieces of the path.
How do I do this "properly" without hacky solutions?
You can write a simple sanitizer that iterates each character and knows when to expect the colon as a drive separator. This one will catch any combination of letter A-Z followed directly by a ":". It will also detect path separators and not escape them. It will not detect whitespace at the beginning of the input string, so in case your input data might come with them, you will have to trim it first or modify the sanitizer accordingly:
enum ParserState {
PossibleDriveLetter,
PossibleDriveLetterSeparator,
Path
}
static string SanitizeFileName(string input) {
StringBuilder output = new StringBuilder(input.Length);
ParserState state = ParserState.PossibleDriveLetter;
foreach(char current in input) {
if (((current >= 'a') && (current <= 'z')) || ((current >= 'A') && (current <= 'Z'))) {
output.Append(current);
if (state == ParserState.PossibleDriveLetter) {
state = ParserState.PossibleDriveLetterSeparator;
}
else {
state = ParserState.Path;
}
}
else if ((current == Path.DirectorySeparatorChar) ||
(current == Path.AltDirectorySeparatorChar) ||
((current == ':') && (state == ParserState.PossibleDriveLetterSeparator)) ||
!Path.GetInvalidFileNameChars().Contains(current)) {
output.Append(current);
state = ParserState.Path;
}
else {
output.Append('_');
state = ParserState.Path;
}
}
return output.ToString();
}
You can try it out here.
You definitely should make sure that you only receive valid filenames.
If you can't, and you're certain your directory names will be, you could split the path the last backslash (assuming Windows) and reassemble the string:
public static string SanitizePath(string path)
{
var lastBackslash = path.LastIndexOf('\\');
var dir = path.Substring(0, lastBackslash);
var file = path.Substring(lastBackslash, path.Length - lastBackslash);
foreach (var invalid in Path.GetInvalidFileNameChars())
{
file = file.Replace(invalid, '_');
}
return dir + file;
}
I´m working in a MVC project and receive a file(HttpPostedFileBase property) in my controller via modelbinding and what I want is to delete all the empty spaces in the name of the file I just received, for that purpose I use this
var nombre_archivo = DateTime.Now.ToString("yyyyMMddHHmm.") +"_"+ (info.file.FileName.ToString()).Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
but the var "nombre_archivo" is always: 201801240942.System.String[] and what I want is 201801240942.nameOfFile, could you please tell me where is the error?
Your are splitting on an array of dots.
Use replace instead :
var nombre_archivo = string.Format("{0}_{1}",
DateTime.Now.ToString("yyyyMMddHHmm."),
info.file.FileName.replace(" ", "")
);
Moreover, we recommend to use string.Format instead of + concatenation. It's faster and clearer
var name = $"{DateTime.Now.ToString("yyyyMMddHHmm.")}_{info.file.FileName.Replace(" ", "")}";
You can use String.Replace to replace a space with an empty string. The method has other issues though. It doesn't check whether FileName is valid which means someone could make a POST request with a hand-coded path like ../../ or E:\somepath\myinnocentprogram.exe to write a file to the server's disk. Or worse, ../index.htm.
Replacing spaces doesn't make much sense. It's the dots and slashes that can result
If you check Uploading a File (Or Files) With ASP.NET MVC you'll see that the author uses Path.GetFileName() to retrieve only the file's name before saving it in the proper folder. Your code should look like this::
[HttpPost]
public ActionResult Index(HttpPostedFileBase file) {
if (file.ContentLength > 0) {
var fileName = Path.GetFileName(file.FileName)
.Replace(" ","");
var finalName=String.Format("{0:yyyyMMddHHmm}._{1}",DateTime.Now,fileName);
var path = Path.Combine(Server.MapPath("~/App_Data/uploads"), finalName);
file.SaveAs(path);
}
return RedirectToAction("Index");
}
This ensures that only the filename part of the file is used, and that the file is saved in the appropriate folder, even if someone posted an invalid path
This is how you should be able to fix your problem
string nombre_archivo = string.Format("{0}_{1}", DateTime.Now.ToString("yyyyMMddHHmm."), info.file.FileName.Where(c => !Char.IsWhiteSpace(c))
EDIT :
You should use string.Replace instead of using Linq query.
There is a format problem I did not expect.
So, the right answer is given below, but basically, it would look like :
string nombre_archivo = string.Format("{0}_{1}", DateTime.Now.ToString("yyyyMMddHHmm."), info.file.FileName.Replace(" ", ""));
I have a list of filename with full path which I need to remove the filename and part of the file path considering a filter list I have.
Path.GetDirectoryName(file)
Does part of the job but I was wondering if there is a simple way to filter the paths using .Net 2.0 to remove part of it.
For example:
if I have the path + filename equal toC:\my documents\my folder\my other folder\filename.exe and all I need is what is above my folder\ means I need to extract only my other folder from it.
UPDATE:
The filter list is a text box with folder names separated by a , so I just have partial names on it like the above example the filter here would be my folder
Current Solution based on Rob's code:
string relativeFolder = null;
string file = #"C:\foo\bar\magic\bar.txt";
string folder = Path.GetDirectoryName(file);
string[] paths = folder.Split(Path.DirectorySeparatorChar);
string[] filterArray = iFilter.Text.Split(',');
foreach (string filter in filterArray)
{
int startAfter = Array.IndexOf(paths, filter) + 1;
if (startAfter > 0)
{
relativeFolder = string.Join(Path.DirectorySeparatorChar.ToString(), paths, startAfter, paths.Length - startAfter);
break;
}
}
How about something like this:
private static string GetRightPartOfPath(string path, string startAfterPart)
{
// use the correct seperator for the environment
var pathParts = path.Split(Path.DirectorySeparatorChar);
// this assumes a case sensitive check. If you don't want this, you may want to loop through the pathParts looking
// for your "startAfterPath" with a StringComparison.OrdinalIgnoreCase check instead
int startAfter = Array.IndexOf(pathParts, startAfterPart);
if (startAfter == -1)
{
// path not found
return null;
}
// try and work out if last part was a directory - if not, drop the last part as we don't want the filename
var lastPartWasDirectory = pathParts[pathParts.Length - 1].EndsWith(Path.DirectorySeparatorChar.ToString());
return string.Join(
Path.DirectorySeparatorChar.ToString(),
pathParts, startAfter,
pathParts.Length - startAfter - (lastPartWasDirectory?0:1));
}
This method attempts to work out if the last part is a filename and drops it if it is.
Calling it with
GetRightPartOfPath(#"C:\my documents\my folder\my other folder\filename.exe", "my folder");
returns
my folder\my other folder
Calling it with
GetRightPartOfPath(#"C:\my documents\my folder\my other folder\", "my folder");
returns the same.
you could use this method to split the path by "\" sign (or "/" in Unix environments). After this you get an array of strings back and you can pick what you need.
public static String[] SplitPath(string path)
{
String[] pathSeparators = new String[]
{
Path.DirectorySeparatorChar.ToString()
};
return path.Split(pathSeparators, StringSplitOptions.RemoveEmptyEntries);
}