@@ -231,6 +231,7 @@ void guac_terminal_reset(guac_terminal* term) {
231231    term -> application_cursor_keys  =  false;
232232    term -> automatic_carriage_return  =  false;
233233    term -> insert_mode  =  false;
234+     term -> bracketed_paste_mode  =  false;
234235
235236    /* Reset tabs */ 
236237    term -> tab_interval  =  8 ;
@@ -1574,6 +1575,141 @@ int guac_terminal_send_string(guac_terminal* term, const char* data) {
15741575
15751576}
15761577
1578+ #define  IS_UTF8_START_1_BYTE (c ) ((c & 0x80) == 0x00)
1579+ #define  IS_UTF8_START_2_BYTE (c ) ((c & 0xe0) == 0xc0)
1580+ #define  IS_UTF8_START_3_BYTE (c ) ((c & 0xf0) == 0xe0)
1581+ #define  IS_UTF8_START_4_BYTE (c ) ((c & 0xf8) == 0xf0)
1582+ #define  IS_UTF8_CONTINUATION (c ) ((c & 0xc0) == 0x80)
1583+ 
1584+ int  guac_terminal_send_clipboard (guac_terminal  * term ) {
1585+ 
1586+     /* Allocate a temporary buffer for filtering the clipboard contents. 
1587+      * As we're removing characters, we know it will be at most the size 
1588+      * of the original plus the two bracketed paste markers. */ 
1589+     char  * filtered  =  guac_mem_alloc (term -> clipboard -> length  + 
1590+             strlen (GUAC_TERMINAL_BRACKETED_PASTE_START ) + 
1591+             strlen (GUAC_TERMINAL_BRACKETED_PASTE_STOP ));
1592+     uint8_t  * src_ptr  =  (uint8_t  * )term -> clipboard -> buffer ;
1593+     uint8_t  * src_end  =  (uint8_t  * )(term -> clipboard -> buffer  +  term -> clipboard -> length );
1594+     uint8_t  * dst_ptr  =  (uint8_t  * )filtered ;
1595+ 
1596+     /* Keep track of exactly how much data we've sieved */ 
1597+     int  filtered_len  =  0 ;
1598+ 
1599+     /* Send the paste start sequence */ 
1600+     if  (term -> bracketed_paste_mode ) {
1601+         size_t  seq_len  =  strlen (GUAC_TERMINAL_BRACKETED_PASTE_START );
1602+         memcpy (dst_ptr , GUAC_TERMINAL_BRACKETED_PASTE_START , seq_len );
1603+         dst_ptr  +=  seq_len ;
1604+         filtered_len  +=  seq_len ;
1605+     }
1606+ 
1607+     while  (src_ptr  <  src_end ) {
1608+ 
1609+         /* Allow UTF-8 codepoints. 
1610+          * A valid UTF-8 sequence is between one and four bytes in length, and 
1611+          * we can confirm the validity by testing the start bits of each byte. 
1612+          * 
1613+          * A Unicode codepoint is only valid for the smallest UTF-8 sequence that 
1614+          * it fits into; larger UTF-8 sequences can only contain larger codepoints. 
1615+          * Therefore, some bits in the sequence are required to be used as part of 
1616+          * the codepoint number. 
1617+          * 
1618+          * If the sequence is valid, copy it in full. */ 
1619+ 
1620+         /* UTF-8 1-byte codepoint (U+0000 to U+007F) 
1621+          * Start bits:  0xxxxxxx */ 
1622+         if  (IS_UTF8_START_1_BYTE (src_ptr [0 ])) {
1623+ 
1624+             /* Exclude Unicode CO (U+0000 to U+001F) control characters, except 
1625+              * for tab (U+0009), line feed (U+000A) and carriage return (U+000D). */ 
1626+             if  (!((src_ptr [0 ] >= 0x00 ) &&  (src_ptr [0 ] <  0x20 )) || 
1627+                    (src_ptr [0 ] ==  0x09 ) ||  (src_ptr [0 ] ==  0x0a ) ||  (src_ptr [0 ] ==  0x0d )) {
1628+                 dst_ptr [0 ] =  src_ptr [0 ];
1629+                 dst_ptr ++ ;
1630+                 filtered_len ++ ;
1631+                 src_ptr ++ ;
1632+                 continue ;
1633+             }
1634+         }
1635+ 
1636+         /* UTF-8 2-byte codepoint (U+0080 to U+07FF) 
1637+          * Start bits:  110xxxxx 10xxxxxx 
1638+          * Required:    xxxYYYYx xxxxxxxx */ 
1639+         else  if  (IS_UTF8_START_2_BYTE (src_ptr [0 ])) {
1640+             if  ((src_ptr  +  1  <  src_end ) && 
1641+                     IS_UTF8_CONTINUATION (src_ptr [1 ]) && 
1642+                     ((src_ptr [0 ] &  0x1e ) !=  0x00 )) {
1643+ 
1644+                 /* Exclude Unicode C1 (U+0080 to U+009F) control characters: 11000010 100xxxxx */ 
1645+                 if  ((src_ptr [0 ] !=  0xc2 ) ||  ((src_ptr [1 ] &  0xe0 ) !=  0x80 )) {
1646+                     dst_ptr [0 ] =  src_ptr [0 ];
1647+                     dst_ptr [1 ] =  src_ptr [1 ];
1648+                     dst_ptr  +=  2 ;
1649+                     filtered_len  +=  2 ;
1650+                     src_ptr  +=  2 ;
1651+                     continue ;
1652+                 }
1653+             }
1654+         }
1655+ 
1656+         /* UTF-8 3-byte codepoint (U+0800 to U+FFFF) 
1657+          * Start bits:  1110xxxx 10xxxxxx 10xxxxxx 
1658+          * Required:    xxxxYYYY xxYxxxxx xxxxxxxx */ 
1659+         else  if  (IS_UTF8_START_3_BYTE (src_ptr [0 ])) {
1660+             if  ((src_ptr  +  2  <  src_end ) && 
1661+                     IS_UTF8_CONTINUATION (src_ptr [1 ]) && 
1662+                     IS_UTF8_CONTINUATION (src_ptr [2 ]) && 
1663+                     (((src_ptr [0 ] &  0x0f ) !=  0x00 ) || 
1664+                      ((src_ptr [1 ] &  0x20 ) !=  0x00 ))) {
1665+                 dst_ptr [0 ] =  src_ptr [0 ];
1666+                 dst_ptr [1 ] =  src_ptr [1 ];
1667+                 dst_ptr [2 ] =  src_ptr [2 ];
1668+                 dst_ptr  +=  3 ;
1669+                 filtered_len  +=  3 ;
1670+                 src_ptr  +=  3 ;
1671+                 continue ;
1672+             }
1673+         }
1674+ 
1675+         /* UTF-8 4-byte codepoint (U+10000 to U+1FFFF) 
1676+          * Start bits:  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
1677+          * Required:    xxxxxYYY xxYYxxxx xxxxxxxx xxxxxxxx */ 
1678+         else  if  (IS_UTF8_START_4_BYTE (src_ptr [0 ])) {
1679+             if  ((src_ptr  +  3  <  src_end ) && 
1680+                     IS_UTF8_CONTINUATION (src_ptr [1 ]) && 
1681+                     IS_UTF8_CONTINUATION (src_ptr [2 ]) && 
1682+                     IS_UTF8_CONTINUATION (src_ptr [3 ]) && 
1683+                     (((src_ptr [0 ] &  0x07 ) !=  0x00 ) || 
1684+                      ((src_ptr [1 ] &  0x30 ) !=  0x00 ))) {
1685+                 dst_ptr [0 ] =  src_ptr [0 ];
1686+                 dst_ptr [1 ] =  src_ptr [1 ];
1687+                 dst_ptr [2 ] =  src_ptr [2 ];
1688+                 dst_ptr [3 ] =  src_ptr [3 ];
1689+                 dst_ptr  +=  4 ;
1690+                 filtered_len  +=  4 ;
1691+                 src_ptr  +=  4 ;
1692+                 continue ;
1693+             }
1694+         }
1695+ 
1696+         /* If the sequence is invalid, skip to the next byte. */ 
1697+         src_ptr ++ ;
1698+     }
1699+ 
1700+     /* Send the paste stop sequence */ 
1701+     if  (term -> bracketed_paste_mode ) {
1702+         size_t  seq_len  =  strlen (GUAC_TERMINAL_BRACKETED_PASTE_STOP );
1703+         memcpy (dst_ptr , GUAC_TERMINAL_BRACKETED_PASTE_STOP , seq_len );
1704+         dst_ptr  +=  seq_len ;
1705+         filtered_len  +=  seq_len ;
1706+     }
1707+ 
1708+     int  result  =  guac_terminal_send_data (term , filtered , filtered_len );
1709+     guac_mem_free (filtered );
1710+     return  result ;
1711+ }
1712+ 
15771713static  int  __guac_terminal_send_key (guac_terminal *  term , int  keysym , int  pressed ) {
15781714
15791715    /* Ignore user input if terminal is not started */ 
@@ -1605,7 +1741,7 @@ static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed
16051741
16061742        /* Ctrl+Shift+V or Cmd+v (mac style) shortcuts for paste */ 
16071743        if  ((keysym  ==  'V'  &&  term -> mod_ctrl ) ||  (keysym  ==  'v'  &&  term -> mod_meta ))
1608-             return  guac_terminal_send_data (term ,  term -> clipboard -> buffer ,  term -> clipboard -> length );
1744+             return  guac_terminal_send_clipboard (term );
16091745
16101746        /* 
16111747         * Ctrl+Shift+C and Cmd+c shortcuts for copying are not handled, as 
@@ -1937,7 +2073,7 @@ static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user,
19372073
19382074    /* Paste contents of clipboard on right or middle mouse button up */ 
19392075    if  ((released_mask  &  GUAC_CLIENT_MOUSE_RIGHT ) ||  (released_mask  &  GUAC_CLIENT_MOUSE_MIDDLE ))
1940-         return  guac_terminal_send_data (term ,  term -> clipboard -> buffer ,  term -> clipboard -> length );
2076+         return  guac_terminal_send_clipboard (term );
19412077
19422078    /* If left mouse button was just released, stop selection */ 
19432079    if  (released_mask  &  GUAC_CLIENT_MOUSE_LEFT )
0 commit comments