Seeking very simple TIdCommandHandler conditional response example - delphi

Delphi XE2, so Indy 10.
My client sends a command which is processed by a TIdCommandHandler of my TIdCmdTCPServer.
I want to be able to perform some logic and return either a success or fail response and check for that back at the client.
Can someone please point me at a few lines of code as an example? Thanks in advance.

Well, here's the simplest demo.
Add an IdCmdTCPServer to your form, and add one command, set its name in the Command property, I originally thought I should handle Response in OnCommand event like this:
procedure TForm1.IdCmdTCPServer1CommandHandlers0Command(ASender: TIdCommand);
begin
//ASender.Response.Add('Hello'); // wrong way
ASender.Reply.SetReply(0,'HELLO');
end;
Update Remy pointed out I shouldn't be using Response.
So you want to return success or failure, it's traditional to use a numeric result followed by the string value. Each string in the response strings list has an implied end-of-line transmitted back to the client:
procedure TForm1.IdCmdTCPServer1CommandHandlers0Command(ASender: TIdCommand);
begin
if DoSomething then
ASender.Reply.SetReply(0,'OK')
else
ASender.Reply.SetReply(999,'ERROR');
end;
The idea with the IdCommandHandler and a CmdTCPServer/Client is that you follow the "RFC" style of protocols, which are ANSI/ASCII text-based. An RFC-style internet protocol's reply is typically encoded over the wire as text with both a numeric and string value. ASender.Response could be used if you needed to take the content of a string list and return that as the response.
As for the client, a question here suggests that TIdCmdTcpClient is not the most natural way to build the client for this server. From their names, you'd have thought they were made for each other, but it's not exactly. For most simple TIdCMDTCPServers that you could build, you would find that a plain-vanilla TIdTCPClient is the simplest building block to start your client with.

Related

Imap4 client command LSUB

I have a problem with function TIdIMAP4.ListSubscribedMailBoxes(AMailBoxList: TStrings): Boolean; with this implementation :
function TIdIMAP4.ListSubscribedMailBoxes(AMailBoxList: TStrings): Boolean;
begin
{CC2: This is one of the few cases where the server can return only "OK completed"
meaning that the user has no subscribed mailboxes.}
Result := False;
CheckConnectionState([csAuthenticated, csSelected]);
SendCmd(NewCmdCounter, IMAP4Commands[cmdLSub] + ' "" *',
[IMAP4Commands[cmdList], IMAP4Commands[cmdLSub]]); {Do not Localize}
if LastCmdResult.Code = IMAP_OK then begin
// ds - fixed bug # 506026
ParseLSubResult(AMailBoxList, LastCmdResult.Text);
Result := True;
end;
end;
When I debug I see that the LastCmdResult.Text stringlist is empty, but the LastCmdResult.FormattedReply stringlist has all folders on my email server (Inbox, Sent, Trash, ...). When I tried to use LastCmdResult.FormattedReply count or text, it had immediately lost its data and gave LastCmdResult.FormattedReply.Count=0 and LastCmdResult.FormattedReply.Text=''. So I'd like to know if there is a way to enter the data inside LastCmdResult.FormattedReply and get my email server folders or there is another way to solve my problem ?
I have a problem with function TIdIMAP4.ListSubscribedMailBoxes(AMailBoxList: TStrings): Boolean; with this implementation :
Works fine for me when I try it using the latest SVN version of Indy.
When I debug I see that the LastCmdResult.Text stringlist is empty, but the LastCmdResult.FormattedReply stringlist has all folders on my email server (Inbox, Sent, Trash, ...).
When I run it, the opposite happens. LastCmdResult.Text contains the expected text, and LastCmdResult.FFormattedReply is empty (notice I mention the FFormattedReply data member directly, see below).
When I tried to use LastCmdResult.FormattedReply count or text, it had immediately lost its data and gave LastCmdResult.FormattedReply.Count=0 and LastCmdResult.FormattedReply.Text=''.
That is by design. The FormattedReply property is intended to be used by a client to parse a server reply so it can populate TIdReply's property values, and to be used by a server to generate a new reply using TIdReply's property values. So, you cannot read from the FormattedReply property on the client side.
So I'd like to know if there is a way to enter the data inside LastCmdResult.FormattedReply and get my email server folders or there is another way to solve my problem ?
The whole purpose of ListSubscribedMailBoxes() is to return the folder names in the AMailBoxList parameter. If that is not working for you, then either
you are using a older/buggy version of Indy.
your server is sending the data in a format that TIdIMAP4 is not able to parse.
Without knowing which version of Indy you are actually using, or what the server's reply data actually looks like, there is no way to diagnose your issue one way or the other.

