consio.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. #include "rar.hpp"
  2. #include "log.cpp"
  3. static MESSAGE_TYPE MsgStream=MSG_STDOUT;
  4. static RAR_CHARSET RedirectCharset=RCH_DEFAULT;
  5. static bool ProhibitInput=false;
  6. const int MaxMsgSize=2*NM+2048;
  7. static bool StdoutRedirected=false,StderrRedirected=false,StdinRedirected=false;
  8. #ifdef _WIN_ALL
  9. static bool IsRedirected(DWORD nStdHandle)
  10. {
  11. HANDLE hStd=GetStdHandle(nStdHandle);
  12. DWORD Mode;
  13. return GetFileType(hStd)!=FILE_TYPE_CHAR || GetConsoleMode(hStd,&Mode)==0;
  14. }
  15. #endif
  16. void InitConsole()
  17. {
  18. #ifdef _WIN_ALL
  19. // We want messages like file names or progress percent to be printed
  20. // immediately. Use only in Windows, in Unix they can cause wprintf %ls
  21. // to fail with non-English strings.
  22. setbuf(stdout,NULL);
  23. setbuf(stderr,NULL);
  24. // Detect if output is redirected and set output mode properly.
  25. // We do not want to send Unicode output to files and especially to pipes
  26. // like '|more', which cannot handle them correctly in Windows.
  27. // In Unix console output is UTF-8 and it is handled correctly
  28. // when redirecting, so no need to perform any adjustments.
  29. StdoutRedirected=IsRedirected(STD_OUTPUT_HANDLE);
  30. StderrRedirected=IsRedirected(STD_ERROR_HANDLE);
  31. StdinRedirected=IsRedirected(STD_INPUT_HANDLE);
  32. #ifdef _MSC_VER
  33. if (!StdoutRedirected)
  34. _setmode(_fileno(stdout), _O_U16TEXT);
  35. if (!StderrRedirected)
  36. _setmode(_fileno(stderr), _O_U16TEXT);
  37. #endif
  38. #elif defined(_UNIX)
  39. StdoutRedirected=!isatty(fileno(stdout));
  40. StderrRedirected=!isatty(fileno(stderr));
  41. StdinRedirected=!isatty(fileno(stdin));
  42. #endif
  43. }
  44. void SetConsoleMsgStream(MESSAGE_TYPE MsgStream)
  45. {
  46. ::MsgStream=MsgStream;
  47. }
  48. void SetConsoleRedirectCharset(RAR_CHARSET RedirectCharset)
  49. {
  50. ::RedirectCharset=RedirectCharset;
  51. }
  52. void ProhibitConsoleInput()
  53. {
  54. ProhibitInput=true;
  55. }
  56. #ifndef SILENT
  57. static void cvt_wprintf(FILE *dest,const wchar *fmt,va_list arglist)
  58. {
  59. // This buffer is for format string only, not for entire output,
  60. // so it can be short enough.
  61. wchar fmtw[1024];
  62. PrintfPrepareFmt(fmt,fmtw,ASIZE(fmtw));
  63. #ifdef _WIN_ALL
  64. safebuf wchar Msg[MaxMsgSize];
  65. if (dest==stdout && StdoutRedirected || dest==stderr && StderrRedirected)
  66. {
  67. HANDLE hOut=GetStdHandle(dest==stdout ? STD_OUTPUT_HANDLE:STD_ERROR_HANDLE);
  68. vswprintf(Msg,ASIZE(Msg),fmtw,arglist);
  69. DWORD Written;
  70. if (RedirectCharset==RCH_UNICODE)
  71. WriteFile(hOut,Msg,(DWORD)wcslen(Msg)*sizeof(*Msg),&Written,NULL);
  72. else
  73. {
  74. // Avoid Unicode for redirect in Windows, it does not work with pipes.
  75. safebuf char MsgA[MaxMsgSize];
  76. if (RedirectCharset==RCH_UTF8)
  77. WideToUtf(Msg,MsgA,ASIZE(MsgA));
  78. else
  79. WideToChar(Msg,MsgA,ASIZE(MsgA));
  80. if (RedirectCharset==RCH_DEFAULT || RedirectCharset==RCH_OEM)
  81. CharToOemA(MsgA,MsgA); // Console tools like 'more' expect OEM encoding.
  82. // We already converted \n to \r\n above, so we use WriteFile instead
  83. // of C library to avoid unnecessary additional conversion.
  84. WriteFile(hOut,MsgA,(DWORD)strlen(MsgA),&Written,NULL);
  85. }
  86. return;
  87. }
  88. // MSVC2008 vfwprintf writes every character to console separately
  89. // and it is too slow. We use direct WriteConsole call instead.
  90. vswprintf(Msg,ASIZE(Msg),fmtw,arglist);
  91. HANDLE hOut=GetStdHandle(dest==stderr ? STD_ERROR_HANDLE:STD_OUTPUT_HANDLE);
  92. DWORD Written;
  93. WriteConsole(hOut,Msg,(DWORD)wcslen(Msg),&Written,NULL);
  94. #else
  95. vfwprintf(dest,fmtw,arglist);
  96. // We do not use setbuf(NULL) in Unix (see comments in InitConsole).
  97. fflush(dest);
  98. #endif
  99. }
  100. void mprintf(const wchar *fmt,...)
  101. {
  102. if (MsgStream==MSG_NULL || MsgStream==MSG_ERRONLY)
  103. return;
  104. fflush(stderr); // Ensure proper message order.
  105. va_list arglist;
  106. va_start(arglist,fmt);
  107. FILE *dest=MsgStream==MSG_STDERR ? stderr:stdout;
  108. cvt_wprintf(dest,fmt,arglist);
  109. va_end(arglist);
  110. }
  111. #endif
  112. #ifndef SILENT
  113. void eprintf(const wchar *fmt,...)
  114. {
  115. if (MsgStream==MSG_NULL)
  116. return;
  117. fflush(stdout); // Ensure proper message order.
  118. va_list arglist;
  119. va_start(arglist,fmt);
  120. cvt_wprintf(stderr,fmt,arglist);
  121. va_end(arglist);
  122. }
  123. #endif
  124. #ifndef SILENT
  125. static void QuitIfInputProhibited()
  126. {
  127. // We cannot handle user prompts if -si is used to read file or archive data
  128. // from stdin.
  129. if (ProhibitInput)
  130. {
  131. mprintf(St(MStdinNoInput));
  132. ErrHandler.Exit(RARX_FATAL);
  133. }
  134. }
  135. static void GetPasswordText(wchar *Str,uint MaxLength)
  136. {
  137. if (MaxLength==0)
  138. return;
  139. QuitIfInputProhibited();
  140. if (StdinRedirected)
  141. getwstr(Str,MaxLength); // Read from pipe or redirected file.
  142. else
  143. {
  144. #ifdef _WIN_ALL
  145. HANDLE hConIn=GetStdHandle(STD_INPUT_HANDLE);
  146. DWORD ConInMode;
  147. GetConsoleMode(hConIn,&ConInMode);
  148. SetConsoleMode(hConIn,ENABLE_LINE_INPUT); // Remove ENABLE_ECHO_INPUT.
  149. // We prefer ReadConsole to ReadFile, so we can read Unicode input.
  150. DWORD Read=0;
  151. ReadConsole(hConIn,Str,MaxLength-1,&Read,NULL);
  152. Str[Read]=0;
  153. SetConsoleMode(hConIn,ConInMode);
  154. // If entered password is longer than MAXPASSWORD and truncated,
  155. // read its unread part anyway, so it isn't read later as the second
  156. // password for -p switch. Low level FlushConsoleInputBuffer doesn't help
  157. // for high level ReadConsole, which in line input mode seems to store
  158. // the rest of string in its own internal buffer.
  159. if (wcschr(Str,'\r')==NULL) // If '\r' is missing, the password was truncated.
  160. while (true)
  161. {
  162. wchar Trail[64];
  163. DWORD TrailRead=0;
  164. // Use ASIZE(Trail)-1 to reserve the space for trailing 0.
  165. ReadConsole(hConIn,Trail,ASIZE(Trail)-1,&TrailRead,NULL);
  166. Trail[TrailRead]=0;
  167. if (TrailRead==0 || wcschr(Trail,'\r')!=NULL)
  168. break;
  169. }
  170. #else
  171. char StrA[MAXPASSWORD*4]; // "*4" for multibyte UTF-8 characters.
  172. #if defined(_EMX) || defined (__VMS)
  173. fgets(StrA,ASIZE(StrA)-1,stdin);
  174. #elif defined(__sun)
  175. strncpyz(StrA,getpassphrase(""),ASIZE(StrA));
  176. #else
  177. strncpyz(StrA,getpass(""),ASIZE(StrA));
  178. #endif
  179. CharToWide(StrA,Str,MaxLength);
  180. cleandata(StrA,sizeof(StrA));
  181. #endif
  182. }
  183. Str[MaxLength-1]=0;
  184. RemoveLF(Str);
  185. }
  186. #endif
  187. #ifndef SILENT
  188. bool GetConsolePassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password)
  189. {
  190. if (!StdinRedirected)
  191. uiAlarm(UIALARM_QUESTION);
  192. while (true)
  193. {
  194. // if (!StdinRedirected)
  195. if (Type==UIPASSWORD_GLOBAL)
  196. eprintf(L"\n%s: ",St(MAskPsw));
  197. else
  198. eprintf(St(MAskPswFor),FileName);
  199. wchar PlainPsw[MAXPASSWORD+1];
  200. GetPasswordText(PlainPsw,ASIZE(PlainPsw));
  201. if (*PlainPsw==0 && Type==UIPASSWORD_GLOBAL)
  202. return false;
  203. if (wcslen(PlainPsw)>=MAXPASSWORD)
  204. {
  205. PlainPsw[MAXPASSWORD-1]=0;
  206. uiMsg(UIERROR_TRUNCPSW,MAXPASSWORD-1);
  207. }
  208. if (!StdinRedirected && Type==UIPASSWORD_GLOBAL)
  209. {
  210. eprintf(St(MReAskPsw));
  211. wchar CmpStr[MAXPASSWORD];
  212. GetPasswordText(CmpStr,ASIZE(CmpStr));
  213. if (*CmpStr==0 || wcscmp(PlainPsw,CmpStr)!=0)
  214. {
  215. eprintf(St(MNotMatchPsw));
  216. cleandata(PlainPsw,sizeof(PlainPsw));
  217. cleandata(CmpStr,sizeof(CmpStr));
  218. continue;
  219. }
  220. cleandata(CmpStr,sizeof(CmpStr));
  221. }
  222. Password->Set(PlainPsw);
  223. cleandata(PlainPsw,sizeof(PlainPsw));
  224. break;
  225. }
  226. return true;
  227. }
  228. #endif
  229. #ifndef SILENT
  230. bool getwstr(wchar *str,size_t n)
  231. {
  232. // Print buffered prompt title function before waiting for input.
  233. fflush(stderr);
  234. QuitIfInputProhibited();
  235. *str=0;
  236. #if defined(_WIN_ALL)
  237. // fgetws does not work well with non-English text in Windows,
  238. // so we do not use it.
  239. if (StdinRedirected) // ReadConsole does not work if redirected.
  240. {
  241. // fgets does not work well with pipes in Windows in our test.
  242. // Let's use files.
  243. Array<char> StrA(n*4); // Up to 4 UTF-8 characters per wchar_t.
  244. File SrcFile;
  245. SrcFile.SetHandleType(FILE_HANDLESTD);
  246. SrcFile.SetLineInputMode(true);
  247. int ReadSize=SrcFile.Read(&StrA[0],StrA.Size()-1);
  248. if (ReadSize<=0)
  249. {
  250. // Looks like stdin is a null device. We can enter to infinite loop
  251. // calling Ask(), so let's better exit.
  252. ErrHandler.Exit(RARX_USERBREAK);
  253. }
  254. StrA[ReadSize]=0;
  255. // We expect ANSI encoding here, but "echo text|rar ..." to pipe to RAR,
  256. // such as send passwords, we get OEM encoding by default, unless we
  257. // use "chcp" in console. But we avoid OEM to ANSI conversion,
  258. // because we also want to handle ANSI files redirection correctly,
  259. // like "rar ... < ansifile.txt".
  260. CharToWide(&StrA[0],str,n);
  261. cleandata(&StrA[0],StrA.Size()); // We can use this function to enter passwords.
  262. }
  263. else
  264. {
  265. DWORD ReadSize=0;
  266. if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE),str,DWORD(n-1),&ReadSize,NULL)==0)
  267. return false;
  268. str[ReadSize]=0;
  269. }
  270. #else
  271. if (fgetws(str,n,stdin)==NULL)
  272. ErrHandler.Exit(RARX_USERBREAK); // Avoid infinite Ask() loop.
  273. #endif
  274. RemoveLF(str);
  275. return true;
  276. }
  277. #endif
  278. #ifndef SILENT
  279. // We allow this function to return 0 in case of invalid input,
  280. // because it might be convenient to press Enter to some not dangerous
  281. // prompts like "insert disk with next volume". We should call this function
  282. // again in case of 0 in dangerous prompt such as overwriting file.
  283. int Ask(const wchar *AskStr)
  284. {
  285. uiAlarm(UIALARM_QUESTION);
  286. const int MaxItems=10;
  287. wchar Item[MaxItems][40];
  288. int ItemKeyPos[MaxItems],NumItems=0;
  289. for (const wchar *NextItem=AskStr;NextItem!=NULL;NextItem=wcschr(NextItem+1,'_'))
  290. {
  291. wchar *CurItem=Item[NumItems];
  292. wcsncpyz(CurItem,NextItem+1,ASIZE(Item[0]));
  293. wchar *EndItem=wcschr(CurItem,'_');
  294. if (EndItem!=NULL)
  295. *EndItem=0;
  296. int KeyPos=0,CurKey;
  297. while ((CurKey=CurItem[KeyPos])!=0)
  298. {
  299. bool Found=false;
  300. for (int I=0;I<NumItems && !Found;I++)
  301. if (toupperw(Item[I][ItemKeyPos[I]])==toupperw(CurKey))
  302. Found=true;
  303. if (!Found && CurKey!=' ')
  304. break;
  305. KeyPos++;
  306. }
  307. ItemKeyPos[NumItems]=KeyPos;
  308. NumItems++;
  309. }
  310. for (int I=0;I<NumItems;I++)
  311. {
  312. eprintf(I==0 ? (NumItems>3 ? L"\n":L" "):L", ");
  313. int KeyPos=ItemKeyPos[I];
  314. for (int J=0;J<KeyPos;J++)
  315. eprintf(L"%c",Item[I][J]);
  316. eprintf(L"[%c]%ls",Item[I][KeyPos],&Item[I][KeyPos+1]);
  317. }
  318. eprintf(L" ");
  319. wchar Str[50];
  320. getwstr(Str,ASIZE(Str));
  321. wchar Ch=toupperw(Str[0]);
  322. for (int I=0;I<NumItems;I++)
  323. if (Ch==Item[I][ItemKeyPos[I]])
  324. return I+1;
  325. return 0;
  326. }
  327. #endif
  328. static bool IsCommentUnsafe(const wchar *Data,size_t Size)
  329. {
  330. for (size_t I=0;I<Size;I++)
  331. if (Data[I]==27 && Data[I+1]=='[')
  332. for (size_t J=I+2;J<Size;J++)
  333. {
  334. // Return true for <ESC>[{key};"{string}"p used to redefine
  335. // a keyboard key on some terminals.
  336. if (Data[J]=='\"')
  337. return true;
  338. if (!IsDigit(Data[J]) && Data[J]!=';')
  339. break;
  340. }
  341. return false;
  342. }
  343. void OutComment(const wchar *Comment,size_t Size)
  344. {
  345. if (IsCommentUnsafe(Comment,Size))
  346. return;
  347. const size_t MaxOutSize=0x400;
  348. for (size_t I=0;I<Size;I+=MaxOutSize)
  349. {
  350. wchar Msg[MaxOutSize+1];
  351. size_t CopySize=Min(MaxOutSize,Size-I);
  352. wcsncpy(Msg,Comment+I,CopySize);
  353. Msg[CopySize]=0;
  354. mprintf(L"%s",Msg);
  355. }
  356. mprintf(L"\n");
  357. }