Felipe Ceballos - Desystec Felipe Ceballos - Desystec - 21 days ago 10
HTTP Question

Uploading a file with WININET on delphi

I have a delphi wininet app.

The past week I tried to send information to execute a JSON method, this week I'm trying to upload a file to a web server. I am using the same code with a few modifications, but at the moment that the InternetWriteFile procedure executes, the program raises me an exception: "Controlador no vĂ¡lido".

The code is here:

function TFormMain.CargarArchivo(Archivo, Server: string; Username, Password: PChar; blnSSL, iftoken: Boolean): Boolean;
var
Url, Header : String;
pSession, pConnection, pRequest : HINTERNET;
flags, dwSize, dwFlags : DWORD;
dwError, Port : Integer;
Escritos : Cardinal;
begin
Result := false;
pSession := InternetOpen(nil, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if not Assigned(pSession) then
raise Exception.Create('InternetOpen failed. ' + WinInetErrorMsg(GetLastError));
try
Url := Copy(Server,pos('/Servidor',Server),length(Server));
if blnSSL then
begin
Server := Copy(Server,9,pos('/Servidor',Server)-1);
Port := INTERNET_DEFAULT_HTTPS_PORT
end
else
begin
Server := Copy(Server,8,pos('/Servidor',Server)-1);
Server := Copy(Server,1,pos(':',Server)-1);
Port := 8080;
end;
pConnection := InternetConnect(pSession, PChar(Server), port, Username, Password, INTERNET_SERVICE_HTTP, 0, 0);
if not Assigned(pConnection) then
raise Exception.Create('InternetConnect failed. ' + WinInetErrorMsg(GetLastError));
try
if blnSSL then
flags := INTERNET_FLAG_SECURE
else
flags := 0;
pRequest := HTTPOpenRequest(pConnection, 'POST', PChar(Url), nil, nil, nil, flags, 0);
if not Assigned(pRequest) then
raise Exception.Create('HttpOpenRequest failed. ' + WinInetErrorMsg(GetLastError));
try
// Set buffer size
dwSize:=SizeOf(dwFlags);
// Get the current flags
if (InternetQueryOption(pRequest, INTERNET_OPTION_SECURITY_FLAGS, @dwFlags, dwSize)) then
begin
// Add desired flags
dwFlags:=dwFlags or SECURITY_FLAG_IGNORE_UNKNOWN_CA or SECURITY_FLAG_IGNORE_CERT_CN_INVALID or SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
// Set new flags
if not(InternetSetOption(pRequest, INTERNET_OPTION_SECURITY_FLAGS, @dwFlags, dwSize)) then
begin
// Get error code
dwError:=GetLastError;
// Failure
MessageBox(Application.Handle, PChar(IntToStr(dwError)), PChar(Application.Title), MB_OK or MB_ICONINFORMATION);
end;
end
else
begin
// Get error code
dwError:=GetLastError;
// Failure
MessageBox(Application.Handle, PChar(IntToStr(dwError)), PChar(Application.Title), MB_OK or MB_ICONINFORMATION);
end;
Header := 'Host: ' + Server + ':' + IntToStr(Port) + #13#10 +
'Content-Type: multipart/form-data; charset=UTF-8'#13#10;
if iftoken then
begin
Header := Header + 'auth_token: '+token+#13#10;
Header := Header + 'Csrf-token: no-check'#13#10;
end;

if not HttpAddRequestHeaders(pRequest, PChar(Header), Length(Header), HTTP_ADDREQ_FLAG_ADD) then
raise Exception.Create('HttpAddRequestHeaders failed. ' + WinInetErrorMsg(GetLastError));

Parameters := TIdMultiPartFormDataStream.Create;
Parameters.AddFile('archivo', Archivo, '');
if not HTTPSendRequest(pRequest, nil, 0, Parameters, Parameters.Size) then
raise Exception.Create('HTTPSendRequest failed. ' + WinInetErrorMsg(GetLastError));

try
if not InternetWriteFile(Parameters, Parameters, Parameters.Size, Escritos) then
raise Exception.Create('InternetWriteFile failed. ' + WinInetErrorMsg(GetLastError));
Result := true;
finally
Parameters.Free;
end;
finally
InternetCloseHandle(pRequest);
end;
finally
InternetCloseHandle(pConnection);
end;
finally
InternetCloseHandle(pSession);
end;
end;


Exception

I have tried to initialize the file pointer with FTPOpenFile procedure, but it doesn't work because the server is HTTP, not FTP, I guess.

Answer Source

You cannot mix Indy and WinInet in the manner that you are trying to mix them.

You are trying to POST an Indy TIdMultipartFormDataStream object directly from memory. That will never work. You have to tell TIdMultipartFormDataStream to generate its MIME data from your input parameters and then post that data instead. TIdHTTTP.Post() handles that for you, but you are not using TIdHTTP, so you have to do it manually. In this case, you would have to save the generated TIdMultipartFormDataStream data to a separate TMemoryStream (using the TStream.CopyFrom() method) and then you can post that data using WinInet.

Even then, your POST would still break, because:

  1. you are not including the required boundary parameter in your Content-Type request header (TIdMultipartFormDataStream generates that value dynamically) so the server can parse the MIME parts correctly.

  2. you cannot mix HTTPSendRequest() and InternetWriteFile() together if they are both trying to send body data for the request. Pick one or the other.

Try something more like this:

PostData := TMemoryStream.Create;
try
  Parameters := TIdMultiPartFormDataStream.Create;
  try
    Parameters.AddFile('archivo', Archivo, '');
    PostData.CopyFrom(Parameters, 0);
    Header := 'Host: ' + Server + ':' + IntToStr(Port) + #13#10 +
              'Content-Type: ' + Parameters.RequestContentType + #13#10;
  finally
    Parameters.Free;
  end;    
  if iftoken then
  begin
    Header := Header + 'auth_token: '+token+#13#10;
    Header := Header + 'Csrf-token: no-check'#13#10;
  end;
  if not HttpAddRequestHeaders(pRequest, PChar(Header), Length(Header), HTTP_ADDREQ_FLAG_ADD) then
    raise Exception.Create('HttpAddRequestHeaders failed. ' + WinInetErrorMsg(GetLastError));
  if not HTTPSendRequest(pRequest, nil, 0, PostData.Memory, PostData.Size) then
    raise Exception.Create('HTTPSendRequest failed. ' + WinInetErrorMsg(GetLastError));
  Result := True;
finally
  PostData.Free;
end;