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