cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

terminal-handlers.c (42190B)


      1/*
      2 * Licensed to the Apache Software Foundation (ASF) under one
      3 * or more contributor license agreements.  See the NOTICE file
      4 * distributed with this work for additional information
      5 * regarding copyright ownership.  The ASF licenses this file
      6 * to you under the Apache License, Version 2.0 (the
      7 * "License"); you may not use this file except in compliance
      8 * with the License.  You may obtain a copy of the License at
      9 *
     10 *   http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing,
     13 * software distributed under the License is distributed on an
     14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     15 * KIND, either express or implied.  See the License for the
     16 * specific language governing permissions and limitations
     17 * under the License.
     18 */
     19
     20#include "config.h"
     21
     22#include "terminal/char-mappings.h"
     23#include "terminal/palette.h"
     24#include "terminal/terminal.h"
     25#include "terminal/terminal-handlers.h"
     26#include "terminal/terminal-priv.h"
     27#include "terminal/types.h"
     28#include "terminal/xparsecolor.h"
     29
     30#include <guacamole/client.h>
     31#include <guacamole/protocol.h>
     32#include <guacamole/socket.h>
     33
     34#include <stdbool.h>
     35#include <stdlib.h>
     36#include <wchar.h>
     37
     38/**
     39 * Response string sent when identification is requested.
     40 */
     41#define GUAC_TERMINAL_VT102_ID    "\x1B[?6c"
     42
     43/**
     44 * Arbitrary response to ENQ control character.
     45 */
     46#define GUAC_TERMINAL_ANSWERBACK  "GUACAMOLE"
     47
     48/**
     49 * Response which indicates the terminal is alive.
     50 */
     51#define GUAC_TERMINAL_OK          "\x1B[0n"
     52
     53/**
     54 * Advances the cursor to the next row, scrolling if the cursor would otherwise
     55 * leave the scrolling region. If the cursor is already outside the scrolling
     56 * region, the cursor is prevented from leaving the terminal bounds.
     57 *
     58 * @param term
     59 *     The guac_terminal whose cursor should be advanced to the next row.
     60 */
     61static void guac_terminal_linefeed(guac_terminal* term) {
     62
     63    /* Scroll up if necessary */
     64    if (term->cursor_row == term->scroll_end)
     65        guac_terminal_scroll_up(term, term->scroll_start,
     66                term->scroll_end, 1);
     67
     68    /* Otherwise, just advance to next row if space remains */
     69    else if (term->cursor_row < term->term_height - 1)
     70        term->cursor_row++;
     71
     72}
     73
     74/**
     75 * Moves the cursor backward to the previous row, scrolling if the cursor would
     76 * otherwise leave the scrolling region. If the cursor is already outside the
     77 * scrolling region, the cursor is prevented from leaving the terminal bounds.
     78 *
     79 * @param term
     80 *     The guac_terminal whose cursor should be moved backward by one row.
     81 */
     82static void guac_terminal_reverse_linefeed(guac_terminal* term) {
     83
     84    /* Scroll down if necessary */
     85    if (term->cursor_row == term->scroll_start)
     86        guac_terminal_scroll_down(term, term->scroll_start,
     87                term->scroll_end, 1);
     88
     89    /* Otherwise, move back one row if space remains */
     90    else if (term->cursor_row > 0)
     91        term->cursor_row--;
     92
     93}
     94
     95/**
     96 * Sets the position of the cursor without exceeding terminal bounds. Values
     97 * which are out of bounds will be shifted to the nearest legal boundary.
     98 *
     99 * @param term
    100 *     The guac_terminal whose cursor position is being set.
    101 *
    102 * @param row
    103 *     The desired new row position.
    104 *
    105 * @param col
    106 *     The desired new column position.
    107 */
    108static void guac_terminal_move_cursor(guac_terminal* term, int row, int col) {
    109
    110    /* Ensure cursor row is within terminal bounds */
    111    if (row >= term->term_height)
    112        row = term->term_height - 1;
    113    else if (row < 0)
    114        row = 0;
    115
    116    /* Ensure cursor column is within terminal bounds */
    117    if (col >= term->term_width)
    118        col = term->term_width - 1;
    119    else if (col < 0)
    120        col = 0;
    121
    122    /* Update cursor position */
    123    term->cursor_row = row;
    124    term->cursor_col = col;
    125
    126}
    127
    128int guac_terminal_echo(guac_terminal* term, unsigned char c) {
    129
    130    int width;
    131
    132    static int bytes_remaining = 0;
    133    static int codepoint = 0;
    134
    135    const int* char_mapping = term->char_mapping[term->active_char_set];
    136
    137    /* Echo to pipe stream if open and not starting an ESC sequence */
    138    if (term->pipe_stream != NULL && c != 0x1B) {
    139
    140        guac_terminal_pipe_stream_write(term, c);
    141
    142        /* Do not render output while pipe is open unless explicitly requested
    143         * via flags */
    144        if (!(term->pipe_stream_flags & GUAC_TERMINAL_PIPE_INTERPRET_OUTPUT))
    145            return 0;
    146
    147    }
    148
    149    /* If using non-Unicode mapping, just map straight bytes */
    150    if (char_mapping != NULL) {
    151        codepoint = c;
    152        bytes_remaining = 0;
    153    }
    154
    155    /* 1-byte UTF-8 codepoint */
    156    else if ((c & 0x80) == 0x00) {    /* 0xxxxxxx */
    157        codepoint = c & 0x7F;
    158        bytes_remaining = 0;
    159    }
    160
    161    /* 2-byte UTF-8 codepoint */
    162    else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */
    163        codepoint = c & 0x1F;
    164        bytes_remaining = 1;
    165    }
    166
    167    /* 3-byte UTF-8 codepoint */
    168    else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */
    169        codepoint = c & 0x0F;
    170        bytes_remaining = 2;
    171    }
    172
    173    /* 4-byte UTF-8 codepoint */
    174    else if ((c & 0xF8) == 0xF0) { /* 11110xxx */
    175        codepoint = c & 0x07;
    176        bytes_remaining = 3;
    177    }
    178
    179    /* Continuation of UTF-8 codepoint */
    180    else if ((c & 0xC0) == 0x80) { /* 10xxxxxx */
    181        codepoint = (codepoint << 6) | (c & 0x3F);
    182        bytes_remaining--;
    183    }
    184
    185    /* Unrecognized prefix */
    186    else {
    187        codepoint = '?';
    188        bytes_remaining = 0;
    189    }
    190
    191    /* If we need more bytes, wait for more bytes */
    192    if (bytes_remaining != 0)
    193        return 0;
    194
    195    switch (codepoint) {
    196
    197        /* Enquiry */
    198        case 0x05:
    199            guac_terminal_send_string(term, GUAC_TERMINAL_ANSWERBACK);
    200            break;
    201
    202        /* Bell */
    203        case 0x07:
    204            break;
    205
    206        /* Backspace */
    207        case 0x08:
    208            guac_terminal_move_cursor(term, term->cursor_row,
    209                    term->cursor_col - 1);
    210            break;
    211
    212        /* Tab */
    213        case 0x09:
    214            guac_terminal_move_cursor(term, term->cursor_row,
    215                    guac_terminal_next_tab(term, term->cursor_col));
    216            break;
    217
    218        /* Line feed / VT / FF */
    219        case '\n':
    220        case 0x0B: /* VT */
    221        case 0x0C: /* FF */
    222
    223            /* Advance to next row */
    224            guac_terminal_linefeed(term);
    225
    226            /* If automatic carriage return, fall through to CR handler */
    227            if (!term->automatic_carriage_return)
    228                break;
    229
    230        /* Carriage return */
    231        case '\r':
    232            guac_terminal_move_cursor(term, term->cursor_row, 0);
    233            break;
    234
    235        /* SO (activates character set G1) */
    236        case 0x0E:
    237            term->active_char_set = 1;
    238            break;
    239
    240        /* SI (activates character set G0) */
    241        case 0x0F:
    242            term->active_char_set = 0;
    243            break;
    244
    245        /* ESC */
    246        case 0x1B:
    247            term->char_handler = guac_terminal_escape; 
    248            break;
    249
    250        /* CSI */
    251        case 0x9B:
    252            term->char_handler = guac_terminal_csi; 
    253            break;
    254
    255        /* DEL (ignored) */
    256        case 0x7F:
    257            break;
    258
    259        /* Displayable chars */
    260        default:
    261
    262            /* Don't bother handling control chars if unknown */
    263            if (codepoint < 0x20)
    264                break;
    265
    266            /* Translate mappable codepoints to whatever codepoint is mapped */
    267            if (codepoint >= 0x20 && codepoint <= 0xFF && char_mapping != NULL)
    268                codepoint = char_mapping[codepoint - 0x20];
    269
    270            /* Wrap if necessary */
    271            if (term->cursor_col >= term->term_width) {
    272                term->cursor_col = 0;
    273                guac_terminal_linefeed(term);
    274            }
    275
    276            /* If insert mode, shift other characters right by 1 */
    277            if (term->insert_mode)
    278                guac_terminal_copy_columns(term, term->cursor_row,
    279                        term->cursor_col, term->term_width-2, 1);
    280
    281            /* Write character */
    282            guac_terminal_set(term,
    283                    term->cursor_row,
    284                    term->cursor_col,
    285                    codepoint);
    286
    287            width = wcwidth(codepoint);
    288            if (width < 0)
    289                width = 1;
    290
    291            /* Advance cursor */
    292            term->cursor_col += width;
    293
    294    }
    295
    296    return 0;
    297
    298}
    299
    300int guac_terminal_escape(guac_terminal* term, unsigned char c) {
    301
    302    switch (c) {
    303
    304        case '(':
    305            term->char_handler = guac_terminal_g0_charset; 
    306            break;
    307
    308        case ')':
    309            term->char_handler = guac_terminal_g1_charset; 
    310            break;
    311
    312        case ']':
    313            term->char_handler = guac_terminal_osc; 
    314            break;
    315
    316        case '[':
    317            term->char_handler = guac_terminal_csi; 
    318            break;
    319
    320        case '#':
    321            term->char_handler = guac_terminal_ctrl_func; 
    322            break;
    323
    324        /* Save Cursor (DECSC) */
    325        case '7':
    326            term->saved_cursor_row = term->cursor_row;
    327            term->saved_cursor_col = term->cursor_col;
    328
    329            term->char_handler = guac_terminal_echo; 
    330            break;
    331
    332        /* Restore Cursor (DECRC) */
    333        case '8':
    334            guac_terminal_move_cursor(term,
    335                    term->saved_cursor_row,
    336                    term->saved_cursor_col);
    337
    338            term->char_handler = guac_terminal_echo; 
    339            break;
    340
    341        /* Index (IND) */
    342        case 'D':
    343            guac_terminal_linefeed(term);
    344            term->char_handler = guac_terminal_echo; 
    345            break;
    346
    347        /* Next Line (NEL) */
    348        case 'E':
    349            guac_terminal_move_cursor(term, term->cursor_row, 0);
    350            guac_terminal_linefeed(term);
    351            term->char_handler = guac_terminal_echo; 
    352            break;
    353
    354        /* Set Tab (HTS) */
    355        case 'H':
    356            guac_terminal_set_tab(term, term->cursor_col);
    357            term->char_handler = guac_terminal_echo; 
    358            break;
    359
    360        /* Reverse Linefeed */
    361        case 'M':
    362            guac_terminal_reverse_linefeed(term);
    363            term->char_handler = guac_terminal_echo; 
    364            break;
    365
    366        /* DEC Identify */
    367        case 'Z':
    368            guac_terminal_send_string(term, GUAC_TERMINAL_VT102_ID);
    369            term->char_handler = guac_terminal_echo; 
    370            break;
    371
    372        /* Reset */
    373        case 'c':
    374            guac_terminal_reset(term);
    375            break;
    376
    377        case '_':
    378            term->char_handler = guac_terminal_apc;
    379            break;
    380
    381        default:
    382            guac_client_log(term->client, GUAC_LOG_DEBUG,
    383                    "Unhandled ESC sequence: %c", c);
    384            term->char_handler = guac_terminal_echo; 
    385
    386    }
    387
    388    return 0;
    389
    390}
    391
    392/**
    393 * Given a character mapping specifier (such as B, 0, U, or K),
    394 * returns the corresponding character mapping.
    395 */
    396static const int* __guac_terminal_get_char_mapping(char c) {
    397
    398    /* Translate character specifier to actual mapping */
    399    switch (c) {
    400        case 'B': return NULL;
    401        case '0': return vt100_map;
    402        case 'U': return null_map;
    403        case 'K': return user_map;
    404    }
    405
    406    /* Default to Unicode */
    407    return NULL;
    408
    409}
    410
    411int guac_terminal_g0_charset(guac_terminal* term, unsigned char c) {
    412
    413    term->char_mapping[0] = __guac_terminal_get_char_mapping(c);
    414    term->char_handler = guac_terminal_echo; 
    415    return 0;
    416
    417}
    418
    419int guac_terminal_g1_charset(guac_terminal* term, unsigned char c) {
    420
    421    term->char_mapping[1] = __guac_terminal_get_char_mapping(c);
    422    term->char_handler = guac_terminal_echo; 
    423    return 0;
    424
    425}
    426
    427/**
    428 * Looks up the flag specified by the given number and mode. Used by the Set/Reset Mode
    429 * functions of the terminal.
    430 */
    431static bool* __guac_terminal_get_flag(guac_terminal* term, int num, char private_mode) {
    432
    433    if (private_mode == '?') {
    434        switch (num) {
    435            case 1:  return &(term->application_cursor_keys); /* DECCKM */
    436            case 25: return &(term->cursor_visible); /* DECTECM */
    437        }
    438    }
    439
    440    else if (private_mode == 0) {
    441        switch (num) {
    442            case 4:  return &(term->insert_mode); /* DECIM */
    443            case 20: return &(term->automatic_carriage_return); /* LF/NL */
    444        }
    445    }
    446
    447    /* Unknown flag */
    448    return NULL;
    449
    450}
    451
    452/**
    453 * Parses an xterm SGR sequence specifying the RGB values of a color.
    454 *
    455 * @param argc
    456 *     The number of arguments within the argv array.
    457 *
    458 * @param argv
    459 *     The SGR arguments to parse, with the first relevant argument the
    460 *     red component of the RGB color.
    461 *
    462 * @param color
    463 *     The guac_terminal_color structure which should receive the parsed
    464 *     color values.
    465 *
    466 * @return
    467 *     The number of arguments parsed, or zero if argv does not contain
    468 *     enough elements to represent an RGB color.
    469 */
    470static int guac_terminal_parse_xterm256_rgb(int argc, const int* argv,
    471        guac_terminal_color* color) {
    472
    473    /* RGB color palette entries require three arguments */
    474    if (argc < 3)
    475        return 0;
    476
    477    /* Read RGB components from arguments */
    478    int red   = argv[0];
    479    int green = argv[1];
    480    int blue  = argv[2];
    481
    482    /* Ignore if components are out of range */
    483    if (   red   < 0 || red   > 255
    484        || green < 0 || green > 255
    485        || blue  < 0 || blue  > 255)
    486        return 3;
    487
    488    /* Store RGB components */
    489    color->red   = (uint8_t) red;
    490    color->green = (uint8_t) green;
    491    color->blue  = (uint8_t) blue;
    492
    493    /* Color is not from the palette */
    494    color->palette_index = -1;
    495
    496    /* Done */
    497    return 3;
    498
    499}
    500
    501/**
    502 * Parses an xterm SGR sequence specifying the index of a color within the
    503 * 256-color palette.
    504 *
    505 * @param terminal
    506 *     The terminal associated with the palette.
    507 *
    508 * @param argc
    509 *     The number of arguments within the argv array.
    510 *
    511 * @param argv
    512 *     The SGR arguments to parse, with the first relevant argument being
    513 *     the index of the color.
    514 *
    515 * @param color
    516 *     The guac_terminal_color structure which should receive the parsed
    517 *     color values.
    518 *
    519 * @return
    520 *     The number of arguments parsed, or zero if the palette index is
    521 *     absent.
    522 */
    523static int guac_terminal_parse_xterm256_index(guac_terminal* terminal,
    524        int argc, const int* argv, guac_terminal_color* color) {
    525
    526    /* 256-color palette entries require only one argument */
    527    if (argc < 1)
    528        return 0;
    529
    530    /* Copy palette entry */
    531    guac_terminal_display_lookup_color(terminal->display, argv[0], color);
    532
    533    /* Done */
    534    return 1;
    535
    536}
    537
    538/**
    539 * Parses an xterm SGR sequence specifying the index of a color within the
    540 * 256-color palette, or specfying the RGB values of a color. The number of
    541 * arguments required by these sequences varies. If a 256-color sequence is
    542 * recognized, the number of arguments parsed is returned.
    543 *
    544 * @param terminal
    545 *     The terminal whose palette state should be used when parsing the xterm
    546 *     256-color SGR sequence.
    547 *
    548 * @param argc
    549 *     The number of arguments within the argv array.
    550 *
    551 * @param argv
    552 *     The SGR arguments to parse, with the first relevant argument being
    553 *     the first element of the array. In the case of an xterm 256-color
    554 *     SGR sequence, the first element here will be either 2, for an
    555 *     RGB color, or 5, for a 256-color palette index. All other values
    556 *     are invalid and will not be parsed.
    557 *
    558 * @param color
    559 *     The guac_terminal_color structure which should receive the parsed
    560 *     color values.
    561 *
    562 * @return
    563 *     The number of arguments parsed, or zero if argv does not point to
    564 *     the first element of an xterm 256-color SGR sequence.
    565 */
    566static int guac_terminal_parse_xterm256(guac_terminal* terminal,
    567        int argc, const int* argv, guac_terminal_color* color) {
    568
    569    /* All 256-color codes must have at least a type */
    570    if (argc < 1)
    571        return 0;
    572
    573    switch (argv[0]) {
    574
    575        /* RGB */
    576        case 2:
    577            return guac_terminal_parse_xterm256_rgb(
    578                    argc - 1, &argv[1], color) + 1;
    579
    580        /* Palette index */
    581        case 5:
    582            return guac_terminal_parse_xterm256_index(terminal,
    583                    argc - 1, &argv[1], color) + 1;
    584
    585    }
    586
    587    /* Invalid type */
    588    return 0;
    589
    590}
    591
    592int guac_terminal_csi(guac_terminal* term, unsigned char c) {
    593
    594    /* CSI function arguments */
    595    static int argc = 0;
    596    static int argv[16] = {0};
    597
    598    /* Sequence prefix, if any */
    599    static char private_mode_character = 0;
    600
    601    /* Argument building counter and buffer */
    602    static int argv_length = 0;
    603    static char argv_buffer[256];
    604
    605    /* Digits get concatenated into argv */
    606    if (c >= '0' && c <= '9') {
    607
    608        /* Concatenate digit if there is space in buffer */
    609        if (argv_length < sizeof(argv_buffer)-1)
    610            argv_buffer[argv_length++] = c;
    611
    612    }
    613
    614    /* Specific non-digits stop the parameter, and possibly the sequence */
    615    else if ((c >= 0x40 && c <= 0x7E) || c == ';') {
    616
    617        int i, row, col, amount;
    618        bool* flag;
    619
    620        /* At most 16 parameters */
    621        if (argc < 16) {
    622
    623            /* Finish parameter */
    624            argv_buffer[argv_length] = 0;
    625            argv[argc++] = atoi(argv_buffer);
    626
    627            /* Prepare for next parameter */
    628            argv_length = 0;
    629
    630        }
    631
    632        /* Handle CSI functions */ 
    633        switch (c) {
    634
    635            /* @: Insert characters (scroll right) */
    636            case '@':
    637
    638                amount = argv[0];
    639                if (amount == 0) amount = 1;
    640
    641                /* Scroll right by amount */
    642                if (term->cursor_col + amount < term->term_width)
    643                    guac_terminal_copy_columns(term, term->cursor_row,
    644                            term->cursor_col, term->term_width - amount - 1,
    645                            amount);
    646
    647                /* Clear left */
    648                guac_terminal_clear_columns(term, term->cursor_row,
    649                        term->cursor_col, term->cursor_col + amount - 1);
    650
    651                break;
    652
    653            /* A: Move up */
    654            case 'A':
    655
    656                /* Get move amount */
    657                amount = argv[0];
    658                if (amount == 0) amount = 1;
    659
    660                /* Move cursor */
    661                guac_terminal_move_cursor(term,
    662                        term->cursor_row - amount,
    663                        term->cursor_col);
    664
    665                break;
    666
    667            /* B: Move down */
    668            case 'e':
    669            case 'B':
    670
    671                /* Get move amount */
    672                amount = argv[0];
    673                if (amount == 0) amount = 1;
    674
    675                /* Move cursor */
    676                guac_terminal_move_cursor(term,
    677                        term->cursor_row + amount,
    678                        term->cursor_col);
    679
    680                break;
    681
    682            /* C: Move right */
    683            case 'a':
    684            case 'C':
    685
    686                /* Get move amount */
    687                amount = argv[0];
    688                if (amount == 0) amount = 1;
    689
    690                /* Move cursor */
    691                guac_terminal_move_cursor(term,
    692                        term->cursor_row,
    693                        term->cursor_col + amount);
    694
    695                break;
    696
    697            /* D: Move left */
    698            case 'D':
    699
    700                /* Get move amount */
    701                amount = argv[0];
    702                if (amount == 0) amount = 1;
    703
    704                /* Move cursor */
    705                guac_terminal_move_cursor(term,
    706                        term->cursor_row,
    707                        term->cursor_col - amount);
    708
    709                break;
    710
    711            /* E: Move cursor down given number rows, column 1 */
    712            case 'E':
    713
    714                /* Get move amount */
    715                amount = argv[0];
    716                if (amount == 0) amount = 1;
    717
    718                /* Move cursor down, reset to column 1 */
    719                guac_terminal_move_cursor(term,
    720                        term->cursor_row + amount,
    721                        0);
    722
    723                break;
    724
    725            /* F: Move cursor up given number rows, column 1 */
    726            case 'F':
    727
    728                /* Get move amount */
    729                amount = argv[0];
    730                if (amount == 0) amount = 1;
    731
    732                /* Move cursor up , reset to column 1 */
    733                guac_terminal_move_cursor(term,
    734                        term->cursor_row - amount,
    735                        0);
    736
    737                break;
    738
    739            /* G: Move cursor, current row */
    740            case '`':
    741            case 'G':
    742                col = argv[0]; if (col != 0) col--;
    743                guac_terminal_move_cursor(term, term->cursor_row, col);
    744                break;
    745
    746            /* H: Move cursor */
    747            case 'f':
    748            case 'H':
    749
    750                row = argv[0]; if (row != 0) row--;
    751                col = argv[1]; if (col != 0) col--;
    752
    753                guac_terminal_move_cursor(term, row, col);
    754                break;
    755
    756            /* J: Erase display */
    757            case 'J':
    758 
    759                /* Erase from cursor to end of display */
    760                if (argv[0] == 0)
    761                    guac_terminal_clear_range(term,
    762                            term->cursor_row, term->cursor_col,
    763                            term->term_height-1, term->term_width-1);
    764                
    765                /* Erase from start to cursor */
    766                else if (argv[0] == 1)
    767                    guac_terminal_clear_range(term,
    768                            0, 0,
    769                            term->cursor_row, term->cursor_col);
    770
    771                /* Entire screen */
    772                else if (argv[0] == 2 || argv[0] == 3)
    773                    guac_terminal_clear_range(term,
    774                            0, 0, term->term_height - 1, term->term_width - 1);
    775
    776                break;
    777
    778            /* K: Erase line */
    779            case 'K':
    780
    781                /* Erase from cursor to end of line */
    782                if (argv[0] == 0)
    783                    guac_terminal_clear_columns(term, term->cursor_row,
    784                            term->cursor_col, term->term_width - 1);
    785
    786                /* Erase from start to cursor */
    787                else if (argv[0] == 1)
    788                    guac_terminal_clear_columns(term, term->cursor_row,
    789                            0, term->cursor_col);
    790
    791                /* Erase line */
    792                else if (argv[0] == 2)
    793                    guac_terminal_clear_columns(term, term->cursor_row,
    794                            0, term->term_width - 1);
    795
    796                break;
    797
    798            /* L: Insert blank lines (scroll down) */
    799            case 'L':
    800
    801                amount = argv[0];
    802                if (amount == 0) amount = 1;
    803
    804                guac_terminal_scroll_down(term,
    805                        term->cursor_row, term->scroll_end, amount);
    806
    807                break;
    808
    809            /* M: Delete lines (scroll up) */
    810            case 'M':
    811
    812                amount = argv[0];
    813                if (amount == 0) amount = 1;
    814
    815                guac_terminal_scroll_up(term,
    816                        term->cursor_row, term->scroll_end, amount);
    817
    818                break;
    819
    820            /* P: Delete characters (scroll left) */
    821            case 'P':
    822
    823                amount = argv[0];
    824                if (amount == 0) amount = 1;
    825
    826                /* Scroll left by amount */
    827                if (term->cursor_col + amount < term->term_width)
    828                    guac_terminal_copy_columns(term, term->cursor_row,
    829                            term->cursor_col + amount, term->term_width - 1,
    830                            -amount);
    831
    832                /* Clear right */
    833                guac_terminal_clear_columns(term, term->cursor_row,
    834                        term->term_width - amount, term->term_width - 1);
    835
    836                break;
    837
    838            /* X: Erase characters (no scroll) */
    839            case 'X':
    840
    841                amount = argv[0];
    842                if (amount == 0) amount = 1;
    843
    844                /* Clear characters */
    845                guac_terminal_clear_columns(term, term->cursor_row,
    846                        term->cursor_col, term->cursor_col + amount - 1);
    847
    848                break;
    849
    850            /* ]: Linux Private CSI */
    851            case ']':
    852                /* Explicitly ignored */
    853                break;
    854
    855            /* c: Identify */
    856            case 'c':
    857                if (argv[0] == 0 && private_mode_character == 0)
    858                    guac_terminal_send_string(term, GUAC_TERMINAL_VT102_ID);
    859                break;
    860
    861            /* d: Move cursor, current col */
    862            case 'd':
    863                row = argv[0]; if (row != 0) row--;
    864                guac_terminal_move_cursor(term, row, term->cursor_col);
    865                break;
    866
    867            /* g: Clear tab */
    868            case 'g':
    869
    870                /* Clear tab at current location */
    871                if (argv[0] == 0)
    872                    guac_terminal_unset_tab(term, term->cursor_col);
    873
    874                /* Clear all tabs */
    875                else if (argv[0] == 3)
    876                    guac_terminal_clear_tabs(term);
    877
    878                break;
    879
    880            /* h: Set Mode */
    881            case 'h':
    882             
    883                /* Look up flag and set */ 
    884                flag = __guac_terminal_get_flag(term, argv[0], private_mode_character);
    885                if (flag != NULL)
    886                    *flag = true;
    887
    888                break;
    889
    890            /* l: Reset Mode */
    891            case 'l':
    892              
    893                /* Look up flag and clear */ 
    894                flag = __guac_terminal_get_flag(term, argv[0], private_mode_character);
    895                if (flag != NULL)
    896                    *flag = false;
    897
    898                break;
    899
    900            /* m: Set graphics rendition */
    901            case 'm':
    902
    903                for (i=0; i<argc; i++) {
    904
    905                    int value = argv[i];
    906
    907                    /* Reset attributes */
    908                    if (value == 0)
    909                        term->current_attributes = term->default_char.attributes;
    910
    911                    /* Bold */
    912                    else if (value == 1)
    913                        term->current_attributes.bold = true;
    914
    915                    /* Faint (low intensity) */
    916                    else if (value == 2)
    917                        term->current_attributes.half_bright = true;
    918
    919                    /* Underscore on */
    920                    else if (value == 4)
    921                        term->current_attributes.underscore = true;
    922
    923                    /* Reverse video */
    924                    else if (value == 7)
    925                        term->current_attributes.reverse = true;
    926
    927                    /* Normal intensity (not bold) */
    928                    else if (value == 21 || value == 22) {
    929                        term->current_attributes.bold = false;
    930                        term->current_attributes.half_bright = false;
    931                    }
    932
    933                    /* Reset underscore */
    934                    else if (value == 24)
    935                        term->current_attributes.underscore = false;
    936
    937                    /* Reset reverse video */
    938                    else if (value == 27)
    939                        term->current_attributes.reverse = false;
    940
    941                    /* Foreground */
    942                    else if (value >= 30 && value <= 37)
    943                        guac_terminal_display_lookup_color(term->display,
    944                                value - 30,
    945                                &term->current_attributes.foreground);
    946
    947                    /* Underscore on, default foreground OR 256-color
    948                     * foreground */
    949                    else if (value == 38) {
    950
    951                        /* Attempt to set foreground with 256-color entry */
    952                        int xterm256_length =
    953                            guac_terminal_parse_xterm256(term,
    954                                    argc - i - 1, &argv[i + 1],
    955                                    &term->current_attributes.foreground);
    956
    957                        /* If valid 256-color entry, foreground has been set */
    958                        if (xterm256_length > 0)
    959                            i += xterm256_length;
    960
    961                        /* Otherwise interpret as underscore and default
    962                         * foreground  */
    963                        else {
    964                            term->current_attributes.underscore = true;
    965                            term->current_attributes.foreground =
    966                                term->default_char.attributes.foreground;
    967                        }
    968
    969                    }
    970
    971                    /* Underscore off, default foreground */
    972                    else if (value == 39) {
    973                        term->current_attributes.underscore = false;
    974                        term->current_attributes.foreground =
    975                            term->default_char.attributes.foreground;
    976                    }
    977
    978                    /* Background */
    979                    else if (value >= 40 && value <= 47)
    980                        guac_terminal_display_lookup_color(term->display,
    981                                value - 40,
    982                                &term->current_attributes.background);
    983
    984                    /* 256-color background */
    985                    else if (value == 48)
    986                        i += guac_terminal_parse_xterm256(term,
    987                                argc - i - 1, &argv[i + 1],
    988                                &term->current_attributes.background);
    989
    990                    /* Reset background */
    991                    else if (value == 49)
    992                        term->current_attributes.background =
    993                            term->default_char.attributes.background;
    994
    995                    /* Intense foreground */
    996                    else if (value >= 90 && value <= 97)
    997                        guac_terminal_display_lookup_color(term->display,
    998                                value - 90 + GUAC_TERMINAL_FIRST_INTENSE,
    999                                &term->current_attributes.foreground);
   1000
   1001                    /* Intense background */
   1002                    else if (value >= 100 && value <= 107)
   1003                        guac_terminal_display_lookup_color(term->display,
   1004                                value - 100 + GUAC_TERMINAL_FIRST_INTENSE,
   1005                                &term->current_attributes.background);
   1006
   1007                }
   1008
   1009                break;
   1010
   1011            /* n: Status report */
   1012            case 'n':
   1013
   1014                /* Device status report */
   1015                if (argv[0] == 5 && private_mode_character == 0)
   1016                    guac_terminal_send_string(term, GUAC_TERMINAL_OK);
   1017
   1018                /* Cursor position report */
   1019                else if (argv[0] == 6 && private_mode_character == 0)
   1020                    guac_terminal_sendf(term, "\x1B[%i;%iR", term->cursor_row+1, term->cursor_col+1);
   1021
   1022                break;
   1023
   1024            /* q: Set keyboard LEDs */
   1025            case 'q':
   1026                /* Explicitly ignored */
   1027                break;
   1028
   1029            /* r: Set scrolling region */
   1030            case 'r':
   1031
   1032                /* If parameters given, set region */
   1033                if (argc == 2) {
   1034                    term->scroll_start = argv[0]-1;
   1035                    term->scroll_end   = argv[1]-1;
   1036                }
   1037
   1038                /* Otherwise, reset scrolling region */
   1039                else {
   1040                    term->scroll_start = 0;
   1041                    term->scroll_end = term->term_height - 1;
   1042                }
   1043
   1044                break;
   1045
   1046            /* Save Cursor */
   1047            case 's':
   1048                term->saved_cursor_row = term->cursor_row;
   1049                term->saved_cursor_col = term->cursor_col;
   1050                break;
   1051
   1052            /* Restore Cursor */
   1053            case 'u':
   1054                guac_terminal_move_cursor(term,
   1055                        term->saved_cursor_row,
   1056                        term->saved_cursor_col);
   1057                break;
   1058
   1059            /* Warn of unhandled codes */
   1060            default:
   1061                if (c != ';') {
   1062
   1063                    guac_client_log(term->client, GUAC_LOG_DEBUG,
   1064                            "Unhandled CSI sequence: %c", c);
   1065
   1066                    for (i=0; i<argc; i++)
   1067                        guac_client_log(term->client, GUAC_LOG_DEBUG,
   1068                                " -> argv[%i] = %i", i, argv[i]);
   1069
   1070                }
   1071
   1072        }
   1073
   1074        /* If not a semicolon, end of CSI sequence */
   1075        if (c != ';') {
   1076            term->char_handler = guac_terminal_echo;
   1077
   1078            /* Reset parameters */
   1079            for (i=0; i<argc; i++)
   1080                argv[i] = 0;
   1081
   1082            /* Reset private mode character */
   1083            private_mode_character = 0;
   1084
   1085            /* Reset argument counters */
   1086            argc = 0;
   1087            argv_length = 0;
   1088        }
   1089
   1090    }
   1091
   1092    /* Set private mode character if given and unset */
   1093    else if (c >= 0x3A && c <= 0x3F && private_mode_character == 0)
   1094        private_mode_character = c;
   1095
   1096    return 0;
   1097
   1098}
   1099
   1100int guac_terminal_set_directory(guac_terminal* term, unsigned char c) {
   1101
   1102    static char filename[2048];
   1103    static int length = 0;
   1104
   1105    /* Stop on ECMA-48 ST (String Terminator */
   1106    if (c == 0x9C || c == 0x5C || c == 0x07) {
   1107        filename[length++] = '\0';
   1108        term->char_handler = guac_terminal_echo;
   1109        if (term->upload_path_handler)
   1110            term->upload_path_handler(term->client, filename);
   1111        else
   1112            guac_client_log(term->client, GUAC_LOG_DEBUG,
   1113                    "Cannot set upload path. File transfer is not enabled.");
   1114        length = 0;
   1115    }
   1116
   1117    /* Otherwise, store character */
   1118    else if (length < sizeof(filename)-1)
   1119        filename[length++] = c;
   1120
   1121    return 0;
   1122
   1123}
   1124
   1125int guac_terminal_download(guac_terminal* term, unsigned char c) {
   1126
   1127    static char filename[2048];
   1128    static int length = 0;
   1129
   1130    /* Stop on ECMA-48 ST (String Terminator */
   1131    if (c == 0x9C || c == 0x5C || c == 0x07) {
   1132        filename[length++] = '\0';
   1133        term->char_handler = guac_terminal_echo;
   1134        if (term->file_download_handler)
   1135            term->file_download_handler(term->client, filename);
   1136        else
   1137            guac_client_log(term->client, GUAC_LOG_DEBUG,
   1138                    "Cannot send file. File transfer is not enabled.");
   1139        length = 0;
   1140    }
   1141
   1142    /* Otherwise, store character */
   1143    else if (length < sizeof(filename)-1)
   1144        filename[length++] = c;
   1145
   1146    return 0;
   1147
   1148}
   1149
   1150int guac_terminal_open_pipe_stream(guac_terminal* term, unsigned char c) {
   1151
   1152    static char param[2048];
   1153    static int length = 0;
   1154    static int flags = 0;
   1155
   1156    /* Open pipe on ECMA-48 ST (String Terminator) */
   1157    if (c == 0x9C || c == 0x5C || c == 0x07) {
   1158
   1159        /* End parameter string */
   1160        param[length++] = '\0';
   1161        length = 0;
   1162
   1163        /* Open new pipe stream using final parameter as name */
   1164        guac_terminal_pipe_stream_open(term, param, flags);
   1165
   1166        /* Return to echo mode */
   1167        term->char_handler = guac_terminal_echo;
   1168
   1169        /* Reset tracked flags for sake of any future pipe streams */
   1170        flags = 0;
   1171
   1172    }
   1173
   1174    /* Interpret all parameters prior to the final parameter as integer
   1175     * flags which should affect the pipe stream when opened */
   1176    else if (c == ';') {
   1177
   1178        /* End parameter string */
   1179        param[length++] = '\0';
   1180        length = 0;
   1181
   1182        /* Parse parameter string as integer flags */
   1183        flags |= atoi(param);
   1184
   1185    }
   1186
   1187    /* Otherwise, store character within parameter */
   1188    else if (length < sizeof(param)-1)
   1189        param[length++] = c;
   1190
   1191    return 0;
   1192
   1193}
   1194
   1195int guac_terminal_close_pipe_stream(guac_terminal* term, unsigned char c) {
   1196
   1197    /* Handle closure on ECMA-48 ST (String Terminator) */
   1198    if (c == 0x9C || c == 0x5C || c == 0x07) {
   1199
   1200        /* Close any existing pipe */
   1201        guac_terminal_pipe_stream_close(term);
   1202
   1203        /* Return to echo mode */
   1204        term->char_handler = guac_terminal_echo;
   1205
   1206    }
   1207
   1208    /* Ignore all other characters */
   1209    return 0;
   1210
   1211}
   1212
   1213int guac_terminal_set_scrollback(guac_terminal* term, unsigned char c) {
   1214
   1215    static char param[16];
   1216    static int length = 0;
   1217
   1218    /* Open pipe on ECMA-48 ST (String Terminator) */
   1219    if (c == 0x9C || c == 0x5C || c == 0x07) {
   1220
   1221        /* End parameter string */
   1222        param[length++] = '\0';
   1223        length = 0;
   1224
   1225        /* Assign scrollback size */
   1226        term->requested_scrollback = atoi(param);
   1227
   1228        /* Update scrollbar bounds */
   1229        guac_terminal_scrollbar_set_bounds(term->scrollbar,
   1230                -guac_terminal_get_available_scroll(term), 0);
   1231
   1232        /* Return to echo mode */
   1233        term->char_handler = guac_terminal_echo;
   1234
   1235    }
   1236
   1237    /* Otherwise, store character within parameter */
   1238    else if (length < sizeof(param) - 1)
   1239        param[length++] = c;
   1240
   1241    return 0;
   1242
   1243}
   1244
   1245
   1246int guac_terminal_window_title(guac_terminal* term, unsigned char c) {
   1247
   1248    static int position = 0;
   1249    static char title[4096];
   1250
   1251    guac_socket* socket = term->client->socket;
   1252
   1253    /* Stop on ECMA-48 ST (String Terminator */
   1254    if (c == 0x9C || c == 0x5C || c == 0x07) {
   1255
   1256        /* Terminate and reset stored title */
   1257        title[position] = '\0';
   1258        position = 0;
   1259
   1260        /* Send title as connection name */
   1261        guac_protocol_send_name(socket, title);
   1262        guac_socket_flush(socket);
   1263
   1264        term->char_handler = guac_terminal_echo;
   1265
   1266    }
   1267
   1268    /* Store all other characters within title, space permitting */
   1269    else if (position < sizeof(title) - 1)
   1270        title[position++] = (char) c;
   1271
   1272    return 0;
   1273
   1274}
   1275
   1276int guac_terminal_xterm_palette(guac_terminal* term, unsigned char c) {
   1277
   1278    /**
   1279     * Whether we are currently reading the color spec. If false, we are
   1280     * currently reading the color index.
   1281     */
   1282    static bool read_color_spec = false;
   1283
   1284    /**
   1285     * The index of the palette entry being modified.
   1286     */
   1287    static int index = 0;
   1288
   1289    /**
   1290     * The color spec string, valid only if read_color_spec is true.
   1291     */
   1292    static char color_spec[256];
   1293
   1294    /**
   1295     * The current position within the color spec string, valid only if
   1296     * read_color_spec is true.
   1297     */
   1298    static int color_spec_pos = 0;
   1299
   1300    /* If not reading the color spec, parse the index */
   1301    if (!read_color_spec) {
   1302
   1303        /* If digit, append to index */
   1304        if (c >= '0' && c <= '9')
   1305            index = index * 10 + c - '0';
   1306
   1307        /* If end of parameter, switch to reading the color */
   1308        else if (c == ';') {
   1309            read_color_spec = true;
   1310            color_spec_pos = 0;
   1311        }
   1312
   1313    }
   1314
   1315    /* Once the index has been parsed, read the color spec */
   1316    else {
   1317
   1318        /* Modify palette once index/spec pair has been read */
   1319        if (c == ';' || c == 0x9C || c == 0x5C || c == 0x07) {
   1320
   1321            guac_terminal_color color;
   1322
   1323            /* Terminate color spec string */
   1324            color_spec[color_spec_pos] = '\0';
   1325
   1326            /* Modify palette if color spec is valid */
   1327            if (!guac_terminal_xparsecolor(color_spec, &color))
   1328                guac_terminal_display_assign_color(term->display,
   1329                        index, &color);
   1330            else
   1331                guac_client_log(term->client, GUAC_LOG_DEBUG,
   1332                        "Invalid XParseColor() color spec: \"%s\"",
   1333                        color_spec);
   1334
   1335            /* Resume parsing index */
   1336            read_color_spec = false;
   1337            index = 0;
   1338
   1339        }
   1340
   1341        /* Append characters to color spec as long as available space is not
   1342         * exceeded */
   1343        else if (color_spec_pos < 255) {
   1344            color_spec[color_spec_pos++] = c;
   1345        }
   1346
   1347    }
   1348
   1349    /* Stop on ECMA-48 ST (String Terminator */
   1350    if (c == 0x9C || c == 0x5C || c == 0x07)
   1351        term->char_handler = guac_terminal_echo;
   1352
   1353    return 0;
   1354
   1355}
   1356
   1357int guac_terminal_osc(guac_terminal* term, unsigned char c) {
   1358
   1359    static int operation = 0;
   1360
   1361    /* If digit, append to operation */
   1362    if (c >= '0' && c <= '9')
   1363        operation = operation * 10 + c - '0';
   1364
   1365    /* If end of parameter, check value */
   1366    else if (c == ';') {
   1367
   1368        /* Download OSC */
   1369        if (operation == 482200)
   1370            term->char_handler = guac_terminal_download;
   1371
   1372        /* Set upload directory OSC */
   1373        else if (operation == 482201)
   1374            term->char_handler = guac_terminal_set_directory;
   1375
   1376        /* Open and redirect output to pipe stream OSC */
   1377        else if (operation == 482202)
   1378            term->char_handler = guac_terminal_open_pipe_stream;
   1379
   1380        /* Close pipe stream OSC */
   1381        else if (operation == 482203)
   1382            term->char_handler = guac_terminal_close_pipe_stream;
   1383
   1384        /* Set scrollback size OSC */
   1385        else if (operation == 482204)
   1386            term->char_handler = guac_terminal_set_scrollback;
   1387
   1388        /* Set window title OSC */
   1389        else if (operation == 0 || operation == 2)
   1390            term->char_handler = guac_terminal_window_title;
   1391
   1392        /* xterm 256-color palette redefinition */
   1393        else if (operation == 4)
   1394            term->char_handler = guac_terminal_xterm_palette;
   1395
   1396        /* Reset parameter for next OSC */
   1397        operation = 0;
   1398
   1399    }
   1400
   1401    /* Stop on ECMA-48 ST (String Terminator */
   1402    else if (c == 0x9C || c == 0x5C || c == 0x07)
   1403        term->char_handler = guac_terminal_echo;
   1404
   1405    /* Stop on unrecognized character */
   1406    else {
   1407        guac_client_log(term->client, GUAC_LOG_DEBUG,
   1408                "Unexpected character in OSC: 0x%X", c);
   1409        term->char_handler = guac_terminal_echo;
   1410    }
   1411
   1412    return 0;
   1413}
   1414
   1415int guac_terminal_ctrl_func(guac_terminal* term, unsigned char c) {
   1416
   1417    int row;
   1418
   1419    /* Build character with current attributes */
   1420    guac_terminal_char guac_char;
   1421    guac_char.value = 'E';
   1422    guac_char.attributes = term->current_attributes;
   1423
   1424    switch (c) {
   1425
   1426        /* Alignment test (fill screen with E's) */
   1427        case '8':
   1428
   1429            for (row=0; row<term->term_height; row++)
   1430                guac_terminal_set_columns(term, row, 0, term->term_width-1, &guac_char);
   1431
   1432            break;
   1433
   1434    }
   1435
   1436    term->char_handler = guac_terminal_echo; 
   1437
   1438    return 0;
   1439
   1440}
   1441
   1442int guac_terminal_apc(guac_terminal* term, unsigned char c) {
   1443
   1444    /* xterm does not implement APC functions and neither do we. Look for the
   1445     * "ESC \" (string terminator) sequence, while ignoring other chars. */
   1446    static bool escaping = false;
   1447
   1448    if (escaping) {
   1449        if (c == '\\')
   1450            term->char_handler = guac_terminal_echo;
   1451        escaping = false;
   1452    }
   1453
   1454    if (c == 0x1B)
   1455        escaping = true;
   1456    return 0;
   1457}