libclamunrar/consio.cpp
d39cb658
 #include "rar.hpp"
 #include "log.cpp"
 
 static MESSAGE_TYPE MsgStream=MSG_STDOUT;
 static RAR_CHARSET RedirectCharset=RCH_DEFAULT;
 
 const int MaxMsgSize=2*NM+2048;
 
 static bool StdoutRedirected=false,StderrRedirected=false,StdinRedirected=false;
 
 #ifdef _WIN_ALL
 static bool IsRedirected(DWORD nStdHandle)
 {
   HANDLE hStd=GetStdHandle(nStdHandle);
   DWORD Mode;
   return GetFileType(hStd)!=FILE_TYPE_CHAR || GetConsoleMode(hStd,&Mode)==0;
 }
 #endif
 
 
 void InitConsole()
 {
 #ifdef _WIN_ALL
   // We want messages like file names or progress percent to be printed
   // immediately. Use only in Windows, in Unix they can cause wprintf %ls
   // to fail with non-English strings.
   setbuf(stdout,NULL);
   setbuf(stderr,NULL);
 
   // Detect if output is redirected and set output mode properly.
   // We do not want to send Unicode output to files and especially to pipes
   // like '|more', which cannot handle them correctly in Windows.
   // In Unix console output is UTF-8 and it is handled correctly
   // when redirecting, so no need to perform any adjustments.
   StdoutRedirected=IsRedirected(STD_OUTPUT_HANDLE);
   StderrRedirected=IsRedirected(STD_ERROR_HANDLE);
   StdinRedirected=IsRedirected(STD_INPUT_HANDLE);
 #ifdef _MSC_VER
   if (!StdoutRedirected)
     _setmode(_fileno(stdout), _O_U16TEXT);
   if (!StderrRedirected)
     _setmode(_fileno(stderr), _O_U16TEXT);
 #endif
 #elif defined(_UNIX)
   StdoutRedirected=!isatty(fileno(stdout));
   StderrRedirected=!isatty(fileno(stderr));
   StdinRedirected=!isatty(fileno(stdin));
 #endif
 }
 
 
 void SetConsoleMsgStream(MESSAGE_TYPE MsgStream)
 {
   ::MsgStream=MsgStream;
 }
 
 
 void SetConsoleRedirectCharset(RAR_CHARSET RedirectCharset)
 {
   ::RedirectCharset=RedirectCharset;
 }
 
 
 #ifndef SILENT
 static void cvt_wprintf(FILE *dest,const wchar *fmt,va_list arglist)
 {
   // This buffer is for format string only, not for entire output,
   // so it can be short enough.
   wchar fmtw[1024];
   PrintfPrepareFmt(fmt,fmtw,ASIZE(fmtw));
 #ifdef _WIN_ALL
   safebuf wchar Msg[MaxMsgSize];
   if (dest==stdout && StdoutRedirected || dest==stderr && StderrRedirected)
   {
     HANDLE hOut=GetStdHandle(dest==stdout ? STD_OUTPUT_HANDLE:STD_ERROR_HANDLE);
     vswprintf(Msg,ASIZE(Msg),fmtw,arglist);
     DWORD Written;
     if (RedirectCharset==RCH_UNICODE)
       WriteFile(hOut,Msg,(DWORD)wcslen(Msg)*sizeof(*Msg),&Written,NULL);
     else
     {
       // Avoid Unicode for redirect in Windows, it does not work with pipes.
       safebuf char MsgA[MaxMsgSize];
       if (RedirectCharset==RCH_UTF8)
         WideToUtf(Msg,MsgA,ASIZE(MsgA));
       else
         WideToChar(Msg,MsgA,ASIZE(MsgA));
       if (RedirectCharset==RCH_DEFAULT || RedirectCharset==RCH_OEM)
         CharToOemA(MsgA,MsgA); // Console tools like 'more' expect OEM encoding.
 
       // We already converted \n to \r\n above, so we use WriteFile instead
       // of C library to avoid unnecessary additional conversion.
       WriteFile(hOut,MsgA,(DWORD)strlen(MsgA),&Written,NULL);
     }
     return;
   }
   // MSVC2008 vfwprintf writes every character to console separately
   // and it is too slow. We use direct WriteConsole call instead.
   vswprintf(Msg,ASIZE(Msg),fmtw,arglist);
   HANDLE hOut=GetStdHandle(dest==stderr ? STD_ERROR_HANDLE:STD_OUTPUT_HANDLE);
   DWORD Written;
   WriteConsole(hOut,Msg,(DWORD)wcslen(Msg),&Written,NULL);
 #else
   vfwprintf(dest,fmtw,arglist);
   // We do not use setbuf(NULL) in Unix (see comments in InitConsole).
   fflush(dest);
 #endif
 }
 
 
 void mprintf(const wchar *fmt,...)
 {
   if (MsgStream==MSG_NULL || MsgStream==MSG_ERRONLY)
     return;
 
   fflush(stderr); // Ensure proper message order.
 
   va_list arglist;
   va_start(arglist,fmt);
   FILE *dest=MsgStream==MSG_STDERR ? stderr:stdout;
   cvt_wprintf(dest,fmt,arglist);
   va_end(arglist);
 }
 #endif
 
 
 #ifndef SILENT
 void eprintf(const wchar *fmt,...)
 {
   if (MsgStream==MSG_NULL)
     return;
 
   fflush(stdout); // Ensure proper message order.
 
   va_list arglist;
   va_start(arglist,fmt);
   cvt_wprintf(stderr,fmt,arglist);
   va_end(arglist);
 }
 #endif
 
 
 #ifndef SILENT
 static void GetPasswordText(wchar *Str,uint MaxLength)
 {
   if (MaxLength==0)
     return;
   if (StdinRedirected)
     getwstr(Str,MaxLength); // Read from pipe or redirected file.
   else
   {
 #ifdef _WIN_ALL
     HANDLE hConIn=GetStdHandle(STD_INPUT_HANDLE);
     HANDLE hConOut=GetStdHandle(STD_OUTPUT_HANDLE);
     DWORD ConInMode,ConOutMode;
     DWORD Read=0;
     GetConsoleMode(hConIn,&ConInMode);
     GetConsoleMode(hConOut,&ConOutMode);
     SetConsoleMode(hConIn,ENABLE_LINE_INPUT);
     SetConsoleMode(hConOut,ENABLE_PROCESSED_OUTPUT|ENABLE_WRAP_AT_EOL_OUTPUT);
 
     ReadConsole(hConIn,Str,MaxLength-1,&Read,NULL);
     Str[Read]=0;
     SetConsoleMode(hConIn,ConInMode);
     SetConsoleMode(hConOut,ConOutMode);
 #else
     char StrA[MAXPASSWORD];
 #if defined(_EMX) || defined (__VMS)
     fgets(StrA,ASIZE(StrA)-1,stdin);
 #elif defined(__sun)
     strncpyz(StrA,getpassphrase(""),ASIZE(StrA));
 #else
     strncpyz(StrA,getpass(""),ASIZE(StrA));
 #endif
     CharToWide(StrA,Str,MaxLength);
     cleandata(StrA,sizeof(StrA));
 #endif
   }
   Str[MaxLength-1]=0;
   RemoveLF(Str);
 }
 #endif
 
 
 #ifndef SILENT
 bool GetConsolePassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password)
 {
   if (!StdinRedirected)
     uiAlarm(UIALARM_QUESTION);
   
   while (true)
   {
     if (!StdinRedirected)
       if (Type==UIPASSWORD_GLOBAL)
         eprintf(L"\n%s: ",St(MAskPsw));
       else
         eprintf(St(MAskPswFor),FileName);
 
     wchar PlainPsw[MAXPASSWORD];
     GetPasswordText(PlainPsw,ASIZE(PlainPsw));
     if (*PlainPsw==0 && Type==UIPASSWORD_GLOBAL)
       return false;
     if (!StdinRedirected && Type==UIPASSWORD_GLOBAL)
     {
       eprintf(St(MReAskPsw));
       wchar CmpStr[MAXPASSWORD];
       GetPasswordText(CmpStr,ASIZE(CmpStr));
       if (*CmpStr==0 || wcscmp(PlainPsw,CmpStr)!=0)
       {
         eprintf(St(MNotMatchPsw));
         cleandata(PlainPsw,sizeof(PlainPsw));
         cleandata(CmpStr,sizeof(CmpStr));
         continue;
       }
       cleandata(CmpStr,sizeof(CmpStr));
     }
     Password->Set(PlainPsw);
     cleandata(PlainPsw,sizeof(PlainPsw));
     break;
   }
   return true;
 }
 #endif
 
 
 #ifndef SILENT
 bool getwstr(wchar *str,size_t n)
 {
   // Print buffered prompt title function before waiting for input.
   fflush(stderr);
 
   *str=0;
 #if defined(_WIN_ALL)
   // fgetws does not work well with non-English text in Windows,
   // so we do not use it.
   if (StdinRedirected) // ReadConsole does not work if redirected.
   {
     // fgets does not work well with pipes in Windows in our test.
     // Let's use files.
     Array<char> StrA(n*4); // Up to 4 UTF-8 characters per wchar_t.
     File SrcFile;
     SrcFile.SetHandleType(FILE_HANDLESTD);
     int ReadSize=SrcFile.Read(&StrA[0],StrA.Size()-1);
     if (ReadSize<=0)
     {
       // Looks like stdin is a null device. We can enter to infinite loop
       // calling Ask(), so let's better exit.
       ErrHandler.Exit(RARX_USERBREAK);
     }
     StrA[ReadSize]=0;
     CharToWide(&StrA[0],str,n);
     cleandata(&StrA[0],StrA.Size()); // We can use this function to enter passwords.
   }
   else
   {
     DWORD ReadSize=0;
     if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE),str,DWORD(n-1),&ReadSize,NULL)==0)
       return false;
     str[ReadSize]=0;
   }
 #else
   if (fgetws(str,n,stdin)==NULL)
     ErrHandler.Exit(RARX_USERBREAK); // Avoid infinite Ask() loop.
 #endif
   RemoveLF(str);
   return true;
 }
 #endif
 
 
 #ifndef SILENT
 // We allow this function to return 0 in case of invalid input,
 // because it might be convenient to press Enter to some not dangerous
 // prompts like "insert disk with next volume". We should call this function
 // again in case of 0 in dangerous prompt such as overwriting file.
 int Ask(const wchar *AskStr)
 {
   uiAlarm(UIALARM_QUESTION);
 
   const int MaxItems=10;
   wchar Item[MaxItems][40];
   int ItemKeyPos[MaxItems],NumItems=0;
 
   for (const wchar *NextItem=AskStr;NextItem!=NULL;NextItem=wcschr(NextItem+1,'_'))
   {
     wchar *CurItem=Item[NumItems];
     wcsncpyz(CurItem,NextItem+1,ASIZE(Item[0]));
     wchar *EndItem=wcschr(CurItem,'_');
     if (EndItem!=NULL)
       *EndItem=0;
     int KeyPos=0,CurKey;
     while ((CurKey=CurItem[KeyPos])!=0)
     {
       bool Found=false;
       for (int I=0;I<NumItems && !Found;I++)
         if (toupperw(Item[I][ItemKeyPos[I]])==toupperw(CurKey))
           Found=true;
       if (!Found && CurKey!=' ')
         break;
       KeyPos++;
     }
     ItemKeyPos[NumItems]=KeyPos;
     NumItems++;
   }
 
   for (int I=0;I<NumItems;I++)
   {
     eprintf(I==0 ? (NumItems>4 ? L"\n":L" "):L", ");
     int KeyPos=ItemKeyPos[I];
     for (int J=0;J<KeyPos;J++)
       eprintf(L"%c",Item[I][J]);
     eprintf(L"[%c]%ls",Item[I][KeyPos],&Item[I][KeyPos+1]);
   }
   eprintf(L" ");
   wchar Str[50];
   getwstr(Str,ASIZE(Str));
   wchar Ch=toupperw(Str[0]);
   for (int I=0;I<NumItems;I++)
     if (Ch==Item[I][ItemKeyPos[I]])
       return I+1;
   return 0;
 }
 #endif
 
 
 static bool IsCommentUnsafe(const wchar *Data,size_t Size)
 {
   for (size_t I=0;I<Size;I++)
     if (Data[I]==27 && Data[I+1]=='[')
       for (size_t J=I+2;J<Size;J++)
       {
         // Return true for <ESC>[{key};"{string}"p used to redefine
         // a keyboard key on some terminals.
         if (Data[J]=='\"')
           return true;
         if (!IsDigit(Data[J]) && Data[J]!=';')
           break;
       }
   return false;
 }
 
 
 void OutComment(const wchar *Comment,size_t Size)
 {
   if (IsCommentUnsafe(Comment,Size))
     return;
   const size_t MaxOutSize=0x400;
   for (size_t I=0;I<Size;I+=MaxOutSize)
   {
     wchar Msg[MaxOutSize+1];
     size_t CopySize=Min(MaxOutSize,Size-I);
     wcsncpy(Msg,Comment+I,CopySize);
     Msg[CopySize]=0;
     mprintf(L"%s",Msg);
   }
   mprintf(L"\n");
 }