<commit_before>unit utils_dvalue_multiparts;

interface

uses
  utils_dvalue, utils.strings, SysUtils, Classes;

type
  TMultiPartsParser = class(TObject)
  private
    FBoundaryMatchIndex:Integer;
    FBoundaryBytes:TBytes;
    FBoundary: RAWString;
    FDecodeState:Integer;
    FData:TDValue;
    FCurrentHeaders:TDValue;
    FBuffer: TDBufferBuilder;
    FCurrentFieldName: String;
    FCurrentRAWHeader: RAWString;

    function ParseContentDisposition: Integer;
    function DecodeHeader: Integer;
    function DecodeContent: Integer;
    procedure DecodeHeaderLine(pvLine:string);

  public
    constructor Create;
    destructor Destroy; override;
    procedure SetDValue(pvData:TDValue);

    /// <summary>
    ///   0: Ҫ
    ///   1: 뵽ͷ
    ///   2뵽Content
    ///   9: 
    /// </summary>
    function InputBuffer(pvByte:Byte): Integer;

    property Boundary: RAWString read FBoundary;
    property CurrentFieldName: String read FCurrentFieldName write
        FCurrentFieldName;

    property CurrentRAWHeader: RAWString read FCurrentRAWHeader;
    


  end;


procedure MultiPartsEncode(v: TDValue; pvBuilder: TDBufferBuilder; pvBoundary:
    string; pvIgnoreValueTypes: TDValueDataTypes = [vdtInterface, vdtObject,
    vdtPtr]);

/// <summary>procedure MultiPartsParseFromStream
/// </summary>
/// <returns> Integer
/// </returns>
/// <param name="v"> (TDValue) </param>
/// <param name="pvStream"> (TStream) </param>
function MultiPartsParseFromStream(v: TDValue; pvStream: TStream): Integer;

function MultiPartsParseFromBuffer(v: TDValue; pvBuff: Pointer; pvOffset,
    pvBuffLen: Integer): Integer;

function MultiPartsParseFromFile(v:TDValue; pvFile:String): Integer;

procedure AddFieldValue(v: TDValue; pvFieldID:string; pvValue:String);
    
procedure AddFilePart(v: TDValue; pvFieldID:string; pvFileName:String;
    pvContentType:string = 'application/x-msdownload');

function SavePartValueToFile(v:TDValue; pvFieldID:string; pvFileName:String):
    Boolean;

function ExtractPartFileName(v: TDValue; pvFieldID: string; pvDefault: string =
    ''): string;

function ExtractValueAsRAWString(v: TDValue; pvFieldID: string; pvDefault:
    string = ''): String;

function ExtractValueAsUtf8String(v: TDValue; pvFieldID: string; pvDefault:
    string = ''): String;

function CreateUniqueBoundary: RAWString;




implementation

function CreateUniqueBoundary: RAWString;
begin
  Result := 'dvalue' + FormatDateTime('mmddyyhhnnsszzz', Now);
end;

procedure MultiPartsEncode(v: TDValue; pvBuilder: TDBufferBuilder; pvBoundary:
    string; pvIgnoreValueTypes: TDValueDataTypes = [vdtInterface, vdtObject, vdtPtr]);
var
  i, j:Integer;
  lvItem, lvName, lvValue, lvFileNameItem, lvChildItem:TDValue;
  lvBoundary, lvTempStr, lvNameStr:RAWString;
  lvFmt:RAWString;
