Indy 10 FTP empty list - delphi

I have been receiving reports from some of my users that, when using idFTP.List() from some servers (MS FTP) then the listing is received as empty (no files) when in reality there are (non-hidden) files on the current directory. May this be a case of a missing parser? The funny think, when I use the program to get the list from MY server (MSFTP on W2003) everything seems OK but on some servers I've been hitting this problem.
Using latest Indy10 on D2010. Any idea?

IdFTPListParseWindowsNT is broken.
The function CheckListing returns false because of a bad parsing:
if sDir = ' <DI' then begin {do not localize}
sDir := Copy(SData, 27, 5);
end else begin
sDir := Copy(SData, 26,28); <---------------BAD PASRSING
Result := TextStartsWith(sDir,' <DI') or IsNumeric(TrimLeft(sDir));
if not Result then begin
Exit;
end;
end;
Commenting this part to make it work like in older versions
if sDir = ' <DI' then begin {do not localize}
sDir := Copy(SData, 27, 5);
end;
{ else begin
sDir := Copy(SData, 26,28); <---------------BAD PASRSING
Result := TextStartsWith(sDir,' <DI') or IsNumeric(TrimLeft(sDir));
if not Result then begin
Exit;
end;
end;}
Showuld solve your problem. Don't know why this change was introduced, though.

This is usually caused by something unexpected in the directory listing which makes the list parser fail. IIS might support both NT-style and Unix-style directory listings, so make sure that you're including both listing parsers in your application and picking between them using IdFTPLaistParse.pas::CheckListing. If that doesn't help it's probably a goofy date or a something in the filename; the best way to debug it is to add code to save the raw directory listing to a file so the end user can send you a copy.

Are you sure you can actually establish the data connection ? The directly listing command is usually the first occasion such a listing is requested and, if you're in the wrong mode, it's usually the point where the failure occurs (i.e. the data channel connection timesout).

Related

IdHTTP Post returns connection reset by peer for Streams over 32KB

