0

I have a winCGI executable script returning a image. I am using Intraweb as server.

I have a custom web server made in delphi and I create a function to run the cgi and return the image.

The cgi is from a third party and I can't change your code. I have the next code returning from CGI when I query a image:

'Content-type: image/gif'#$A'Access-Control-Allow-Origin: *'#$A#$A'GIF89a@'#1'@'#1#0#0#0'!ÿ'#$B'NETSCAPE2.0'#3#1'ÿÿ'#0'!ù'#4#0'!'#0#0#0','#0#0#0#0'@'#1'@'#1'‡'#0#0#0#1#1#1#2#2#2#3#3#3#4#4#4#5#5#5#6#6#6#7#7#7#8#8#8#9#9#9#$A#$A#$A#$B#$B#$B#$C#$C#$C#$D#$D#$D#$E#$E#$E#$F#$F#$F#$10#$10#$10#$11#$11#$11#$12#$12#$12#$13#$13#$13#$14#$14#$14#$15#$15#$15#$16#$16#$16#$17#$17#$17#$18#$18#$18#$19#........

I need to send the image to a front end app in the browser.

<div>
 <img src="getImage(1)">
</div>

Here, getImage function takes the image from server, but not is showing, because I think the format I am returning the image from server to front end has something wrong. How could I fix the content text of the image on server to be a valid image in the front end?

function RunCGIOutput(CommandLine: string; Stream:TStream;Folder: string = ''): string;
const
  CReadBuffer = 2400;
var
  saSecurity: TSecurityAttributes;
  hRead: THandle;
  hWrite: THandle;
  suiStartup: TStartupInfo;
  piProcess: TProcessInformation;
  dRead: DWORD;
  Handle,WasOK:Boolean;
  Buffer: array[0..CReadBuffer] of AnsiChar;
  BytesRead: Cardinal;
  WorkingDirP,EnvBlock:PChar;

