-2

I'm using StringStream to read the result of a command that is sent to chromedriver.

The problem is that the returned result is messing up the memory addresses and it give me an Access Violation error.

I have tried multiple different methods to catch the exceptions, but the program stops working. When I use breakpoints, I see that the DataString has the requested value, but when it tries to put it in the variable, it terminates the program.

This is the code I'm using:

function TDelphiCommand.ExecutePost(port: WORD; const URL, Data: string): string;
var
Header      : TStringStream;
pSession    : HINTERNET;
pConnection : HINTERNET;
pRequest    : HINTERNET;
flags       : DWord;
AcceptType: array[0..1] of PChar;// = ('*/*', nil);
sStream: TStringStream;
Buffer: array[0..255] of Byte;
aBufferSize: Cardinal;
b1: Boolean;
AData: TMemoryStream;
sUrl: string;
aMem: PByte;
begin
Result := '';
pSession := InternetOpen('Mozila/5.0', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

if Assigned(pSession) then
try
    pConnection := InternetConnect(pSession, '127.0.0.1', port, nil, nil, INTERNET_SERVICE_HTTP, 0, 0);

    if Assigned(pConnection) then
    try
    AcceptType[0] := 'application/json';
    AcceptType[1] := nil;
    sUrl := Copy(URL, Pos(IntToStr(port), URL)+Length(IntToStr(port))+1, Length(URL));
    pRequest := HTTPOpenRequest(pConnection, 'POST', PChar(sUrl), 'HTTP/1.1', nil, @AcceptType[0], INTERNET_FLAG_KEEP_CONNECTION or INTERNET_FLAG_ASYNC or INTERNET_FLAG_MAKE_PERSISTENT, 0);

    if Assigned(pRequest) then
    try
        Header := TStringStream.Create('');
        try
        with Header do
        begin
            WriteString('Accept: text/html, application/json, application/xhtml+xml, image/jxr, */*' + sLineBreak);
            WriteString('Accept-Encoding: gzip, deflate' + sLineBreak);
            WriteString('Accept-Language: en-US, en; q=0.5' + sLineBreak);
                                    //text/html
            WriteString('Content-Type: application/json; charset=UTF-8' + sLineBreak);
            WriteString('Connection: keep-alive'+ SlineBreak);
            WriteString('Host: 127.0.0.1' + sLineBreak);
            WriteString('User-Agent: Mozilla/5.0'+ sLineBreak);
        end;
        HttpAddRequestHeaders(pRequest, PChar(Header.DataString), Length(Header.DataString), HTTP_ADDREQ_FLAG_ADD);

        AData := TMemoryStream.Create;
        try
            AData.Write(TEncoding.UTF8.GetBytes(Data), Length(Data));
            b1 := HTTPSendRequest(pRequest, nil, 0, AData.Memory, AData.Size);
            if b1 then
            begin
            sStream := TStringStream.Create('');
            try
                try
                sStream.Position := 0;
                while InternetReadFile(pRequest, @Buffer[0], SizeOf(Buffer), aBufferSize) and (aBufferSize <> 0) do
                    sStream.Write(Buffer[0], aBufferSize);

                Result := sStream.Datastring;//UTF8ToString(RawByteString(sStream.Datastring));
                except
                sStream.Free;
                end;
            finally
                sStream.Free;
            end;
            end;
        finally
            AData.Free;
        end;
        finally
        Header.Free;
        end;
    finally
        InternetCloseHandle(pRequest);
    end;
    finally
    InternetCloseHandle(pConnection);
    end;
finally
    InternetCloseHandle(pSession);
end;
end;

This is one of the other ways that I have tried, and this one also gives the same error:

function TDelphiCommand.ExecutePost(const URL, Data: string): string;
type
PMemArray = ^TMemArray;
TMemArray = array [0..0] of byte;
var
RespStream: TMemoryStream;
ResBytes: TBytes;
aPointer: PMemArray;
PostStream: TStringStream;
S: string;
begin
Result := '{"value":""}';
PostStream := TStringStream.Create(Data, TEncoding.UTF8);
RespStream := TMemoryStream.Create;
try
    PostStream.WriteString(Data);
    PostStream.Position := 0;
    RespStream.Size := 0;
    RespStream.Position := 0;
    InitHeader;
    Fhttp.Request.ContentLength := PostStream.Size;
    try
//        Fhttp.Post(URL, PostStream, aResponseStream);
    Fhttp.Post(URL, PostStream, RespStream);
    RespStream.Seek(0, soFromBeginning);
    aPointer := HeapAlloc(GetProcessHeap, 0, Fhttp.Response.ContentLength);
    RespStream.Read(aPointer^, Fhttp.Response.ContentLength);
    SetLength(ResBytes, Fhttp.Response.ContentLength);
    RespStream.Read(aPointer, Fhttp.Response.ContentLength);
//  VirtualProtect(@NOP_Proc, FSizeOfNOP_Proc, PAGE_EXECUTE_READWRITE, @c1);
        if PByte(ResBytes)^ > 0 then
        Result := TEncoding.UTF8.GetString(ResBytes);

    Debug_Log(Result);
    except
    Debug_Log('ExecuteGet POST: ' + Result);
//      Result := 'Disconnected!';
    FreeAndNil(PostStream);
        FreeAndNil(PostStream);
    FreeAndNil(RespStream);
        FreeAndNil(RespStream);
    end;
finally
    if Assigned(PostStream) then
    FreeAndNil(PostStream);
    if Assigned(RespStream) then
    FreeAndNil(RespStream);
end;
end;
Olivier
  • 13,283
  • 1
  • 8
  • 24
Gimmly
  • 443
  • 1
  • 6
  • 15
  • In which line you get your AV? When you try to assign to Result? And just to mention - in first variant of source code - you will try to double free the sStream in case of exception, just a side note. – Miroslav Penchev Oct 05 '21 at 07:00
  • What do you mean by AV? this is the line that sets the result: `Result := sStream.Datastring;` when it breaks with Access Violation, DataString has a value, but result is empty, – Gimmly Oct 05 '21 at 07:12
  • You should make your life easier and use a component like Indy instead of WinINet. – Olivier Oct 05 '21 at 07:14
  • Did you try resetting the position of the stream before calling `Datastring`? – Olivier Oct 05 '21 at 07:15
  • I have also tried with Indy, it still breaks, then I switched to WinINet, The second variant that I have posted is using the Indy – Gimmly Oct 05 '21 at 07:16
  • Yes I have used `sStream.Position := 0`, that didn't work as well. The issue is that is sometimes work, in my code, I close the Chrome and reopen it again, it works on the first try, but in the second or third, it throws the Access Violation – Gimmly Oct 05 '21 at 07:19
  • @Gimmly that's important clue that it crash after first use of code. What exact Access violation you get? – Miroslav Penchev Oct 05 '21 at 07:23
  • @MiroslavPenchev this is the error: `raised exception class EAccessViolation with message 'Access violation at address 00405BB5 in module 'Player.exe'. Read of address 003D0064'.` – Gimmly Oct 05 '21 at 07:30
  • @MiroslavPenchev When it throws the access violation, the program stops, but when I click on break, I see that the DataString has the value, and it was unable to set it into Result – Gimmly Oct 05 '21 at 07:31
  • You earlier said that you have reset the position of the stream to 0. Was it just before you assigned `Result`? – Tom Brunberg Oct 05 '21 at 07:33
  • @TomBrunberg Yes, I have used the `sStream.Position := 0` both before writing to it and before reading the `DataString` with no luck. I have also tried using a buffer instead of stream, it works on a portion of data, but after that it throws the AccessViolation. Is there any way to check for it before I read from it? Can I check and if there is an Access Violation I ignore the rest of the stream? – Gimmly Oct 05 '21 at 07:40
  • There are probably other bugs but this is wrong for sure `AData.Write(TEncoding.UTF8.GetBytes(Data), Length(Data))`. You presumably mean to pass the length of the byte array. – David Heffernan Oct 05 '21 at 07:43
  • 1
    It's also kind of odd to create a byte array with UTF8 bytes and then write that to a memory stream in order to pass a pointer to the bytes. Just create the byte array and there's your pointer to bytes. There is so much extraneous code here that I can't face digging any deeper. – David Heffernan Oct 05 '21 at 07:51
  • You should really drop WinINet and focus on the Indy version. Check [this](https://stackoverflow.com/a/28464982/12763954) out for instance. – Olivier Oct 05 '21 at 08:01
  • @DavidHeffernan The issue happens when I read the data. Converting to UTF8 messes up the sizes somehow. I need to convert them to UTF8 and that's why we're using these codes – Gimmly Oct 05 '21 at 08:37
  • @Olivier We did try Indy, and we also used almost all of the related posts in Stack Overflow, the problem seems to be somewhere else – Gimmly Oct 05 '21 at 08:37
  • 1
    I'm not questioning whether or not you need to use UTF8, I'm just pointing out that you are doing it wrong. – David Heffernan Oct 05 '21 at 09:00
  • Another error is that in the face of an exception you call `sStream.Free` twice. The mistake there is that you need to remove that `try/except` block altogether and let the `try/finally` do the work. – David Heffernan Oct 05 '21 at 09:31
  • @DavidHeffernan Thanks. what would be the correct way to convert to UTF8? – Gimmly Oct 05 '21 at 09:37
  • I already explained it in my first comment. Read it again carefully. – David Heffernan Oct 05 '21 at 10:25
  • 1
    @Gimmly in your Indy code, you are writing `Data` to the `TStringStream` twice. And double-freeing the streams. But more importantly, all of the code after `Post()` is wrong, way overkill, and unnecessary. `Post()` has an overload that returns a string, and will handle the UTF-8 decoding for you, if the server claims the data is using UTF-8: `Result := Fhttp.Post(URL, PostStream);` – Remy Lebeau Oct 05 '21 at 14:54

1 Answers1

-1

I recreated your first code in a little VCL-App, using TButton and TMemo and called the function like this:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Text := ExecutePost(80, 'HTTPS://google.com', 'MyData');
end;

And it works well with no exceptions. Just a few notes:

  • I don't understand sURL and discarded the variable, using URL for HTTPOpenRequest
  • Deleted the declaration of flags and aMem cause you don't use them
  • As Miroslav wrote, I delete of course the double of sStream.Free. Try .. except is not necessary. Free sStream twice causes an exception!!

So I get the response form google.com in the Memo.

function TForm1.ExecutePost(port: WORD; const URL, Data: string): string;
var
  Header: TStringStream;
  pSession: HINTERNET;
  pConnection: HINTERNET;
  pRequest: HINTERNET;
  AcceptType: array [0 .. 1] of PChar; // = ('*/*', nil);
  sStream: TStringStream;
  Buffer: array [0 .. 255] of Byte;
  aBufferSize: Cardinal;
  b1: Boolean;
  AData: TMemoryStream;
begin
  Result := '';
  pSession := InternetOpen('Mozila/5.0', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

  if Assigned(pSession) then
    try
      pConnection := InternetConnect(pSession, '127.0.0.1', port, nil, nil,
        INTERNET_SERVICE_HTTP, 0, 0);

      if Assigned(pConnection) then
        try
          AcceptType[0] := 'application/json';
          AcceptType[1] := nil;

          pRequest := HTTPOpenRequest(pConnection, 'POST', PChar(Url),
            'HTTP/1.1', nil, @AcceptType[0], INTERNET_FLAG_KEEP_CONNECTION or
            INTERNET_FLAG_ASYNC or INTERNET_FLAG_MAKE_PERSISTENT, 0);

          if Assigned(pRequest) then
            try
              Header := TStringStream.Create('');
              try
                with Header do
                begin
                  WriteString('Accept: text/html, application/json, '
                    +'application/xhtml+xml, image/jxr, */*'+ sLineBreak);
                  WriteString('Accept-Encoding: gzip, deflate' + sLineBreak);
                  WriteString('Accept-Language: en-US, en; q=0.5' + sLineBreak);
                  // text/html
                  WriteString('Content-Type: application/json; charset=UTF-8' +
                    sLineBreak);
                  WriteString('Connection: keep-alive' + sLineBreak);
                  WriteString('Host: 127.0.0.1' + sLineBreak);
                  WriteString('User-Agent: Mozilla/5.0' + sLineBreak);
                end;
                HttpAddRequestHeaders(pRequest, PChar(Header.DataString),
                  Length(Header.DataString), HTTP_ADDREQ_FLAG_ADD);

                AData := TMemoryStream.Create;
                try
                  AData.Write(TEncoding.UTF8.GetBytes(Data), Length(Data));
                  b1 := HTTPSendRequest(pRequest, nil, 0, AData.Memory,
                    AData.Size);
                  if b1 then
                  begin
                    sStream := TStringStream.Create('');
                    try
                      sStream.Position := 0;
                      while InternetReadFile(pRequest, @Buffer[0],
                        SizeOf(Buffer), aBufferSize) and (aBufferSize <> 0) do
                        sStream.Write(Buffer[0], aBufferSize);

                      Result := sStream.DataString;
                      // UTF8ToString(RawByteString(sStream.Datastring));
                    finally
                      sStream.Free;
                    end;
                  end;
                finally
                  AData.Free;
                end;
              finally
                Header.Free;
              end;
            finally
              InternetCloseHandle(pRequest);
            end;
        finally
          InternetCloseHandle(pConnection);
        end;
    finally
      InternetCloseHandle(pSession);
    end;
end;