{ ******************************************************************************** SPackGui (common files) Copyright (C) 2008-2013 Geoffray Levasseur . Copyright (C) Parts inspired from PeaZip souce code: Copyright (C) 2006 Giorgio Tani Parts inspired from Lazarus source code: Copyright (C) 2001-2010 The Lazarus developpers http://www.geoffray-levasseur.org/ http://0.tuxfamilly.org/ This software is governed by the CeCILL license under French law and abiding by the rules of distribution of free software. You can use, modify and/ or redistribute the software under the terms of the CeCILL license as circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info". As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license, users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the successive licensors have only limited liability. In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or developing or reproducing the software by the user in light of its specific status of free software, that may mean that it is complicated to manipulate, and that also therefore means that it is reserved for developers and experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the software's suitability as regards their requirements in conditions enabling the security of their systems and/or data to be ensured and, more generally, to use and operate it in the same conditions as regards security. The fact that you are presently reading this means that you have had knowledge of the CeCILL license and that you accept its terms. ******************************************************************************** Description: Various utilities and functions ******************************************************************************** } unit uUtils; {$include defines.inc} interface uses Classes, SysUtils, Graphics, StdCtrls; type TOperatingSystem = (osWindows, osWin32, osWin64, osWinCE, osGo32V2, osOS2, osGenUnix, osFreeBSD, osNetBSD, osLinux, osBeOS, osQNX, osSun, osDarwin, osAmiga, osAtari, osMacOS, osPalmOS, osUnknow); TPathType = (ptIsDircetory, ptIsFile, ptIsExecutable, ptNotExists, ptInvalid, ptDosPathOnUnix, ptUnixPathOnDos); TDesktopEnv = (deUnknow, deGnome, deKDE, deMacOSX, deWindows, deLXDE, deXFCE); Str4 = string[4]; TFileCountOpt = set of (fcIncludeSubDirs, fcIncludeDirs); const {$warning we should be able to detect more desktop environment} ADescEnvDesc: array[TDesktopEnv] of string = ('Unknow', 'Gnome', 'KDE', 'Mac OS X', 'Microsoft Windows', 'LXDE', 'XFCE'); AOSDesc: array[TOperatingSystem] of string = ('Microsoft Windows (generic)', 'Microsoft Windows (32 bits)', 'Microsoft Windows (64 bits)', 'Microsoft Windows CE', 'Microsoft Windows with Go32 v2', 'IBM OS/2', 'Generic Unix', 'Free BSD', 'Net BSD', 'Linux', 'BeOS', 'QNX', 'Sun Solaris', 'Mac OS X (Darwin)', 'Amiga OS', 'Atari OS', 'MacOS', 'Palm OS', 'Unknow Operating System'); cpuUnknow = $00; cpu32Bit = $10; cpu64Bit = $20; cpuGenericMoto = $30; cpuMiscRisc = $40; cpuGenericPPC = $50; cpu86 = $11; cpu87 = $12; cpu386 = $13; cpuX86_64 = $21; cpu68k = $31; cpuM68020 = $32; cpu68 = $33; cpuAlpha = $41; cpuSparc = $42; cpuMips = $43; cpuPowerPC = $51; cpuArm = $60; cpuBigEndian = $80; var //default values and variable declaration for configuration DateFormat: string = 'dd/mm/yyyy'; TwoDigitDateLimit: integer = 30; //get the user name from personal directory path (multiplatform way) //note that builtin FPC GetUserName function is for Windows only //on unix su command is keeping the username as before launching it: find a workarround function GetUserName: string; //get computer name function GetComputerName: string; //Get the running operating system function GetCurrentOS: TOperatingSystem; //get CPU type and endianness (in MSB) function GetUsedCPU: Word; //Find the default browser according system configuration function FindDefaultBrowser(out ABrowser, AParams: String): Boolean; //Open URL in the default browser function OpenURL(AURL: String): Boolean; //convert a 4 char string (maximum) into a Word function StrToWord(const S: Str4): Word; //Convert a 3 number date into a string (deprecated) function DateToStr(Y, M, D: Word): string; deprecated; //Remove the extention of a file name function RemoveFileExt(const aFileName: string): string; //Get the executable path of the program with final / function GetProgramPath: string; //Get the active user working directory function GetHomePath: string; //Give the number of items present in a listed string for the given separator function CountItemsInStr(Str: string; Separator: Char): integer; //Return the item in a listed string at the given index function GetItemInStr(Str: string; Separator: Char; Index: integer): string; //find which desktop environment is currently running function GetDesktopEnv: TDesktopEnv; //0 unknown, 1 Gnome, 2 KDE, 20 MS Windows, 30 OSX //return the position of a string in a stringlist, -1 if no occurence function StrExistsInList(const StrList: TStringList; S: string): integer; function StrExistsInListBox(const StrList: TListBox; S: string): integer; //remove and change (repectively) a string from a StringList, returning position //of the change in the SL, -1 if no occurence function StrRemove(var StrList: TStringList; S: string): integer; function StrChange(var StrList: TStringList; const sOld, sNew: string): integer; //check filename before use regarding the environment function FormatFileName(S: String; out VS: string): Integer; //check if a directory is empty function DirectoryIsEmpty(ADir: string): Boolean; //check for possible problematic chars in a string that could be used as filename function RemoveSpecialChar(const S: UnicodeString): UnicodeString; //create a lock file containing the computer name and user name of the locker function CreateLockFile(const AName: string): Boolean; //count the number of files in a directory and optionnally its subdirs function GetNumberOfFiles(const ADir: string; const AOpt: TFileCountOpt): LongWord; //check if we can write in a directory function DirectoryIsReadOnly(const ADirName: string): Boolean; //get the host name of an URL/URI function ExtractHostName(const S: string): string; //get proxy settings from http_proxy or ftp_proxy environment variables procedure GetProxyInfo(const S: string; out User, Pass, Address, Port: string); implementation uses BaseUnix, Forms, FileUtil, UTF8Process, Dialogs, uDebug, uStrings; function GetUserName: string; begin {$warning Find a workarround to get the good username in case where su or sudo is being used} Result := GetEnvironmentVariable(rsEnvUser); end; function GetComputerName: String; var AUtsName: UtsName; begin Result := ''; AUtsName.Nodename[0] := #0; //this is just to avoid the compiler warning FillChar(AUtsName, SizeOf(AUtsName), 0); if FpUname(AUtsName) <> -1 then Result := AUtsName.Nodename; end; function GetCurrentOS: TOperatingSystem; begin //unrecognized system, it is overwritten if one of the following conditions is met Result := osUnknow; // that software is useless and would not work on DOS/Windows type systems so it's bypassed // Unix type systems {$IFDEF UNIX} //generic UNIX, replaced with some following codes //if a more specific match is found Result := osGenUnix; {$ENDIF} {$IFDEF FREEBSD} Result := osFreeBSD; {$ENDIF} {$IFDEF NETBSD} Result := osNetBSD; {$ENDIF} {$IFDEF LINUX} Result := osLinux; {$ENDIF} {$IFDEF BEOS} Result := osBeOS; {$ENDIF} {$IFDEF QNX} Result := osQNX; {$ENDIF} {$IFDEF SUNOS} Result := osSunOS; {$ENDIF} //Other systems are also ignored for the same reason as for DOS/Windows end; //if cpu type, < $80 (encoded in ls 7 bit); //0 if unrecognized, replaced if a match is found function GetUsedCPU: Word; //CPU and endianness (in MSB) begin Result := cpuUnknow; //cpu type, < $80 (encoded in ls 7 bit); 0: unrecognized, replaced if a match is found {$IFDEF CPU32} //generic 32 bit CPU, replaced if a more specific match is found Result := cpu32Bit; {$ENDIF} {$IFDEF CPU64} //generic 64 bit CPU, replaced if a more specific match is found Result := cpu64Bit; {$ENDIF} {$IFDEF CPU86} Result := cpu86; {$ENDIF} {$IFDEF CPU87} Result := cpu87; {$ENDIF} {$IFDEF CPUI386} Result := cpu386; {$ENDIF} {$IFDEF CPUX86_64} Result := cpuX86_64; {$ENDIF} {$IFDEF CPU68k} Result := cpu68k; {$ENDIF} {$IFDEF CPUM68020} Result := cpuM68020; {$ENDIF} {$IFDEF CPU68} Result := cpu68; {$ENDIF} {$IFDEF CPUSPARC} Result := cpuSpark; {$ENDIF} {$IFDEF CPUALPHA} Result := cpuAlpha; {$ENDIF} {$IFDEF CPUMIPS} Result := cpuMips; {$ENDIF} {$IFDEF CPUPOWERPC} Result := cpuPowerPC; {$ENDIF} {$IFDEF CPUARM} Result := cpuArm; {$ENDIF} {$IFDEF ENDIAN_BIG} //if processor is declared big endian (some processors don't have a fixed //endian setting) the msb is set to 1, otherwise is 0 Result := Result + cpuBigEndian; {$ENDIF} end; //This is a somewhat altered version of the Lazarus' source code //FindDefaultBrowser function function FindDefaultBrowser(out ABrowser, AParams: String): Boolean; function Find(const ShortFilename: String; out ABrowser: String): Boolean; inline; begin ABrowser := SearchFileInPath(ShortFilename + GetExeExt, '', GetEnvironmentVariable(rsEnvPath), PathSeparator, [sffDontSearchInBasePath]); Result := ABrowser <> ''; end; begin if ABrowser = '' then begin AParams := '%s'; // Then search in path. Firsts are prefered ;) if Find('xdg-open', ABrowser) // Portland OSDL/FreeDesktop standard on Linux or Find('htmlview', ABrowser) // some redhat systems or Find('konqueror', ABrowser) or Find('firefox', ABrowser) or Find('mozilla', ABrowser) or Find('midori', ABrowser) or Find('rekonq', ABrowser) or Find('galeon', ABrowser) or Find('safari', ABrowser) or Find('netscape', ABrowser) or Find('opera', ABrowser) then PrintLnDbg(Format(rsInfoFoundBrowser, [ABrowser]), vlHigh); end; Result := ABrowser <> ''; end; //each system has a different OpenURL working. this is mainly took from //Lazarus source code with minor changes //we suppose here a Unix system... exotic or very old OS not supported. function OpenURL(AURL: String): Boolean; var ABrowser, AParams: String; BrowserProcess: TProcessUTF8; begin Result := FindDefaultBrowser(ABrowser, AParams) and FileExists(ABrowser) {and FileIsExecutable(ABrowser)}; if not Result then Exit; //run BrowserProcess := TProcessUTF8.Create(nil); try {$warning test if @ exists and add mailto to open it with default mail composer!} BrowserProcess.CommandLine := ABrowser + ' ' + Format(AParams, [AURL]); BrowserProcess.Execute; finally BrowserProcess.Free; end; end; //if your string is more than 4 char you may have a range error function StrToWord(const S: Str4): Word; var I: Integer; begin Result := 0; I := 1; while I <= Length(S) do begin Result := Result * 10 + Ord(S[I]) - Ord('0'); Inc(I); end; end; // Deprecated with Lazarus > 0.9.27: use FormatDateTime for further development function DateToStr(Y, M, D: Word): string; deprecated; begin if Y < TwoDigitDateLimit then Inc(Y, 2000); if (Y < 100) and (Y > TwoDigitDateLimit) then Inc(Y, 1900); Result := FormatDateTime(DateFormat, EncodeDate(Y, M, D)); end; function RemoveFileExt(const aFileName: string): string; var I: integer; begin I := Length(aFileName); while aFileName[I] <> '.' do Dec(I); if I > 1 then Result := Copy(aFileName, 1, I - 1) else Result := aFileName; //on unix this is not an extention but an hidden file end; function GetProgramPath: string; //this may work on all platform begin Result := Trim(ExtractFilePath(Application.ExeName)); if Result[Length(Result)] <> DirectorySeparator then Result := Result + DirectorySeparator; end; function CountItemsInStr(Str: string; Separator: Char): integer; var I: integer; begin if Str = '' then begin Result := 0; Exit; end; Result := 1; for I := 1 to Length(Str) do if Str[I] = Separator then Inc(Result); if Str[Length(Str)] = Separator then Dec(Result); end; function GetItemInStr(Str: string; Separator: Char; Index: integer): string; var I, J: integer; S: string; begin //note that result = '' is equivalent to an error S := ''; if Str = '' then Exit; I := 1; J := 1; while (I < Index) and (J < Length(Str)) do begin if Str[J] = Separator then Inc(I); Inc(J); end; if J >= Length(Str) then //in case of a terminal separator Exit; if Str[J] = Separator then Inc(J); repeat S := S + Str[J]; Inc(J); until (J > Length(Str)) or (Str[J] = Separator); Result := Trim(S); end; function GetDesktopEnv: TDesktopEnv; begin Result := deUnknow; //unrecognized desktop environment //if this Gnome specific env variable is set, probably the user is running Gnome if GetEnvironmentVariable(rsEnvGnomeSession) <> '' then Result := deGnome; //if this KDE specific env variable is set, probably the user is running KDE if GetEnvironmentVariable(rsEnvKDESession) <> '' then Result := deKDE; //if gnome or kde is explicitely declared in DESKTOP_SESSION env variable, //override previously assumed result {$warning Complete with other unixes desktop environment} if GetEnvironmentVariable(rsEnvDesktopSession) = 'gnome' then Result := deGnome else if getenvironmentvariable(rsEnvDesktopSession) = 'kde' then Result := deKDE else if GetEnvironmentVariable(rsEnvDesktopSession) = 'xfce' then Result := deXFCE else if GetEnvironmentVariable(rsEnvDesktopSession) = 'lxde' then Result := deLXDE; end; function GetDesktopPath: string; //superseeded in Windows begin {$IFDEF LINUX} //this should work on every unixes but have to be tested Result := GetEnvironmentVariable(rsEnvUnixHomeDir) + '/Desktop/'; {$ENDIF} {$IFDEF FREEBSD} Result := GetEnvironmentVariable(rsEnvUnixHomeDir) + '/Desktop/'; {$ENDIF} {$IFDEF NETBSD} Result := GetEnvironmentVariable(rsEnvUnixHomeDir) + '/Desktop/'; {$ENDIF} //generic, superseeded by system specific values, if not empty if Result = '' then Result := GetCurrentDir; if Result[Length(Result)] <> DirectorySeparator then Result := Result + DirectorySeparator; end; function GetHomePath: string; begin {$IFDEF LINUX} //this should work on every unixes but have to be tested Result := GetEnvironmentVariable(rsEnvUnixHomeDir); {$ENDIF} {$IFDEF FREEBSD} Result := GetEnvironmentVariable(rsEnvUnixHomeDir); {$ENDIF} {$IFDEF NETBSD} Result := GetEnvironmentVariable(rsEnvUnixHomeDir); {$ENDIF} //generic, superseeded by system specific values, if not empty if Result = '' then Result := GetCurrentDir; if Result[Length(Result)] <> DirectorySeparator then Result := Result + DirectorySeparator; end; //return the first occurence index, not usefull if you need multiple ones function StrExistsInList(const StrList: TStringList; S: string): integer; var I: Integer; begin Result := -1; if not Assigned(StrList) then Exit; I := 0; while (I < StrList.Count - 1) and (StrList[I] <> S) do Inc(I); //increase until we have an occurence or we get to the end of the string if StrList[I] = S then Result := I; end; function StrExistsInListBox(const StrList: TListBox; S: string): integer; var SL: TStringList; begin SL := TStringList.Create; SL.Assign(StrList.Items); Result := StrExistsInList(SL, S); end; //for both following functions a "repeat [...] until StrXXX(<...>) = -1;" //will make it happen on every possible occurence if any function StrRemove(var StrList: TStringList; S: string): integer; begin Result := StrExistsInList(StrList, S); StrList.Delete(Result); end; function StrChange(var StrList: TStringList; const sOld, sNew: string): integer; begin Result := StrExistsInList(StrList, sOld); StrList[Result] := sNew; end; //////////////////////////////////////////////////////////////////////////////// //in Linux and BSDs if filename contains delimiter ' change the character in ", //and checking special cases for Gnome and KDE in Windows delimiter is " and it's //not a valid character in filenames, so this control returns the input string //(which doesn't need to be variable) on other systems filenames are not escaped // //- convert %XX (where XX is an hexa value) to the equivalent char by code //- braces management (' on unix and " on Windows) as unneeded with Lazarus. //- deleting « file:// » if present. //- Supporting environnement variables (eg. $(USER)\MonProg). //- deleting double directory separators (eg. \\ or // -> \ or /). //- automatic separator convertion (eg. with Windows a / would be \) //- adds terminal \ or / for directories if not exists. //- detect invalid chars (depending on the OS) // //Returns: Negative when errors, positive when Ok where //Low(Result) is pos of the error when applicable and High(Result) the error code // //List of error codes: //-$01XX: error in %XX convertion (mostly non hexa value) //-$02XX: "$(" found but no ")" while checking environment (XX pos of "$(") //-$0300: directory separator in a filename //-$0400: Comma in a filename //-$0500: windows only: use of a system reserved file extention //-$10XX: empty env var (XX pos of "$(") //-$20XX: invalid char where XX is the invalid char code //+$0000: valid filename, no such file or directory //+$1000: valid filename, directory exists //+$2000: valid filename, file exist function FormatFileName(S: String; out VS: string): Integer; {$warning This should be splited in several functions for selective checkings} var Str, TmpS, EnvVar: String; I, J: Integer; Ch: Char; const FilePrefix = 'file://'; procedure InvalidChar(C: Char); begin VS := Str; Result := $2000 + Ord(C); end; begin Result := $0000; Str := Trim(S); // Delete ' on Unix or " on Windows as it is not needed when assuming there's // one file path per string I := 1; repeat I := Pos('''', Str); if I > 0 then Delete(Str, I, 1); until I = 0; //find and delete 'file://' (and any part before) if it is passed as part of //filename (it happens sometimes in Gnome, i.e. using "open with" context //menu entry) or if it has been entered as is by the user I := Pos(FilePrefix, Str); if I > 0 then Str := Copy(Str, I + Length(FilePrefix), Length(Str) - I - Length(FilePrefix) - 1); //replace %xx with the appropriate character used sometimes to pass non //available keyboards char or spaces. xx must be 2 hexa char repeat I := Pos('%', Str); if I <> 0 then try //if there's synthax error like non hexa char Ch := Char(StrToWord(Str[I + 1] + Str[I + 2])); Delete(Str, I, 3); Insert(Ch, Str, I); except Result := $0100 + I; end; //if error leave everything else as is as it's not valid path anyway until (I = 0) or (Result <> 0); //the application support the use of environment variable in filenames just //like Unix command line does, so this extract environment variable when $() //symbol is found repeat I := Pos('$(', Str); if I <> 0 then try J := I; while Str[J] <> ')' do Inc(J); //on windows we use Windows unt that have an incompatible GetEnvironmentVariable EnvVar := SysUtils.GetEnvironmentVariable(Copy(Str, I + 2, J - I - 2)); if EnvVar <> '' then begin Delete(Str, I, I - J); Insert(EnvVar, Str, I); end else Result := $1000 + I; except Result := $0200 + I; end; until (I = 0) or (Result <> 0); // remove double / on unix or double \\ on windows {$warning This is not working when we have network address} repeat I := Pos('//', Str); if I = 1 then I := 0; if I <> 0 then if Str[I - 1] <> ':' then //it's looking like an URL so don't erase it Delete(Str, I, 1); until I = 0; // check for remaining invalid char for I := 0 to 31 do if (Pos(Char(I), Str) <> 0) then InvalidChar(Char(I)); if Pos('*', Str) <> 0 then InvalidChar('*'); if Pos('?' , Str) <> 0 then InvalidChar('?'); if Pos('''', Str) <> 0 then InvalidChar(''''); if Pos(':', Str) <> 0 then InvalidChar(':'); if Pos('<', Str) <> 0 then InvalidChar('<'); if Pos('>', Str) <> 0 then InvalidChar('>'); if Pos('|', Str) <> 0 then InvalidChar('|'); TmpS := ExtractFileName(Str); //reserved characters, filename only (others are checked for the full name) if (Pos('\', TmpS) <> 0) or (Pos('/', TmpS) <> 0) then Result := $0300; //we should get the pos for low(result) if Pos(':', TmpS) <> 0 then Result := $0400; //we should get the pos for low(result) //reserved filename extentions (Windows) if Result <> $0000 then Result := -Result else if DirectoryExists(Str) then begin if Str[Length(Str)] <> '/' then Str := Str + '/'; Result := $1000; end else if FileExists(Str) then Result := $2000; VS := Str; end; function DirectoryIsEmpty(ADir: string): Boolean; var SR: TSearchRec; Mask: string; begin Mask := '*'; if ADir[Length(ADir)] <> DirectorySeparator then ADir := ADir + DirectorySeparator; Result := FindFirst(ADir + Mask, faAnyFile, SR) <> 0; FindClose(SR); end; function RemoveSpecialChar(const S: UnicodeString): UnicodeString; var I: Integer; Tmp: UnicodeString; begin Tmp := S; for I := 1 to Length(S) do if not (S[I] in ['0'..'9', '.', '-', '_', 'A'..'Z', 'a'..'z']) then Tmp[I] := '_' else Tmp[I] := S[I]; Result := Tmp; end; function CreateLockFile(const AName: string): Boolean; var AFile: Text; begin Result := True; try if FileExists(AName) then if not DeleteFile(AName) then begin Result := False; Exit; end; {$I-} Assign(AFile, AName); Rewrite(AFile); if IOResult = 0 then begin Writeln(AFile, GetUserName + '@' + GetComputerName); Writeln(AFile, IntToStr(GetProcessID)); Close(AFile); Result := IOResult = 0; end else Result := False; {$I+} except Result := False; end; end; function GetNumberOfFiles(const ADir: string; const AOpt: TFileCountOpt): LongWord; var Rec: TSearchRec; S: string; begin Result := 0; if ADir[Length(ADir)] <> DirectorySeparator then S := ADir + DirectorySeparator else S := ADir; if FindFirst(S + '*', faAnyFile, Rec) = 0 then begin repeat if ((Rec.Attr and faDirectory) <> faDirectory) then //file ? Inc(Result) else //directories: if Rec.Name[1] <> '.' then //don't count '.' and '..' begin if fcIncludeDirs in AOpt then Inc(Result); if fcIncludeSubDirs in AOpt then Result := Result + GetNumberOfFiles(S + '/' + Rec.Name, AOpt); end; until FindNext(Rec) <> 0; FindClose(Rec); end; end; function DirectoryIsReadOnly(const ADirName: string): Boolean; begin //pure Unix type checking (will not work on anything else) Result := fpAccess(PChar(pointer(ADirName)), W_OK) <> 0; end; function ExtractHostName(const S: string): string; var Start: integer; Delim: Char; Tmp: string; begin Result := ''; Delim := '/'; Start := Pos('//', S); if Start = 0 then begin Start := Pos('\\', S); //this is for some windows servers Delim := '\'; end; if Start = 0 then Exit; Tmp := Copy(S, Start + 1, Length(S) - Start); if Pos(Delim, Tmp) <> 0 then Result := Copy(Tmp, 1, Pos(Delim, Tmp)) else Result := Tmp; end; procedure GetProxyInfo(const S: string; out User, Pass, Address, Port: string); var Tmp, UsrInfo, AddrInfo: string; begin if Pos(sHttpPrefix, S) = 1 then Tmp := Copy(S, Length(sHttpPrefix) + 1, Length(S) - Length(sHttpPrefix)) else if Pos( sFtpPrefix, S) = 1 then Tmp := Copy(S, Length(sFtpPrefix) + 1, Length(S) - Length(sFtpPrefix)); if Pos('@', S) <> 0 then begin UsrInfo := Copy(Tmp, 1, Pos('@', Tmp) - 1); AddrInfo := Copy(Tmp, Pos('@', Tmp) + 1, Length(Tmp) - Pos('@', Tmp)); end else begin UsrInfo := ''; AddrInfo := Tmp; end; if (AddrInfo <> '') and ((AddrInfo[Length(AddrInfo)] = '/') or (AddrInfo[Length(AddrInfo)] = ';')) then AddrInfo := Copy(AddrInfo, 1, Length(AddrInfo) - 1); if UsrInfo <> '' then if Pos(':', UsrInfo) <> 0 then begin User := Copy(UsrInfo, 1, Pos(':', UsrInfo) - 1); Pass := Copy(UsrInfo, Pos(':', UsrInfo) + 1, Length(UsrInfo) - Pos(':', UsrInfo)); end else begin User := UsrInfo; Pass := ''; end; if Pos(':', AddrInfo) <> 0 then begin Address := Copy(AddrInfo, 1, Pos(':', AddrInfo) - 1); Port := Copy(AddrInfo, Pos(':', AddrInfo) + 1, Length(AddrInfo) - Pos(':', AddrInfo)); end else begin Address := AddrInfo; Port := ''; end; end; end.