1 /*
2  * Copyright (C) 2006,2007  Justin Karneges <justin@affinix.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301  USA
18  *
19  */
20 
21 #include "qca_support.h"
22 
23 #include "qca_safeobj.h"
24 #include "qpipe.h"
25 
26 #include <QMutex>
27 #include <QPointer>
28 #include <QTextCodec>
29 
30 #ifdef Q_OS_WIN
31 #include <windows.h>
32 #else
33 #if defined(Q_OS_ANDROID) || defined(Q_OS_FREEBSD)
34 #include <termios.h>
35 #else
36 #include <sys/termios.h>
37 #endif
38 #include <fcntl.h>
39 #include <unistd.h>
40 #endif
41 
42 #include <cstdio>
43 #include <cstdlib>
44 
45 #define CONSOLEPROMPT_INPUT_MAX 56
46 
47 Q_DECLARE_METATYPE(QCA::SecureArray)
48 
49 namespace QCA {
50 
51 //----------------------------------------------------------------------------
52 // ConsoleWorker
53 //----------------------------------------------------------------------------
54 class ConsoleWorker : public QObject
55 {
56     Q_OBJECT
57 private:
58     QPipeEnd   in, out;
59     bool       started;
60     QByteArray in_left, out_left;
61 
62 public:
ConsoleWorker(QObject * parent=nullptr)63     ConsoleWorker(QObject *parent = nullptr)
64         : QObject(parent)
65         , in(this)
66         , out(this)
67     {
68         started = false;
69     }
70 
~ConsoleWorker()71     ~ConsoleWorker() override
72     {
73         stop();
74     }
75 
start(Q_PIPE_ID in_id,Q_PIPE_ID out_id)76     void start(Q_PIPE_ID in_id, Q_PIPE_ID out_id)
77     {
78         Q_ASSERT(!started);
79 
80         if (in_id != INVALID_Q_PIPE_ID) {
81             in.take(in_id, QPipeDevice::Read);
82             connect(&in, &QPipeEnd::readyRead, this, &ConsoleWorker::in_readyRead);
83             connect(&in, &QPipeEnd::closed, this, &ConsoleWorker::in_closed);
84             connect(&in, &QPipeEnd::error, this, &ConsoleWorker::in_error);
85             in.enable();
86         }
87 
88         if (out_id != INVALID_Q_PIPE_ID) {
89             out.take(out_id, QPipeDevice::Write);
90             connect(&out, &QPipeEnd::bytesWritten, this, &ConsoleWorker::out_bytesWritten);
91             connect(&out, &QPipeEnd::closed, this, &ConsoleWorker::out_closed);
92             out.enable();
93         }
94 
95         started = true;
96     }
97 
stop()98     void stop()
99     {
100         if (!started)
101             return;
102 
103         if (in.isValid())
104             in.finalizeAndRelease();
105         if (out.isValid())
106             out.release();
107 
108         in_left  = in.read();
109         out_left = out.takeBytesToWrite();
110 
111         started = false;
112     }
113 
isValid() const114     Q_INVOKABLE bool isValid() const
115     {
116         return in.isValid();
117     }
118 
bytesAvailable() const119     Q_INVOKABLE int bytesAvailable() const
120     {
121         return in.bytesAvailable();
122     }
123 
bytesToWrite() const124     Q_INVOKABLE int bytesToWrite() const
125     {
126         return in.bytesToWrite();
127     }
128 
129 public Q_SLOTS:
130 
setSecurityEnabled(bool enabled)131     void setSecurityEnabled(bool enabled)
132     {
133         if (in.isValid())
134             in.setSecurityEnabled(enabled);
135         if (out.isValid())
136             out.setSecurityEnabled(enabled);
137     }
138 
read(int bytes=-1)139     QByteArray read(int bytes = -1)
140     {
141         return in.read(bytes);
142     }
143 
write(const QByteArray & a)144     void write(const QByteArray &a)
145     {
146         out.write(a);
147     }
148 
readSecure(int bytes=-1)149     QCA::SecureArray readSecure(int bytes = -1)
150     {
151         return in.readSecure(bytes);
152     }
153 
writeSecure(const QCA::SecureArray & a)154     void writeSecure(const QCA::SecureArray &a)
155     {
156         out.writeSecure(a);
157     }
158 
closeOutput()159     void closeOutput()
160     {
161         out.close();
162     }
163 
164 public:
takeBytesToRead()165     QByteArray takeBytesToRead()
166     {
167         QByteArray a = in_left;
168         in_left.clear();
169         return a;
170     }
171 
takeBytesToWrite()172     QByteArray takeBytesToWrite()
173     {
174         QByteArray a = out_left;
175         out_left.clear();
176         return a;
177     }
178 
179 Q_SIGNALS:
180     void readyRead();
181     void bytesWritten(int bytes);
182     void inputClosed();
183     void outputClosed();
184 
185 private Q_SLOTS:
in_readyRead()186     void in_readyRead()
187     {
188         emit readyRead();
189     }
190 
out_bytesWritten(int bytes)191     void out_bytesWritten(int bytes)
192     {
193         emit bytesWritten(bytes);
194     }
195 
in_closed()196     void in_closed()
197     {
198         emit inputClosed();
199     }
200 
in_error(QCA::QPipeEnd::Error)201     void in_error(QCA::QPipeEnd::Error)
202     {
203         emit inputClosed();
204     }
205 
out_closed()206     void out_closed()
207     {
208         emit outputClosed();
209     }
210 };
211 
212 //----------------------------------------------------------------------------
213 // ConsoleThread
214 //----------------------------------------------------------------------------
215 class ConsoleThread : public SyncThread
216 {
217     Q_OBJECT
218 public:
219     ConsoleWorker *worker;
220     Q_PIPE_ID      _in_id, _out_id;
221     QByteArray     in_left, out_left;
222     QMutex         call_mutex;
223 
ConsoleThread(QObject * parent=nullptr)224     ConsoleThread(QObject *parent = nullptr)
225         : SyncThread(parent)
226     {
227         qRegisterMetaType<SecureArray>("QCA::SecureArray");
228     }
229 
~ConsoleThread()230     ~ConsoleThread() override
231     {
232         stop();
233     }
234 
start(Q_PIPE_ID in_id,Q_PIPE_ID out_id)235     void start(Q_PIPE_ID in_id, Q_PIPE_ID out_id)
236     {
237         _in_id  = in_id;
238         _out_id = out_id;
239         SyncThread::start();
240     }
241 
stop()242     void stop()
243     {
244         SyncThread::stop();
245     }
246 
mycall(QObject * obj,const char * method,const QVariantList & args=QVariantList ())247     QVariant mycall(QObject *obj, const char *method, const QVariantList &args = QVariantList())
248     {
249         QVariant ret;
250         bool     ok;
251 
252         call_mutex.lock();
253         ret = call(obj, method, args, &ok);
254         call_mutex.unlock();
255 
256         Q_ASSERT(ok);
257         if (!ok) {
258             fprintf(stderr, "QCA: ConsoleWorker call [%s] failed.\n", method);
259             abort();
260             return QVariant();
261         }
262         return ret;
263     }
264 
isValid()265     bool isValid()
266     {
267         return mycall(worker, "isValid").toBool();
268     }
269 
setSecurityEnabled(bool enabled)270     void setSecurityEnabled(bool enabled)
271     {
272         mycall(worker, "setSecurityEnabled", QVariantList() << enabled);
273     }
274 
read(int bytes=-1)275     QByteArray read(int bytes = -1)
276     {
277         return mycall(worker, "read", QVariantList() << bytes).toByteArray();
278     }
279 
write(const QByteArray & a)280     void write(const QByteArray &a)
281     {
282         mycall(worker, "write", QVariantList() << a);
283     }
284 
readSecure(int bytes=-1)285     SecureArray readSecure(int bytes = -1)
286     {
287         return mycall(worker, "readSecure", QVariantList() << bytes).value<SecureArray>();
288     }
289 
writeSecure(const SecureArray & a)290     void writeSecure(const SecureArray &a)
291     {
292         mycall(worker, "writeSecure", QVariantList() << QVariant::fromValue<SecureArray>(a));
293     }
294 
closeOutput()295     void closeOutput()
296     {
297         mycall(worker, "closeOutput");
298     }
299 
bytesAvailable()300     int bytesAvailable()
301     {
302         return mycall(worker, "bytesAvailable").toInt();
303     }
304 
bytesToWrite()305     int bytesToWrite()
306     {
307         return mycall(worker, "bytesToWrite").toInt();
308     }
309 
takeBytesToRead()310     QByteArray takeBytesToRead()
311     {
312         QByteArray a = in_left;
313         in_left.clear();
314         return a;
315     }
316 
takeBytesToWrite()317     QByteArray takeBytesToWrite()
318     {
319         QByteArray a = out_left;
320         out_left.clear();
321         return a;
322     }
323 
324 Q_SIGNALS:
325     void readyRead();
326     void bytesWritten(int);
327     void inputClosed();
328     void outputClosed();
329 
330 protected:
atStart()331     void atStart() override
332     {
333         worker = new ConsoleWorker;
334 
335         // use direct connections here, so that the emits come from
336         //   the other thread.  we can also connect to our own
337         //   signals to avoid having to make slots just to emit.
338         connect(worker, &ConsoleWorker::readyRead, this, &ConsoleThread::readyRead, Qt::DirectConnection);
339         connect(worker, &ConsoleWorker::bytesWritten, this, &ConsoleThread::bytesWritten, Qt::DirectConnection);
340         connect(worker, &ConsoleWorker::inputClosed, this, &ConsoleThread::inputClosed, Qt::DirectConnection);
341         connect(worker, &ConsoleWorker::outputClosed, this, &ConsoleThread::outputClosed, Qt::DirectConnection);
342 
343         worker->start(_in_id, _out_id);
344     }
345 
atEnd()346     void atEnd() override
347     {
348         in_left  = worker->takeBytesToRead();
349         out_left = worker->takeBytesToWrite();
350         delete worker;
351     }
352 };
353 
354 //----------------------------------------------------------------------------
355 // Console
356 //----------------------------------------------------------------------------
357 class ConsolePrivate : public QObject
358 {
359     Q_OBJECT
360 public:
361     Console *q;
362 
363     bool                  started;
364     Console::Type         type;
365     Console::ChannelMode  cmode;
366     Console::TerminalMode mode;
367     ConsoleThread *       thread;
368     ConsoleReference *    ref;
369     Q_PIPE_ID             in_id;
370 
371 #ifdef Q_OS_WIN
372     DWORD old_mode;
373 #else
374     struct termios old_term_attr;
375 #endif
376 
ConsolePrivate(Console * _q)377     ConsolePrivate(Console *_q)
378         : QObject(_q)
379         , q(_q)
380     {
381         started = false;
382         mode    = Console::Default;
383         thread  = new ConsoleThread(this);
384         ref     = nullptr;
385     }
386 
~ConsolePrivate()387     ~ConsolePrivate() override
388     {
389         delete thread;
390         setInteractive(Console::Default);
391     }
392 
setInteractive(Console::TerminalMode m)393     void setInteractive(Console::TerminalMode m)
394     {
395         // no change
396         if (m == mode)
397             return;
398 
399         if (m == Console::Interactive) {
400 #ifdef Q_OS_WIN
401             GetConsoleMode(in_id, &old_mode);
402             SetConsoleMode(in_id, old_mode & (~ENABLE_LINE_INPUT & ~ENABLE_ECHO_INPUT));
403 #else
404             int            fd = in_id;
405             struct termios attr;
406             tcgetattr(fd, &attr);
407             old_term_attr = attr;
408 
409             attr.c_lflag &= ~(ECHO);   // turn off the echo flag
410             attr.c_lflag &= ~(ICANON); // no wait for a newline
411             attr.c_cc[VMIN]  = 1;      // read at least 1 char
412             attr.c_cc[VTIME] = 0;      // set wait time to zero
413 
414             // set the new attributes
415             tcsetattr(fd, TCSAFLUSH, &attr);
416 #endif
417         } else {
418 #ifdef Q_OS_WIN
419             SetConsoleMode(in_id, old_mode);
420 #else
421             int fd = in_id;
422             tcsetattr(fd, TCSANOW, &old_term_attr);
423 #endif
424         }
425 
426         mode = m;
427     }
428 };
429 
430 static Console *g_tty_console = nullptr, *g_stdio_console = nullptr;
431 
Console(Type type,ChannelMode cmode,TerminalMode tmode,QObject * parent)432 Console::Console(Type type, ChannelMode cmode, TerminalMode tmode, QObject *parent)
433     : QObject(parent)
434 {
435     if (type == Tty) {
436         Q_ASSERT(g_tty_console == nullptr);
437         g_tty_console = this;
438     } else {
439         Q_ASSERT(g_stdio_console == nullptr);
440         g_stdio_console = this;
441     }
442 
443     d        = new ConsolePrivate(this);
444     d->type  = type;
445     d->cmode = cmode;
446 
447     Q_PIPE_ID in  = INVALID_Q_PIPE_ID;
448     Q_PIPE_ID out = INVALID_Q_PIPE_ID;
449 
450 #ifdef Q_OS_WIN
451     if (type == Tty) {
452         in = CreateFileA(
453             "CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
454     } else {
455         in = GetStdHandle(STD_INPUT_HANDLE);
456     }
457 #else
458     if (type == Tty) {
459         in = open("/dev/tty", O_RDONLY);
460     } else {
461         in = 0; // stdin
462     }
463 #endif
464     if (cmode == ReadWrite) {
465 #ifdef Q_OS_WIN
466         if (type == Tty) {
467             out = CreateFileA("CONOUT$",
468                               GENERIC_READ | GENERIC_WRITE,
469                               FILE_SHARE_READ | FILE_SHARE_WRITE,
470                               NULL,
471                               OPEN_EXISTING,
472                               0,
473                               NULL);
474         } else {
475             out = GetStdHandle(STD_OUTPUT_HANDLE);
476         }
477 #else
478         if (type == Tty) {
479             out = open("/dev/tty", O_WRONLY);
480         } else {
481             out = 1; // stdout
482         }
483 #endif
484     }
485 
486     d->in_id = in;
487     d->setInteractive(tmode);
488     d->thread->start(in, out);
489 }
490 
~Console()491 Console::~Console()
492 {
493     release();
494     Console::Type type = d->type;
495     delete d;
496     if (type == Tty)
497         g_tty_console = nullptr;
498     else
499         g_stdio_console = nullptr;
500 }
501 
type() const502 Console::Type Console::type() const
503 {
504     return d->type;
505 }
506 
channelMode() const507 Console::ChannelMode Console::channelMode() const
508 {
509     return d->cmode;
510 }
511 
terminalMode() const512 Console::TerminalMode Console::terminalMode() const
513 {
514     return d->mode;
515 }
516 
isStdinRedirected()517 bool Console::isStdinRedirected()
518 {
519 #ifdef Q_OS_WIN
520     HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
521     DWORD  mode;
522     if (GetConsoleMode(h, &mode))
523         return false;
524     return true;
525 #else
526     return (isatty(0) ? false : true); // 0 == stdin
527 #endif
528 }
529 
isStdoutRedirected()530 bool Console::isStdoutRedirected()
531 {
532 #ifdef Q_OS_WIN
533     HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
534     DWORD  mode;
535     if (GetConsoleMode(h, &mode))
536         return false;
537     return true;
538 #else
539     return (isatty(1) ? false : true); // 1 == stdout
540 #endif
541 }
542 
ttyInstance()543 Console *Console::ttyInstance()
544 {
545     return g_tty_console;
546 }
547 
stdioInstance()548 Console *Console::stdioInstance()
549 {
550     return g_stdio_console;
551 }
552 
release()553 void Console::release()
554 {
555     d->thread->stop();
556 }
557 
bytesLeftToRead()558 QByteArray Console::bytesLeftToRead()
559 {
560     return d->thread->takeBytesToRead();
561 }
562 
bytesLeftToWrite()563 QByteArray Console::bytesLeftToWrite()
564 {
565     return d->thread->takeBytesToWrite();
566 }
567 
568 //----------------------------------------------------------------------------
569 // ConsoleReference
570 //----------------------------------------------------------------------------
571 class ConsoleReferencePrivate : public QObject
572 {
573     Q_OBJECT
574 public:
575     ConsoleReference *q;
576 
577     Console *                      console;
578     ConsoleThread *                thread;
579     ConsoleReference::SecurityMode smode;
580     SafeTimer                      lateTrigger;
581     bool                           late_read, late_close;
582 
ConsoleReferencePrivate(ConsoleReference * _q)583     ConsoleReferencePrivate(ConsoleReference *_q)
584         : QObject(_q)
585         , q(_q)
586         , lateTrigger(this)
587     {
588         console = nullptr;
589         thread  = nullptr;
590         connect(&lateTrigger, &SafeTimer::timeout, this, &ConsoleReferencePrivate::doLate);
591         lateTrigger.setSingleShot(true);
592     }
593 
594 private Q_SLOTS:
doLate()595     void doLate()
596     {
597         QPointer<QObject> self = this;
598         if (late_read)
599             emit q->readyRead();
600         if (!self)
601             return;
602         if (late_close)
603             emit q->inputClosed();
604     }
605 };
606 
ConsoleReference(QObject * parent)607 ConsoleReference::ConsoleReference(QObject *parent)
608     : QObject(parent)
609 {
610     d = new ConsoleReferencePrivate(this);
611 }
612 
~ConsoleReference()613 ConsoleReference::~ConsoleReference()
614 {
615     stop();
616     delete d;
617 }
618 
start(Console * console,SecurityMode mode)619 bool ConsoleReference::start(Console *console, SecurityMode mode)
620 {
621     // make sure this reference isn't using a console already
622     Q_ASSERT(!d->console);
623 
624     // one console reference at a time
625     Q_ASSERT(console->d->ref == nullptr);
626 
627     // let's take it
628     d->console         = console;
629     d->thread          = d->console->d->thread;
630     d->console->d->ref = this;
631 
632     const bool valid = d->thread->isValid();
633     const int  avail = d->thread->bytesAvailable();
634 
635     // pipe already closed and no data?  consider this an error
636     if (!valid && avail == 0) {
637         d->console->d->ref = nullptr;
638         d->thread          = nullptr;
639         d->console         = nullptr;
640         return false;
641     }
642 
643     // enable security?  it will last for this active session only
644     d->smode = mode;
645     if (mode == SecurityEnabled)
646         d->thread->setSecurityEnabled(true);
647 
648     connect(d->thread, &ConsoleThread::readyRead, this, &ConsoleReference::readyRead);
649     connect(d->thread, &ConsoleThread::bytesWritten, this, &ConsoleReference::bytesWritten);
650     connect(d->thread, &ConsoleThread::inputClosed, this, &ConsoleReference::inputClosed);
651     connect(d->thread, &ConsoleThread::outputClosed, this, &ConsoleReference::outputClosed);
652 
653     d->late_read  = false;
654     d->late_close = false;
655 
656     if (avail > 0)
657         d->late_read = true;
658 
659     if (!valid)
660         d->late_close = true;
661 
662     if (d->late_read || d->late_close)
663         d->lateTrigger.start();
664 
665     return true;
666 }
667 
stop()668 void ConsoleReference::stop()
669 {
670     if (!d->console)
671         return;
672 
673     d->lateTrigger.stop();
674 
675     disconnect(d->thread, nullptr, this, nullptr);
676 
677     // automatically disable security when we go inactive
678     d->thread->setSecurityEnabled(false);
679 
680     d->console->d->ref = nullptr;
681     d->thread          = nullptr;
682     d->console         = nullptr;
683 }
684 
console() const685 Console *ConsoleReference::console() const
686 {
687     return d->console;
688 }
689 
securityMode() const690 ConsoleReference::SecurityMode ConsoleReference::securityMode() const
691 {
692     return d->smode;
693 }
694 
read(int bytes)695 QByteArray ConsoleReference::read(int bytes)
696 {
697     return d->thread->read(bytes);
698 }
699 
write(const QByteArray & a)700 void ConsoleReference::write(const QByteArray &a)
701 {
702     d->thread->write(a);
703 }
704 
readSecure(int bytes)705 SecureArray ConsoleReference::readSecure(int bytes)
706 {
707     return d->thread->readSecure(bytes);
708 }
709 
writeSecure(const SecureArray & a)710 void ConsoleReference::writeSecure(const SecureArray &a)
711 {
712     d->thread->writeSecure(a);
713 }
714 
closeOutput()715 void ConsoleReference::closeOutput()
716 {
717     d->thread->closeOutput();
718 }
719 
bytesAvailable() const720 int ConsoleReference::bytesAvailable() const
721 {
722     return d->thread->bytesAvailable();
723 }
724 
bytesToWrite() const725 int ConsoleReference::bytesToWrite() const
726 {
727     return d->thread->bytesToWrite();
728 }
729 
730 //----------------------------------------------------------------------------
731 // ConsolePrompt
732 //----------------------------------------------------------------------------
733 class ConsolePrompt::Private : public QObject
734 {
735     Q_OBJECT
736 public:
737     ConsolePrompt *q;
738 
739     Synchronizer                sync;
740     Console *                   con;
741     bool                        own_con;
742     ConsoleReference            console;
743     QString                     promptStr;
744     SecureArray                 result;
745     bool                        waiting;
746     int                         at;
747     bool                        done;
748     bool                        charMode;
749     QTextCodec *                codec;
750     QTextCodec::ConverterState *encstate, *decstate;
751 
Private(ConsolePrompt * _q)752     Private(ConsolePrompt *_q)
753         : QObject(_q)
754         , q(_q)
755         , sync(_q)
756         , console(this)
757     {
758         connect(&console, &ConsoleReference::readyRead, this, &Private::con_readyRead);
759         connect(&console, &ConsoleReference::inputClosed, this, &Private::con_inputClosed);
760 
761         con     = nullptr;
762         own_con = false;
763         waiting = false;
764 
765 #ifdef Q_OS_WIN
766         codec = QTextCodec::codecForMib(106); // UTF-8
767 #else
768         codec = QTextCodec::codecForLocale();
769 #endif
770         encstate = nullptr;
771         decstate = nullptr;
772     }
773 
~Private()774     ~Private() override
775     {
776         reset();
777     }
778 
reset()779     void reset()
780     {
781         delete encstate;
782         encstate = nullptr;
783         delete decstate;
784         decstate = nullptr;
785 
786         console.stop();
787         if (own_con) {
788             delete con;
789             con     = nullptr;
790             own_con = false;
791         }
792     }
793 
start(bool _charMode)794     bool start(bool _charMode)
795     {
796         own_con = false;
797         con     = Console::ttyInstance();
798         if (!con) {
799             con     = new Console(Console::Tty, Console::ReadWrite, Console::Interactive);
800             own_con = true;
801         }
802 
803         result.clear();
804         at       = 0;
805         done     = false;
806         charMode = _charMode;
807 
808         encstate = new QTextCodec::ConverterState(QTextCodec::IgnoreHeader);
809         decstate = new QTextCodec::ConverterState(QTextCodec::IgnoreHeader);
810 
811         if (!console.start(con, ConsoleReference::SecurityEnabled)) {
812             reset();
813             fprintf(stderr, "Console input not available or closed\n");
814             return false;
815         }
816 
817         if (!charMode)
818             writeString(promptStr + QStringLiteral(": "));
819 
820         return true;
821     }
822 
writeString(const QString & str)823     void writeString(const QString &str)
824     {
825         console.writeSecure(codec->fromUnicode(str.unicode(), str.length(), encstate));
826     }
827 
828     // process each char.  internally store the result as utf16, which
829     //   is easier to edit (e.g. backspace)
processChar(QChar c)830     bool processChar(QChar c)
831     {
832         if (charMode) {
833             appendChar(c);
834             done = true;
835             return false;
836         }
837 
838         if (c == QLatin1Char('\r') || c == QLatin1Char('\n')) {
839             writeString(QStringLiteral("\n"));
840             done = true;
841             return false;
842         }
843 
844         if (c == QLatin1Char('\b') || c.unicode() == 0x7f) {
845             if (at > 0) {
846                 --at;
847                 writeString(QStringLiteral("\b \b"));
848                 result.resize(at * sizeof(ushort));
849             }
850             return true;
851         } else if (c.unicode() < 0x20)
852             return true;
853 
854         if (at >= CONSOLEPROMPT_INPUT_MAX)
855             return true;
856 
857         appendChar(c);
858 
859         writeString(QStringLiteral("*"));
860         return true;
861     }
862 
appendChar(QChar c)863     void appendChar(QChar c)
864     {
865         if ((at + 1) * (int)sizeof(ushort) > result.size())
866             result.resize((at + 1) * sizeof(ushort));
867         ushort *p = reinterpret_cast<ushort *>(result.data());
868         p[at++]   = c.unicode();
869     }
870 
convertToUtf8()871     void convertToUtf8()
872     {
873         // convert result from utf16 to utf8, securely
874         QTextCodec *               codec = QTextCodec::codecForMib(106);
875         QTextCodec::ConverterState cstate(QTextCodec::IgnoreHeader);
876         SecureArray                out;
877         const ushort *             ustr = reinterpret_cast<ushort *>(result.data());
878         const int                  len  = result.size() / sizeof(ushort);
879         for (int n = 0; n < len; ++n) {
880             QChar c(ustr[n]);
881             out += codec->fromUnicode(&c, 1, &cstate);
882         }
883         result = out;
884     }
885 
886 private Q_SLOTS:
con_readyRead()887     void con_readyRead()
888     {
889         while (console.bytesAvailable() > 0) {
890             SecureArray buf = console.readSecure(1);
891             if (buf.isEmpty())
892                 break;
893 
894             // convert to unicode and process
895             const QString str  = codec->toUnicode(buf.data(), 1, decstate);
896             bool          quit = false;
897             for (const QChar &c : str) {
898                 if (!processChar(c)) {
899                     quit = true;
900                     break;
901                 }
902             }
903             if (quit)
904                 break;
905         }
906 
907         if (done) {
908             convertToUtf8();
909 
910             reset();
911             if (waiting)
912                 sync.conditionMet();
913             else
914                 emit q->finished();
915         }
916     }
917 
con_inputClosed()918     void con_inputClosed()
919     {
920         fprintf(stderr, "Console input closed\n");
921         if (!done) {
922             done = true;
923             result.clear();
924 
925             reset();
926             if (waiting)
927                 sync.conditionMet();
928             else
929                 emit q->finished();
930         }
931     }
932 };
933 
ConsolePrompt(QObject * parent)934 ConsolePrompt::ConsolePrompt(QObject *parent)
935     : QObject(parent)
936 {
937     d = new Private(this);
938 }
939 
~ConsolePrompt()940 ConsolePrompt::~ConsolePrompt()
941 {
942     delete d;
943 }
944 
getHidden(const QString & promptStr)945 void ConsolePrompt::getHidden(const QString &promptStr)
946 {
947     d->reset();
948 
949     d->promptStr = promptStr;
950     if (!d->start(false)) {
951         QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
952         return;
953     }
954 }
955 
getChar()956 void ConsolePrompt::getChar()
957 {
958     d->reset();
959 
960     if (!d->start(true)) {
961         QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
962         return;
963     }
964 }
965 
waitForFinished()966 void ConsolePrompt::waitForFinished()
967 {
968     // reparent the Console under us (for Synchronizer)
969     QObject *orig_parent = d->con->parent();
970     d->con->setParent(this);
971 
972     // block while prompting
973     d->waiting = true;
974     d->sync.waitForCondition();
975     d->waiting = false;
976 
977     // restore parent (if con still exists)
978     if (d->con)
979         d->con->setParent(orig_parent);
980 }
981 
result() const982 SecureArray ConsolePrompt::result() const
983 {
984     return d->result;
985 }
986 
resultChar() const987 QChar ConsolePrompt::resultChar() const
988 {
989     const QString str = QString::fromUtf8(d->result.toByteArray());
990 
991     // this will never happen if getChar completes
992     if (str.isEmpty())
993         return QChar();
994 
995     return str[0];
996 }
997 
998 }
999 
1000 #include "console.moc"
1001