//
//    RFC 3986 Uniform Resource Identifier (URI): Generic Syntax
//      https://tools.ietf.org/html/rfc3986
//
{$MODE FPC}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}
unit duri;

interface

uses
  dansiview;

// Low-level reading uri components
function UriParseScheme(Cursor, UriEnd: PAnsiChar;
                      out Scheme, SchemeEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
function UriParseUserInfo(Cursor, UriEnd: PAnsiChar;
                      out UserInfo, UserInfoEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
function UriParseHost(Cursor, UriEnd: PAnsiChar;
                      out Host, HostEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
function UriParsePort(Cursor, UriEnd: PAnsiChar;
                      out Port, PortEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
function UriParsePath(Cursor, UriEnd: PAnsiChar;
                      out Path, PathEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
function UriParseQuery(Cursor, UriEnd: PAnsiChar;
                      out Query, QueryEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
function UriParseFragment(Cursor, UriEnd: PAnsiChar;
                      out Fragment, FragmentEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;

type
TParsedUri = record
  // Full Uri
  Uri: TAnsiView;
  // Five components of uri
  SchemeDefined: Boolean;
  Scheme: TAnsiView;
  AuthorityDefined: Boolean;
  Authority: TAnsiView;
  PathDefined: Boolean;
  Path: TAnsiView;
  QueryDefined: Boolean;
  Query: TAnsiView;
  FragmentDefined: Boolean;
  Fragment: TAnsiView;
  // Parsed authority
  AuthorityParsed: Boolean;
  UserInfo: TAnsiView;
  Host: TAnsiView;
  Port: TAnsiView;
end;

function ParseUri(Uri: TAnsiView; out Parsed: TParsedUri): Boolean;
function ParseUriLight(Uri: TAnsiView; out Parsed: TParsedUri): Boolean;
function ParseUriAuthority(Authority: TAnsiView; out Parsed: TParsedUri): Boolean;
// URI-reference
function ParseUriReference(Uri: TAnsiView; out Parsed: TParsedUri): Boolean;
// absolute-URI
function ParseAbsoluteUri(Uri: TAnsiView; out Parsed: TParsedUri): Boolean;
// Same-document reference?
// Suffix reference?

function TransformUriReference(const Base: TParsedUri;
                               Uri, UriEnd: PAnsiChar;
                               out R: TParsedUri;
                               Strict: Boolean): Boolean;

function NextCgiParam(CgiParams, CgiParamsEnd: PAnsiChar;
                      out Key, Value: TAnsiView;
                      out NewCgiParams: PAnsiChar): Boolean
function URLDecode(Value, ValueEnd: PAnsiChar;
                   OutBuffer: PAnsiChar): PAnsiChar;
function TryCgiEncode(Value: TAnsiView;
                      OutBuffer, OutBufferEnd: PAnsiChar;
                      out NewBuffer: PAnsiChar): Boolean;

function AppendCgiParam(Buffer, BufferEnd: PAnsiChar;
                        Key, Value: TAnsiView;
                        out NewBuffer: PAnsiChar): Boolean;

implementation

function UriParseScheme(Cursor, UriEnd: PAnsiChar;
                        out Scheme, SchemeEnd: PAnsiChar;
                        out NewCursor: PAnsiChar): Boolean;
var
  L: LongInt;
begin
  L := IndexChar(Cursor^, UriEnd - Cursor, ':');
  if L <> -1 then begin
    Scheme := Cursor;
    SchemeEnd := Cursor + L;
    NewCursor := SchemeEnd + 1;
    Exit(True);
  end else
    Exit(False);
end;

function UriParseUserInfo(Cursor, UriEnd: PAnsiChar;
                      out UserInfo, UserInfoEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
begin
  Result := False;
end;

function UriParseHost(Cursor, UriEnd: PAnsiChar;
                      out Host, HostEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
begin
  Result := False;
end;

function UriParsePort(Cursor, UriEnd: PAnsiChar;
                      out Port, PortEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
begin
  Result := False;
end;

function UriParsePath(Cursor, UriEnd: PAnsiChar;
                      out Path, PathEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
begin
  Result := False;
end;

function UriParseQuery(Cursor, UriEnd: PAnsiChar;
                      out Query, QueryEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
begin
  Result := False;
end;

function UriParseFragment(Cursor, UriEnd: PAnsiChar;
                      out Fragment, FragmentEnd: PAnsiChar;
                      out NewCursor: PAnsiChar): Boolean;
begin
  Result := False;
end;

function ParseUri(Uri: TAnsiView; out Parsed: TParsedUri): Boolean;
begin
  Result := ParseUriLight(Uri, Parsed)
        and ParseUriAuthority(Parsed.Authority, Parsed);
end;

function ParseUriLight(Uri: TAnsiView; out Parsed: TParsedUri): Boolean;
begin
  Result := False;
end;

function ParseUriAuthority(Authority: TAnsiView; out Parsed: TParsedUri): Boolean;
begin
  Exit(False);
end;

function ParseUriReference(Uri: TAnsiView; out Parsed: TParsedUri): Boolean;
begin
  Exit(False);
end;

function ParseAbsoluteUri(Uri: TAnsiView; out Parsed: TParsedUri): Boolean;
begin
  Exit(False);
end;

function _RemoveDotSegments(Path, PathEnd: PAnsiChar): PAnsiChar;
begin
  // TODO
  Result := PathEnd;
end;

function TransformUriReference(const Base: TParsedUri;
                               Uri, UriEnd: PAnsiChar;
                               out R: TParsedUri;
                               Strict: Boolean): Boolean;
begin
  if not ParseUri(AnsiView(Uri, UriEnd), R) then
    Exit(False);
  if (not Strict) and (R.Scheme = Base.Scheme) then
    R.SchemeDefined := False;
  if R.SchemeDefined then begin
    R.Path.Tail := _RemoveDotSegments(R.Path.Base, R.Path.Tail);
  end else begin
    if R.AuthorityDefined then begin
    end else begin
      if R.PathDefined then begin
        R.Path := Base.Path;
        if R.QueryDefined then
          R.Query := Base.Query;
      end else begin
        if R.Path.StartsWith('/') then begin
          R.Path.Tail := _RemoveDotSegments(R.Path.Base, R.Path.Tail);
        end else begin
          if (Base.Authority <> '') and (Base.Path = '') then begin
            // TODO R.Path := merge('/', R.Path);
          end else begin
            // TODO Стереть все символы Base.Path после последнего слеша
            //      Затем R.Path := Base.Path + R.Path
          end;
          R.Path.Tail := _RemoveDotSegments(R.Path.Base, R.Path.Tail);
        end;
      end;
      R.UserInfo := Base.UserInfo;
      R.Host := Base.Host;
      R.Port := Base.Port;
    end;
    R.Scheme := Base.Scheme;
  end;
end;

function NextCgiParam(CgiParams, CgiParamsEnd: PAnsiChar;
                      out Key, Value: TAnsiView;
                      out NewCgiParams: PAnsiChar): Boolean
begin
  // TODO
  NewCgiParams := CgiParamsEnd;
  Result := False;
end;

function URLDecode(Value, ValueEnd: PAnsiChar;
                   OutBuffer: PAnsiChar): PAnsiChar;
begin
  // TODO идём слева направо, заменяем %xx на символ
  Result := ValueEnd;
end;

function TryCgiEncode(Value: TAnsiView;
                      OutBuffer, OutBufferEnd: PAnsiChar;
                      out NewBuffer: PAnsiChar): Boolean;
begin
  Move(Value.Base^, NewBuffer^, Value.Tail - Value.Base);
end;

function AppendCgiParam(Buffer, BufferEnd: PAnsiChar;
                        Key, Value: TAnsiView;
                        out NewBuffer: PAnsiChar): Boolean;
begin
  Result := True;
end;

end.