I have a problem posting to a web server using HTTPS. I am not sure if the problem is with me or with the server. So it appeared that if I try to post a Stream greater than 32KB, Delphi crashes with Socket Error 10054 - Connection reset by peer.
I am using Delphi XE5 with the internal version of Indy and latest to date open ssl dlls.
I also try this on XE with latest to date Indy and ssl dlls.
Here is part of my code
function TForm1.SendItemsList(aDataList: TStringList): Boolean;
var
aHTTP: TIdHTTP;
aRes: String;
aURL: String;
aErrMsg: String;
aStrm: TMemoryStream;
aResStrm: TMemoryStream;
aXML: TNativeXML;
aTmpNode: TXmlNode;
aErrNode: TXmlNode;
aList: TList;
i: Integer;
begin
Result := False;
aStrm := TMemoryStream.Create;
aResStrm := TMemoryStream.Create;
aXML := TNativeXML.Create(nil);
aHTTP := CreateHTTP('application/x-www-form-urlencoded');
try
aDataList.SaveToStream(aStrm);
aStrm.Position := 0;
aURL := Format(cIRPURL, ['1']);
try
aHTTP.Post(aURL, aStrm, aResStrm);
aResStrm.Position := 0;
aXML.LoadFromStream(aResStrm);
aTmpNode := aXML.Root.FindNode('ResponseCode');
if aTmpNode <> nil then
begin
if aTmpNode.Value <> '0' then
begin
aErrNode := aXML.Root.FindNode('ResponseText');
aErrMsg := '';
if aErrNode <> nil then
aErrMsg := aErrNode.Value;
aList := TList.Create;
try
aXML.Root.FindNodes('Detail', aList);
for i := 0 to aList.Count-1 do
begin
aErrMsg := aErrMsg+#13#10+TXmlNode(aList[i]).Value;
end;
finally
aList.Free;
end;
end;
end;
except
on E:Exception do
begin
if E is EIdHTTPProtocolException then
aErrMsg := E.Message + #13#10 + (E as EIdHTTPProtocolException).ErrorMessage
else
aErrMsg := E.Message;
Exit;
end;
end;
finally
aXML.Free;
aStrm.Free;
aHTTP.Free;
aResStrm.Free;
end;
Result := True;
end;
Where CreateHTTP looks like
function TForm1.CreateHTTP(aContentType: String): TIdHTTP;
begin
Result := TIdHTTP.Create(nil);
Result.ConnectTimeout:=60000;
Result.ReadTimeout:=90000;
Result.ProtocolVersion:=pv1_1;
Result.HTTPOptions := [hoForceEncodeParams];
Result.HandleRedirects:=True;
Result.IOHandler := SSLHandler;
SSLHandler.ReadTimeout := 30000;
Result.Request.Accept:='*/*';
Result.Request.AcceptLanguage:='en-US';
Result.Request.ContentType:=aContentType;
Result.Request.CharSet:='utf-8';
Result.Request.UserAgent := 'Mozilla/5.0';
end;
All these timeouts exist just because I was testing why I get that error. Then I realized that the problem is when the stream to be sent is larger than 32KB.
I can't really say that there is something wrong with the code at all, because in the same way I send data to several other services like Amazon and Walmart for example where I send sometimes megabytes and I don't receive any errors.
The server is IIS but I don't know what version, the support doesn't seem to believe me that I am doing everything OK.
What I notice is that the SSL handler has some default buffer sizes - SendBufferSize and RecvBufferSize which default to 32KB. Well I tried setting that to 1MB but still I get the same error.
If I send something which is less than 32KB then everything is OK. The error is returned immediately after line with POST is executed - there is no delay, just immediate error. Otherwise sending small streams results in having a delay of a second or two before it gets processed and then the debugger goes to the next line. I started believing it is a setting of the IIS and there is really such a setting, but the guys there say everything on their side is ok and they have 4MB of limit for the requests.
The service provider is IRPCommerce but unfortunately I can't give links for testing because of IP filtering which takes place at the moment there.
I spent several days discussing this with them, searching the web for problems any limitations etc.
So is there something I am missing here, any limitations in Indy which may cause this problem, I doubt but just to be sure I am asking? Anything else I can do to make it clearer where the problem might be?
EDIT:
Here is excerpt of the aDataList:
Stock_ExternalStockID|Brands_Active|Brands_Brand|Models_Active|Models_Model|Models_Description|Models_AdditionalInformation1|Models_AdditionalInformation2|Stock_DisplayOrder|Stock_Option|Stock_Price|Stock_RRP|Stock_SupplierCost|Categories_Active|Categories_Name|Stock_PostageWeight|Stock_PartCode|Stock_ISBNNumber|Stock_UPCAPartCode|Stock_EAN13PartCode|Models_ImageURLs|OptionSelector|OptionSelectorAttributes|OptionSelectorCount|Stock_OutOfStockStatus
17664-00001|TRUE|Polypads|TRUE|Polypads Plus One Outsider Pet Bed|<ul><li>The perfect pet bed for any animal around the house or for covering car seats or boots for travelling. </li><li>Convenient to use. </li><li>6cm Plus One thickness. </li><li>Fully machine washable and quick drying. </li><li>As there is such an extensive range of colours available for the Polypad collection many colour combinations will have to be ordered in specifically; this service could take up to two weeks. </li><li>If you do not have any specific colours in mind please select, Colour Not Important, from the drop down menu.</li></ul>| | |10|Royal Blue-Navy|43.95|48.99|21.69|TRUE|Dog Beds|1000|160||||https://saddlery.biz/media/catalog/product/o/u/outp1.jpg|1|21,44|2|10
17664-00002|TRUE|Polypads|TRUE|Polypads Plus One Outsider Pet Bed|<ul><li>The perfect pet bed for any animal around the house or for covering car seats or boots for travelling. </li><li>Convenient to use. </li><li>6cm Plus One thickness. </li><li>Fully machine washable and quick drying. </li><li>As there is such an extensive range of colours available for the Polypad collection many colour combinations will have to be ordered in specifically; this service could take up to two weeks. </li><li>If you do not have any specific colours in mind please select, Colour Not Important, from the drop down menu.</li></ul>| | |20|Soft Blue-Royal Blue|43.95|48.99|21.69|TRUE|Dog Beds|1000|160||||https://saddlery.biz/media/catalog/product/o/u/outp1.jpg|1|21,44|2|10
17664-00003|TRUE|Polypads|TRUE|Polypads Plus One Outsider Pet Bed|<ul><li>The perfect pet bed for any animal around the house or for covering car seats or boots for travelling. </li><li>Convenient to use. </li><li>6cm Plus One thickness. </li><li>Fully machine washable and quick drying. </li><li>As there is such an extensive range of colours available for the Polypad collection many colour combinations will have to be ordered in specifically; this service could take up to two weeks. </li><li>If you do not have any specific colours in mind please select, Colour Not Important, from the drop down menu.</li></ul>| | |30|Black-Purple|43.95|48.99|21.69|TRUE|Dog Beds|1000|160||||https://saddlery.biz/media/catalog/product/o/u/outp1.jpg|1|21,44|2|10
Here I have 165 rows. If I send about 40 of them they go, just because 40 are just about 32KB. I have confirmed that data is not the problem, because I have tried sending one by one each of the lines.
I tried multipart/form-data with no luck. Actually they haven't told me what to use, no matter how much times I have asked about that, so I used the same thing I am using with Walmart.
I think the server is IIS 8.5.
It seems that the "solution" is to change the Content-type to text/xml. None of the other mentioned content-types work with streams larger than 32KB. At the same time I got a confirmation from the developers of the site that the content-type is not considered at all on the server side.
So I am really confused what is going on here and why only 'text/xml' works fine.

