fpc/utils/fppkg/lnet/lhttputil.pp
Michaël Van Canneyt 28569057be * PChar -> PAnsiChar
2023-07-24 14:55:41 +02:00

302 lines
7.7 KiB
ObjectPascal

{ Utility routines for HTTP server component
Copyright (C) 2006-2008 by Micha Nelissen
This library is Free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is diStributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; withOut even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
for more details.
You should have received a Copy of the GNU Library General Public License
along with This library; if not, Write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
This license has been modified. See file LICENSE.ADDON for more information.
Should you find these sources without a LICENSE File, please contact
me at ales@chello.sk
}
unit lHTTPUtil;
{$mode objfpc}{$h+}
{$inline on}
interface
uses
sysutils,
strutils;
const
HTTPDateFormat: string = 'ddd, dd mmm yyyy hh:nn:ss';
HTTPAllowedChars = ['A'..'Z','a'..'z', '*','@','.','_','-',
'0'..'9', '$','!','''','(',')'];
type
PSearchRec = ^TSearchRec;
function GMTToLocalTime(ADateTime: TDateTime): TDateTime;
function LocalTimeToGMT(ADateTime: TDateTime): TDateTime;
function TryHTTPDateStrToDateTime(ADateStr: pansichar; var ADest: TDateTime): boolean;
function SeparatePath(var InPath: string; out ExtraPath: string; const Mode:Longint;
ASearchRec: PSearchRec = nil): boolean;
function CheckPermission(const ADocument: pansichar): boolean;
function HTTPDecode(AStr: pansichar): pansichar;
function HTTPEncode(const AStr: string): string;
function HexToNum(AChar: char): byte;
function DecomposeURL(const URL: string; out Host, URI: string; out Port: Word): Boolean;
function ComposeURL(Host, URI: string; const Port: Word): string;
implementation
uses
lCommon;
function GMTToLocalTime(ADateTime: TDateTime): TDateTime;
begin
Result := ADateTime + (TZSeconds*1000/MSecsPerDay);
end;
function LocalTimeToGMT(ADateTime: TDateTime): TDateTime;
begin
Result := ADateTime - (TZSeconds*1000/MSecsPerDay);
end;
function TryHTTPDateStrToDateTime(ADateStr: pansichar; var ADest: TDateTime): boolean;
var
lYear, lMonth, lDay: word;
lTime: array[0..2] of word;
I, lCode: integer;
begin
if StrLen(ADateStr) < Length(HTTPDateFormat)+4 then exit(false);
{ skip redundant short day string }
Inc(ADateStr, 5);
{ day }
if ADateStr[2] = ' ' then
ADateStr[2] := #0
else
exit(false);
Val(ADateStr, lDay, lCode);
if lCode <> 0 then exit(false);
Inc(ADateStr, 3);
{ month }
lMonth := 1;
repeat
if CompareMem(ADateStr, @ShortMonthNames[lMonth][1], 3) then break;
inc(lMonth);
if lMonth = 13 then exit(false);
until false;
Inc(ADateStr, 4);
{ year }
if ADateStr[4] = ' ' then
ADateStr[4] := #0
else
exit(false);
Val(ADateStr, lYear, lCode);
if lCode <> 0 then exit(false);
Inc(ADateStr, 5);
{ hour, minute, second }
for I := 0 to 2 do
begin
ADateStr[2] := #0;
Val(ADateStr, lTime[I], lCode);
Inc(ADateStr, 3);
if lCode <> 0 then exit(false);
end;
ADest := EncodeDate(lYear, lMonth, lDay) + EncodeTime(lTime[0], lTime[1], lTime[2], 0);
Result := true;
end;
function SeparatePath(var InPath: string; out ExtraPath: string; const Mode:Longint;
ASearchRec: PSearchRec = nil): boolean;
var
lFullPath: string;
lPos: integer;
lSearchRec: TSearchRec;
begin
if ASearchRec = nil then
ASearchRec := @lSearchRec;
ExtraPath := '';
if Length(InPath) <= 2 then exit(false);
lFullPath := InPath;
if InPath[Length(InPath)] = PathDelim then
SetLength(InPath, Length(InPath)-1);
repeat
Result := SysUtils.FindFirst(InPath, Mode, ASearchRec^) = 0;
SysUtils.FindClose(ASearchRec^);
if Result then
begin
ExtraPath := Copy(lFullPath, Length(InPath)+1, Length(lFullPath)-Length(InPath));
break;
end;
lPos := RPos(PathDelim, InPath);
if lPos > 0 then
SetLength(InPath, lPos-1)
else
break;
until false;
end;
function HexToNum(AChar: char): byte;
begin
if ('0' <= AChar) and (AChar <= '9') then
Result := ord(AChar) - ord('0')
else if ('A' <= AChar) and (AChar <= 'F') then
Result := ord(AChar) - (ord('A') - 10)
else if ('a' <= AChar) and (AChar <= 'f') then
Result := ord(AChar) - (ord('a') - 10)
else
Result := 0;
end;
function HTTPDecode(AStr: pansichar): pansichar;
var
lPos, lNext, lDest: pansichar;
begin
lDest := AStr;
repeat
lPos := AStr;
while not (lPos^ in ['%', '+', #0]) do
Inc(lPos);
if (lPos[0]='%') and (lPos[1] <> #0) and (lPos[2] <> #0) then
begin
lPos^ := ansichar((HexToNum(lPos[1]) shl 4) + HexToNum(lPos[2]));
lNext := lPos+2;
end else if lPos[0] = '+' then
begin
lPos^ := ' ';
lNext := lPos+1;
end else
lNext := nil;
Inc(lPos);
if lDest <> AStr then
Move(AStr^, lDest^, lPos-AStr);
Inc(lDest, lPos-AStr);
AStr := lNext;
until lNext = nil;
Result := lDest;
end;
function HTTPEncode(const AStr: string): string;
{ code from MvC's web }
var
src, srcend, dest: pchar;
hex: string[2];
len: integer;
begin
len := Length(AStr);
SetLength(Result, len*3); // Worst case scenario
if len = 0 then
exit;
dest := pchar(Result);
src := pchar(AStr);
srcend := src + len;
while src < srcend do
begin
if src^ in HTTPAllowedChars then
dest^ := src^
else if src^ = ' ' then
dest^ := '+'
else begin
dest^ := '%';
inc(dest);
hex := HexStr(Ord(src^),2);
dest^ := hex[1];
inc(dest);
dest^ := hex[2];
end;
inc(dest);
inc(src);
end;
SetLength(Result, dest - pchar(Result));
end;
function CheckPermission(const ADocument: pansichar): boolean;
var
lPos: pansichar;
begin
lPos := ADocument;
repeat
lPos := StrScan(lPos, '/');
if lPos = nil then exit(true);
if (lPos[1] = '.') and (lPos[2] = '.') and ((lPos[3] = '/') or (lPos[3] = #0)) then
exit(false);
inc(lPos);
until false;
end;
function DecomposeURL(const URL: string; out Host, URI: string; out Port: Word): Boolean;
var
n: Integer;
tmp: string;
begin
Result := False;
try
tmp := Trim(URL);
if Length(tmp) < 1 then // don't do empty
Exit;
Port := 80;
if tmp[Length(tmp)] = '/' then // remove trailing /
Delete(tmp, Length(tmp), 1);
if Pos('https://', tmp) = 1 then begin // check for HTTPS
Result := True;
Port := 443;
Delete(tmp, 1, 8); // delete the https part for parsing reasons
end else if Pos('http://', tmp) = 1 then begin
Delete(tmp, 1, 7); // delete the http part for parsing reasons
end;
n := Pos(':', tmp); // find if we have a port at the end
if n > 0 then begin
Port := StrToInt(Copy(tmp, n + 1, Length(tmp)));
Delete(tmp, n, Length(tmp));
end;
n := Pos('/', tmp); // find if we have a uri section
if n > 0 then begin
URI := Copy(tmp, n, Length(tmp));
Delete(tmp, n, Length(tmp));
end;
Host := tmp;
except
Host := 'error';
URI := '';
Port := 0;
end;
end;
function ComposeURL(Host, URI: string; const Port: Word): string;
begin
Host := Trim(Host);
URI := StringReplace(Trim(URI), '%20', ' ', [rfReplaceAll]);
if (Pos('http://', Host) <> 1)
and (Pos('https://', Host) <> 1) then
Host := 'http://' + Host;
if URI[Length(URI)] = '/' then
Delete(URI, Length(URI), 1);
if (Host[Length(Host)] = '/')
and (URI[1] = '/') then
Delete(Host, Length(Host), 1)
else if (URI[1] <> '/')
and (Host[Length(Host)] <> '/') then
Host := Host + '/';
Result := Host + URI + ':' + IntToStr(Port);
end;
end.