begin
  saSecurity.nLength := SizeOf(TSecurityAttributes);
  saSecurity.bInheritHandle := true;
  saSecurity.lpSecurityDescriptor := nil;
  EnvBlock := BuildEnvBlock(True);
  if Folder <> '' then WorkingDirP := PChar(Folder)
  else WorkingDirP := nil;
  if CreatePipe(hRead, hWrite, @saSecurity, 0) then
    try
      FillChar(suiStartup, SizeOf(TStartupInfo), #0);
      suiStartup.cb := SizeOf(TStartupInfo);
      suiStartup.hStdInput := hRead;
      suiStartup.hStdOutput := hWrite;
      suiStartup.hStdError := hWrite;
      suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
      suiStartup.wShowWindow := SW_HIDE;
      Handle:=CreateProcess(nil, PChar(CommandLine), @saSecurity, @saSecurity, true, CREATE_UNICODE_ENVIRONMENT, EnvBlock, WorkingDirP, suiStartup,
        piProcess);

    CloseHandle(hWrite);
    if Handle then
      try
        repeat
              WasOK := ReadFile(hRead, Buffer, CReadBuffer, BytesRead, nil) and (BytesRead>0);
              if WasOK then
                Stream.WriteBuffer(Buffer, BytesRead);
        until not WasOK;
        WaitForSingleObject(piProcess.hProcess, INFINITE);
      finally
        CloseHandle(piProcess.hThread);
        CloseHandle(piProcess.hProcess);
      end;
  finally
    CloseHandle(hRead);
    StrDispose(EnvBlock);
  end;
end;


//Run CGI, take image and send to browser
function TContentImaqge.Execute(aRequest: THttpRequest; aReply: THttpReply;
  const aPathname: string; aSession: TIWApplication;
  aParams: TStrings): boolean;
var
  s,wresult,saida,LocalDoc:string;
  i:integer;
  Stream:TMemoryStream;
begin
    Result:=True;
    LocalDoc:=TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\dgate.exe';
    Stream:=TMemoryStream.Create;
    saida:=RunCGIOutput(LocalDoc,Stream,TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\');
    Stream.Position:=0;
    saida:=ReadStringFromStream(Stream, -1, IndyTextEncoding_OSDefault);
    with aReply do
     begin
       ResetReplyType;
       Code := 200;
       ContentType := MIME_GIF; // MIME_HTML;
       SendStream(Stream);
    end;
end;
Luiz Alves
  • 2,575
  • 4
  • 34
  • 75
  • If only you could show us the code to execute CGI within your IW app. I've got a hunch that you you write returned CGI HTTP headers and body to the response body when serving request in IW app. Inspecting network using browser's developer tools would also help here. – Peter Wolf Oct 07 '19 at 06:12
  • @PeterWolf Hi Peter, I have posted the code in IW to un the CGI and send the return to browser. – Luiz Alves Oct 07 '19 at 21:51

1 Answers1

1

Your CGI module generates raw HTTP response that should be transmitted as-is by your IW content handler. The response message consists of:

  1. Status line
  2. HTTP header lines
  3. An empty line separating headers and body
  4. Body (actual image data in your case)

IntraWeb's THTTPReply doesn't seem to give you control over raw HTTP response. It provides dedicated interface to separately manipulate with status, headers and body. That's why you need to pre-process the stream returned from CGI and split headers apart from body by the empty line. Then you can transmit them via THTTPReply. Maybe you'd also like to whitelist only some headers and ignore the rest.

In your code snippet you use ReadStringFromStream which is pointless, because you discard the returned value anyway. It moves the position of Stream to the end of the stream, but that doesn't matter, because aReply.SendStream(Stream) sends the whole stream content from the beginning.

Another point is that you use IndyTextEncoding_OSDefault as the last parameter to ReadStringFromStream, which is probably wrong, because HTTP header is encoded in ASCII and therefore you should use IndyTextEncoding_ASCII.

Try this code instead:

function TContentImaqge.Execute(aRequest: THttpRequest; aReply: THttpReply;
  const aPathname: string; aSession: TIWApplication;
  aParams: TStrings): Boolean;
var
  CGIOutput, ContentStream: TMemoryStream;
  LocalDoc, Line: string;
  CharPos: Integer;
begin
  Result := True;
  CGIOutput := TMemoryStream.Create;
  try
    LocalDoc := TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\dgate.exe';
    RunCGIOutput(LocalDoc, CGIOutput, TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\');

    if ReadLnFromStream(CGIOutput, Line, -1, IndyTextEncoding_ASCII) then
    begin
      { process status line }
      CharPos := Pos(' ', Line);
      if CharPos > 0 then
      begin
        aReply.Code := StrToInt(Copy(Line, CharPos + 1, 3));
        CharPos := Pos(' ', Line, CharPos);
        if CharPos > 0 then
          aReply.CodeText := Copy(Line, CharPos + 1);
      end;

      { process headers (copy headers as they are) }
      while ReadLnFromStream(CGIOutput, Line, -1, IndyTextEncoding_ASCII) and (Line <> '') do
        aReply.Headers.Add(Line);

      { at this point CGIOutput.Position is at the beginning of body, so let's just copy
        the content to a separate memory stream }
      ContentStream := TMemoryStream.Create;
      try
        ContentStream.CopyFrom(CGIOutput, CGIOutput.Size - CGIOutput.Position);
      except
        ContentStream.Free;
        raise;
      end;
      aReply.SendStream(ContentStream);
    end
    else
    begin
      aReply.Code := 500;
      aReply.CodeText := RSHTTPInternalServerError;
      aReply.WriteString('CGI module returned malformed response.');
    end;
  finally
    CGIOutput.Free;
  end;
end;

Also please note that the CGI output you enclosed doesn't contain status line and starts straight with headers (Content-type: ...). If that's what you get from CGI then you should update the code to handle such a case.

Peter Wolf
  • 3,700
  • 1
  • 15
  • 30
  • Thank you Peter. My flag to create process is CREATE_UNICODE_ENVIRONMENT. This indicate I want the buffer in Unicode. Your code is great. Then I use IndyTextEncoding_OSDefault. – Luiz Alves Oct 10 '19 at 23:30
  • It's OK to create CGI process with Unicode flag, but you read lines of its output (`ReadLnFromStream`) in IW app, which is a different process. Anyhow, it's better to be explicit here than to rely on a global state (encoding). – Peter Wolf Oct 11 '19 at 08:17