begin
  lvBoundary := pvBoundary;
  if lvBoundary = '' then
  begin
     lvBoundary := CreateUniqueBoundary;
  end;

  for i := 0 to v.Count - 1 do
  begin
    lvItem := v.Items[i];
    if lvItem.Count = 0 then
    begin
      pvBuilder.AppendRawStr('--').AppendRawStr(lvBoundary).AppendBreakLineBytes;
      pvBuilder.AppendRawStr('Content-Disposition: form-data; name="').AppendUtf8(lvItem.Name.AsString).AppendRawStr('"').AppendBreakLineBytes;
      pvBuilder.AppendBreakLineBytes;
      pvBuilder.AppendUtf8(lvItem.AsString).AppendBreakLineBytes;
    end else
    begin
      lvName := lvItem.FindByName('name');
      lvValue := lvItem.FindByName('value');
      if (lvName <> nil) and (lvValue <> nil) then
      begin
        lvFileNameItem := lvItem.FindByName('fileName');
        if lvFileNameItem <> nil then
        begin
          pvBuilder.AppendRawStr('--').AppendRawStr(lvBoundary).AppendBreakLineBytes;
          pvBuilder.AppendRawStr('Content-Disposition: form-data; name="').AppendUtf8(lvName.AsString).AppendRawStr('"');
          pvBuilder.AppendRawStr('; filename="').AppendUtf8(lvFileNameItem.AsString).Append(Byte(Ord('"')));
          pvBuilder.AppendBreakLineBytes;
          for j := 0 to lvItem.Count - 1 do
          begin
            lvChildItem := lvItem.Items[j];
            lvNameStr := LowerCase(Trim(lvChildItem.Name.AsString));
            if (lvNameStr <> 'name') and (lvNameStr <> 'value') and (lvNameStr <> 'filename') then
            begin
              pvBuilder.AppendRawStr(lvNameStr).Append(Byte(Ord(':'))).AppendRawStr(lvChildItem.AsString).AppendBreakLineBytes;          
            end;
          end;
          pvBuilder.AppendBreakLineBytes;
          if lvValue.Value.DataType in [vdtObject] then
          begin
            if (lvValue.AsObject is TStream) then
            begin
              TStream(lvValue.AsObject).Position := 0;
              pvBuilder.LoadFromStream(TStream(lvValue.AsObject), TStream(lvValue.AsObject).Size);
            end;
          end else if lvValue.Value.DataType = vdtStream then
          begin
            lvValue.AsStream.Position := 0;
            pvBuilder.LoadFromStream(lvValue.AsStream, lvValue.AsStream.Size);
          end;
          pvBuilder.AppendBreakLineBytes;
        end else
        begin   // ֶͨ
          pvBuilder.AppendRawStr('--').AppendRawStr(lvBoundary).AppendBreakLineBytes;
          pvBuilder.AppendRawStr('Content-Disposition: form-data; name="').AppendUtf8(lvName.AsString).AppendRawStr('"').AppendBreakLineBytes;
          pvBuilder.AppendBreakLineBytes;
          pvBuilder.AppendUtf8(lvValue.AsString).AppendBreakLineBytes;
        end;
      end;
    end;
  end;
  pvBuilder.AppendRawStr('--').AppendRawStr(lvBoundary).AppendRawStr('--').AppendBreakLineBytes;
end;


procedure AddFilePart(v: TDValue; pvFieldID:string; pvFileName:String;
    pvContentType:string = 'application/x-msdownload');
var
  lvItem:TDValue;
begin
  if not FileExists(pvFileName) then
  begin
    raise Exception.CreateFmt('ļ[%s]', [pvFileName]);
  end;
  lvItem := v.ForceByName(pvFieldID);
  lvItem.ForceByName('name').AsString := pvFieldID;
  lvItem.ForceByName('fileName').AsString := ExtractFileName(pvFileName);
  lvItem.ForceByName('Content-Type').AsString := pvContentType;
  lvItem.ForceByName('value').AsStream.LoadFromFile(pvFileName);
end;

function MultiPartsParseFromStream(v: TDValue; pvStream: TStream): Integer;
const
  READ_SIZE = 4096;
var
  lvParser:TMultiPartsParser;
  lvBuff:array [ 0.. READ_SIZE - 1] of Byte;
  r, l, i, c, lvPrePos: Integer;
