How to handle very long file names (with TPath)? - delphi

I have a program that downloads some files from Internet. The file name could be very long (100 chars).
The user may choose to save these these files in a folder that has a very long name (200 chars).
So, the total length of the path is over 260 chars.
I tried to create a function that checks if the path is too long and truncates the filename so the whole path will be exactly 260 chars. But functions in TPath class fails to work if the path is over 260 chars. More exactly GetDirectoryName raises a specific error when the path is over 260 chars, so I cannot use it to split the folder from file name.
A major design flaw in Delphi?
I think that TPath raising an error when working on long file names is a big flaw. Simply using GetDirectoryName to READ (not to write) will simply crash your program. TPath should allow processing long paths. MAYBE it should raise the error ONLY when you try to WRITE files with long path. But not even then. NTFS accepts long paths. So, why Delphi should force you to stick to 260? Some programs can handle longs paths. For example, I use Total Commander (never Explorer) so I am not affected by the long file name issue.
Any idea on how to treat this case?
Note: The download process is automated so I won't stop to ask the user to enter a new file name for each file that fails to be under 260 chars. I want to handle this silently.

Personally, my opinion is that TPath is simply wrong here. To assert that Windows paths cannot be greater than 260 characters is simply denying reality. What's more, to deny you the ability to perform text processing on paths is really quite inexplicable. In my opinion then, TPath should be avoided.
Which leads you back to the good old days. You can call ExtractFileDir from SysUtils. It works as well as it ever did.

Related

Does Ruby on Rails have read stream for files?

Does rails have a way to implement read streams like Node js for file reading?
i.e.
fs.createReadStream(__dirname + '/data.txt');
as apposed to
fs.readFile(__dirname + '/data.txt');
Where I see ruby has
file = File.new("data.txt")
I am unsure of the equivalent in ruby/rails for creating a stream and would like to know if this is possible. The reasons I ask is for memory management as a stream will be delivered piece by piece as apposed to one whole file.
If you want to read a file in Ruby piece-by-piece, there are a host of methods available to you.
IO#each_line/IO::foreach, also implemented in File to iterate over each line of the file. Neither reads the whole file into memory; instead, both simply read up until the next newline, return, and pause reading, barring a possible buffer.
IO#read/IO::read takes a length parameter, which allows you to specify for it to read up to length bytes from the file. This will only read that many, and not the whole thing.
IO::binread does the same as IO::read, but will open the file in binary mode.
IO#readpartial appears to be very similar or identical to IO#read, but is also worth looking at.
IO#getc and IO#gets both read from the file until they reach the end of what they'll return, as far as I can tell.
There are several more that I'm looking for right now.

Is original_filename method in ActionDispatch::Http::UploadedFile safe?

Is original_filename method in ActionDispatch::Http::UploadedFile safe to use to save as file in host system, without further sanitize it?
Looking at the source, it doesn't look like they do any checking of the filename, so unless they do it somewhere else (which would be bad design and thus unlike the Rails team), the real question is: what harm can a filename do? The only cases I can think of that it might be possible to use it maliciously is:
if the file is named ".". Those can be hard to delete if you actually succeed in creating them. I doubt that Ruby would let you save a file by that name, you can try it and see. If it doesn't, this this point can be ignored.
or maybe a really long name might cause a buffer overflow somewhere deeper in the API code of the OS.
Note that neither of those should be a problem. OSes try to make it impossible to create . files, but I've seen it done. And since (most?) filesystems already have a max filename limit, they should just error or truncate the file for you; truncating yourself is a borderline paranoid measure to protect against buffer overflow exploits that may be found in the OS's API code in the future. Such an exploit is very unlikely to exist.
So, if you really want to, just check for these two cases and you should be okay. You might want to do this by subclassing the UploadedFile class and adding the functionality that, if the name is "." or "..", then you just give it a random name; and if it is over, say 100 chars, then truncate it.
But I would say that neither of these are likely enough to warrant the introduction of a nonstandard class into your code base. I would just try saving the file by the given name and depend on the underlying file saving API to catch errors, check for said errors, and report them back to the user.

How Can I Handle Parameters With Spaces in Delphi?

