{*******************************************************} { } { Tocsg.Url } { } { Copyright (C) 2022 kku } { } {*******************************************************} unit Tocsg.Url; interface uses System.SysUtils, System.Classes; function GetDomainFromUrl(const sUrl: String): String; function ExtractUrlPath(const sUrl: String): String; function IncludeTrailingSlash(const sUrl: String): String; function ExcludeTrailingSlash(const sUrl: String): String; function RefineUrl(sHomeUrl, sSubUrl: String): String; function ExtractIPsFromUrl(sUrl: String; bIncIPv6: Boolean = false; sDm: String = ','): String; function IsUrlValid(const url: string; nTmSec : Integer = 0): boolean; function UrlEncodeUTF8(const sText: String): String; function UrlDecodeUTF8(const sText: String): String; implementation uses Tocsg.Exception, Tocsg.Process, Tocsg.Safe, Tocsg.Strings, Tocsg.Network, Winapi.WinInet, Winapi.Windows; function GetDomainFromUrl(const sUrl: String): String; var nPos, b: Integer; begin nPos := Pos('://', sUrl); if nPos > 0 then Inc(nPos, 3); nPos := FindDelimiter('/', sUrl, nPos); if nPos > 0 then Result := Copy(sUrl, 1, nPos - 1) else Result := sUrl; end; function ExtractUrlPath(const sUrl: String): String; var nPos: Integer; begin nPos := LastDelimiter('/', sUrl); if nPos > 0 then Result := Copy(sUrl, 1, nPos) else Result := sUrl; end; function IncludeTrailingSlash(const sUrl: String): String; var nLen: Integer; begin nLen := Length(sUrl); if (nLen = 0) or (sUrl[nLen] <> '/') then Result := sUrl + '/' else Result := sUrl; end; function ExcludeTrailingSlash(const sUrl: String): String; var nLen: Integer; begin Result := sUrl; nLen := Length(Result); if (nLen > 0) and (sUrl[nLen] = '/') then SetLength(Result, nLen - 1); end; function RefineUrl(sHomeUrl, sSubUrl: String): String; var nPos, nSubLen: Integer; begin nSubLen := Length(sSubUrl); if Pos('://', sSubUrl) > 0 then begin Result := sSubUrl; exit; end else if nSubLen = 0 then begin Result := ''; exit; end else if sSubUrl[1] = '/' then begin Result := GetDomainFromUrl(sHomeUrl) + sSubUrl; end else if sSubUrl[1] = '?' then begin nPos := Pos('?', sHomeUrl); if nPos > 0 then Delete(sHomeUrl, nPos, Length(sHomeUrl) - nPos + 1); Result := sHomeUrl + sSubUrl; end else if Pos('./', sSubUrl) = 1 then begin Result := ExtractUrlPath(sHomeUrl) + Copy(sSubUrl, 3, nSubLen - 2); end else if Pos('../', sSubUrl) = 1 then begin Result := ExtractUrlPath(sHomeUrl); Result := ExcludeTrailingSlash(Result); Result := ExtractUrlPath(Result) + Copy(sSubUrl, 4, nSubLen - 3); end else Result := ExtractUrlPath(sHomeUrl) + sSubUrl; end; function ExtractIPsFromUrl(sUrl: String; bIncIPv6: Boolean = false; sDm: String = ','): String; var ss: TStringStream; sIp, sData: String; IpList, ExtrIpList: TStringList; nPos, c: Integer; begin Result := ''; try Guard(ss, TStringStream.Create('', TEncoding.UTF8)); Guard(IpList, TStringList.Create); Guard(ExtrIpList, TStringList.Create); if GetCmdTextToStream('cmd.exe', Format('/c nslookup %s', [sUrl]), ss) then begin sData := ss.DataString.ToLower; nPos := Pos('addresses:', sData); if nPos <> 0 then begin Delete(sData, 1, nPos + 9); end else begin nPos := Pos('address:', sData); if nPos <> 0 then begin Delete(sData, 1, nPos + 9); nPos := Pos('address:', sData); Delete(sData, 1, nPos + 8); end; end; sData := StringReplace(sData, #9, '', [rfReplaceAll]); SplitString(sData, #13#10, IpList); for c := 0 to IpList.Count - 1 do begin sIp := IpList[c]; if IsValidIP(sIp, true, bIncIPv6) and (ExtrIpList.IndexOf(sIp) = -1) then begin ExtrIpList.Add(sIp); SumString(Result, sIp, sDm); end; end; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. ExtractIPsFromUrl()'); end; end; // https://niceit.tistory.com/264 여기서 가져옴 23_0517 16:54:28 kku function IsUrlValid(const url: string; nTmSec : Integer = 0): boolean; var hInet: HINTERNET; hConnect: HINTERNET; infoBuffer: array [0..512] of char; dummy: DWORD; bufLen: DWORD; okay: LongBool; sReply: String; begin Result := false; try if pos('://-', url) > 0 then exit; hConnect := nil; hInet := InternetOpen(PChar('EYECOMA'), INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY, nil, nil, 0); if hInet = nil then exit; try //타임아웃을 지정할 수 있음 //간혹 지정하지 않는 경우 무한루프에 빠질 수도 있으니 지정하기를 권장 //적어도 1~2초 이내에는 반환 하는 것이 정상이므로 2초 정도 권장 if nTmSec > 0 then InternetSetOption(hInet, INTERNET_OPTION_CONNECT_TIMEOUT, @nTmSec, SizeOf(nTmSec)); //연결해 본다.. hConnect := InternetOpenUrl(hInet,PChar(url), nil, 0, INTERNET_FLAG_NO_UI, 0); if Assigned(hConnect) then begin // Wininet을 이용해 호출할 URL 정보를 만든다. dummy := 0; bufLen := Length(infoBuffer); okay := HttpQueryInfo(hConnect, HTTP_QUERY_STATUS_CODE, @infoBuffer[0], bufLen, dummy); if okay then begin sReply := infoBuffer; if (sReply = '200') or (sReply = '401') or (sReply = '500') then Result := true; // if sReply = '200' then // // 네비게이션 웹페이지가 존재 한다 // Result := true // else if sReply = '401' then // // 대부분 페이지는 존재하지만 인증 문제가 있다. // // 어떤 인증문제인지는 체크 불가함 // Result := true; // else if sReply = '404' then // // 호출 대상 파일을 찾을 수 없다. // exit; // else if sReply = '500' then // // 대부분 웹서버 내부 오류 // exit; // else // {TODO:HTTP프로토콜의 다른 응답 코드에 대해 이 부분에서 추가 처리할것} // Result := False; end; end; finally if hConnect <> nil then InternetCloseHandle(hConnect); if hInet <> nil then InternetCloseHandle(hInet); end; except on E: Exception do ETgException.TraceException(E, 'Fail .. IsUrlValid()'); end; end; function UrlEncodeUTF8(const sText: String): String; var i: Integer; c: AnsiChar; sUtf8: UTF8String; begin Result := ''; sUtf8 := UTF8Encode(sText); // UTF-8 변환 for i := 1 to Length(sUtf8) do begin c := sUtf8[i]; // URL-safe 문자 if (c in ['A'..'Z', 'a'..'z', '0'..'9', '-', '_', '.', '~']) then Result := Result + Char(c) else Result := Result + '%' + IntToHex(Ord(c), 2); // %XX 형태로 인코딩 end; end; function UrlDecodeUTF8(const sText: String): String; var i, nLen: Integer; sHex: String; pBuf: TBytes; uc: Byte; begin SetLength(pBuf, 0); i := 1; nLen := Length(sText); while i <= nLen do begin if sText[i] = '%' then begin // %XX 형태인지 확인 if (i + 2 <= nLen) then begin sHex := Copy(sText, i + 1, 2); uc := Byte(StrToInt('$' + sHex)); pBuf := pBuf + [uc]; Inc(i, 3); // %XX 건너뛰기 Continue; end; end else if sText[i] = '+' then begin // 보통 URL 인코딩에서는 + 를 공백으로 처리하는 경우도 있음 pBuf := pBuf + [Ord(' ')]; Inc(i); Continue; end; // 일반 ASCII 문자 pBuf := pBuf + [Ord(sText[i])]; Inc(i); end; // UTF-8 → Unicode string 변환 Result := TEncoding.UTF8.GetString(pBuf); end; end.