How to activate/force Basic Auth on THTTPClient - delphi

I can't find an option to activate Basic Auth on THTTPClient.
My main "issue" is that the first request response with a HTTP/1.1 401 Unauthorized and it triggers an auth event. I want to do it on the first "try".
Here is my code:
uses
System.Net.HTTPClient, System.Net.URLClient, System.NetEncoding;
procedure DoRequest(const url, username, password, filename: string);
var
http: THTTPClient;
ss: TStringStream;
base64: TBase64Encoding;
begin
http := THTTPClient.Create;
ss := TStringStream.Create('', TEncoding.UTF8);
base64 := TBase64Encoding.Create(0);
try
// option #1 - build in credential storage
http.CredentialsStorage.AddCredential(
TCredentialsStorage.TCredential.Create(
TAuthTargetType.Server, '', url, username, password
));
// option #2 - custom "Authorization"
http.CustomHeaders['Authorization'] := 'Basic ' + base64.Encode(username + ':' + password);
http.ProxySettings.Create('127.0.0.1', 8888); // for fiddler
http.Get(url, ss);
ss.SaveToFile(filename);
finally
base64.Free;
ss.Free;
http.Free;
end;
end;
Option #2 includes the Authorization in the first request. Option #1 does not.
How can I do that?
Update - PreemptiveAuthentication does not exist in Seattle

In Delphi 11 and later, using THTTPClient.CredentialsStorage should work, you just need to set THTTPClient.PreemptiveAuthentication to true:
Property controls preemptive authentication. When set to True, then basic authentication will be provided before the server gives an unauthorized response.
However, PreemptiveAuthentication does not exist in Delphi 10.x, including Seattle, so you will have to stick with your CustomHeaders solution until you can upgrade to a modern Delphi version.

Related

IdHTTP and IdCookieManager miss one cookie

I use TIdHTTP for web-service request, after autentification I must keep session information but IdCookieManager seems lost always one cookie...
This is my very simple snippet
procedure TForm1.Button5Click(Sender: TObject);
var
i : Integer;
Cookie : TIdCookies;
begin
Memo1.Lines.Clear;
try
IdHTTP1.AllowCookies := true;
IdHTTP1.CookieManager := IdCookieManager1;
IdHTTP1.Get(Edit2.Text);
if IdCookieManager1.CookieCollection.Count = 0
then Memo1.Lines.Add('Empty');
Cookie := IdCookieManager1.CookieCollection;
for i := 0 to Cookie.Count -1 do
Memo1.Lines.Add(Cookie.Cookies[i].Domain + ': ' + Cookie.Cookies[i].CookieName +
'=' + Cookie.Cookies[i].Value);
except
on E : Exception do
Memo1.Lines.Add(E.Message);
end;
end;
For example if I do IdHTTP1.Get('www.google.com'); I get two cookie (1P_JAR,NID), but if I do on a web browser it give three (1P_JAR,NID and CONSENT).
And this is for all URL, It seems like it "loses" always one cookie
You're trying to compare a single GET request response to a browser response. It's not the same thing.
If you use e.g. Postman you will get the very same result (2 cookies for www.google.com).
I think you should modify the service server side if you are in control of it or consume it in a different way if you are not.

URL Not Returning Data Delphi Indy TIdHttp