Testing Somesite CRLF Bugs Hole Using Indy IdMappedPortTCP

I want to create a small tool like CRLF Injection or HTTP header respons splitting. I was successful created thousands NetData pattern (data payload) lists. The NetData pattern like this example:
GET http://somebug.com/ HTTP/1.1[CRLF]Host : somehost.com[CRLF]GET somesite.com HTTP/1.1[CRLF][CRLF]
GET http://somebug.com/ HTTP/1.1[CRLF]Host : somehost.com[CRLF][CRLF]GET somesitesite.com HTTP/1.1[CRLF][CRLF]
HEAD http://somebug.com/ HTTP/1.1[CRLF]Host : somehost.com[CRLF]CONNECT somesitesite.com HTTP/1.0[CRLF][CRLF][CRLF][CRLF]
...
If just one data pattern/data payload, I can write example code like:
procedure T_CRLFTest.IdMappedPortTCP1Execute(AContext: TIdContext);
begin
if(Pos('CONNECT',TIdMappedPortContext(AContext).NetData)<>0) then
TIdMappedPortContext(AContext).NetData := 'GET http://somebug.com/ HTTP/1.1'#13#10'Host : somehost.com'#13#10+TIdMappedPortContext(AContext).NetData+#13#10#13#10
end;
The problem is, how to test all data pattern let say over 20,000 lists using IdMappedPortTCP with multi threaded technique?
I'm using Delphi 2007 and Indy 10.
NetData contains whatever raw data was available on the socket at the moment the OnExecute event was fired. There is no guarantee of the content of NetData on any given triggering of the event. So every time the event is triggered, you need to store that data to your own per-connection buffer somewhere, then you can parse that buffer looking for complete lines and tweaking them as needed, then update the NetData with new data as needed. Whatever data is in NetData when the event handler exits is the data that gets passed along to the target server.
BTW, HEAD http://somebug.com/ HTTP/1.1[CRLF]Host : somehost.com[CRLF]CONNECT somesitesite.com HTTP/1.0[CRLF][CRLF][CRLF][CRLF] is two HTTP commands overlapping each other. That should never happen in a real scenario. If it is, then the client that is sending those commands is faulty.

TIdMessageParts.CountParts Returns 0

