Upload multipart POST request with Synapse does not work expected way.[SOLVED] lazarus / 66-Unix ---------------------------------------------------------------------- OH1KH · 2026-02-05 09:50 I have a problem that I can not resolve. I need to send a mutipart POST request to server but it rejects my tries with Freepascal. Using command terminal and curl the server accepts upload without problems. If I send with curl using script: Code: Bash  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i #!/bin/bash  API='api'PASS='pass'CALL='oh1kh'FILE='ex.adi'EMAIL='oh1kh@oh1kh.fi' c url -v -i -X POST \    -F file=@${FILE} \    -F email=${EMAIL} \    -F callsign=${CALL} \    -F password=${PASS} \    -F api=${API} \    https://httpbin.org/anything  Result is: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {  "args": {},   "data": "",   "files": {    "file": "202602030830OH1KJ40MCW7.025083 0599599NNKP01tn1001815NNEU\n202602030830OH 1KJ30MCW10.10830599599NNKP01tn1001815NNEU< EOR>\n202602030831OH1KJ20MCW140831599599NNKP01tn10018 15NNEU\n"  },   "form": {    "api": "api",     "callsign": "oh1kh",     "email": "oh1kh@oh1kh.fi",     "password": "pass"  },   "headers": {    "Accept": "*/*",     "Content-Length": "1419",     "Content-Type": "multipart/form-data; boundary=------------------------191zKPATSeNPAJLX3oWkyt",     "Host": "httpbin.org",     "User-Agent": "curl/8.11.1",     "X-Amzn-Trace-Id": "Root=1-69845c03-16584d767851ee66237add89"  },   "json": null,   "method": "POST",   "origin": "84.231.202.222",   "url": "https://httpbin.org/anything"}  And this works. File key and contents are placed in "files:{ }" part of response. Then I use Synapse/FPC to do the same, but result from httpbin.org is not similar. data:TStringList has the information I push to upload function : Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i file=/tmp/C lubStream.txtemail=oh1kh@oh1kh.fipassword=passwordcallsign=oh1khapi=ap i  And the result from httpbin.org for this is: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {  "args": {},   "data": "",   "files": {},   "form": {    "api": "api",     "callsign": "oh1kh",     "email": "oh1kh@oh1kh.fi",     "file": "202602030830OH1KJ40MCW7.0250830599599NNKP01tn1001815NNEU\n202602030830OH1KJ30MCW10.1 0830599599NNKP01tn1001815NNEU\n202602030831OH1KJ20MCW140831599599NNKP01tn1001815NNNNEU\n" And that is the proper one. For some reason Fpc/Synaptic cuts the file from end. This is the current code Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     for i:=0 to data.Count-1 do    begin       Key   := ExtractWord(1,data.Strings[i],['=']);      Value := ExtractWord(2,data.Strings[i],['=']);       if Key='file' then        begin          WriteStrToStream(HTTP.Document, CRLF +            'Content-Disposition: form-data; name=' + AnsiQuotedStr(Key, '"') + ';' +            ' filename=' + AnsiQuotedStr(Value, '"')  + CRLF+          'Content-Type: application/octet-stream' +  CRLF + CRLF);        F:=TMemoryStream.Create;          F.LoadFromFile(Value);          HTTP.Document.CopyFrom(F, 0);          F.Destroy;          WriteStrToStream(HTTP.Document,CRLF +CRLF + '--------------------------' +Bound);        end        else         begin          WriteStrToStream(HTTP.Document, CRLF +            'Content-Disposition: form-data; name=' + AnsiQuotedStr(Key, '"') + CRLF + CRLF);          WriteStrToStream(HTTP.Document, Value + CRLF);          WriteStrToStream(HTTP.Document, '--------------------------' + Bound );         end;     end;    WriteStrToStream(HTTP.Document, '--' + CRLF);     HTTP.MimeType := 'multipart/form-data; boundary=' + '--------------------------' + Bound;    if HTTP.HTTPMethod('POST',Url) then                                                     Instead of F :TMemoryStream I have also tested with F: TFileStream but results are same. Adding extra CRLF to end of the file does not help. Same with LineEnding. I have also tried HTTP.Document.CopyFrom(F, F.Size); instead of HTTP.Document.CopyFrom(F, 0); But that does not make any change. No more ideas.... Lazarus 4.4 (rev Unknown) FPC 3.2.2 x86_64-linux-gtk2 ---------------------------------------------------------------------- rvk · 2026-02-05 15:44 Quote from: OH1KH on February 05, 2026, 04:23:49 pmI found better way to monitor and compare to curl output: Code: Bash  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i while true; do printf '' | nc -l localhost 60006; doneUsing 3 v parameters with curl (so curl -vvv) will also give you exactly what curl is sending. Quote from: OH1KH on February 05, 2026, 04:23:49 pmNo more ideas.... Feels like a flushing problem. But I've never had to do that. But... you have this after writing the file content: Code: [Select]WriteStrToStream(HTTP.Document,CRLF +CRLF + '--------------------------' +Bound);Why are there two CRLF's ? Normally 1 is sufficient. And this after everything: Code: [Select]WriteStrToStream(HTTP.Document, '--' + CRLF);Aren't you missing the CRLF in front of this one? Edit: Ha, you have it after writing the field. BTW.   F.Destroy; ??? Why not F.Free? (preferably with a try/finally around create/free.) ---------------------------------------------------------------------- rvk · 2026-02-05 15:51 BTW. You can also use TMimeMess and TMimePart from Synapse to do a lot of this automatically. ---------------------------------------------------------------------- OH1KH · 2026-02-05 15:55 Hi ! ---------------- was "just to make output similar as curl" As the file is text I replaced a part of code like: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i       if Key='file' then        begin          WriteStrToStream(HTTP.Document, CRLF +            'Content-Disposition: form-data; name=' + AnsiQuotedStr(Key, '"') + ';' +            ' filename=' + AnsiQuotedStr(Value, '"')  + CRLF+            'Content-Type: application/octet-stream' +  CRLF + CRLF);          {          F:=TMemoryStream.Create;          F.LoadFromFile(Value);          HTTP.Document.CopyFrom(F, F.Size);          F.Destroy;          }        AssignFile(FT,Value);          Reset(FT);          While not Eof(FT) do             Begin              ReadLn(FT,s);              WriteStrToStream(HTTP.Document,s);             end;           WriteStrToStream(HTTP.Document,CRLF +CRLF + '--------------------------' +Bound);  But the result is same! When looking at result it is 511 bytes (ASCII text, one byte / char). Could it be that Synaptic THTTPSend has some kind of  limitation as 512 bytes  ? (that could do 255 chars when using two byte / char) ---------------------------------------------------------------------- OH1KH · 2026-02-05 16:01 On the other hand, elsewhere in same program is similar part of code: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  m.LoadFromFile(FileName);    http.Sock.OnStatus := @SockCallBack;    s := '--' + Bound + CRLF;    s := s + 'content-disposition: form-data; name="upfile";';    s := s + ' filename="' + FileName +'"' + CRLF;    s := s + 'Content-Type: Application/octet-string' + CRLF + CRLF;    WriteStrToStream(http.Document, s);    http.Document.CopyFrom(m, 0);  s := CRLF + '--' + Bound + '--' + CRLF;    WriteStrToStream(http.Document, s);    http.MimeType := 'multipart/form-data; boundary=' + Bound;                                                              That is, so far I can see, just similar case. And I have used it with lot more longer files without problems. ---------------------------------------------------------------------- rvk · 2026-02-05 16:02 Quote from: OH1KH on February 05, 2026, 04:55:39 pmWhen looking at result it is 511 bytes (ASCII text, one byte / char). Could it be that Synaptic THTTPSend has some kind of  limitation as 512 bytes  ? (that could do 255 chars when using two byte / char) The WriteStrToStream for after the TTP.Document.CopyFrom (and WriteStrToStream in your while/eof) is done correctly... so there is no such limitation. in HTTP.Document. Otherwise the rest would also be cut off. You could create a small testproject to show the problem. Usually when doing that, you end up finding the problem yourself (and otherwise you can post it here). ---------------------------------------------------------------------- OH1KH · 2026-02-05 18:13 Interesting point is that adding one line more to file will cut 4 chrs away from last line. Every time line is added 4chrs more will be cut from last line (4chrs per every line) ---------------------------------------------------------------------- rvk · 2026-02-05 18:36 Quote from: OH1KH on February 05, 2026, 07:13:00 pmInteresting point is that adding one line more to file will cut 4 chrs away from last line. From what kind of file are you reading? Are you sure there is no BOM at the beginning of the file? It would be weird that in that case the last characters are stripped but maybe the stream doesn't handle BOM files correctly. Plus that BOM for utf-8 is 3 characters. What if you try a different file? Or just put something in a string and use WriteStrToStream(). Does it happen then too? ---------------------------------------------------------------------- OH1KH · 2026-02-05 19:45 OK! I made a separate test program (unit1.zip) It works with file file.zip  and the file does not cut when sent to Code: Bash  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i clear;while true; do printf '' | nc -l localhost 60006; done  Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i RE:6>KP01tn 1001815NNEU --463F8555_Synapse_boundary   Then I transferred a part of unit1.pas to the other program Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   try  //this loads file before the  data.loop just like unit1.pas do  F:=TMemoryStream.Create;    F.LoadFromFile('/tmp/ClubStream.adi');//-- --------------------------------------------------------     HTTP.ProxyHost := cqrini.ReadString('Program','Proxy','');    HTTP.ProxyPort := cqrini.ReadString('Program','Port','');    HTTP.UserName  := cqrini.ReadString('Program','User','');    HTTP.Password  := cqrini.ReadString('Program','Passwd','');     WriteStrToStream(HTTP.Document,CRLF + '--' + Bound);     for i:=0 to data.Count-1 do    begin       Key   := ExtractWord(1,data.Strings[i],['=']);      Value := ExtractWord(2,data.Strings[i],['=']);       if Key='file' then        begin// here is the copied part from unit1.pas           s := 'content-disposition: form-data; name="' + AnsiQuotedStr(Key, '"')  + '";';          s := s + ' filename="' + Value +'"' + CRLF;          s := s + 'Content-Type: Application/octet-string' + CRLF + CRLF;        WriteStrToStream(HTTP.Document, s);          HTTP.Document.CopyFrom(F, 0);          s := CRLF + '--' + Bound;        WriteStrToStream(HTTP.Document, s);//copy ends          {          WriteStrToStream(HTTP.Document, CRLF +            'Content- Disposition: form-data; name=' + AnsiQuotedStr(Key, '"') + ';' +          ' filename=' + AnsiQuotedStr(Value, '"')  + CRLF+            'Content-Type: application/octet-stream' +  CRLF + CRLF);            F.LoadFromFile(Value);          HTTP.Document.CopyFrom(F, 0);         WriteStrToStream(HTTP.Document,CRLF +CRLF + '--' +Bound);          }        end        else     And quess what? It cuts the last line like it has always done Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i RE:6>KP01tn1001815N202602051752OH1KJ40MC W7.0251752599599NNKP01tn100181 5NNEU202602 051806OH1KJ20MSSB14.218065959NNKP01tn1001815NNEU202602051807OH1KJ80MCW3.5180759 9599NNKP01tn1001815NNEU202602051807OH1KJ10MCW281807599599NNKP01tn1001815NNEU202 602051807OH1KJ17MCW18.11807599599NNKP01tn1001815 NNEU202602051808< CALL:5>OH1KJ17MFT818.11808599599NNKP01t n1001815NN< CONT:2>EU202602051809OH1KJ 20MFT8141809599599 NNKP01tn100 1815NNEU -- 6F64822D_Synapse_boundaryContent-Disposition: form-data; name="api"Content-Type: text/plain API --6F64822D_Synapse_boundary--  Also with a test with just reading it in a TStringList just before sending the data was correct. You can check on your end if the data you are about to send is ok. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  Dummy: TStringList;//...    Dummy := TStringList.Create;    try      HTTP.Document.Position := 0;      Dummy.LoadFromStream(HTTP.Document);      ShowMessage(Dummy.Text);    finally      Dummy.Free;    end;    Result := HTTP.HTTPMethod('POST', URL); Code: [Select][Window Title] project1 [Content] --6F64822D_Synapse_boundary Content-Disposition: form-data; name="api" Content-Type: text/plain API --6F64822D_Synapse_boundary content-disposition: form-data; name="file"; filename="c:\temp\ClubStream.adi" Content-Type: Application/octet-string 202602051752OH1KJ40MCW< FREQ:5>7.0251752599599 NNKP01tn1001815< LOTW_QSL_SENT:1>NNEU 202602051806OH1KJ20MSSB 14.218065959N< QSL_RCVD:1>NKP01tn1001815NNEU 202602051807OH1KJ80MCW< FREQ:3>3.51807599599N< QSL_RCVD:1>NKP01tn1001815NNEU 202602051807OH1KJ10MCW< FREQ:2>281807599599NNKP01tn1001815NNEU 202602051807OH1KJ17MCW< FREQ:4>18.11807599599N NKP01tn1001815NNEU 202602051808OH1KJ17MFT8 18.11808599599 NNKP01tn1001815< LOTW_QSL_SENT:1>NNEU 202602051809OH1KJ20MFT8 141809599599N< QSL_RCVD:1>NKP01tn1001815NNEU --6F64822D_Synapse_boundary Content-Disposition: form-data; name="api" Content-Type: text/plain API --6F64822D_Synapse_boundary-- [OK] ---------------------------------------------------------------------- OH1KH · 2026-02-06 09:50 Yes the test works fine also here. Thanks for debug code. I added that and it looks same as the result at localhost:60006 So the synapse does not change anything, the last line is broken already in HTTP.Document I made another test program taking whole function from original program and added just variables needed for running the function. Now the result is broken, just like in original program. Please test this one, you should now see it in windows, too. I think... ---------------------------------------------------------------------- rvk · 2026-02-06 10:03 Quote from: OH1KH on February 06, 2026, 10:50:16 amPlease test this one, you should now see it in windows, too. I think... You do know that the file you gave in this zip is cut off too?? ---------------------------------------------------------------------- OH1KH · 2026-02-06 10:04 Hi! There is error in last test program. In function  the F.LoadFromFile appears twice. There should only be F.LoadFromFile(Value); and not the one where filename exist. How ever removing that extra loadFromFile does not effect the result. It is still broken. 1815N15N