{ +-------------------------------------------------------------+ } { | | } { | GM-Software | } { | =========== | } { | | } { | Project: All Projects | } { | | } { | Description: Basic Internet things. | } { | | } { | | } { | Copyright (C) - 2013 - Gerrit Moeller. | } { | | } { | Source code distributed under MIT license. | } { | | } { | See: https://www.gm-software.de | } { | | } { +-------------------------------------------------------------+ } {$INCLUDE GMCompilerSettings.inc} unit GMINetBase; interface uses {$IFDEF JEDIAPI}{$IFNDEF FPC}jwaWinType,{$ENDIF}{$ELSE}Windows,{$ENDIF} GMActiveX, GMStrDef, GMCollections, GMIntf, GMCommon, GMUnionValue, GMSockets; const CR = #$0d; // : AnsiChar LF = #$0a; // : AnsiChar CRLF = CR + LF; // : AnsiString = CR + LF; //cUriGenericDelimiters = ':/?#[]@'; //cUriSubDelimiters = '!$&''()*+,;='; //cUriUnreservedChars = '-._~'; cUriGenericDelimiters = [':', '/', '?', '#', '[', ']', '@']; cUriSubDelimiters = ['!', '$', '&', '''', '(', ')', '*', '+', ',', ';', '=']; cUriUnreservedChars = ['-', '.', '_', '~']; cUriNotPercentEncodeChars = cUriGenericDelimiters + cUriSubDelimiters + cUriUnreservedChars; cStrCommand = 'Command'; cStrHidden = '*hidden*'; cStrINetHeaderWordSeparators = cWhiteSpace + ';:,!^??$&/=?\[]{}()<>?`*+~#''|'; cStrContent = 'CONTENT'; type EGMINetException = class(EGMException); TGMINetExceptionClass = class of EGMINetException; TCmdResponse = record Code: AnsiString; Text: TGMStringArray; end; TGMINetHeaderAddMode = (hamAlwaysAdd, hamAddIfNew, hamReplaceIfExists, hamCoalesce); TGMINetProtocolBase = class; IGMINetProtocolBase = interface(IUnknown) ['{F3DACEB3-8B3B-41B2-998E-8E9FFA12B65F}'] function Obj: TGMINetProtocolBase; end; TGMINetProtocolBase = class(TGMRefCountedObj, IGMINetProtocolBase) protected FHeadersToSend, FReceivedHeaders: IGMIntfCollection; function ExceptClassForCode(const ACode: AnsiString): TGMINetExceptionClass; virtual; function IsHeaderTermLine(const ALine: AnsiString): Boolean; virtual; function BuildErrorMsgPostfixFromResponseContent(const AResponseContent: ISequentialStream): TGMString; virtual; public // TransportLayer: ISequentialStream; // constructor Create(const ATransportLayer: ISequentialStream; const ARefLifeTime: Boolean = True); reintroduce; overload; function ConsumeContent(const ATransportLayer: ISequentialStream): TGMString; virtual; function Obj: TGMINetProtocolBase; function ProtocolDisplayName: TGMString; virtual; function ReadResponseLine(const ATransportLayer: ISequentialStream): AnsiString; function ReceiveCmdResponse(const ATransportLayer: ISequentialStream): TCmdResponse; function CheckCmdResponse(const ACommand: TGMString; const ACmdResponse: TCmdResponse; const ASuccessCodes: TGMString; const ACallingName: TGMString = ''): TCmdResponse; function ExecCommandStr(const ATransportLayer: ISequentialStream; ACommand, ASuccessCodes: TGMString; ACallingName: TGMString = ''; const AShowTrace: Boolean = True): TCmdResponse; function ReceiveHeaders(const ATransportLayer: ISequentialStream; const AHeaders: IGMIntfCollection): TGMString; virtual; function HeadersToSend: IGMIntfCollection; function ReceivedHeaders: IGMIntfCollection; end; TGMINetHeaderEntry = class(TGMNameAndStrValueObj) public constructor Create(const AHeaderLine: TGMString; const ARefLifeTime: Boolean = True); reintroduce; overload; end; TGMInetHeaderIterator = class(TGMRefCountedObj, IGMIterator) protected FBaseIterator: IGMIterator; FHeaderName: TGMString; public constructor Create(const ABaseIterator: IGMIterator; const AHeaderName: TGMString; const ARefLifeTime: Boolean = True); reintroduce; function NextEntry(out AEntry): Boolean; procedure Reset; end; IGMGenericHeaderCollection = interface(IGMGenericCollection<RGMNameAndStrValue>) procedure AddHeadersFromLines(const AHeaderLines: TGMString; const AAddMode: TGMINetHeaderAddMode = hamAlwaysAdd); procedure AddHeader(const ANewHeader: RGMNameAndStrValue; const AAddMode: TGMINetHeaderAddMode= hamAlwaysAdd); overload; procedure AddHeader(const AName, AValue: TGMString; const AAddMode: TGMINetHeaderAddMode = hamAlwaysAdd); overload; function CreateHeaderIterator(const AHeaderName: TGMString; const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMGenericIterator<RGMNameAndStrValue>; end; TGMGenericHeaderCollection = class(TGMGenericArrayCollection<RGMNameAndStrValue>, IGMGenericHeaderCollection) // , IGMGenericHeaderCollection<RGMNameAndStrValue> public procedure AddHeadersFromLines(const AHeaderLines: TGMString; const AAddMode: TGMINetHeaderAddMode = hamAlwaysAdd); procedure AddHeader(const ANewHeader: RGMNameAndStrValue; const AAddMode: TGMINetHeaderAddMode = hamAlwaysAdd); overload; procedure AddHeader(const AName, AValue: TGMString; const AAddMode: TGMINetHeaderAddMode = hamAlwaysAdd); overload; function CreateHeaderIterator(const AHeaderName: TGMString; const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMGenericIterator<RGMNameAndStrValue>; end; TGMGenericHeaderIterator = class(TGMRefCountedObj, IGMGenericIterator<RGMNameAndStrValue>) protected FBaseIterator: IGMGenericIterator<RGMNameAndStrValue>; FHeaderName: TGMString; public constructor Create(const ABaseIterator: IGMGenericIterator<RGMNameAndStrValue>; const AHeaderName: TGMString; const ARefLifeTime: Boolean = True); reintroduce; function NextEntry(var AEntry: RGMNameAndStrValue): Boolean; procedure Reset; end; EGMUriComponent = (uriScheme, uriUser, uriPassword, uriHost, uriPort, uriPath, uriQuery, uriFragment); EGMInitUriComponents = set of EGMUriComponent; const cAllUriComponents = [uriScheme .. uriFragment]; cDfltUriComponents = [uriScheme .. uriPath]; type RGMUriComponents = record Scheme: TGMString; User: TGMString; Password: TGMString; Host: TGMString; Port: TGMString; Path: TGMString; Query: TGMString; Fragment: TGMString; function Uri: TGMString; //function HostHeader(const ADefaultPort: TGMString): TGMString; procedure Clear; end; function GMInitUriComponents(const AScheme, AUser, APassword, AHost, APort, APath, AQuery, AFragment: TGMString): RGMUriComponents; function GMUriEncode(const AUrl: AnsiString; const AIgnorePercentChars: Boolean = True): AnsiString; function GMUriDecode(const AUrl: AnsiString): AnsiString; //function GMDecodeURLParams(const AUrlParams: AnsiString): AnsiString; //function GMEncodeURLParams(const AUrlParams: AnsiString): AnsiString; function GMBuildUri(const AUriComponents: RGMUriComponents): TGMString; overload; function GMBuildUri(const AScheme, AUser, APassword, AHost, APort, APath: TGMString; const AQuery: TGMSTring = ''; const AFragment: TGMString = ''): TGMString; overload; function GMParseUri(const AUri: TGMString): RGMUriComponents; function GMMakeUriDefaultScheme(const AUri: TGMString; const ADefaultScheme: TGMString): TGMString; function GMMergeUris(const AMergeFromUri, AMergeToUri: TGMString; const AComponents: EGMInitUriComponents = cDfltUriComponents): TGMString; function GMTimeZoneBias: LongInt; function GMINetTimeZoneOffsetFromUtc(const ATimeZone: TGMString; const ACaller: TObject = nil): TDateTime; function GetINetMonthNo(const AMonthName: TGMString; const ACaller: TObject = nil): Word; function GMDecodeINetTimeToUtc(const ATimeValue: TGMString; const ACaller: TObject = nil): TDateTime; function GMEncodeUtcToINetTime(const AUtcTime: TDateTime; const ACaller: TObject = nil): TGMString; function GMIso8601DateTimeToStr(const ALocalTime: TDateTime): TGMString; //function GMReadResponseLine(const AStream: ISequentialStream; const AProtocolDisplayName: TGMString; const ACaller: TObject): AnsiString; function GMGetINetHeaderStrValue(const AHttpHeaders: IGMIntfCollection; const AHeaderName: TGMString; const ADefaultValue: TGMString = ''; const ACheckHeaderExists: Boolean = False; const ACaller: TObject = nil): TGMString; function GMGetINetHeaderIntValue(const AHttpHeaders: IGMIntfCollection; const AHeaderName: TGMString; const ADefaultValue: Int64 = 0): Int64; procedure GMAddINetHeader(const AHeaders: IGMIntfCollection; const AName: TGMString; const AValue: RGMUnionValue; const AAddStrategy: TGMINetHeaderAddMode = hamReplaceIfExists); function GMHeadersAsString(const AHeaders: IGMIntfCollection): TGMString; function GMSplitHeaderLine(const AHeaderLine: TGMString): RGMNameAndStrValue; function GMSplitURIParams(const AURI: TGMString; const AParams: IGMIntfCollection): TGMString; function GMDeleteCharsA(const AValue, ADelChars: AnsiString; const NotDelChars: Boolean = False): AnsiString; function GMCharCodingOfContentType(const AContentType: TGMString; const ADefaultCharKind: TGMCharKind = ckUnknown): TGMCharKind; function CompareHeaderByName(const EntryA, EntryB: RGMNameAndStrValue): TGMCompareResult; procedure GMParseHeadersToArray(const AHeaderLines: TGMString; var AHeaderArray: TGMNameAndStrValArray); function IsIP6Address(const AAddress: TGMString): Boolean; // Helper routines function Int16ToLittleEndian(AValue: SmallInt): SmallInt; function UInt16ToLittleEndian(AValue: Word): Word; function Int32ToLittleEndian(AValue: LongInt): LongInt; function UInt32ToLittleEndian(AValue: LongWord): LongWord; function Int64ToLittleEndian(AValue: Int64): Int64; function Int16fromLittleEndian(AValue: SmallInt): SmallInt; function UInt16FromLittleEndian(AValue: Word): Word; function Int32FromLittleEndian(AValue: LongInt): LongInt; function UInt32FromLittleEndian(AValue: LongWord): LongWord; function Int64FromLittleEndian(AValue: Int64): Int64; var vINetDayNames: array [0..6] of TGMString = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'); vINetMonthNames: array [1..12] of TGMString = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); resourcestring RStrInvalidItemFmt = 'Invalid %s: %s'; RStrMonthName = 'month name'; RStrHour = 'hour'; RStrMinute = 'minute'; RStrSecond = 'second'; RStrMilliSecond = 'millisecond'; RStrDay = 'day'; RStrMonth = 'month'; RStrYear = 'year'; RStrTimeZone = 'time zone'; RStrWrongContentType = 'The server response is of type "%s" instead of "%s"'; RStrUnsupportedINetProtocol = 'Unsupported network protocol: "%s"'; RStrHeaderFieldNotFound = 'Header field "%s" not found'; implementation uses SysUtils {$IFDEF JEDIAPI},jwaWinBase, jwaWinNT{$ENDIF}; //const // //cStrWinINetDLL = 'WinINet.dll'; //INTERNET_RFC1123_FORMAT = 0; //INTERNET_RFC1123_BUFSIZE = 35; {$IFDEF UNICODE} //function InternetTimeToSystemTime(lpszTime: PWideChar; out pst: TSystemTime; dwReserved: DWORD): BOOL; stdcall; external cStrWinINetDLL name 'InternetTimeToSystemTimeW'; //function InternetTimeFromSystemTime(const pst: TSystemTime; dwRFC: DWORD; lpszTime: PWideChar; cbTime: DWORD): BOOL; stdcall; external cStrWinINetDLL name 'InternetTimeFromSystemTimeW'; {$ELSE} //function InternetTimeToSystemTime(lpszTime: PAnsiChar; out pst: TSystemTime; dwReserved: DWORD): BOOL; stdcall; external cStrWinINetDLL name 'InternetTimeToSystemTimeA'; //function InternetTimeFromSystemTime(const pst: TSystemTime; dwRFC: DWORD; lpszTime: PAnsiChar; cbTime: DWORD): BOOL; stdcall; external cStrWinINetDLL name 'InternetTimeFromSystemTimeA'; {$ENDIF} resourcestring //RStrInvalidHeaderSeparator = 'Invalid %s Header separator'; RStrInvalidCommandTerm = 'Invalid %s command response terminator'; RStrInvalidResponeCodeFmt = 'Invalid %s command response code: %s'; RStrCmdError = '%s "%s" command error (%s): %s'; RStrTransportLayerStream = 'The transport layer stream'; //RStrBadResponseCode = 'The %s command returned a bad code: %s'; RStrNoResponseCode = 'No code returned for %s command "%s"'; RStrResponse = 'Response'; { ------------------------- } { ---- Global Routines ---- } { ------------------------- } function Int16ToLittleEndian(AValue: SmallInt): SmallInt; begin // To Do: Swap if meory layout ist not little endian Result := AValue; end; function UInt16ToLittleEndian(AValue: Word): Word; begin // To Do: Swap if meory layout ist not little endian Result := AValue; end; function Int32ToLittleEndian(AValue: LongInt): LongInt; begin // To Do: Swap if meory layout ist not little endian Result := AValue; end; function UInt32ToLittleEndian(AValue: LongWord): LongWord; begin // To Do: Swap if meory layout ist not little endian Result := AValue; end; function Int64ToLittleEndian(AValue: Int64): Int64; begin // To Do: Swap if meory layout ist not little endian Result := AValue; end; function Int16fromLittleEndian(AValue: SmallInt): SmallInt; begin // To Do: Swap if meory layout ist not little endian Result := AValue; end; function UInt16FromLittleEndian(AValue: Word): Word; begin // To Do: Swap if meory layout ist not little endian Result := AValue; end; function Int32FromLittleEndian(AValue: LongInt): LongInt; begin // To Do: Swap if meory layout ist not little endian Result := AValue; end; function UInt32FromLittleEndian(AValue: LongWord): LongWord; begin // To Do: Swap if meory layout ist not little endian Result := AValue; end; function Int64FromLittleEndian(AValue: Int64): Int64; begin // To Do: Swap if meory layout ist not little endian Result := AValue; end; function GMTimeZoneBias: LongInt; var tzInfo: TTimeZoneInformation; begin Result := 0; FillByte(tzInfo, SizeOf(tzInfo), 0); case GetTimeZoneInformation(tzInfo) of TIME_ZONE_ID_UNKNOWN: Result := tzInfo.Bias; TIME_ZONE_ID_STANDARD: Result := tzInfo.Bias + tzInfo.StandardBias; TIME_ZONE_ID_DAYLIGHT: Result := tzInfo.Bias + tzInfo.DaylightBias; TIME_ZONE_ID_INVALID: GMAPICheckObj('GetTimeZoneInformation', '', GetLastError, False); end; end; function GMINetTimeZoneOffsetFromUtc(const ATimeZone: TGMString; const ACaller: TObject): TDateTime; type TTimeZoneDelta = record ZoneName: TGMString; DeltaHours: LongInt; end; const cOneHour = 1 / 24; cOneMinute = cOneHour / 60; // see: http://www.timeanddate.com/library/abbreviations/timezones/ cTimeZones: array [0..50] of TTimeZoneDelta = ( (ZoneName: 'NZDT'; DeltaHours: 13), (ZoneName: 'IDLE'; DeltaHours: 12), (ZoneName: 'NZST'; DeltaHours: 12), (ZoneName: 'NZT'; DeltaHours: 12), (ZoneName: 'EADT'; DeltaHours: 11), (ZoneName: 'GST'; DeltaHours: 10), (ZoneName: 'JST'; DeltaHours: 9), (ZoneName: 'CCT'; DeltaHours: 8), (ZoneName: 'WADT'; DeltaHours: 8), (ZoneName: 'WAST'; DeltaHours: 7), (ZoneName: 'ZP6'; DeltaHours: 6), (ZoneName: 'ZP5'; DeltaHours: 5), (ZoneName: 'ZP4'; DeltaHours: 4), (ZoneName: 'BT'; DeltaHours: 3), (ZoneName: 'EET'; DeltaHours: 2), (ZoneName: 'MEST'; DeltaHours: 2), (ZoneName: 'MESZ'; DeltaHours: 2), (ZoneName: 'SST'; DeltaHours: 2), (ZoneName: 'FST'; DeltaHours: 2), (ZoneName: 'CEST'; DeltaHours: 2), (ZoneName: 'CET'; DeltaHours: 1), (ZoneName: 'FWT'; DeltaHours: 1), (ZoneName: 'MET'; DeltaHours: 1), (ZoneName: 'MEWT'; DeltaHours: 1), (ZoneName: 'SWT'; DeltaHours: 1), (ZoneName: 'UT'; DeltaHours: 0), (ZoneName: 'UTC'; DeltaHours: 0), (ZoneName: 'GMT'; DeltaHours: 0), (ZoneName: 'WET'; DeltaHours: 0), (ZoneName: 'WAT'; DeltaHours: -1), (ZoneName: 'BST'; DeltaHours: -1), (ZoneName: 'AT'; DeltaHours: -2), (ZoneName: 'ADT'; DeltaHours: -3), (ZoneName: 'AST'; DeltaHours: -4), (ZoneName: 'EDT'; DeltaHours: -4), (ZoneName: 'EST'; DeltaHours: -5), (ZoneName: 'CDT'; DeltaHours: -5), (ZoneName: 'CST'; DeltaHours: -6), (ZoneName: 'MDT'; DeltaHours: -6), (ZoneName: 'MST'; DeltaHours: -7), (ZoneName: 'PDT'; DeltaHours: -7), (ZoneName: 'PST'; DeltaHours: -8), (ZoneName: 'YDT'; DeltaHours: -8), (ZoneName: 'YST'; DeltaHours: -9), (ZoneName: 'HDT'; DeltaHours: -9), (ZoneName: 'AHST'; DeltaHours: -10), (ZoneName: 'CAT'; DeltaHours: -10), (ZoneName: 'HST'; DeltaHours: -10), (ZoneName: 'EAST'; DeltaHours: -10), (ZoneName: 'NT'; DeltaHours: -11), (ZoneName: 'IDLW'; DeltaHours: -12)); var i: LongInt; begin if (Length(ATimeZone) <= 0) or ((Length(ATimeZone) = 1) and (GMUpCase(ATimeZone[1]) = 'Z')) then begin Result := 0; Exit; end; if (Length(ATimeZone) > 0) and GMIsDelimiter('+-', ATimeZone, 1) then begin Result := StrToIntDef(Copy(ATimeZone, 2, 2), 0) * cOneHour + StrToIntDef(Copy(ATimeZone, 4, 2), 0) * cOneMinute; if (ATimeZone[1] = '-') then if Result <> 0 then Result := Result * -1 else Result := -GMTimeZoneBias * cOneMinute; end else begin for i:=Low(cTimeZones) to High(cTimeZones) do if GMSameText(ATimeZone, cTimeZones[i].ZoneName) then begin Result := cTimeZones[i].DeltaHours * cOneHour; Exit; end; raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrTimeZone, ATimeZone]), ACaller); end; end; function GetINetMonthNo(const AMonthName: TGMString; const ACaller: TObject): Word; begin for Result:=Low(vINetMonthNames) to High(vINetMonthNames) do if GMSameText(AMonthName, vINetMonthNames[Result]) then Exit; raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrMonthName, AMonthName]), ACaller); end; function GMNextDigitToken(var AChPos: LongInt; const AValue: TGMString; const ASkipLeadingNonDigits: Boolean = True): TGMString; var startPos: Integer; begin if ASkipLeadingNonDigits then while (AChPos <= Length(AValue)) and not GMIsDigit(AValue[AChPos]) do Inc(AChPos); startPos := AChPos; while (AChPos <= Length(AValue)) and GMIsDigit(AValue[AChPos]) do Inc(AChPos); if AChPos > startPos then Result := Copy(AValue, startPos, AChPos-startPos) else Result := ''; {if SkipSeparators then} while (AChPos <= Length(AValue)) and GMIsDelimiter(cWhiteSpace, AValue, AChPos) do Inc(AChPos); end; function GMNextLetterToken(var AChPos: LongInt; const AValue: TGMString; const ASkipLeadingNonLetters: Boolean = True): TGMString; var startPos: Integer; begin if ASkipLeadingNonLetters then while (AChPos <= Length(AValue)) and not GMIsLetter(AValue[AChPos]) do Inc(AChPos); startPos := AChPos; while (AChPos <= Length(AValue)) and GMIsLetter(AValue[AChPos]) do Inc(AChPos); if AChPos > startPos then Result := Copy(AValue, startPos, AChPos-startPos) else Result := ''; {if SkipSeparators then} while (AChPos <= Length(AValue)) and GMIsDelimiter(cWhiteSpace, AValue, AChPos) do Inc(AChPos); end; function GMAdjust2DigitYears(AYear: Integer): Integer; const cOneCentury: array [Boolean] of Integer = (0, 1); var year, month, day: Word; // negMul: Integer; begin // NOTE: Apply this to positive years only! //if AYear < 0 then begin AYear := -AYear; negMul := -1; end else negMul := 1; if (AYear >= 0) and (AYear < 100) then begin DecodeDate(Now, year, month, day); Result := ((((year div 100) - cOneCentury[AYear > 50]) * 100) + AYear); // * negMul; end else Result := AYear; // * negMul; end; procedure GMParseTime(const ATimeValue: TGMString; var AChPos: Integer; var AHour, AMinute, ASecond, AMilliSecond: Word; const ACaller: TObject); var token: TGMString; begin AHour := 0; AMinute := 0; ASecond := 0; AMilliSecond := 0; if (AChPos <= Length(ATimeValue)) and not GMIsDigit(ATimeValue[AChPos]) then Exit; token := GMNextDigitToken(AChPos, ATimeValue); AHour := GMStrToInt(GMMakeDezInt(token, -1)); if not GMIsInRange(AHour, 0, 23) then raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrHour, token]), ACaller); if (AChPos <= Length(ATimeValue)) and (ATimeValue[AChPos] <> ':') then Exit; token := GMNextDigitToken(AChPos, ATimeValue); AMinute := GMStrToInt(GMMakeDezInt(token, -1)); if not GMIsInRange(AMinute, 0, 59) then raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrMinute, token]), ACaller); if (AChPos <= Length(ATimeValue)) and (ATimeValue[AChPos] <> ':') then Exit; token := GMNextDigitToken(AChPos, ATimeValue); ASecond := GMStrToInt(GMMakeDezInt(token, -1)); if not GMIsInRange(ASecond, 0, 59) then raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrSecond, token]), ACaller); if (AChPos <= Length(ATimeValue)) and (ATimeValue[AChPos] <> '.') then Exit; token := GMNextDigitToken(AChPos, ATimeValue); AMilliSecond := GMStrToInt(GMMakeDezInt(token, -1)); if not GMIsInRange(ASecond, 0, 999) then raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrMilliSecond, token]), ACaller); end; function GMDecodeRfcINetTime(const ATimeValue: TGMString; var AChPos: Integer; const ACaller: TObject): TDateTime; var token: TGMString; hour, min, sec, milliSec, month, day: Word; year: Integer; begin token := GMNextDigitToken(AChPos, ATimeValue); day := GMStrToInt(GMMakeDezInt(token, -1)); if not GMIsInRange(day, 1, 31) then raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrDay, token]), ACaller); token := GMNextLetterToken(AChPos, ATimeValue); month := GetINetMonthNo(token, ACaller); token := GMNextDigitToken(AChPos, ATimeValue); year := GMAdjust2DigitYears(GMStrToInt(GMMakeDezInt(token, Low(Integer)))); if year = Low(Integer) then raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrYear, token]), ACaller); GMParseTime(ATimeValue, AChPos, hour, min, sec, milliSec, ACaller); Result := EncodeTime(hour, min, sec, milliSec) + EncodeDate(year, month, day) - GMINetTimeZoneOffsetFromUtc(Copy(ATimeValue, AChPos, Length(ATimeValue) - AChPos + 1), ACaller); end; function GMDecodeIsoINetTime(const ATimeValue: TGMString; var AChPos: Integer; const ACaller: TObject): TDateTime; var token: TGMString; hour, min, sec, milliSec, month, day: Word; year: Integer; begin day := 1; month := 1; token := GMNextDigitToken(AChPos, ATimeValue); year := GMAdjust2DigitYears(GMStrToInt(GMMakeDezInt(token, Low(Integer)))); if year = Low(Integer) then raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrYear, token]), ACaller); if (AChPos <= Length(ATimeValue)) and (ATimeValue[AChPos] <> 'T') then begin token := GMNextDigitToken(AChPos, ATimeValue); month := GMStrToInt(GMMakeDezInt(token, -1)); if not GMIsInRange(month, 1, 12) then raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrMonth, token]), ACaller); if (AChPos <= Length(ATimeValue)) and (ATimeValue[AChPos] <> 'T') then begin token := GMNextDigitToken(AChPos, ATimeValue); day := GMStrToInt(GMMakeDezInt(token, -1)); if not GMIsInRange(day, 1, 31) then raise EGMINetException.ObjError(GMFormat(RStrInvalidItemFmt, [RStrDay, token]), ACaller); end; end; if (AChPos <= Length(ATimeValue)) and (ATimeValue[AChPos] = 'T') then Inc(AChPos); while (AChPos <= Length(ATimeValue)) and GMIsDelimiter(cWhiteSpace, ATimeValue, AChPos) do Inc(AChPos); GMParseTime(ATimeValue, AChPos, hour, min, sec, milliSec, ACaller); Result := EncodeTime(hour, min, sec, milliSec) + EncodeDate(year, month, day) - GMINetTimeZoneOffsetFromUtc(Copy(ATimeValue, AChPos, Length(ATimeValue) - AChPos + 1), ACaller); end; function GMDecodeINetTimeToUtc(const ATimeValue: TGMString; const ACaller: TObject): TDateTime; var chPos: Integer; isRFCFmt: Boolean; begin //GMApiCheckObj(GetLastError, InternetTimeToSystemTime(PGMChar(ATimeValue), systemTime, 0), 'InternetTimeToSystemTime("'+ATimeValue+'")', ACaller); //Result := SystemTimeToDateTime(systemTime); chPos := 1; isRFCFmt := False; while (chPos <= Length(ATimeValue)) and not GMIsDigit(ATimeValue[chPos]) do begin // isRFCFmt := isRFCFmt or GMIsInRange(Ord(GMUpCase(ATimeValue[chPos])), Ord('A'), Ord('Z')); if GMIsLetter(ATimeValue[chPos]) then isRFCFmt := True; Inc(chPos); end; if isRFCFmt then Result := GMDecodeRfcINetTime(ATimeValue, chPos, ACaller) else Result := GMDecodeIsoINetTime(ATimeValue, chPos, ACaller); end; function GMEncodeUtcToINetTime(const AUtcTime: TDateTime; const ACaller: TObject): TGMString; var st: TSystemTime; begin DateTimeToSystemTime(AUtcTime, st); Result := GMFormat('%s, %2d %s %4d %2d:%2d:%2d GMT', [vINetDayNames[st.wDayOfWeek], st.wDay, vINetMonthNames[st.wMonth], st.wYear, st.wHour, st.wMinute, st.wSecond]); // SetLength(bufStr, INTERNET_RFC1123_BUFSIZE); // GMApiCheckObj(GetLastError, InternetTimeFromSystemTime(st, INTERNET_RFC1123_FORMAT, PGMChar(bufStr), (Length(bufStr)+1) * SizeOf(TGMChar)), 'InternetTimeFromSystemTime("'+DateTimeToStr(AUtcTime)+'")', ACaller); // SetString(Result, PGMChar(bufStr), GMStrLen(PGMChar(bufStr), Length(bufStr))); end; function GMIso8601DateTimeToStr(const ALocalTime: TDateTime): TGMString; //const cDiffSign: array [Boolean] of TGMString = ('+', '-'); //var DiffToUTC: TDateTime; begin //DiffToUTC := GMLocalToUTCTimeDiff; //Result := FormatDateTime('yyyy"-"mm"-"dd"T"hh":"nn":"ss', ALocalTime) + // cDiffSign[DiffToUTC < 0] + FormatDateTime('hh":"nn', Abs(DiffToUTC)); Result := FormatDateTime('yyyy"-"mm"-"dd"T"hh":"nn":"ss"Z"', GMLocalTimeToUTC(ALocalTime)); end; function GMDeleteCharsA(const AValue, ADelChars: AnsiString; const NotDelChars: Boolean = False): AnsiString; var i: LongInt; begin Result := AValue; i:=1; if NotDelChars then while i<= Length(Result) do if not GMIsDelimiterA(ADelChars, Result, i) then System.Delete(Result, i, 1) else Inc(i) else while i<= Length(Result) do if GMIsDelimiterA(ADelChars, Result, i) then System.Delete(Result, i, 1) else Inc(i); end; function IsIP6Address(const AAddress: TGMString): Boolean; begin Result := GMStrLScan(PGMChar(AAddress), ':', Length(AAddress)) <> nil; end; function GMUriDecode(const AUrl: AnsiString): AnsiString; var i: integer; begin Result := AUrl; i := 1; while i <= Length(Result) do begin case Result[i] of '%': begin Result[i] := Chr(GMStrToInt('$' + Copy(Result, i+1, 2))); System.Delete(Result, i+1, 2); end; '+': Result[i] := ' '; end; //if Result[i] = '%' then // begin // Result[i] := Chr(GMStrToInt('$' + Copy(Result, i+1, 2))); // System.Delete(Result, i+1, 2); // end; Inc(i); end; end; function GMUriEncode(const AUrl: AnsiString; const AIgnorePercentChars: Boolean): AnsiString; var i: Integer; begin Result := AUrl; i:=1; while i <= Length(Result) do begin if ((Result[i] >= #48) and (Result[i] <= #57)) or ((Result[i] >= #65) and (Result[i] <= #90)) or ((Result[i] >= #97) and (Result[i] <= #122)) or (Result[i] in cUriNotPercentEncodeChars) or (AIgnorePercentChars and (Result[i] = '%')) then Inc(i) else //GMIsDelimiter(cUriReservedChars, Result, i) then Inc(i) else begin Insert(IntToHex(Ord(Result[i]), 2), Result, i+1); Result[i] := '%'; Inc(i, 3); end; end; end; function GMInitUriComponents(const AScheme, AUser, APassword, AHost, APort, APath, AQuery, AFragment: TGMString): RGMUriComponents; begin Result.Scheme := AScheme; Result.User := AUser; Result.Password := APassword; Result.Host := AHost; Result.Port := APort; Result.Path := APath; Result.Query := AQuery; Result.Fragment := AFragment; end; function GMBuildUri(const AScheme, AUser, APassword, AHost, APort, APath, AQuery, AFragment: TGMString): TGMString; overload; var userInfo, hostInfo: TGMString; begin if Length(AScheme) > 0 then Result := AScheme + ':' else Result := ''; if (Length(AUser) > 0) or (Length(APassword) > 0) or (Length(AHost) > 0) or (Length(APort) > 0) then begin userInfo := AUser; if Length(APassword) > 0 then userInfo := userInfo + ':' + APassword; hostInfo := AHost; if Length(APort) > 0 then hostInfo := hostInfo + ':' + APort; Result := Result + '//' + GMStringJoin(userInfo, '@', hostInfo); // if (Length(AUriComponents.Path) <= 0) or (AUriComponents.Path[1] <> '/') then AUriComponents.Path := '/' + AUriComponents.Path; end; Result := Result + APath; Result := GMStringJoin(Result, '?', AQuery); Result := GMStringJoin(Result, '#', AFragment); end; function GMBuildUri(const AUriComponents: RGMUriComponents): TGMString; begin Result := GMBuildUri(AUriComponents.Scheme, AUriComponents.User, AUriComponents.Password, AUriComponents.Host, AUriComponents.Port, AUriComponents.Path, AUriComponents.Query, AUriComponents.Fragment); end; //function NextUrlPart2(const AValue: AnsiString; var ApCh: PAnsiChar; ADelimChars: AnsiString): AnsiString; //var i: Integer; pCh, pChStart: PAnsiChar; valLen: Integer; //begin //if ApCh = nil then begin Result := ''; Exit; end; //pCh := ApCh; valLen := Length(AValue); //for i:=1 to Length(ADelimChars) do // begin // ApCh := GMStrLScanA(pCh, ADelimChars[i], PAnsiChar(AValue) + valLen - pCh); // if ApCh <> nil then // begin // Result := Copy(AValue, pCh - PAnsiChar(AValue) + 1, ApCh - pCh); // Inc(ApCh); // Exit; // end; // end; // //Result := Copy(AValue, pCh - PAnsiChar(AValue) + 1, PAnsiChar(AValue) + valLen - pCh); //end; //function GMParseUri(const AUri: TGMString): RGMUriComponents; //var hierPart: TGMString; pCh, pChStart: PGMChar; //procedure ParseUserInfo(const AValue: TGMString); //var pCh: PGMChar; //begin // pCh := PGMChar(AValue); // Result.User := NextUrlPart(AValue, pCh, ':'); // if pCh <> nil then Result.Password := Copy(AValue, pCh - PGMChar(AValue) + 1, PGMChar(AValue) + Length(AValue) - pCh); //end; // //procedure ParseHostAndPort(const AValue: TGMString); //var pCh: PGMChar; //begin // pCh := PGMChar(AValue); // Result.Host := NextUrlPart(AValue, pCh, ':'); // if pCh <> nil then Result.Port := Copy(AValue, pCh - PGMChar(AValue) + 1, PGMChar(AValue) + Length(AValue) - pCh); //end; // //procedure ParseAuthority(const AValue: TGMString); //var pCh: PGMChar; userInfo: TGMString; //begin // pCh := PGMChar(AValue); // userInfo := NextUrlPart(AValue, pCh, '@'); // if pCh <> nil then ParseUserInfo(userInfo) else pCh := PGMChar(AValue); // ParseHostAndPort(Copy(AValue, pCh - PGMChar(AValue) + 1, PGMChar(AValue) + Length(AValue) - pCh)); //end; // //procedure ParseHierPart(const AValue: TGMString); //var pCh: PGMChar; //begin // if Copy(AValue, 1, 2) <> '//' then Result.Path := AValue else // begin // pCh := PGMChar(AValue) + 2; // ParseAuthority(NextUrlPart(AValue, pCh, '/')); // Result.Path := Copy(AValue, pCh - PGMChar(AValue), PGMChar(AValue) + Length(AValue) - pCh + 1); // end; //end; // //begin //Result := GMInitUriComponents('', '', '', '', '', '', '', ''); // //pCh := PGMChar(AUri); //Result.Scheme := NextUrlPart(AUri, pCh, ':'); //if pCh = nil then Exit; // //pChStart := pCh; //hierPart := NextUrlPart(AUri, pCh, '?'); // //if pCh = nil then // begin // Result.Query := ''; // pCh := pChStart; // hierPart := NextUrlPart(AUri, pCh, '#'); // end //else // Result.Query := NextUrlPart(AUri, pCh, '#'); // //ParseHierPart(hierPart); //if pCh = nil then Exit; // //Result.Fragment := Copy(AUri, pCh - PGMChar(AUri) + 1, PGMChar(AUri) + Length(AUri) - pCh); //end; function NextUrlPart(const AValue: TGMString; var ApCh: PGMChar; ADelimChar: TGMChar): TGMString; // AResetIfNotFound: Boolean var pCh: PGMChar; valLen: Integer; begin Result := ''; if ApCh = nil then Exit; pCh := ApCh; valLen := Length(AValue); ApCh := GMStrLScan(pCh, ADelimChar, PGMChar(AValue) + valLen - pCh); if ApCh = nil then Result := Copy(AValue, pCh - PGMChar(AValue) + 1, PGMChar(AValue) + valLen - pCh) else begin Result := Copy(AValue, pCh - PGMChar(AValue) + 1, ApCh - pCh); Inc(ApCh); end; //else // if AResetIfNotFound then ApCh := pCh else // Result := Copy(AValue, pCh - PGMChar(AValue) + 1, PGMChar(AValue) + valLen - pCh); end; function GMParseUri(const AUri: TGMString): RGMUriComponents; var hierPart: TGMString; pCh, pChStart: PGMChar; chPos: Integer; procedure ParseUserInfo(const AValue: TGMString); var pCh: PGMChar; begin pCh := PGMChar(AValue); Result.User := NextUrlPart(AValue, pCh, ':'); if pCh <> nil then Result.Password := Copy(AValue, pCh - PGMChar(AValue) + 1, PGMChar(AValue) + Length(AValue) - pCh); end; procedure ParseHostAndPort(const AValue: TGMString); var pCh: PGMChar; begin pCh := PGMChar(AValue); Result.Host := NextUrlPart(AValue, pCh, ':'); if pCh <> nil then Result.Port := Copy(AValue, pCh - PGMChar(AValue) + 1, PGMChar(AValue) + Length(AValue) - pCh); end; procedure ParseAuthority(const AValue: TGMString); var pCh: PGMChar; userInfo: TGMString; begin pCh := PGMChar(AValue); userInfo := NextUrlPart(AValue, pCh, '@'); if pCh <> nil then ParseUserInfo(userInfo) else pCh := PGMChar(AValue); ParseHostAndPort(Copy(AValue, pCh - PGMChar(AValue) + 1, PGMChar(AValue) + Length(AValue) - pCh)); end; procedure ParseHierPart(const AValue: TGMString); var pCh: PGMChar; begin if Copy(AValue, 1, 3) = '///' then Result.Path := Copy(AValue, 4, Length(AValue)-3) else //if Copy(AValue, 1, 2) <> '//' then Result.Path := AValue else begin pCh := PGMChar(AValue); // + 2; if Copy(AValue, 1, 2) = '//' then Inc(pCh, 2); ParseAuthority(NextUrlPart(AValue, pCh, '/')); if pCh = nil then Result.Path := '/' else Result.Path := Copy(AValue, pCh - PGMChar(AValue), PGMChar(AValue) + Length(AValue) - pCh + 1); //if Length(Result.Path) <= 0 then Result.Path := '/'; end; end; begin Result := GMInitUriComponents('', '', '', '', '', '', '', ''); //pCh := PGMChar(AUri); //Result.Scheme := NextUrlPart(AUri, pCh, ':'); //if pCh = nil then Exit; chPos := Pos('://', AUri); if chPos > 0 then begin Result.Scheme := Copy(AUri, 1, chPos-1); pCh := PGMChar(AUri) + chPos; end else pCh := PGMChar(AUri); // Result.Scheme := ''; pChStart := pCh; hierPart := NextUrlPart(AUri, pCh, '?'); if pCh = nil then begin Result.Query := ''; pCh := pChStart; hierPart := NextUrlPart(AUri, pCh, '#'); end else Result.Query := NextUrlPart(AUri, pCh, '#'); ParseHierPart(hierPart); if pCh = nil then Exit; Result.Fragment := Copy(AUri, pCh - PGMChar(AUri) + 1, PGMChar(AUri) + Length(AUri) - pCh); end; function GMMakeUriDefaultScheme(const AUri: TGMString; const ADefaultScheme: TGMString): TGMString; var chPos: PtrInt; begin chPos := 1; if GMFindToken(AUri, '://', chPos, '', False, False) then Result := AUri else Result := GMStripRight(ADefaultScheme, '/:') + '://' + AUri; end; function GMMergeUris(const AMergeFromUri, AMergeToUri: TGMString; const AComponents: EGMInitUriComponents): TGMString; var srcUriComponents, dstUriComponents: RGMUriComponents; begin srcUriComponents := GMParseUri(AMergeFromUri); dstUriComponents := GMParseUri(AMergeToUri); if (uriScheme in AComponents) and (Length(dstUriComponents.Scheme) <= 0) then dstUriComponents.Scheme := srcUriComponents.Scheme; if (uriUser in AComponents) and (Length(dstUriComponents.User) <= 0) then dstUriComponents.User := srcUriComponents.User; if (uriPassword in AComponents) and (Length(dstUriComponents.Password) <= 0) then dstUriComponents.Password := srcUriComponents.Password; if (uriHost in AComponents) and (Length(dstUriComponents.Host) <= 0) then dstUriComponents.Host := srcUriComponents.Host; if (uriPort in AComponents) and (Length(dstUriComponents.Port) <= 0) then dstUriComponents.Port := srcUriComponents.Port; if uriPath in AComponents then begin if (Length(dstUriComponents.Path) > 0) and GMIsRelativePath(dstUriComponents.Path) then begin if Length(srcUriComponents.Path) > 0 then dstUriComponents.Path := GMAppendPath(srcUriComponents.Path, dstUriComponents.Path, '/') else dstUriComponents.Path := '/' + dstUriComponents.Path; end else if Length(dstUriComponents.Path) <= 0 then dstUriComponents.Path := srcUriComponents.Path; end; if (uriQuery in AComponents) and (Length(dstUriComponents.Query) <= 0) then dstUriComponents.Query := srcUriComponents.Query; if (uriFragment in AComponents) and (Length(dstUriComponents.Fragment) <= 0) then dstUriComponents.Fragment := srcUriComponents.Fragment; Result := GMBuildUri(dstUriComponents); end; //function GMDecodeURLParams(const AUrlParams: AnsiString): AnsiString; //var i: LongInt; //begin // Result := AUrlParams; // for i:=1 to Length(Result) do if Result[i] = '+' then Result[i] := ' '; //end; //function GMEncodeURLParams(const AUrlParams: AnsiString): AnsiString; //var i: LongInt; //begin // Result := AUrlParams; // for i:=1 to Length(Result) do if Result[i] = ' ' then Result[i] := '+'; //end; function CreateHeaderCollection: IGMIntfCollection; begin Result := TGMIntfArrayCollection.Create(True, False, GMCompareByName, True); // <- Allow duplicate Headers! end; function GMGetINetHeaderStrValue(const AHttpHeaders: IGMIntfCollection; const AHeaderName, ADefaultValue: TGMString; const ACheckHeaderExists: Boolean; const ACaller: TObject): TGMString; var searchName, foundEntry: IUnknown; strVal: IGMGetStringValue; begin if AHttpHeaders = nil then Exit(ADefaultValue); // begin Result := ADefaultValue; Exit; end; searchName := TGMNameObj.Create(AHeaderName, True); if not AHttpHeaders.Find(searchName, foundEntry) then begin if ACheckHeaderExists then raise EGMINetException.ObjError(GMFormat(RStrHeaderFieldNotFound, [AHeaderName]), ACaller, {$I %CurrentRoutine%}); Result := ADefaultValue; end else if GMQueryInterface(foundEntry, IGMGetStringValue, strVal) then Result := strVal.StringValue else Result := ADefaultValue; end; function GMGetINetHeaderIntValue(const AHttpHeaders: IGMIntfCollection; const AHeaderName: TGMString; const ADefaultValue: Int64 = 0): Int64; var valStr: TGMString; begin valStr := GMGetINetHeaderStrValue(AHttpHeaders, AHeaderName, GMIntToStr(ADefaultValue)); Result := GMStrToInt64(GMMakeDezInt(valStr, ADefaultValue)); end; procedure GMAddINetHeader(const AHeaders: IGMIntfCollection; const AName: TGMString; const AValue: RGMUnionValue; const AAddStrategy: TGMINetHeaderAddMode = hamReplaceIfExists); var searchName, unkHeader: IUnknown; setHeaderValue: IGMGetSetUnionValue; begin if AHeaders = nil then Exit; searchName := TGMNameObj.Create(AName, True); case AAddStrategy of hamAlwaysAdd: AHeaders.Add(TGMINetHeaderEntry.Create(AName, AValue.AsStringDflt, True)); hamAddIfNew: if not AHeaders.Find(searchName, unkHeader) then AHeaders.Add(TGMINetHeaderEntry.Create(AName, AValue.AsStringDflt, True)); hamReplaceIfExists: if not AHeaders.Find(searchName, unkHeader) or not GMQueryInterface(unkHeader, IGMGetSetUnionValue, setHeaderValue) then AHeaders.Add(TGMINetHeaderEntry.Create(AName, AValue.AsStringDflt, True)) else setHeaderValue.Value := AValue; hamCoalesce: if not AHeaders.Find(searchName, unkHeader) or not GMQueryInterface(unkHeader, IGMGetSetUnionValue, setHeaderValue) then AHeaders.Add(TGMINetHeaderEntry.Create(AName, AValue.AsStringDflt, True)) else setHeaderValue.Value := GMStringJoin(setHeaderValue.Value, '; ', AValue.AsStringDflt); end; end; function GMHeadersAsString(const AHeaders: IGMIntfCollection): TGMString; var it: IGMIterator; unkHdr: IUnknown; name: IGMGetName; value: IGMGetStringValue; begin Result := ''; if AHeaders <> nil then begin it := AHeaders.CreateIterator; while it.NextEntry(unkHdr) do if GMQueryInterface(unkHdr, IGMGetName, name) and GMQueryInterface(unkHdr, IGMGetStringValue, value) then Result := GMStringJoin(Result, CRLF, name.Name + ': ' + value.StringValue); end; //Result := GMStringJoin(AMethodHeader, CRLF, Result) + CRLF + CRLF; //Result := GMStrArrayAsText(AHeaders, CRLF) + CRLF + CRLF; end; function GMSplitURIParams(const AURI: TGMString; const AParams: IGMIntfCollection): TGMString; var paramStr, pair, name, value: TGMString; chPos, i, j: PtrInt; begin chPos := 1; Result := GMNextWord(chPos, AURI, '?'); //Result := GMNextWord(chPos, AURI, '?', False); if AParams = nil then Exit; paramStr := Copy(AURI, chPos, Length(AURI) - chPos + 1); i := 1; repeat pair := GMNextWord(i, paramStr, '&'); if Length(pair) > 0 then begin j := 1; name := GMStrip(GMNextWord(j, pair, '=')); value := Copy(pair, j, Length(pair)-j+1); if Length(name) > 0 then AParams.Add(TGMNameAndStrValueObj.Create(name, value)); end; until i > Length(paramStr); end; //function GMSplitURIParams(const AURL: TGMString; const AParams: IGMIntfCollection): TGMString; //var params, pair, name, value: TGMString; i, j: LongInt; //begin //Result := GMFirstWord(AURL, '?', False); //if AParams = nil then Exit; //params := Copy(AURL, Length(Result)+2, Length(AUrl) - Length(Result) + 1); //i := 1; //repeat // pair := GMNextWord(i, params, '&'); // if Length(pair) > 0 then // begin // j := 1; // name := GMStrip(GMNextWord(j, pair, '=')); // value := Copy(pair, j, Length(pair)-j+1); // if Length(name) > 0 then AParams.Add(TGMNameAndStrValueObj.Create(name, value)); // end; //until i > Length(params); //end; function GMCharCodingOfContentType(const AContentType: TGMString; const ADefaultCharKind: TGMCharKind = ckUnknown): TGMCharKind; var chPos, chPos2: PtrInt; token, valName, val: TGMString; begin Result := ADefaultCharKind; chPos := 1; repeat token := GMStrip(GMNextWord(chPos, AContentType, ';')); if Length(token) > 0 then begin chPos2 := 1; valName := GMStrip(GMNextWord(chPos2, token, '=')); if GMSameText(valName, 'charset') then begin val := GMStrip(Copy(token, chPos2, Length(token) - chPos2 + 1)); if GMSameText(val, 'utf-8') then Result := ckUtf8 else if GMSameText(val, 'ISO-8859-1') then Result := ckAnsi; end; end; until Length(token) <= 0; end; function GMSplitHeaderLine(const AHeaderLine: TGMString): RGMNameAndStrValue; var chPos: PtrInt; //valName: TGMString; begin chPos := 1; Result.Name := GMNextWord(chPos, AHeaderLine, ':'); Result.StrValue := GMStripLeft(Copy(AHeaderLine, chPos, Length(AHeaderLine) - chPos + 1)); end; function CompareHeaderByName(const EntryA, EntryB: RGMNameAndStrValue): TGMCompareResult; begin Result := GMCompareNames(EntryA.Name, EntryB.Name); end; function AddHeaderLineToArray(const AHeaderLine: TGMString; const ALastLine: Boolean; const AData: Pointer): Boolean; begin if AData = nil then Result := False else begin SetLength(PGMNameAndStrValArray(AData)^, Length(PGMNameAndStrValArray(AData)^)+1); PGMNameAndStrValArray(AData)^[High(PGMNameAndStrValArray(AData)^)] := GMSplitHeaderLine(AHeaderLine); Result := True; end; end; procedure GMParseHeadersToArray(const AHeaderLines: TGMString; var AHeaderArray: TGMNameAndStrValArray); begin GMParseLines(AHeaderLines, AddHeaderLineToArray, @AHeaderArray, False); end; { ------------------------------------ } { ---- TGMGenericHeaderCollection ---- } { ------------------------------------ } //procedure TGMGenericHeaderCollection.AddHeadersFromLines(const AHeaderLines: TGMString; const AAddMode: TGMINetHeaderAddMode); //const cLineEndSize: array [Boolean] of PtrInt = (1, 2); //var lnStart, lnEnd: PWideChar; lineLenInChars, lineEndSizeInChars: PtrInt; i: LongWord; line: TGMString; // procedure AddHeaderLine(const AHeaderLine: TGMString); // begin // Add(GMSplitHeaderLine(AHeaderLine)); // end; //begin // lnStart := PGMChar(AHeaderLines); // lnEnd := lnStart; lineEndSizeInChars := 0; // SetLength(line, 0); // // repeat // for i:=0 to High(i) do // case lnEnd^ of // #0: begin lnEnd := nil; Break; end; // #10: begin lineEndSizeInChars := cLineEndSize[(lnEnd + 1)^ = #13]; Break; end; // #13: begin lineEndSizeInChars := cLineEndSize[(lnEnd + 1)^ = #10]; Break; end; // else Inc(lnEnd); // end; // // if lnEnd <> nil then // lineLenInChars := lnEnd - lnStart // else // lineLenInChars := PGMChar(AHeaderLines) + Length(AHeaderLines) - lnStart; // // //SetLength(line, lineLenInChars); // SetString(line, lnStart, lineLenInChars); // if Length(line) > 0 then AddHeaderLine(line); // if lnEnd <> nil then lnEnd += lineEndSizeInChars; // lnStart := lnEnd; // until lnEnd = nil; //end; type PAddHeaderData = ^RAddHeaderData; RAddHeaderData = record HeaderCollection: TGMGenericHeaderCollection; AddMode: TGMINetHeaderAddMode; end; function InitRAddHeaderData(const AHeaderCollection: TGMGenericHeaderCollection; const AAddMode: TGMINetHeaderAddMode): RAddHeaderData; begin Result.HeaderCollection := AHeaderCollection; Result.AddMode := AAddMode; end; function AddHeaderLine(const ALine: TGMString; const ALastLine: Boolean; const AData: Pointer): Boolean; var nameAndStrVal: RGMNameAndStrValue; begin Result := True; if AData <> nil then begin nameAndStrVal := GMSplitHeaderLine(ALine); PAddHeaderData(AData).HeaderCollection.AddHeader(nameAndStrVal, PAddHeaderData(AData).AddMode); end; end; procedure TGMGenericHeaderCollection.AddHeader(const ANewHeader: RGMNameAndStrValue; const AAddMode: TGMINetHeaderAddMode); var foundHeader: RGMNameAndStrValue; begin //Result := True; case AAddMode of hamAlwaysAdd: Add(ANewHeader); hamAddIfNew: if not Find(ANewHeader, foundHeader) then Add(ANewHeader); // else Result := False; hamReplaceIfExists: Add(ANewHeader, True); hamCoalesce: if not Find(ANewHeader, foundHeader) then Add(ANewHeader) else begin foundHeader.StrValue := GMStringJoin(foundHeader.StrValue, '; ', ANewHeader.StrValue); Add(foundHeader, True); end; //else Result := False; end; end; procedure TGMGenericHeaderCollection.AddHeader(const AName, AValue: TGMString; const AAddMode: TGMINetHeaderAddMode); begin AddHeader(InitRGMNameAndStrValue(AName, AValue), AAddMode); end; function TGMGenericHeaderCollection.CreateHeaderIterator(const AHeaderName: TGMString; const AReverse: Boolean; const AConcurrentThreadLock: Boolean): IGMGenericIterator<RGMNameAndStrValue>; var baseIterator: IGMGenericIterator<RGMNameAndStrValue>; begin baseIterator := CreateIterator(AReverse, AConcurrentThreadLock); Result := TGMGenericHeaderIterator.Create(baseIterator, AHeaderName); end; procedure TGMGenericHeaderCollection.AddHeadersFromLines(const AHeaderLines: TGMString; const AAddMode: TGMINetHeaderAddMode); var addData: RAddHeaderData; begin //addData.HeaderCollection := Self; //addData.AddMode := AAddMode; addData := InitRAddHeaderData(Self, AAddMode); GMParseLines(AHeaderLines, AddHeaderLine, @addData, False); end; { -------------------------- } { ---- RGMUriComponents ---- } { -------------------------- } function RGMUriComponents.Uri: TGMString; begin Result := GMBuildUri(Self); end; procedure RGMUriComponents.Clear; begin Self := GMInitUriComponents('', '', '', '', '', '', '', ''); //Scheme := ''; //User := ''; //Password := ''; //Host := ''; //Port := ''; //Path := ''; //Query := ''; //Fragment := ''; end; { ---------------------------- } { ---- TGMINetHeaderEntry ---- } { ---------------------------- } constructor TGMINetHeaderEntry.Create(const AHeaderLine: TGMString; const ARefLifeTime: Boolean); var nameAndStrVal: RGMNameAndStrValue; begin nameAndStrVal := GMSplitHeaderLine(AHeaderLine); inherited Create(nameAndStrVal.Name, nameAndStrVal.StrValue, ARefLifeTime); end; { ---------------------------------- } { ---- TGMGenericHeaderIterator ---- } { ---------------------------------- } constructor TGMGenericHeaderIterator.Create(const ABaseIterator: IGMGenericIterator<RGMNameAndStrValue>; const AHeaderName: TGMString; const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); FBaseIterator := ABaseIterator; FHeaderName := AHeaderName; end; procedure TGMGenericHeaderIterator.Reset; begin if FBaseIterator <> nil then FBaseIterator.Reset; end; function TGMGenericHeaderIterator.NextEntry(var AEntry: RGMNameAndStrValue): Boolean; var currentEntry: RGMNameAndStrValue; begin if (FBaseIterator = nil) or (Length(FHeaderName) <= 0) then Result := False else begin while FBaseIterator.NextEntry(currentEntry) do begin if GMSameText(currentEntry.Name, FHeaderName) then begin AEntry := currentEntry; Exit(True); // <- NOTE: Exit Here! end; end; Result := False; end; end; { ------------------------------- } { ---- TGMInetHeaderIterator ---- } { ------------------------------- } constructor TGMInetHeaderIterator.Create(const ABaseIterator: IGMIterator; const AHeaderName: TGMString; const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); FBaseIterator := ABaseIterator; FHeaderName := AHeaderName; end; procedure TGMInetHeaderIterator.Reset; begin if FBaseIterator <> nil then FBaseIterator.Reset; end; function TGMInetHeaderIterator.NextEntry(out AEntry): Boolean; var unkHdr: IUnknown; hdrName: IGMGetName; begin if (FBaseIterator = nil) or (Length(FHeaderName) <= 0) then Result := False else begin while FBaseIterator.NextEntry(unkHdr) do begin if GMQueryInterface(unkHdr, IGMGetName, hdrName) and GMSameText(hdrName.Name, FHeaderName) then begin IUnknown(AEntry) := unkHdr; Exit(True); // <- NOTE: Exit Here! //Result := True; //Exit; // <- NOTE: Exit Here! end; end; Result := False; end; end; { ----------------------------- } { ---- TGMINetProtocolBase ---- } { ----------------------------- } //constructor TGMINetProtocolBase.Create(const ATransportLayer: ISequentialStream; const ARefLifeTime: Boolean); //begin ////inherited //Create(ARefLifeTime); //TransportLayer := ATransportLayer; //end; function TGMINetProtocolBase.Obj: TGMINetProtocolBase; begin Result := Self; end; function TGMINetProtocolBase.HeadersToSend: IGMIntfCollection; begin if FHeadersToSend = nil then FHeadersToSend := CreateHeaderCollection; Result := FHeadersToSend; end; function TGMINetProtocolBase.ReceivedHeaders: IGMIntfCollection; begin if FReceivedHeaders = nil then FReceivedHeaders := CreateHeaderCollection; Result := FReceivedHeaders; end; function TGMINetProtocolBase.ProtocolDisplayName: TGMString; begin Result := 'INet'; end; function TGMINetProtocolBase.BuildErrorMsgPostfixFromResponseContent(const AResponseContent: ISequentialStream): TGMString; begin Result := ''; end; function TGMINetProtocolBase.ConsumeContent(const ATransportLayer: ISequentialStream): TGMString; var stringStream: IStream; // ansiText: IGMGetByteText; begin if ATransportLayer = nil then begin Result := ''; Exit; end; stringStream := TGMByteStringIStream.Create; GMCopyIStream(ATransportLayer, stringStream); GMSetIStreamAbsPos(stringStream, 0, {$I %CurrentRoutine%}); Result := BuildErrorMsgPostfixFromResponseContent(stringStream); //GMSetIStreamAbsPos(stringStream, 0, {$I %CurrentRoutine%}); //if vfGMDoTracing and GMIStreamContainsASCIIText(stringStream) then // vfGMTrace(GMInsertXMLLineBreaks(GMGetIntfText(stringStream)), cStrContent); end; function TGMINetProtocolBase.ReadResponseLine(const ATransportLayer: ISequentialStream): AnsiString; var n, cb: LongWord; ch: AnsiChar; sepStr: AnsiString; hr: HResult; begin Result := ''; // <- important when using fastMM! if ATransportLayer = nil then Exit; cb := SizeOf(ch); sepStr := ''; repeat //GMSafeIStreamRead(ATransportLayer, @ch, SizeOf(ch), {$I %CurrentRoutine%}); hr := ATransportLayer.Read(@ch, cb, Pointer(@n)); if hr < 0 then GMHrCheckIntf(hr, ATransportLayer, {$I %CurrentRoutine%}); if n <> cb then raise EGMException.IntfError({$I %CurrentRoutine%} + ': ' + GMFormat(RStrReadErrorFmt, [cb, n]), ATransportLayer, {$I %CurrentRoutine%}); case ch of CR, LF: begin sepStr := sepStr + ch; if sepStr = CRLF then Break; if Length(sepStr) >= Length(CRLF) then raise EGMINetException.ObjError(GMFormat(RStrInvalidCommandTerm, [ProtocolDisplayName]), Self, {$I %CurrentRoutine%}); end; else begin if Length(sepStr) > 0 then raise EGMINetException.ObjError(GMFormat(RStrInvalidCommandTerm, [ProtocolDisplayName]), Self, {$I %CurrentRoutine%}); Result := Result + ch; end; end; until False; end; function TGMINetProtocolBase.IsHeaderTermLine(const ALine: AnsiString): Boolean; begin Result := Length(Aline) <= 0; end; function TGMINetProtocolBase.ReceiveHeaders(const ATransportLayer: ISequentialStream; const AHeaders: IGMIntfCollection): TGMString; var line, header: AnsiString; procedure AddHeader(const AHeader: TGMString); begin if Length(AHeader) <= 0 then Exit; vfGMTrace(AHeader, ProtocolDisplayName); // // First response line is returned (status line), other lines are added to headers // if Length(Result) = 0 then Result := AHeader else if (AHeaders <> nil) and (Length(AHeader) > 0) then AHeaders.Add(TGMINetHeaderEntry.Create(AHeader)); end; begin Result := ''; header := ''; repeat line := ReadResponseLine(ATransportLayer); if IsHeaderTermLine(line) then Break; if (Length(line) > 0) and (line[1] in [' ', #9]) then header := header + line // Copy(hdrToken, 2, Length(hdrToken)-1) else begin AddHeader(header); header := line; end; // <- add previous line, wrapping cannot be decided until next line has been read until False; AddHeader(header); // <- add last line end; function TGMINetProtocolBase.ReceiveCmdResponse(const ATransportLayer: ISequentialStream): TCmdResponse; var line: AnsiString; firstLine, moreLines: Boolean; function IsNumber(const AValue: AnsiString): Boolean; var i: LongInt; begin for i:=1 to Length(AValue) do if not GMIsDigitA(AValue[i]) then begin Result := False; Exit; end; Result := Length(AValue) > 0; end; begin firstLine := True; Result.Code := ''; repeat line := ReadResponseLine(ATransportLayer); // , ProtocolDisplayName, Self); if not firstLine then moreLines := (Copy(line, 1, 4) <> Result.Code + ' ') else begin Result.Code := Copy(line, 1, 3); if not IsNumber(Result.Code) then raise EGMINetException.ObjError(GMFormat(RStrInvalidResponeCodeFmt, [ProtocolDisplayName, Result.Code]), Self, {$I %CurrentRoutine%}); moreLines := Copy(line, 4, 1) = '-'; end; if firstLine or not moreLines then line := Copy(line, 5, Length(line) - 4); GMAddStrToArray(line, Result.Text); firstLine := False; until not moreLines; vfGMTrace(ProtocolDisplayName + ' ' + RStrResponse + ': ' + Result.Code + ' ' + GMStrArrayAsText(Result.Text, '<CRLF>'), ProtocolDisplayName); end; function TGMINetProtocolBase.ExceptClassForCode(const ACode: AnsiString): TGMINetExceptionClass; begin Result := EGMINetException; end; function TGMINetProtocolBase.CheckCmdResponse(const ACommand: TGMString; const ACmdResponse: TCmdResponse; const ASuccessCodes: TGMString; const ACallingName: TGMString): TCmdResponse; var exceptClass: EGMExceptionClass; //i: LongInt; begin Result := ACmdResponse; exceptClass := ExceptClassForCode(Result.Code); if exceptClass = nil then exceptClass := EGMINetException; if Length(ACmdResponse.Code) <= 0 then raise exceptClass.ObjError(GMFormat(RStrNoResponseCode, [ProtocolDisplayName, ACommand]), Self, {$I %CurrentRoutine%}); //for i:=1 to Length(ACmdResponse.Code) do // if not GMIsDelimiter(cStrDigits, ACmdResponse.Code, i) then // raise exceptClass.ObjError(GMFormat(RStrBadResponseCode, [ProtocolDisplayName, ACmdResponse.Code]), Self, {$I %CurrentRoutine%}); if not GMIsdelimiter(ASuccessCodes, Result.Code, 1) then raise exceptClass.ObjError(GMFormat(RStrCmdError, [ProtocolDisplayName, ACommand, Result.code, GMStrArrayAsText(Result.Text)]), Self, ACallingName); end; function TGMINetProtocolBase.ExecCommandStr(const ATransportLayer: ISequentialStream; ACommand, ASuccessCodes: TGMString; ACallingName: TGMString; const AShowTrace: Boolean): TCmdResponse; var cmdWithTerm: AnsiString; cmdVerb: TGMString; begin GMCheckPointerAssigned(Pointer(ATransportLayer), RStrTransportLayerStream); if Length(ACallingName) <= 0 then ACallingName := {$I %CurrentRoutine%}; cmdVerb := GMFirstWord(ACommand, cWhiteSpace); // <- avoid showing password etc in error message ACommand := GMStripLeft(GMStripRight(ACommand, CRLF)); cmdWithTerm := ACommand + CRLF; if Length(ACommand) > 0 then begin if AShowTrace then vfGMTrace(cStrCommand + ': ' + ACommand, ProtocolDisplayName); GMSafeIStreamWrite(ATransportLayer, PAnsiChar(cmdWithTerm), Length(cmdWithTerm), ACallingName); end; Result := CheckCmdResponse(cmdVerb, ReceiveCmdResponse(ATransportLayer), ASuccessCodes, ACallingName); if (Length(ASuccessCodes) > 1) and (Result.Code[1] = '1') then begin ASuccessCodes := GMDeleteChars(ASuccessCodes, '1'); if Length(ASuccessCodes) <= 0 then ASuccessCodes := '2'; Result := CheckCmdResponse(cmdVerb, ReceiveCmdResponse(ATransportLayer), ASuccessCodes, ACallingName); end; end; end.