TMemIniFile.Create hanging when called in ServiceStart

In a service running under system account the code below hangs in the TMemIniFile.Create without errors.
If we replace it with TIniFile, it works fine.
It's a Delphi Tokyo 10.2.3 Win32 app running under Windows Server 2012R2. There's no concurrent access to the INI file.
This is the first time (first client) we see this, it has been running fine on many machines.
I have no idea what to look for further. Any ideas?
It 'works' now because we switched to TIniFile, but I'd like to find the cause. From other posts I read here, TINIfile seems to be more finicky than TMemINIfile, my situation is the reverse.
There are no special characters in the INI file and it is created with an ASCII editor.
// This is set in the ServiceCreate:
FIniFileNaam := ChangeFileExt(ParamStr(0),'.INI');
// This is called from the ServiceStart:
procedure TTimetellServiceBase.LeesINI;
var lIniFile : TMemIniFile;
begin
LogMessage(FIniFileNaam, EVENTLOG_INFORMATION_TYPE, cCatInfo, cReadINI); // Logs to event log, we see this
FStartDir := ExtractFilePath(ParamStr(0));
if assigned(FLaunchThreadLijst) then FreeAndNil(FLaunchThreadLijst);
FLaunchThreadLijst := TStringList.Create;
try
if FileExists(FIniFileNaam) then
begin
// Lees waarden uit INI file
lIniFile := TMemIniFile.Create(FIniFileNaam); // This is the call that hangs. The service is unresponsive now.
try
FLaunchThreadLijst.CommaText := lIniFile.ReadString(INISECTIE_SERVICETASKS,'RunIniFiles','');
FMaxTaskDuration := lIniFile.ReadInteger(INISECTIE_SERVICETASKS,'MaxTaskDuration',FMaxTaskDuration);
finally
FreeAndNil(lIniFile);
end;
end;
finally
if (FLaunchThreadLijst.Count = 0) and FileExists(FStartDir + FExeName) then
FLaunchThreadLijst.Add(SDEFAULTTHREADNAME);
LogMessage(Format('FLaunchThreadLijst.CommaText: %s (%d items)',[FLaunchThreadLijst.CommaText,FLaunchThreadLijst.Count]), EVENTLOG_INFORMATION_TYPE, cCatInfo, cLaunchList);
end;
end;
FWIW, INI file contents:
[TASKMANAGER]
RunIniFiles=TTTasks.ini
MaxTaskDuration=2
RestartIniFiles=
KillIniFiles=