I am trying to access a URL in Delphi using a TIdHTTP Indy Tool.
I have done the following:
Set Accept Cookies = True
Set Handle Redirect = True
Added a TIdCookieManager
http://sms.saicomvoice.co.za:8900/saicom/index.php?action=login&username=SOME_USERNAME&password=SOME_PASSWORD&login=login
The Post request works and it returns the HTML. The problem is it doesn't return the correct HTML (See Image Below).
If I take that URL ( Filling in the username and password ) and paste it into my browser exactly The Same as my Delphi Application would then logs into the correct website. But as soon as I do it with my Delphi App it returns the HTML for the login page.
The request is supposed to be executed timeously in a TTimer in Delphi.
Can anyone lead me unto the right path or point me in a direction as to how I can solve this problem ?
Some Additional Information
WriteStatus is a Procedure That writes output to a TListBox
BtnEndPoll Stops the timer
Procedure TfrmMain.TmrPollTimer(Sender: TObject);
Var
ResultHTML: String;
DataToSend: TStringList;
Begin
Inc(Cycle, 1);
LstStatus.Items.Add('');
LstStatus.Items.Add('==================');
WriteStatus('Cycle : ' + IntToStr(Cycle));
LstStatus.Items.Add('==================');
LstStatus.Items.Add('');
DataToSend := TStringList.Create;
Try
WriteStatus('Setting Request Content Type');
HttpRequest.Request.ContentType := 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8';
WriteStatus('Setting Request User Agent');
HttpRequest.Request.UserAgent := 'Mozilla/5.0 (Windows NT 5.1; rv:2.0b8) Gecko/20100101 Firefox/4.0b8';
WriteStatus('Posting Request');
ResultHTML := HttpRequest.Post(FPostToURL, DataToSend);
WriteStatus('Writing Result');
FLastResponse := ResultHTML;
WriteStatus('Cycle : ' + IntToStr(Cycle) + ' -- FINISHED');
LstStatus.Items.Add('');
Except
On E: Exception Do
Begin
MakeNextEntryError := True;
WriteStatus('An Error Occured: ' + E.Message);
If ChkExceptionStop.Checked Then
Begin
BtnEndPoll.Click;
WriteStatus('Stopping Poll Un Expectedly!');
End;
End;
End;
End;
* Image Example *
HttpRequest.Request.ContentType := 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8';
That is not a valid ContentType value. That kind of value belongs in the Request.Accept property instead. It tells the server which ContentTypes the client will accept in the response.
ResultHTML := HttpRequest.Post(FPostToURL, DataToSend);
You are posting a blank TStringList. Putting a URL into a browser's address bar sends a GET request, not a POST request, so you should be using TIdHTTP.Get() instead:
ResultHTML := HttpRequest.Get('http://sms.saicomvoice.co.za:8900/saicom/index.php?action=login&username=SOME_USERNAME&password=SOME_PASSWORD&login=login');
You would use TIdHTTP.Post() if you wanted to simulate the HTML webform being submitted to the server (since it specifies method=post), eg:
DataToSend.Add('username=SOME_USERNAME');
DataToSend.Add('password=SOME_PASSWORD');
DataToSend.Add('login=Login');
ResultHTML := HttpRequest.Post('http://sms.saicomvoice.co.za:8900/saicom/index.php?action=login', DataToSend);

Reading image data from client using Indy command handler