I'm trying to pull multipart emails in MIME format from an IMAP server using Indy 10.5.5 in Delphi 2010. These are the lines of code that I'm having trouble with are below, where I instatiate the curMessage object, retrieve a message into it, and then call CountParts:
var
curMessage: TIdMessage;
IMAP4: TIdIMAP4;
msgIndex: Integer;
begin
...
curMessage := TIdMessage.Create(nil);
IMAP4.Retrieve(msgIndex, curMessage);
curMessage.MessageParts.CountParts;
//code that checks counts
//and
end;
I then have some code that checks the various count properties of curMessage.MessageParts (i.e. TextPartCount). However, the CountPart procedure isn't returning anything, because the Count property referenced in the procedure block is 0, even though I've verified that the message is retrieved and placed into the curMessage.
One thing I've noticed, and haven't gotten to the bottom of yet, is that IsMsgSinglePartMime is coming back as true, even though all the messages on the server have Content-Type: multipart/mixed;.
Any help would be really appreciated.
What am I missing here? I can provide more code if needed,
Without seeing the actual email data, it is difficult to say for sure exactly why the data is not where you expect it to be. But if the TIdMessage.IsMsgSinglePartMime is getting set to True then that means that either:
TIdMessage.Encoding is meMIME but TIdMessage.MIMEBoundary.Count is 0, meaning there was no MIME boundary value detected in the top-level Content-Type header. If the Content-Type is a 'multipart/...' type, a boundary is required. If it is present, it is likely malformed in a way that prevented Indy from parsing it.
TIdMessage.Encoding is mePlainText but TIdMessage.ContentTransferEncoding is either 'base64' or 'quoted-printable'.
In either case, if there is body content present then it would end up in the TIdMessage.Body property if it is textual data, otherwise it would end up in the TIdMessage.MessageParts as an attachment instead. Since TIdMessage.MessageParts.Count is 0 in your case, the data is either in TIdMessage.Body, or is got discarded.
You may want to consider upgrading to a newer Indy version. The version shipped with D2010 is pretty old, and there have been fixes/changes made to TIdIMAP4 and TIdMessage (and its internal parsers) in recent years.

Overbyte ICS HTTPS POST

I'm wanting to create a CloudFlare client in the Firemonkey framework. For those who don't know, CloudFlare serves as a CDN of sorts for anyone with a website. They have an API available, and as with many web API's, they are using JSON with a token-based system. It requires both the account email address and the account token to access the API. It runs on HTTPS, and as you can imagine, attempting to access the API via HTTP/non-SSL simply produces null results.
The application i wish to create would serve as an all-in-one management tool, intending to eliminate the need for me to use a web browser to manage my CloudFlare settings. I'm having the most basic of issues; SSL POST. See, i can submit an API request via a web browser and get a list of results (e.g. https://www.cloudflare.com/api_json.html?a=stats&z=DOMAIN&u=EMAIL&tkn=TOKEN - Personal details removed for obvious reasons), but i'm unsure how i would go about getting these same results (or any results from the API for that matter) in Firemonkey.
I've got Overbyte ICS with SSL installed, as well as the basic bundled Indy components, but i'm struggling to get started with this. I need to post a list of parameters to https://www.cloudflare.com/api_json.html via HTTPS/SSL, but i've very little idea on where to start. I've seen a few various example around SO, mostly using ICS, but i've been unable to find any specific to posting with multiple parameters, how i should format it, etc.
One example i tried was using ICS TSSLHttpCli, writing my parameters as a single string (i.e. a=stats&z=DOMAIN&u=EMAIL&tkn=TOKEN), writing that to the SendStream of TSSLHttpCli, seeking to 0,0, setting the URL (i.e. https://www.cloudflare.com/api_json.html?), and then calling the Post method. However, this gives me Connection aborted on request. This is the code i've tried (though i've replaced personal details with generic values);
var
Data : AnsiString;
RcvStrm, SndStrm : TMemoryStream;
begin
SndStrm := TMemoryStream.Create;
RcvStrm := TMemoryStream.Create;
Data := '?a=stats&z=MYDOMAIN&u=MYEMAIL&tkn=MYTOKEN';
SslHttpCli.SendStream := SndStrm;
SslHttpCli.SendStream.Write(Data[1],Length(Data));
SslHttpCli.SendStream.Seek(0,0);
Memo1.Lines.LoadFromStream(SndStrm);
ShowMessage('Waiting!');
SslHttpCli.RcvdStream := RcvStrm;
SslHttpCli.URL := 'https://www.cloudflare.com/api_json.html';
SslHttpCli.Post;
Memo1.Lines.Clear;
Memo1.Lines.LoadFromStream(RcvStrm);
Memo1.Lines.Add('.....');
RcvStrm.Free;
SndStrm.Free;
ShowMessage('Complete!');
end;
The ShowMessage procedures are simply there to provide a visual break so i can see what data is in the stream at each time. When Memo1.Lines.LoadFromStream(SndStrm); is called, i get a single question mark the contents of the Data in the memo as expected.
When i call Memo1.Lines.LoadFromStream(RcvStrm);, i expect it to add the return result from the API, and then the 5 dots underneath it. However, this does not happen, and it's apparent that the message i'm receiving is related to the issue. I'm assuming i've not set up the data correctly, but i'm simply unsure exactly how i should format it prior to attempting to post it. I've even commented out everything below Memo1.Lines.LoadFromStream(RcvStrm); to the end to see whether the Clear procedure is called on the memo, but the contents of the memo remain the same as they were when i called LoadFromStream(SndStrm). The final ShowMessage is also not called.
I initially tried using String instead of AnsiString, but this simply output the first character of Data rather than the whole string.
There could be numerous reasons why it's not working (all details for API access are correct, so it's an issue with the code), but i need someone with more experience and knowledge to point me in the right direction.
My network coding knowledge is limited, and i've only dealt with basic SQL and FTP in Delphi so far. I've still got to work with the parsed JSON once i do get past this step, but for now, can anyone assist me in this endeavor so i can get started?
I noticed you seemed to solve this with a GET request, but I noticed two immediate problems with your POST request:
as Runner Suggested, drop the '?' in your data. The '?' is only used when appending parameters to the URL in a GET request.
You never set the content type of the HTTP Request (should be application/x-www-form-urlencoded). You can do this with the following code:
SSLHttpCli.ContentTypePost := 'application/x-www-form-urlencoded';
Just a helpful thought. I checked https://www.cloudflare.com/docs/client-api.html and they mention that POST requests are accepted. It's possible the server rejects requests that have any other content type.
Just some food for thought if you ever need to contact another API via POST requests and want to use the Overbyte Components.
Hope the info is useful!
Try this;
SndStrm := TMemoryStream.Create;
RcvStrm := TMemoryStream.Create;
Data := 'a=stats&z=MYDOMAIN&u=MYEMAIL&tkn=MYTOKEN';
SndStrm.Write(Data[1], Length(Data));
SndStrm.Seek(0, 0);
SslHttpCli.SendStream := SndStrm;

