2011년 2월 24일 목요일

델파이 2009 중 Indy FTP와 Http의 한글(or그외문자) 깨짐 임시해결

델파이 2009 문자체계가 변해서 에로사항이 많을줄로 알고 있습니다.
제 경우만해도 http와 ftp등을 이용해서 데이터 리스트 혹은 데이터 자체를 받아와야하는데.
string자체가 wide형식으로 바뀌어서 고생을 하고 있습죠.
특히나 영문은 괜찮은데 한글... 정말 문제 더군요.
또한 영문, 한글, 일어등등의 문자코드(예 한글은 949) 문제 역시 아니었습니다
일단 근본적인 원인을 모른체 TEncoding등을 이용해서 해결해 보려했습니다만..
안됩니다 -_-;; (물론 서버프로그래밍까지해서 C/S간 컨버터를 하면 가능합니다만, 비추천이죠 -_-)

예를 설명하기전에 모든 데이터는 Stream(TStringStream X) 인 TMemoryStream을 이용하여
데이터를 가져오면 제대로 컨버팅 및 문자코드를 확인할 수 있었습니다.
물론 원인을 해결하게 위한 우선 방법 이었죠. 허나 FTP에선 이방법을 취할 수가 없었습니다 -_-;

간단하게 IdFTP와 IdHttp로만 예를 들어보겠습니다.
서버엔 'a바보'라고 html코드안에 스트링 및 이이름의 파일을 맹글어 놓았습니다.
IdFTP.DirectoryListing;
IdHttp.Get('http://test.com/test.htm');

일단 근본적인 원인을 해결하게 위해 IdHttp.Get('유알엘', TMemoryStream); 을 이용해서 데이터를 가져와
헥사코드로 데이터를 열어보니 '61 B9 D9 BA B8'로 5byte로 나오더랩니다.
이게 무슨말이냐~ 하면, 네... AnsiString 이죠.
이걸 걍 TmpStr := IdHttp.Get('유알엘'); 요렇게 해버리면 10byte로 바뀌어서
'61 00 B9 00 D9 00 BA 00 B8 00' 요렇게 나와서 글자자체가 완전히 깨져서 나오더랩니다.
Http는 Stream을 넣어서 바로 해결할 수 있었지만, FTP의 경우는 어찌할 방법이 없어서
걍 Function을 하나 10분동안 날코딩으로 맹글었습니다.
그래서 불필요한 코드가 눈에 띄긴하는데.. 머 대충 정리해 봐주세요.

마무쪼록 임시적으로나마 해결하시길 ㅠㅠ
이문제를 완전히 해결하셨거나, 혹은 다른 방법이 있으면 한수 가르쳐 주세요~

function OutDataToAnsi(aString : String) : String;
var
  aMs      : TMemoryStream;
  aAnsiStr : AnsiString;
  aTmpStr  : AnsiString;
  AnsiL    : Integer;
  I , aCnt : Integer;
begin
  Result := '';
  aMs := TMemoryStream.Create;
  AnsiL := Length(aString) * SizeOf(Char);
  SetLength(aTmpStr, AnsiL);
  SetLength(aAnsiStr, AnsiL div 2);
  aCnt := AnsiL div 2;
  try
    aMs.WriteBuffer(PChar(aString)^, AnsiL);
    aMs.Position := 0;
    aMs.ReadBuffer(PAnsiChar(aTmpStr)^, AnsiL);
  finally
    aMs.Free;
  end;
  for I := 0 to aCnt - 1 do
    aAnsiStr[I + 1] := aTmpStr[I + I + 1];
  Result := String(aAnsiStr);
end;

EX)
tmpstr := IdHttp1.Get('유알엘');
tmpstr := OutDataToAnsi(tmpstr);
showmessage(tmpstr);

====================================================
function GetURLContent(const URL: string; idHTTP: TIdHTTP): string;
var
  Stream: TBytesStream;
begin
  Stream := TBytesStream.Create;
  try
    idHTTP.Get(URL, Stream, []);
    Result := TEncoding.Default.GetString(Stream.Bytes,0,Stream.Size);
  finally
    Stream.Free;
  end;
end;

위와 같이 하면 정상적으로 한글이 표시되네요
==============================================
idFTP소스를 보니 8비트 인코딩을 Windows-1252형식으로 처리하더군요. 그래서 한글 깨짐이 발생합니다.
List메서드의
FListResult.LoadFromStream(LDest, GetEncoder(IOHandler.DefStringEncoding));
와 같이 잘못 인코딩하는 부분을
TEncoding.Default.GetString(LDest.Bytes,0,LDest.Size);
와 같은 형식으로 바꾸니 한글이 정상적으로 보입니다.
간단하게는 IdGlobal.pas에 있는 GetEncoder에서 인코딩을 1252에서 949로 바꿔주면 됩니다.
idFTP소스를 직접 수정하는게 가장 확실한 방법일 것 같습니다
=============================================
Indy 소스에서 유니코드 관련 문제가 있어서 발생하는 문제이기 때문에 해당 소스를 수정하지 않고 별도의 루틴을 작성하게 되면, 나중에 관련 버그가 패치되면 오히려 별도로 작성한 부분으로 인해 다시 문제가 될 소지가 있습니다.
참고로 idHTTP문제도 IdGlobal.pas의 BytesToString함수에 있는
      for i := AStartIndex to LLength - 1 do begin
        Result[ i + 1] := Char(AValue[ i]);
      end;

Result:= StringOf(AValue);
로 수정하면 idHTTP.Get를 그대로 사용해도 정상적으로
동작합니다.

Indy의 idHTTP.Get에서의 한글 깨짐 회피 방법

var
  rbstr: RawByteString;
  HTML: String;
  MemoryStream: TMemoryStream;
begin
  MemoryStream := TMemoryStream.Create;
  IdHTTP1.Get('http://blog.devgear.co.kr/imp', MemoryStream);
  rbstr := PAnsiChar(MemoryStream.Memory);
  MemoryStream.Free;
  if (Pos('utf-8', IdHTTP1.Response.ContentType)=0) and (AnsiPos('charset=utf-8', rbstr)=0) then<BR>    SetCodePage(rbstr, 949, false)
  else
    SetCodePage(rbstr, 65001, false);
  HTML := rbstr;
end;

Adding a Google Gadget to your blog

델파이에서 MD5 해시 사용하기

델파이에서 MD5 해시 사용하기

uses IdHashMessageDigest, idHash;

//returns MD5 has for a file
function MD5(const fileName : string) :string;
var
  idmd5 : TIdHashMessageDigest5;
  fs : TFileStream;
begin
  idmd5 := TIdHashMessageDigest5.Create;
  fs := TFileStream.Create(fileName, fmOpenRead OR fmShareDenyWrite) ;
  try
    result := idmd5.AsHex(idmd5.HashValue(fs)) ;
  finally
    fs.Free;
    idmd5.Free;
  end;
end;

2007 버전 이상에서는, 함수가 변경되어서, 다음과 같이 
//returns MD5 has for a file
function MD5(const fileName : string) :string;
var
  idmd5 : TIdHashMessageDigest5;
  fs : TFileStream;
begin
  idmd5 := TIdHashMessageDigest5.Create;
  fs := TFileStream.Create(fileName, fmOpenRead OR fmShareDenyWrite) ;
  try
    result := idmd5.HashStreamAsHex(fs);
  finally
    fs.Free;
    idmd5.Free;
  end;
end;
로 쓰면 됩니다.