I have a small client-server application project using Indy 9. My server application is using 'command handler' to handle client's command.
In this case my client application using writeLn procedure to send command and data, which is text based. For example:
IdTcpClient1.WriteLn('cmd1'+'#'+'hello server');
note: 'cmd1' is a command, '#' is command delimiter, and 'hello server' is the data
In order to handle this command (cmd1), my server application has a procedure as follows:
procedure TForm1.IdTcpServer1cmd1Command(Asender:TIdCommand);
var
s: string;
begin
if ( Assigned(ASender.Params) ) then
begin
s:= Asender.Param[0];
...............
...............
end;
end;
So far everything is fine. The problem is that I want to add a feature so that my server application is able to request and receive a JPEG_image from client. If client send this image using: WriteStream procedure, for example:
IdTcpClient1.WriteStream(MyImageStream);
How then the the server handle this event considering that there is no specific command to it (such as 'cmd1' in this example)?
You would simply call ReadStream() in the command handler, eg:
IdTcpClient1.WriteLn('JPEG');
IdTcpClient1.WriteStream(MyImageStream, True, True); // AWriteByteCount=True
procedure TForm1.IdTcpServer1JPEGCommand(ASender: TIdCommand);
var
Strm: TMemoryStream;
begin
...
Strm := TMemoryStream.Create;
try
ASender.Thread.Connection.ReadStream(Strm); // expects AWriteByteCount=True by default
...
finally
Strm.Free;
end;
...
end;
Alternatively:
IdTcpClient1.WriteLn('JPEG#' + IntToStr(MyImageStream.Size));
IdTcpClient1.WriteStream(MyImageStream); // AWriteByteCount=False
procedure TForm1.IdTcpServer1JPEGCommand(ASender: TIdCommand);
var
Size: Integer;
Strm: TMemoryStream;
begin
...
Size := StrToInt(ASender.Params[0]);
Strm := TMemoryStream.Create;
try
if Size > 0 then
ASender.Thread.Connection.ReadStream(Strm, Size, False); // AWriteByteCount=True not expected
...
finally
Strm.Free;
end;
...
end;
How then the the server handle this event
So what is you actually want to learn ? What is your actual question ?
If you want to know how your server would behave - then just run your program and see what happens.
If you want to know how to design your server and client, so client could upload a picture to server - then ask just that.
I think that the proper thing to do would be adding the command "picture-upload" with two parameters: an integer token and a jpeg stream.
When the server would reply to cmd1 command, if it would need, it would generate and attach a unique integer token, asking the client to upload the screenshot.
When client would receive this reply, it would parse it, and if the token would be found - would issue one more command, a specially designed "picture-upload#12345" (where 12345 replaced with actual token value) followed by the picture itself. The server would then use the token value to know which client and why did the upload.
PS. though personally I think you'd better just use standardized HTTP REST rather than making your own incompatible protocol.

Accessing Ubuntu One API results in "401 UNAUTHORIZED" or "400 BAD REQUEST"

I am using Delphi XE2 with Indy 10 to access the Ubuntu One API. So far so good. I was able to get the OAuth Token from the Cloud as described. Now I want to start using the API (like described):
function TUbuntuOneApi.DoRequest(const URL: String): String;
var
RequestParams: TStringList;
HeaderIndex: Integer;
const
OAuthAuthorisationHeader = 'OAuth realm="", oauth_version="%s", oauth_nonce="%s", oauth_timestamp="%s", oauth_consumer_key="%s", oauth_token="%s", oauth_signature_method="%s", oauth_signature="%s"';
begin
RequestParams := SignOAuthRequestParams(URL, fOAuthAppToken, fOAuthAccessToken);
{ OAuth realm="", oauth_version="1.0",
oauth_nonce="$nonce", oauth_timestamp="$timestamp",
oauth_consumer_key="$consumer_key", oauth_token="$token",
oauth_signature_method="PLAINTEXT",
oauth_signature="$consumer_secret%26$token_secret"
}
HeaderIndex := IdHTTP.Request.CustomHeaders.IndexOfName('Authorization');
if HeaderIndex >= 0 then
begin
IdHTTP.Request.CustomHeaders.Delete(HeaderIndex);
end;
// Solving: http://stackoverflow.com/questions/7323036/twitter-could-not-authenticate-with-oauth-401-error
IdHTTP.Request.CustomHeaders.FoldLines := false;
IdHTTP.Request.CustomHeaders.AddValue('Authorization', Format(OAuthAuthorisationHeader, [
TIdURI.URLDecode(RequestParams.Values['oauth_version']),
TIdURI.URLDecode(RequestParams.Values['oauth_nonce']),
TIdURI.URLDecode(RequestParams.Values['oauth_timestamp']),
TIdURI.URLDecode(RequestParams.Values['oauth_consumer_key']),
TIdURI.URLDecode(RequestParams.Values['oauth_token']),
TIdURI.URLDecode(RequestParams.Values['oauth_signature_method']),
TIdURI.URLDecode(RequestParams.Values['oauth_signature'])
]));
// Execute
Result := IdHTTP.Get(URL);
RequestParams.Free;
end;
function TUbuntuOneApi.Test: String;
begin
//Result := DoRequest('https://one.ubuntu.com/api/file_storage/v1');
Result := DoRequest('https://one.ubuntu.com/api/account/');
end;
Calling https://one.ubuntu.com/api/file_storage/v1 results in "401 UNAUTHORIZED". Calling https://one.ubuntu.com/api/account/ results in "400 BAD REQUEST". The OAuth Library is from SourceForge and works with Dropbox (Dropbox accepts the OAuth-Params if they are in the Query-String). The Tokens are correct and valid. What am I doing wrong?
BTW:
function SignOAuthRequestParams(const URL: String; OAuthAppToken, OAuthAccessToken: TOAuthToken): TStringList;
var
Consumer: TOAuthConsumer;
ARequest: TOAuthRequest;
Response: String;
HMAC: TOAuthSignatureMethod;
begin
HMAC := TOAuthSignatureMethod_PLAINTEXT.Create;
// Consumer erzeugen
Consumer := TOAuthConsumer.Create(OAuthAppToken.Key, OAuthAppToken.Secret);
// Request erzeugen
ARequest := TOAuthRequest.Create(URL);
ARequest := ARequest.FromConsumerAndToken(Consumer, OAuthAccessToken, URL);
ARequest.Sign_Request(HMAC, Consumer, OAuthAccessToken);
Result := TStringList.Create;
Result.AddStrings(ARequest.Parameters);
ARequest.Free;
Consumer.Free;
HMAC.Free;
end;
At this point, the newly created token can be used to authenticate to
other methods, on the Ubuntu SSO service, but can not yet be used with
the Ubuntu One API. We need to tell Ubuntu One to copy the newly
created token from the SSO service. This is done by issuing a GET
request to the following URL, signed with the new OAuth token.
From Ubuntu One: Authorization page.
Shame on me, I forgot to do that.

