EIPHTTPProtocolExceptionPeer exception using PutBlock with array of bytes all set to zero - delphi

Using Delphi XE2 Update 3 I'm having a problem uploading a block of zero bytes to Azure. When I say zero bytes, I mean an array of bytes with each element set to zero (it's not zero length).
For example:
var
ConInfo: TAzureConnectionInfo;
RespInfo: TCloudResponseInfo;
BlobService: TAzureBlobService;
Content: TBytes;
MD5: String;
Hasher: TIdHashMessageDigest5;
begin
ConInfo:=TAzureConnectionInfo.Create(self);
ConInfo.AccountName:='YOUR ACCOUNT NAME';
ConInfo.AccountKey:='YOUR ACCOUNT KEY';
ConInfo.Protocol:='http';
BlobService:=TAzureBlobService.Create(ConInfo);
RespInfo:=TCloudResponseInfo.Create;
SetLength(Content, 1048576); // 1 MByte
FillMemory(#Content[0], 1048576, 0); // << --- fill it with anything except 0 and it works
Hasher:=TIdHashMessageDigest5.Create;
MD5:=Data.Cloud.CloudAPI.EncodeBytes64(Hasher.HashBytes(Content));
Hasher.Free;
BlobService.PutBlock('CONTAINER NAME', 'BLOB NAME', MD5, Content, MD5, '', RespInfo);
If the above code is run then an exception is raised on the call to PutBlock:
Project Project1.exe raised exception class
EIPHTTPProtocolExceptionPeer with message 'HTTP/1.1 403 Server failed
to authenticate the request. Make sure the value of Authorization
header is formed correctly including the signature.'.
But if you change the FillMemory line so that it fills it with 1's (or anything but zero), then it works without error. Why?
Thanks for any help.

Unbelievably, it seems that the problem is the block ID value of ttgbNgpWctgMJ0MPORU+LA==
If you use it as a block ID then you get that error.

Related

How to get a parsable response from SendCmd in Indy?

If I use TIdImap4.SendCmd to manually send an unsupported command to Indy, I'm a little bit confused how I retrieve the full response to manually parse it.
I'm sending the following command to manually request the Gmail labels for a message since this is not yet supported by Indy:
IMAP.SendCmd(ImapCmdNum(),'UID FETCH '+uid+' (X-GM-LABELS)',['OK','BAD','NO'], false);
After calling this command, I checked my Indy Log file and it's successfully receiving an appropriate response from the server:
Sent 9/19/2015 11:10:40 AM: C5 UID FETCH 2385 (X-GM-LABELS)<EOL>
Recv 9/19/2015 11:10:40 AM: * 542 FETCH (X-GM-LABELS (testlabel) UID 2385)<EOL>C5 OK Success<EOL>
But now I can't seem to get any part of that response other than 'OK' from Indy. I've tried the following in the debugger and none of them have the raw response or anything else I could possibly manually parse:
IMAP.LastCmdResult = ('OK', $2521E60, nil, $2521EC0)
IMAP.LastCmdResult.Text = ()
IMAP.LastCmdResult.Code = 'OK'
IMAP.LastCmdResult.NumericCode = 0
IMAP.LastCmdResult.FormattedReply = ()
From the SendCmd documentation:
SendCmd is an overloaded function used to send the command specified
in AOut to the peer connection.
SendCmd uses IOHandler to write the command in AOut to the peer
connection.
AResponse indicates the response allowed for the command.
SendCmd calls GetResponse to determine if the response from the peer
connection is allowed. If the response is not allowed, an exception is
raised during processing in GetResponse.
When AResponse is contains -1, GetResponse is called with an empty
array to indicate that any response code is permitted for the command.
Otherwise, the value in AResponse is used to valid the response code.
Use LastCmdResult to access the numeric and text portions of the
response for the command.
My understanding of this is that I should be using LastCmdResult to access various "portions of the response", but none of them have the raw response or any part of the response except "OK", so how do I get something parsable from the response to SendCmd?
The text you are looking for is, in fact, in the LastCmdResult.Text property. The debugger is not showing it to you, but that is where the label data is.
As I told you 2 months ago in comments to my other answer that you linked to:
Look at the implementation of TIdIMAP4.UIDRetrieveFlags(). It calls SendCmd() followed by ParseLastCmdResult() to parse the returned flags. You will have to replicate the same logic, substituting fdGmailLabels where fdFlags is currently being used (minus the call to ParseMessageFlagString() that is parsing the flags string to a TIdMessageFlagsSet).
If you look at the implementation of TIdIMAP4.UIDRetrieveFlags() and then look at your code, you are not even calling SendCmd() correctly to begin with. You are passing the wrong value to the ATag parameter (unless ImapCmdNum() is simply calling TIdIMAP4.NewCmdCounter - TIdIMAP4 needs to generate the command counters so it can match them to the replies), and moe importantly you are passing the wrong values to the AExpectedResponses parameter.
Try this instead (I tested it and it works):
type
TIdIMAP4Access = class(TIdIMAP4);
TIdIMAPLineStructAccess = class(TIdIMAPLineStruct);
var
uid: string;
labels: string;
begin
...
uid := ...;
labels := '';
IMAP.SendCmd('UID FETCH ' + uid + ' (X-GM-LABELS)', ['FETCH','UID']);
if IMAP.LastCmdResult.Code = IMAP_OK then
begin
if IMAP.LastCmdResult.Text.Count > 0 then
begin
// The requested data is in IMAP.LastCmdResult.Text[0].
// You can either parse it manually, or use the below
// code to let TIdIMAP4 parse it for you...
if TIdIMAP4Access(IMAP).ParseLastCmdResult(IMAP.LastCmdResult.Text[0], 'FETCH', ['X-GM-LABELS']) then begin
labels := TIdIMAPLineStructAccess(TIdIMAP4Access(IMAP).FLineStruct).IMAPValue;
end;
end;
end;
...
end;

Serving files with IdHTTPServer when the files are being written

I'm working with a TIdHTTPServer to serve files to clients, using the ResponseInfo->ServeFile function. This works fine for files that are "static": not being written by some other process. As far as I can see from the code, the ServeFile function internally uses a TIdReadFileExclusiveStream, which disallows me from reading a file being written, but I need to be able to send also files that are being written by some other process.
So, I moved to create a FileStream myself and use the ContentStream property to return it to the client, but I get a 0 bytes file in the client (for any file, being written or not), and I can't see what I'm missing or doing wrong. Here is the code I'm using on the OnCommandGet event handler:
AResponseInfo->ContentStream = new TFileStream(path, fmOpenRead | fmShareDenyNone);
AResponseInfo->ContentStream->Position = 0;
AResponseInfo->ContentLength = AResponseInfo->ContentStream->Size;
AResponseInfo->ResponseNo = 200;
AResponseInfo->WriteHeader();
AResponseInfo->WriteContent();
The ContentLength property at this point has a valid value (i.e., the file size when calling ContentStream->Size), and that's what I would like to send to the client, even if the file changes in between.
I have tried removing the WriteContent() function, the WriteHeader(), but the results are the same. I searched for some examples but the few I found are more or less the same than this code, so I don't know what's wrong. Most examples don't include the WriteContent() call, that's why I have tried removing them, but there doesn't seem to be any difference.
As a side note: the files being written take 24 hours to finish writing, but that's to be expected from the client side: I just need the bytes already written at the time of the request (even somewhat less is valid). The files will never get deleted: they will just keep getting bigger.
Any ideas?
Update
Using Fiddler, I get some warnings on protocol violations, that would be related to this. I get, for instance:
Content-Length mismatch: Response Header indicated 111,628,288 bytes, but server sent 41 bytes
The content length is correct, it's the file size, but I don't know what I'm doing wrong that makes the app sent just 41 bytes.
WriteHeader() and WriteContent() expect the ContentStream to be complete and unchanging at the time they are called. WriteHeader() creates a Content-Length header using the current ContentStream->Size value if the AResponseInfo->ContentLength property is -1 (you are actually setting the value yourself), and WriteContent() sends only as many bytes as the current ContentStream->Size value says. So your client is receiving 0 bytes because the file Size is still 0 at the time you are calling WriteHeader() and WriteContent().
Neither ServeFile() nor ContentStream are suitable for your needs. Since the file is being written live, you do not know the final file size when the HTTP headers are created and sent to the client. So you must use HTTP 1.1's chunked transfer coding to send the file data. That will allow you to send the file data in chunks as the file is being written, and then signal the client when the file is finished.
However, TIdHTTPServer does not natively support sending chunked responses, so you will have to implement it manually, eg:
TFileStream *fs = new TFileStream(path, fmOpenRead | fmShareDenyNone);
try
{
AResponseInfo->ResponseNo = 200;
AResponseInfo->TransferEncoding = "chunked";
AResponseInfo->WriteHeader();
TIdBytes buffer;
buffer.Length = 1024;
do
{
int NumRead = fs->Read(&buffer[0], 1024);
if (NumRead == -1) RaiseLastOSError();
if (NumRead == 0)
{
// check for EOF, unless you have another way to detect it...
Sleep(1000);
NumRead = fs->Read(&buffer[0], 1024);
if (NumRead <= 0) break;
}
// send the current chunk
AContext->Connection->IOHandler->WriteLn(IntToHex(NumRead));
AContext->Connection->IOHandler->Write(buffer, NumRead);
AContext->Connection->IOHandler->WriteLn();
}
while (true);
// send the last chunk to signal EOF
AContext->Connection->IOHandler->WriteLn("0");
// send any trailer headers you need, if any...
// finish the transfer encoding
AContext->Connection->IOHandler->WriteLn();
}
__finally
{
delete fs;
}
The final working code is:
std::unique_ptr< TFileStream >fs(new TFileStream(path, fmOpenRead | fmShareDenyNone));
fs->Position = 0;
__int64 size = fs->Size;
AResponseInfo->ContentLength = size;
AResponseInfo->ResponseNo = 200;
AResponseInfo->WriteHeader();
AContext->Connection->IOHandler->Write(fs.get(), size);
This allows the client to receive up to size bytes of the original file, even if the file is being written to at the same time.
For some reason passing the ContentStream did not return any content to the client, but doing the IOHandler->Write directly (which is what the ServeFile ends doing internally) works fine.

How set value Rate for Items for new Estimate into my app

I use QBFC v13 and Delphi XE6.
The goal is to create Estimate and set its parameters into QuickBooks from my app.
I imported the type library from QBFC13 and added it to my project.
My project was compiled without error.
I created Estimate from my app (fragment 1, line: 04) and then I try set rate for its item.
When I called function Rate.SetValue (fragment 1, line: 06), I had not had error message.
Then I call DoRequest (fragment 1, line: 07) it all right.
When I check StatusCode (fragment 1, line 09) it must be 0. But it is 3045, StatusMessage (fragment 1, line 10):
"There was an error when converting the price '23.00' in the field 'item cost'. QuickBooks error message: This field contains an invalid character" .
Question #1: why it happened? (variable 'rate' is double and prototype SetValue(val: Double) has type of double).
Fragment 1:
01: var
02: rate: double;
i: integer;
03: rate := 23.00;
....
04: estimateAdd := requestMsgSet.AppendEstimateAddRq();
....
05: estimateLineAdd := estimateAdd.OREstimateLineAddList.Append.EstimateLineAdd;
....
06: estimateLineAdd.ORRate.Rate.SetValue(rate);
....
07: QueryResponse := SessionManager.DoRequests(requestMsgSet);
08: i := response.StatusCode;
09: if (i <> 0) then
10: MessageDlg(response.StatusMessage, mtError, [mbOk], 0);
I found as partially resolved my problem. I can set value Rate make call Rate.SetAsString(const val: WideString) (fragment 2, line: 01)
But appear other the problem. When variable rate has decimal part is zero all right, Estimate was added into QuickBooks.
if decimal part is not zero (for example: rate := 23.10) I get exception in my app when called SetAsString procedure.
and error message: "Invalid Price value".
Question #2: Why i can not rate with decimal part?
Fragment 2:
01: estimateLineAdd.ORRate.Rate.SetAsString(FloatToStr(rate));
If I try to change parameter of procedure as string value:
estimateLineAdd.ORRate.Rate.SetAsString('23.21') I have error as in first question.
If I try to change parameter of procedure as:
estimateLineAdd.ORRate.Rate.SetAsString('23,21')
I get same exception and error message: "Invalid Price value."
It is understandable, delimeter is '.' instead ','.
delimeter is '.' instead ','.
I have the same issue because QB was running on machine where , was used as separator, which is not supported: https://community.intuit.com/questions/1012431-how-to-change-number-formatting-decimal-and-grouping-separators
My regional settings in Windows are set to ru-RU locale, so QB shows prices as "60,12".
Seems like QBFC excepts .:
queryRq.ORSalesPurchase.SalesOrPurchase.ORPrice.Price.SetAsString("60,12"); // this immediatly throws
queryRq.ORSalesPurchase.SalesOrPurchase.ORPrice.Price.SetAsString("60.12"); // this is OK
When I do "get all Items" request, QB returns price with ,, (according to reginal settings):
<?xml version="1.0" ?>
<QBXML>
<QBXMLMsgsRs>
<ItemServiceQueryRs requestID="0" statusCode="0" statusSeverity="Info" statusMessage="Status OK">
<ItemServiceRet>
...
<SalesOrPurchase>
<Price>60,07</Price>
but obj.ORSalesPurchase.SalesOrPurchase.ORPrice.Price.GetValue(); returns 60 without any exception.
What is really strange, that (after I manually fix xml string returning from sendRequestXML) nor , neither . accepted my QuickBooks Pro 2017! In receiveResponseXML I got:
<ItemServiceAddRs requestID=\"0\" statusCode=\"3045\" statusSeverity=\"Error\" statusMessage=\"There was an error when converting the price "60.12"
and
<ItemServiceAddRs requestID=\"0\" statusCode=\"3045\" statusSeverity=\"Error\" statusMessage=\"There was an error when converting the price "60,12"
After I change settings to en-US (for QB Pro US), all works with "60.12".

How to Get TTetheringProfileInfo from ARemoteResource in ProfileResourceReceived Event?

I have been googling all day and keep seeing the same 10 examples for FireMonkey, apptethering and Delphi XE6. I am new to XE6 and app tethering. I thank you for any help I can get.
MY STORY
I have Delphi XE6. I am trying to create a tethered FireMonkey application for the android platform. I have a VCL application that will run on a server. There will be many android tablets connecting to the server application at the same time.
The user pushes a button on a tablet which will cause a unique id to be sent to the server using the SendString method of the TTetheringAppProfile. The server has a TetherProfileResourceReceived event and gets the unique id from the AResource.Value. The server queries a database and gets a record. This is all good.
Now I need to send the record back to the SAME profile that sent the request. Every example I have seen uses the item index to get the TTetheringProfileInfo for send string (TetherProfile.Resources.Items[0].Value). I think I can't rely on the index because I will have multiple connections. I want to send the response string right back to the requesting profile.
MY FAILED ATTEMPT
procedure TfrmTabletServer.POSTetherProfileResourceReceived(
const Sender: TObject; const AResource: TRemoteResource);
var
RequestID : Integer;
SendRec := String;
Requester : String;
begin
Requester := AResource.Name;
if AResource.ResType = TRemoteResourceType.Data then begin
RequestID := AResource.Value.AsInteger;
SendRec := GetRecord(RequestID);
//this works but I cant rely on index name due to multiple connections
//POSTetherProfile.Resources.Items[0].Value = SendRec;
//I would prefer to use SendString to keep the requests temporary
//I can't figure out how to get the TTetheringProfileInfo from the AResource
POSTetherProfile.SendString('TTetheringProfileInfo from AResource?','Response ' + ID.AsString, SendRec);
end;
MY RESOURCE
http://docwiki.embarcadero.com/RADStudio/XE6/en/Sharing_Data_with_Remote_Applications_Using_App_Tethering
After a while trying to get the working I still couldn't find a way of obtaining profile identifier from the parameters sent to the OnResourceReceived event.
The way I have solved this is to append the profile identifier to AResource.Hint string so the Hint looks like
"{OriginalHint};{ProfileID}"
This way I can always find the profile identifier by looking at the hint string.
This is not ideal but it works until we have the profile identifier passed as part of AResource.

Datasnap & Fmx Mobile App How to send a dataset containing a blob field

I had a multi tier project in which i would collect data from a microsoft sql 2005 through a FDStoredProc with a function and the function would return a dataset to the client. When the server assigns the dataset to the result of the function and the function tries to send it to the client i get this error. Project etctec.exe raised exception class TDBXError with message 'TDBXTypes.BLOB value type cannot be accessed as TDBXTypes.Bytes value type'.
In another project i used the StoredProc of a different database with TFDStoredProc in exactly the same way and it works fine. Any ideas what would raise this error?
This is what i do in the server.
function TServerMethods1.getCategories(): TDataSet;
begin
FDStoredProc1.ParamByName('#val1').AsInteger:= 1;
FDStoredProc1.ParamByName('#val2').AsInteger:= 0;
FDStoredProc1.ParamByName('#val3').AsInteger:= 1;
FDStoredProc1.ParamByName('#val4').AsInteger:= 1;
FDStoredProc1.Open();
result:= FDStoredProc1;
end;
and the client calls it like this...
dataset:=ClientModule1.ServerMethods1Client.getCategories();
Problem comes from some fields that are of type NVARCHAR(max), anyone knows a workaround to this error without changing the field type?
I tried changing the dataset's field type to a string or something with no success. The only thing i can temporarily do is get these fields separately, put them in a stringlist or something like that and pass it to the client.
I think you should use some of similar methods below:
http://docwiki.embarcadero.com/Libraries/XE4/en/Data.DB.TDataSet.CreateBlobStream
http://docwiki.embarcadero.com/Libraries/XE4/en/Data.DB.TDataSet.GetBlobFieldData
You should define you field as a blob field and then put data in/out with the functions described in the links.
Here is also example how to copy data:
http://docwiki.embarcadero.com/Libraries/XE4/en/Data.DB.TDataSet.CreateBlobStream

Resources