begin
  Result := 0;
  lvParser := TMultiPartsParser.Create;
  try
    lvParser.SetDValue(v);

    lvPrePos:= pvStream.Position;
    c := 0;
    try
      while True do
      begin
        l := pvStream.Read(lvBuff[0], READ_SIZE);
        if l = 0 then Break;
        for i := 0 to l - 1 do
        begin
          r :=lvParser.InputBuffer(lvBuff[i]);
          Inc(c);
          if r = 1 then
          begin
            ;
          end else if r = 2 then
          begin
            Inc(Result);
          end else if r = 9 then
          begin
            Inc(Result);
            // 
            exit;
          end;
        end;         
      end;
    finally
      pvStream.Position := lvPrePos + c;
    end;
  finally
    lvParser.Free;
  end;            
end;

function MultiPartsParseFromFile(v:TDValue; pvFile:String): Integer;
var
  lvFileStream:TFileStream;
begin
  lvFileStream := TFileStream.Create(pvFile, fmOpenRead);
  try
    Result := MultiPartsParseFromStream(v, lvFileStream);
  finally
    lvFileStream.Free;
  end;
end;

function MultiPartsParseFromBuffer(v: TDValue; pvBuff: Pointer; pvOffset,
    pvBuffLen: Integer): Integer;
var
  lvParser:TMultiPartsParser;
  lvPtr:PByte;
  r, j: Integer;
begin
  Result := 0;
  j := 0;
  lvPtr := PByte(pvBuff);
  Inc(lvPtr, pvOffset);
  lvParser := TMultiPartsParser.Create;
  try
    lvParser.SetDValue(v);
    while j < pvBuffLen do
    begin
      r :=lvParser.InputBuffer(lvPtr^);
      Inc(lvPtr);
      Inc(j);
      if r = 1 then
      begin
        ;
      end else if r = 2 then
      begin
        Inc(Result);
      end else if r = 9 then
      begin
        Inc(Result);
        // 
        exit;
      end;
    end;
  finally
    lvParser.Free;
  end;
end;

function SavePartValueToFile(v:TDValue; pvFieldID:string; pvFileName:String):
    Boolean;
var
  lvItem:TDValue;
begin
  Result := False;
  lvItem := v.FindByName(pvFieldID);
  if lvItem = nil then Exit;
  lvItem := lvItem.FindByName('__raw');
  if lvItem = nil then Exit;
  lvItem.AsStream.SaveToFile(pvFileName);
  Result := True;
end;

function ExtractValueAsRAWString(v: TDValue; pvFieldID: string; pvDefault:
    string = ''): String;
var
  lvItem:TDValue;
begin
  Result := pvDefault;
  lvItem := v.FindByName(pvFieldID);
  if lvItem = nil then Exit;
  lvItem := lvItem.FindByName('__raw');
  if lvItem = nil then Exit;

  Result := ByteBufferToString(PByte(lvItem.AsStream.Memory), lvItem.AsStream.Size);
end;

function ExtractValueAsUtf8String(v: TDValue; pvFieldID: string; pvDefault:
    string = ''): String;
var
  lvItem:TDValue;
begin
  Result := pvDefault;
  lvItem := v.FindByName(pvFieldID);
  if lvItem = nil then Exit;
  lvItem := lvItem.FindByName('__raw');
  if lvItem = nil then Exit;

  Result := Utf8BufferToString(PByte(lvItem.AsStream.Memory), lvItem.AsStream.Size);
end;

function ExtractPartFileName(v: TDValue; pvFieldID: string; pvDefault: string =
    ''): string;
var
  lvItem:TDValue;
begin
  Result := pvDefault;
  lvItem := v.FindByName(pvFieldID);
  if lvItem = nil then Exit;
  lvItem := lvItem.FindByName('fileName');
  if lvItem = nil then Exit;
  Result :=UTF8Decode(lvItem.AsString);
end;

procedure AddFieldValue(v: TDValue; pvFieldID:string; pvValue:String);
var
  lvItem:TDValue;