Using a cookie with Indy

I'm trying to get data from a site using the Indy components. (This is in Delphi 7 but happy to use anything that works.)
If you go into a normal browser and put in the path:
http://inventory.data.xyz.com/provide_data.aspx?ID=41100&Mixed=no?fc=true&lang=en
it makes you tick a disclaimer before redirecting you to the actual site. This creates a cookie, which if I look at it in Firefox is like this:
http://inventory.data.xyz.com
Name: ASP.NET_SessionId
Content: vm4l0w033cdng5mevz5bkzzq
Path: /
Send For: Any type of connection
Expires: At end of session
I can't get through the disclaimer part using programming but I thought if I manually sign the disclaimer, I can then enter the details of the cookie into my code and connect directly to the data page. I have tried to do this with the code below but it only returns the html for the disclaimer page which tends to imply it's not using the cookie data I've given it. What am I doing wrong?
procedure TfmMain.GetWebpageData;
var
http: TIdHTTP;
cookie: TIdCookieManager;
sResponse: String;
begin
try
http := TIdHTTP.Create(nil);
http.AllowCookies := True;
http.HandleRedirects := True;
cookie := TIdCookieManager.Create(nil);
cookie.AddCookie('ASP.NET_SessionId=vm4l0w033cdng5mevz5bkzzq', 'inventory.data.xyz.com');
http.CookieManager := cookie;
sResponse := http.Get('http://inventory.data.xyz.com/provide_data.aspx?ID=41100&Mixed=no?fc=true&lang=en');
ShowMessage(sResponse); // returns text of disclaimer
except
end;
end;
Since you have not provided a real URL, I can only speculate, but chances are that either the cookie value you are providing to TIdCookieManager is wrong or outdated by the time TIdHTTP.Get() tries to use it, or more likely TIdCookieManager.AddCookie() is rejecting the cookie outright (if the TIdCookieManager.OnNewCookie event is not triggered, then the cookie was not accepted).

Resources