How can I get the TNMHTTP Get method to respond on a redirect

I'm using TNMHTTP in Delphi to retrieve the code from a webpage. The code is relatively simple:
NMHTTP1 := TNMHTTP.Create(Self);
NMHTTP1.InputFileMode := FALSE;
NMHTTP1.OutputFileMode := FALSE;
NMHTTP1.ReportLevel := Status_Basic;
NMHTTP1.TimeOut := 3000;
URL := 'http://www....';
NMHTTP1.Get(URL);
S := NMHTTP1.Body;
I am catching exceptions in a try/except block, but that is not the problem.
The problem is that on executing the NMHTTP1.Get method when the URL is a redirect, that method does not return and the program hangs. This is despite the fact that I've put a timeout of 3000 seconds in.
So I see three possible ways of solving this (in order of easiest to hardest for me to modify my program):
Do whatever is necessary to get the NMHTTP1.Get method to respond.
Do some sort of check in advance of the NMHTTP1.Get statement to see if the URL is a redirect and get the URL it is redirecting to.
Use another method to get a webpage using Delphi. When I wrote this, I used Delphi 4 and did not have Indy. I now have Delphi 2009, so I would be willing to use something that works in it (maybe INDY) if a simple #1 or #2 answer is not available.
I would love to get an answer from someone that will work for me. Thanks in advance.
I would avoid the NetMasters controls, period.
Instead, you can use Indy's IdHTTP component, which has a RedirectMaximum property (defaults to 15) and an OnRedirect event in case you want to track the details.
I can advice you to switch to Indy. They are great for a lot of network protocols (with the exception of the IRC protocol). There are nice examples included so you can examine the working examples yourself.
Also have a look at http://www.indyproject.org/index.en.aspx for more information on indy.

Resources