Handling BabyFTP MKD 250 response with Indy

I'm using BabyFTP as an embedded FTP server for a Delphi 10.1 Berlin application. Even though I'm still not sure it's the best lightweight FTP server I can use with my requirements, it seems to be working well enough... except for one detail:
On a successful MKD command, BabyFTP responds with a 250, instead of the expected (by RFC and Indy) 257. Because of this the TIdFTP raises an EIdReplyRFCError exception which breaks the flow of what I'm trying to do (save a blob field to a file):
s := TBytesStream.Create;
Try
Field.SaveToStream(s);
MakeDir(TPath.GetDirectoryName(url));
Put(s, url);
Finally
s.Free;
End;
Of course, I know I could wrap the MakeDir line in a Try Except block and ignore the specific exception type. But this seems a bit risky as from the raised exception I can't be sure I got a 250 or some other real error.
I've tried looking if Indy's response codes are somehow configurable, but it seems they are hard coded in the specific methods. Subclassing TIdFTP is not feasible as the methods are not virtual. I could customize the IdFTP.pas unit, but I don't want to do that as I'm working in a team and I prefer not having to distribute patches to standard Delphi units.
I could use another FTP server... but I suppose most of them have some not perfectly standard feature like this one.
Does anyone know of other workarounds? Remy?
Ondrej's answer explains how to address the issue of handling a 250 reply in TIdFTP.MakeDir().
Note that TIdFTP.MakeDir() is just a wrapper for TIdTCPConnection.SendCmd():
procedure TIdFTP.MakeDir(const ADirName: string);
begin
SendCmd('MKD ' + ADirName, 257); {do not localize}
end;
SendCmd() is public, so an alternative solution would be to call SendCmd() directly and tell it that 250 is an acceptable reply code:
s := TBytesStream.Create;
Try
Field.SaveToStream(s);
//MakeDir(TPath.GetDirectoryName(url));
SendCmd('MKD ' + TPath.GetDirectoryName(url), [250, 257]);
Put(s, url);
Finally
s.Free;
End;
Or, to accept any 2xx reply code, you can do this:
s := TBytesStream.Create;
Try
Field.SaveToStream(s);
//MakeDir(TPath.GetDirectoryName(url));
if (SendCmd('MKD ' + TPath.GetDirectoryName(url)) div 100) <> 2 then
RaiseExceptionForLastCmdResult;
Put(s, url);
Finally
s.Free;
End;
You could handle just the specific case of EIdReplyRFCError when its ErrorCode property equals 250, re-raising in any other case.
s := TBytesStream.Create;
Try
Field.SaveToStream(s);
Try
MakeDir(TPath.GetDirectoryName(url));
Except
on E: EIdReplyRFCError do
if E.ErrorCode <> 250 then raise;
end
Put(s, url);
Finally
s.Free;
End;

Delphi7, Save User's Changes or other User's Information / Notes