begin
  lvItem := v.ForceByName(pvFieldID);
  lvItem.ForceByName('name').AsString := pvFieldID;
  lvItem.ForceByName('value').AsString := pvValue; 
end;

constructor TMultiPartsParser.Create;
begin
  inherited Create;
  FBuffer := TDBufferBuilder.Create();
end;

destructor TMultiPartsParser.Destroy;
begin
  FreeAndNil(FBuffer);
  inherited Destroy;
end;

function TMultiPartsParser.DecodeContent: Integer;
var
  lvRAW:TMemoryStream;
  l:Integer;
begin
  lvRAW := FCurrentHeaders.ForceByName('__raw').AsStream;
  lvRAW.Clear;
  l := FBuffer.Length - Length(FBoundaryBytes) - 2;
  FCurrentHeaders.ForceByName('__raw-size').AsInteger := l;
  lvRAW.SetSize(l);
  Move(FBuffer.Memory^, lvRAW.Memory^, l);
  Result := 1;
end;

function TMultiPartsParser.DecodeHeader: Integer;
var
  lvPtr:PChar;
  lvLine:String;
begin
  FCurrentRAWHeader := FBuffer.ToRAWString;
  lvPtr := PChar(FCurrentRAWHeader);

  FCurrentHeaders := TDValue.Create;
  try
    Result := -1;
    FCurrentFieldName := '';
    while True do
    begin
      SkipChars(lvPtr,  [#13, #10, ' ', #9]);
      if LeftUntil(lvPtr, [#13, #10], lvLine) = 0 then
      begin
        DecodeHeaderLine(lvLine);
      end else
      begin
        break;
      end;
    end;

    if ParseContentDisposition = 1 then
    begin
      Result := 1;
      FData.AttachDValue(FCurrentFieldName, FCurrentHeaders);
    end;
  finally<commit_msg>* 修复dvalue_multiparts在unicode下面的bug<commit_after>unit utils_dvalue_multiparts;

interface

uses
  utils_dvalue, utils.strings, SysUtils, Classes;

type
  TMultiPartsParser = class(TObject)
  private
    FBoundaryMatchIndex:Integer;
    FBoundaryBytes:TBytes;
    FBoundary: RAWString;
    FDecodeState:Integer;
    FData:TDValue;
    FCurrentHeaders:TDValue;
    FBuffer: TDBufferBuilder;
    FCurrentFieldName: String;
    FCurrentRAWHeader: RAWString;

    function ParseContentDisposition: Integer;
    function DecodeHeader: Integer;
    function DecodeContent: Integer;
    procedure DecodeHeaderLine(pvLine:string);

  public
    constructor Create;
    destructor Destroy; override;
    procedure SetDValue(pvData:TDValue);

    /// <summary>
    ///   0: Ҫ
    ///   1: 뵽ͷ
    ///   2뵽Content
    ///   9: 
    /// </summary>
    function InputBuffer(pvByte:Byte): Integer;

    property Boundary: RAWString read FBoundary;
    property CurrentFieldName: String read FCurrentFieldName write
        FCurrentFieldName;

    property CurrentRAWHeader: RAWString read FCurrentRAWHeader;
    


  end;


procedure MultiPartsEncode(v: TDValue; pvBuilder: TDBufferBuilder; pvBoundary:
    string; pvIgnoreValueTypes: TDValueDataTypes = [vdtInterface, vdtObject,
    vdtPtr]);

/// <summary>procedure MultiPartsParseFromStream
/// </summary>
/// <returns> Integer
/// </returns>
/// <param name="v"> (TDValue) </param>
/// <param name="pvStream"> (TStream) </param>
function MultiPartsParseFromStream(v: TDValue; pvStream: TStream): Integer;

function MultiPartsParseFromBuffer(v: TDValue; pvBuff: Pointer; pvOffset,
    pvBuffLen: Integer): Integer;

function MultiPartsParseFromFile(v:TDValue; pvFile:String): Integer;

procedure AddFieldValue(v: TDValue; pvFieldID:string; pvValue:String);
    
procedure AddFilePart(v: TDValue; pvFieldID:string; pvFileName:String;
    pvContentType:string = 'application/x-msdownload');

function SavePartValueToFile(v:TDValue; pvFieldID:string; pvFileName:String):
    Boolean;

function ExtractPartFileName(v: TDValue; pvFieldID: string; pvDefault: string =
    ''): string;

function ExtractValueAsRAWString(v: TDValue; pvFieldID: string; pvDefault:
    string = ''): String;

function ExtractValueAsUtf8String(v: TDValue; pvFieldID: string; pvDefault:
    string = ''): String;

function CreateUniqueBoundary: RAWString;




implementation

function CreateUniqueBoundary: RAWString;
begin
  Result := 'dvalue' + FormatDateTime('mmddyyhhnnsszzz', Now);
end;

procedure MultiPartsEncode(v: TDValue; pvBuilder: TDBufferBuilder; pvBoundary:
    string; pvIgnoreValueTypes: TDValueDataTypes = [vdtInterface, vdtObject, vdtPtr]);
var
  i, j:Integer;
  lvItem, lvName, lvValue, lvFileNameItem, lvChildItem:TDValue;
  lvBoundary, lvTempStr, lvNameStr:RAWString;
  lvFmt:RAWString;
begin
  lvBoundary := pvBoundary;
  if lvBoundary = '' then
  begin
     lvBoundary := CreateUniqueBoundary;
  end;

  for i := 0 to v.Count - 1 do
  begin
    lvItem := v.Items[i];
    if lvItem.Count = 0 then
    begin
      pvBuilder.AppendRawStr('--').AppendRawStr(lvBoundary).AppendBreakLineBytes;
      pvBuilder.AppendRawStr('Content-Disposition: form-data; name="').AppendUtf8(lvItem.Name.AsString).AppendRawStr('"').AppendBreakLineBytes;
      pvBuilder.AppendBreakLineBytes;
      pvBuilder.AppendUtf8(lvItem.AsString).AppendBreakLineBytes;
    end else
    begin
      lvName := lvItem.FindByName('name');
      lvValue := lvItem.FindByName('value');
      if (lvName <> nil) and (lvValue <> nil) then
      begin
        lvFileNameItem := lvItem.FindByName('fileName');
        if lvFileNameItem <> nil then
        begin
          pvBuilder.AppendRawStr('--').AppendRawStr(lvBoundary).AppendBreakLineBytes;
          pvBuilder.AppendRawStr('Content-Disposition: form-data; name="').AppendUtf8(lvName.AsString).AppendRawStr('"');
          pvBuilder.AppendRawStr('; filename="').AppendUtf8(lvFileNameItem.AsString).Append(Byte(Ord('"')));
          pvBuilder.AppendBreakLineBytes;
          for j := 0 to lvItem.Count - 1 do
          begin
            lvChildItem := lvItem.Items[j];
            lvNameStr := LowerCase(Trim(lvChildItem.Name.AsString));
            if (lvNameStr <> 'name') and (lvNameStr <> 'value') and (lvNameStr <> 'filename') then
            begin
              pvBuilder.AppendRawStr(lvNameStr).Append(Byte(Ord(':'))).AppendRawStr(lvChildItem.AsString).AppendBreakLineBytes;          
            end;
          end;
          pvBuilder.AppendBreakLineBytes;
          if lvValue.Value.DataType in [vdtObject] then
          begin
            if (lvValue.AsObject is TStream) then
            begin
              TStream(lvValue.AsObject).Position := 0;
              pvBuilder.LoadFromStream(TStream(lvValue.AsObject), TStream(lvValue.AsObject).Size);
            end;
          end else if lvValue.Value.DataType = vdtStream then
          begin
            lvValue.AsStream.Position := 0;
            pvBuilder.LoadFromStream(lvValue.AsStream, lvValue.AsStream.Size);
          end;
          pvBuilder.AppendBreakLineBytes;
        end else
        begin   // ֶͨ
          pvBuilder.AppendRawStr('--').AppendRawStr(lvBoundary).AppendBreakLineBytes;
          pvBuilder.AppendRawStr('Content-Disposition: form-data; name="').AppendUtf8(lvName.AsString).AppendRawStr('"').AppendBreakLineBytes;
          pvBuilder.AppendBreakLineBytes;
          pvBuilder.AppendUtf8(lvValue.AsString).AppendBreakLineBytes;
        end;
      end;
    end;
  end;
  pvBuilder.AppendRawStr('--').AppendRawStr(lvBoundary).AppendRawStr('--').AppendBreakLineBytes;
end;


procedure AddFilePart(v: TDValue; pvFieldID:string; pvFileName:String;
    pvContentType:string = 'application/x-msdownload');
var
  lvItem:TDValue;
begin
  if not FileExists(pvFileName) then
  begin
    raise Exception.CreateFmt('ļ[%s]', [pvFileName]);
  end;
  lvItem := v.ForceByName(pvFieldID);
  lvItem.ForceByName('name').AsString := pvFieldID;
  lvItem.ForceByName('fileName').AsString := ExtractFileName(pvFileName);
  lvItem.ForceByName('Content-Type').AsString := pvContentType;
  lvItem.ForceByName('value').AsStream.LoadFromFile(pvFileName);
end;

function MultiPartsParseFromStream(v: TDValue; pvStream: TStream): Integer;
const
  READ_SIZE = 4096;
var
  lvParser:TMultiPartsParser;
  lvBuff:array [ 0.. READ_SIZE - 1] of Byte;
  r, l, i, c, lvPrePos: Integer;
begin
  Result := 0;
  lvParser := TMultiPartsParser.Create;
  try
    lvParser.SetDValue(v);

    lvPrePos:= pvStream.Position;
    c := 0;
    try
      while True do
      begin
        l := pvStream.Read(lvBuff[0], READ_SIZE);
        if l = 0 then Break;
        for i := 0 to l - 1 do
        begin
          r :=lvParser.InputBuffer(lvBuff[i]);
          Inc(c);
          if r = 1 then
          begin
            ;
          end else if r = 2 then
          begin
            Inc(Result);
          end else if r = 9 then
          begin
            Inc(Result);
            // 
            exit;
          end;
        end;         
      end;
    finally
      pvStream.Position := lvPrePos + c;
    end;
  finally
    lvParser.Free;
  end;            
end;

function MultiPartsParseFromFile(v:TDValue; pvFile:String): Integer;
var
  lvFileStream:TFileStream;
begin
  lvFileStream := TFileStream.Create(pvFile, fmOpenRead);
  try
    Result := MultiPartsParseFromStream(v, lvFileStream);
  finally
    lvFileStream.Free;
  end;
end;

function MultiPartsParseFromBuffer(v: TDValue; pvBuff: Pointer; pvOffset,
    pvBuffLen: Integer): Integer;
var
  lvParser:TMultiPartsParser;
  lvPtr:PByte;
  r, j: Integer;
begin
  Result := 0;
  j := 0;
  lvPtr := PByte(pvBuff);
  Inc(lvPtr, pvOffset);
  lvParser := TMultiPartsParser.Create;
  try
    lvParser.SetDValue(v);
    while j < pvBuffLen do
    begin
      r :=lvParser.InputBuffer(lvPtr^);
      Inc(lvPtr);
      Inc(j);
      if r = 1 then
      begin
        ;
      end else if r = 2 then
      begin
        Inc(Result);
      end else if r = 9 then
      begin
        Inc(Result);
        // 
        exit;
      end;
    end;
  finally
    lvParser.Free;
  end;
end;

function SavePartValueToFile(v:TDValue; pvFieldID:string; pvFileName:String):
    Boolean;
var
  lvItem:TDValue;
begin
  Result := False;
  lvItem := v.FindByName(pvFieldID);
  if lvItem = nil then Exit;
  lvItem := lvItem.FindByName('__raw');
  if lvItem = nil then Exit;
  lvItem.AsStream.SaveToFile(pvFileName);
  Result := True;
end;

function ExtractValueAsRAWString(v: TDValue; pvFieldID: string; pvDefault:
    string = ''): String;
var
  lvItem:TDValue;
begin
  Result := pvDefault;
  lvItem := v.FindByName(pvFieldID);
  if lvItem = nil then Exit;
  lvItem := lvItem.FindByName('__raw');
  if lvItem = nil then Exit;

  Result := ByteBufferToString(PByte(lvItem.AsStream.Memory), lvItem.AsStream.Size);
end;

function ExtractValueAsUtf8String(v: TDValue; pvFieldID: string; pvDefault:
    string = ''): String;
var
  lvItem:TDValue;
begin
  Result := pvDefault;
  lvItem := v.FindByName(pvFieldID);
  if lvItem = nil then Exit;
  lvItem := lvItem.FindByName('__raw');
  if lvItem = nil then Exit;

  Result := Utf8BufferToString(PByte(lvItem.AsStream.Memory), lvItem.AsStream.Size);
end;

function ExtractPartFileName(v: TDValue; pvFieldID: string; pvDefault: string =
    ''): string;
var
  lvItem:TDValue;
begin
  Result := pvDefault;
  lvItem := v.FindByName(pvFieldID);
  if lvItem = nil then Exit;
  lvItem := lvItem.FindByName('fileName');
  if lvItem = nil then Exit;
  Result :=UTF8Decode(lvItem.AsString);
end;

procedure AddFieldValue(v: TDValue; pvFieldID:string; pvValue:String);
var
  lvItem:TDValue;
begin
  lvItem := v.ForceByName(pvFieldID);
  lvItem.ForceByName('name').AsString := pvFieldID;
  lvItem.ForceByName('value').AsString := pvValue; 
end;

constructor TMultiPartsParser.Create;
begin
  inherited Create;
  FBuffer := TDBufferBuilder.Create();
end;

destructor TMultiPartsParser.Destroy;
begin
  FreeAndNil(FBuffer);
  inherited Destroy;
end;

function TMultiPartsParser.DecodeContent: Integer;
var
  lvRAW:TMemoryStream;
  l:Integer;
begin
  lvRAW := FCurrentHeaders.ForceByName('__raw').AsStream;
  lvRAW.Clear;
  l := FBuffer.Length - Length(FBoundaryBytes) - 2;
  FCurrentHeaders.ForceByName('__raw-size').AsInteger := l;
  lvRAW.SetSize(l);
  Move(FBuffer.Memory^, lvRAW.Memory^, l);
  Result := 1;
end;

function TMultiPartsParser.DecodeHeader: Integer;
var
  lvPtr:PChar;
  lvLine:String;
  lvHeader:string;
begin
  FCurrentRAWHeader := FBuffer.ToRAWString;
  lvHeader := FCurrentRAWHeader;
  lvPtr := PChar(lvHeader);

  FCurrentHeaders := TDValue.Create;
  try
    Result := -1;
    FCurrentFieldName := '';
    while True do
    begin
      SkipChars(lvPtr,  [#13, #10, ' ', #9]);
      if LeftUntil(lvPtr, [#13, #10], lvLine) = 0 then
      begin
        DecodeHeaderLine(lvLine);
      end else
      begin
        break;
      end;
    end;

    if ParseContentDisposition = 1 then
    begin
      Result := 1;
      FData.AttachDValue(FCurrentFieldName, FCurrentHeaders);
    end;
  finally
{'fraction_non_alphanumeric': 0.11331367657065095, 'fraction_numerical': 0.005080517124064414, 'mean_word_length': 3.338055883510429, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5506395', 'n_tokens_mistral': 7563, 'n_tokens_neox': 7242, 'n_words': 1517}