My program accepts input file names either as command line parameters or in a drag and drop operation or in Explorer by clicking on filenames with an extension that is associated with my program.
The command line and drag and drop work fine, but it is clicking on the filenames in Explorer that causes problems when the filepaths of the files clicked on have spaces in them, e.g.:
c:\temp\file one.txt
c:\my directory\filetwo.txt
c:\my directory\file three.txt
then, the ParamStr function gives me back:
ParamStr(1): c:\temp\file
ParamStr(2): one.txt
ParamStr(3): c:\my
ParamStr(4): directory\filetwo.txt
ParamStr(5): c:\my
ParamStr(6): directory\file
ParamStr(7): three.txt
How can I best reconstitute these back into the three filenames that I need?
It might be your shell file association that does not include the pair of "".
Like these ones for opening:
"C:\Program Files\WinRAR\WinRAR.exe" "%1"
or with DDE message:
[open("%1")]
Command-line parameters with spaces in them, such as filenames, should be quoted. This makes the param parser realize that it's supposed to keep them together. If the user's not quoting the filename, it's operator error.
If a drag-and-drop system is doing this, on the other hand, then you've got a bug in your drag-and-drop library and you need to talk to whoever created it. I'm a bit confused, though, as to why drag-and-drop operations are messing with ParamStr. That should only be set by the params passed to your program at the moment it's invoked, not once it's up and running. Maybe I'm missing something?
i use the CmdLineHelper unit, from here.

Find long (>255) filenames

There are some folder with more than 100 files on it.
But all files and folders names broken with wrong encoding names (UTF->ANSI).
"C:\...\Госдача-Лечебни корпус\вертолетка\Госдача-Лечебни корпус\Госдача-Лечебни корпус\вертолетка\Госдача-Лечебни корпус\вертолетка\Госдача-Лечебни корпус\Госдача-Лечебни корпус\Госдача-Лечебни корпус\вертолетка\Госдача-Лечебни корпус\Госдача-Лечебни корпус\вертолетка\Госдача-Лечебни корпус\..."
Regular function Utf8ToAnsi finxing it, but FindFirst can't search folders with names longer than 255 symbols.
It gaves me only 70/100 files.
FindFirst wraps the Win32 API function FindFirstFile, and the Unicode version of that function can search paths up to 32,767 characters long if you prepend \\?\ to the path you're passing in, like \\?\C:\Folder\Folder\*.
Since Delphi 2009 and newer call the Unicode functions for you, you can just use FindFirst and co there. For Delphi 2007 and earlier (ANSI versions), you'll need to call FindFirstFile/FindNextFile/FindClose from Windows.pas directly. For more information check the Naming a file section of the platform SDK.
Do note that using \\?\ disables various bits of path processing though, so make sure it's a fully qualified path without any '.' or '..' entries. You can use the same trick to open file streams, rename, or copy files with longer paths.
The shell (Explorer) doesn't support this though, so you still need to limit those to at most MAX_PATH characters for things like SHFileOperation (to delete to the recycle bin) or ShellExecute. In many cases you can work around the problem by passing in the DOS 8.3 names instead of the long ones. FindFirst's TSearchRec doesn't expose the short names, but FindFirstFile's TWin32FindData structure does as cAlternateFileName.
Change the current directory (ChDir) to the deepest one you know about, and then pass a relative path to FindFirst or FindFirstFile.
No path component in that file name is longer than MAX_PATH characters, so you should be able to work your way into the directories one step at a time.
Beware that multithreaded programs may be sensitive to changes in the current directory since a process has only one current directory shared by all the threads.

Deleting a temporary directory

I have this code,
showmessage('C:\TEMP\'+openfiles[openfilelist.ItemIndex].ID);
if removedir('C:\TEMP\'+openfiles[openfilelist.ItemIndex].ID) then
showmessage('Removed')
else
showmessage('Failed');
The message shows C:\TEMP\0 and this directory does exist as the program created it earlier and used files inside it and then later deletes them. I can see the files and directories so I know they're there. The program successfully deletes the files but does not remove the directory.
If I hardcode the directory it works - this means that it accepts the string
C:\TEMP\0 but does not accept C:\TEMP\'+openfiles[openfilelist.ItemIndex].ID both equate to C:\TEMP\0. I cannot hardcode these directories, so what can I do? How do I convert from a string + string to whatever removedir() is expecting. I looked this up at Delphi basics and it's expecting a string.
I'm confused, since string + string = string. What is going on?
Make sure that neither your program nor any other program have the directory as their current working directory. When you recompile the program this may no longer be the case, so it may be a red herring that the hardcoded value works for you.
In addition to the other good answers, you should not be storing your temp folder in C:\TEMP. Use the value returned from GetTempFilename, instead. Unlike C:\TEMP, this location (which varies by operating system) will work on all operating systems, and all levels of user access control. This also eliminates the risk that the location you have hardcoded might also be hardcoded into another system.
If I understood correctly, openfiles[openfilelist.ItemIndex].ID is a string that contains number?
If so, did you check that it does not contain blanks? Something like this:
filename := 'C:\TEMP\' + trim(openfiles[openfilelist.ItemIndex].ID);
showmessage(filename);
if removedir(filename) then
showmessage('Removed')
else
showmessage('Failed');
What type of objects are openfiles and openfilelist?
Do they open folders at all, if so they may still be open when your trying to delete the folder.

Resources