In my program, the user completes a form and then presses Submit. Then, a textfile or a random extension file is created, in which all the user's information is written. So, whenever the user runs the application form, it will check if the file, which has all the information, exists, then it copies the information and pastes it to the form. However, it is not working for some reason (no syntax errors):
procedure TForm1.FormCreate(Sender: TObject);
var
filedest: string;
f: TextFile;
info: array[1..12] of string;
begin
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
if FileExists(filedest) then
begin
AssignFile(f,filedest);
Reset(f);
ReadLn(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
Edit1.Text := info[1];
Edit2.Text := info[2];
ComboBox1.Text := info[3];
ComboBox5.Text := info[4];
ComboBox8.Text := info[4];
ComboBox6.Text := info[5];
ComboBox7.Text := info[6];
Edit3.Text := info[7];
Edit4.Text := info[8];
Edit5.Text := info[11];
Edit6.Text := info[12];
ComboBox9.Text := info[9];
ComboBox10.Text := info[10];
CloseFile(f);
end
else
begin
ShowMessage('File not found');
end;
end;
The file exists, but it shows the message File not found. I don't understand.
I took the liberty of formatting the code for you. Do you see the difference (before, after)? Also, if I were you, I would name the controls better. Instead of Edit1, Edit2, Edit3 etc. you could use eFirstName, eLastName, eEmailAddr, etc. Otherwise it will become a PITA to maintain the code, and you will be likely to confuse e.g. ComboBox7 with ComboBox4.
One concrete problem with your code is this line:
readln(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
You forgot to specify the file f!
Also, before I formatted your code, the final end of the procedure was missing. Maybe your blocks are incorrect in your actual code, so that ShowMessage will be displayed even if the file exists? (Yet another reason to format your code properly...)
If I encountered this problem and wanted to do some quick debugging, I'd insert
ShowMessage(BoolToStr(FileExists(filedest), true));
Exit;
just after the line
filedest := ...
just to see what the returned value of FileExists(filedest) is. (Of course, you could also set a breakpoint and use the debugger.)
If you get false, you probably wonder what in the world filedest actually contains: Well, replace the 'debugging code' above with this one:
ShowMessage(filedest);
Exit;
Then use Windows Explorer (or better yet: the command prompt) to see if the file really is there or not.
I'd like to mention an another possibility to output a debug message (assuming we do not know how to operate real debugger yet):
{ ... }
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
AllocConsole; // create console window (uses Windows module) - required(!)
WriteLn('"' + filedest + '"'); // and output the value to verify
if FileExists(filedest) then
{ ... }

Word automation does only work for administrator, or with a delay after creating word.application

We have a program made in Borland Delphi that uses Word automation to create documents.
On an installation (terminal server) we are only able to get the Word automation to work when running as local administrator.
When runnnig as anoter user we get an error message "Opdracht mislukt -2146824090" (its dutch version of Office), wich I guess is translated to "Operation failed" or "Command failed".
The user has read/write access to the folder where the program try to put the new document.
Office 2010
64bits Windows server 2008 R2 standard
The applicaion is 32bit windows application.
If I add a delay (500ms) after the word.application is created, everything works as normall.
WordApp := CreateOleObject('Word.Application');
sleep(500);
Doc := WordApp.documents.Open(sFile,EmptyParam,true);
Anybody knows why the CreateOleObject command now returns before the Word application can be used?
If you want to track out that, you could use a tool like ProcessMonitor to trace the Word automation executions till the point which you can use the app.
Seems some kind of rights check is taking place - but half a second seems too much time just for this.
You could try to open the Document a few times, or is Word totally borked after it gave the error?
WordApp := CreateOleObject('Word.Application');
while True do
begin
try
Doc := WordApp.documents.Open(sFile,EmptyParam,true);
Break;
except
on E: EOleSysError do
begin
// raise error if it's not the expected "Command failed" error
if E.ErrorCode <> -2146824090 then
raise;
end;
end;
end;
Edit:
Please see my answer here which provides a better solution and an explanation why this happens.
The administrator account working wihtout delay, seems not to have anything with rights to do, but that Word happens to start much faster with this account than the normal domain user accounts.
I can live with the delay workaround, but if anyone knows a better way please let me know.
I realize this thread is quite old, but I solved this issue by making sure to close the document before quitting (oleDocument.Close). By doing so there is no need for any type of delays, etc. See Delphi code snippet below.
Example:
oleWord := Unassigned;
oleDocument := Unassigned;
Screen.Cursor := crHourGlass;
try
oleWord := CreateOleObject('Word.Application');
oleWord.Visible := False;
oleWord.DisplayAlerts := False;
oleDocument := oleWord.Documents.Open(Worklist.Filename);
oleDocument.SaveAs(Worklist.Filename, wdFormatDOSTextLineBreaks);
oleDocument.Close;
oleWord.Quit(False);
finally
oleDocument := Unassigned;
oleWord := Unassigned;
Screen.Cursor := crDefault;
end;

Resources