1010#include < sys/socket.h>
1111using json = nlohmann::ordered_json;
1212
13+ // brain damage (this may not be needed but whatever)
14+ static std::string sanitize_for_json (std::string_view input) {
15+ std::string result;
16+ result.reserve (input.size ());
17+
18+ for (size_t i = 0 ; i < input.size ();) {
19+ unsigned char c = input[i];
20+
21+ // ASCII (0x00-0x7F)
22+ if (c <= 0x7F ) {
23+ result += c;
24+ i++;
25+ }
26+ // 2-byte UTF-8 (0xC0-0xDF)
27+ else if ((c & 0xE0 ) == 0xC0 && i + 1 < input.size ()) {
28+ unsigned char c2 = input[i + 1 ];
29+ if ((c2 & 0xC0 ) == 0x80 ) {
30+ result += c;
31+ result += c2;
32+ i += 2 ;
33+ } else {
34+ i++;
35+ }
36+ }
37+ // 3-byte UTF-8 (0xE0-0xEF)
38+ else if ((c & 0xF0 ) == 0xE0 && i + 2 < input.size ()) {
39+ unsigned char c2 = input[i + 1 ];
40+ unsigned char c3 = input[i + 2 ];
41+ if ((c2 & 0xC0 ) == 0x80 && (c3 & 0xC0 ) == 0x80 ) {
42+ result += c;
43+ result += c2;
44+ result += c3;
45+ i += 3 ;
46+ } else {
47+ i++;
48+ }
49+ }
50+ // 4-byte UTF-8 (0xF0-0xF7)
51+ else if ((c & 0xF8 ) == 0xF0 && i + 3 < input.size ()) {
52+ unsigned char c2 = input[i + 1 ];
53+ unsigned char c3 = input[i + 2 ];
54+ unsigned char c4 = input[i + 3 ];
55+ if ((c2 & 0xC0 ) == 0x80 && (c3 & 0xC0 ) == 0x80 &&
56+ (c4 & 0xC0 ) == 0x80 ) {
57+ result += c;
58+ result += c2;
59+ result += c3;
60+ result += c4;
61+ i += 4 ;
62+ } else {
63+ i++;
64+ }
65+ } else {
66+ i++;
67+ }
68+ }
69+
70+ return result;
71+ }
72+
73+ // brain damage
74+ static std::string sanitize_for_json (const char *str) {
75+ if (!str)
76+ return " " ;
77+
78+ std::string result;
79+ size_t i = 0 ;
80+
81+ while (str[i] != ' \0 ' ) {
82+ unsigned char c = str[i];
83+ // ASCII (0x00-0x7F)
84+ if (c <= 0x7F ) {
85+ result += c;
86+ i++;
87+ }
88+ // 2-byte UTF-8 (0xC0-0xDF)
89+ else if ((c & 0xE0 ) == 0xC0 && str[i + 1 ] != ' \0 ' ) {
90+ unsigned char c2 = str[i + 1 ];
91+ if ((c2 & 0xC0 ) == 0x80 ) {
92+ result += c;
93+ result += c2;
94+ i += 2 ;
95+ } else {
96+ i++;
97+ }
98+ }
99+ // 3-byte UTF-8 (0xE0-0xEF)
100+ else if ((c & 0xF0 ) == 0xE0 && str[i + 1 ] != ' \0 ' &&
101+ str[i + 2 ] != ' \0 ' ) {
102+ unsigned char c2 = str[i + 1 ];
103+ unsigned char c3 = str[i + 2 ];
104+ if ((c2 & 0xC0 ) == 0x80 && (c3 & 0xC0 ) == 0x80 ) {
105+ result += c;
106+ result += c2;
107+ result += c3;
108+ i += 3 ;
109+ } else {
110+ i++;
111+ }
112+ }
113+ // 4-byte UTF-8 (0xF0-0xF7)
114+ else if ((c & 0xF8 ) == 0xF0 && str[i + 1 ] != ' \0 ' &&
115+ str[i + 2 ] != ' \0 ' && str[i + 3 ] != ' \0 ' ) {
116+ unsigned char c2 = str[i + 1 ];
117+ unsigned char c3 = str[i + 2 ];
118+ unsigned char c4 = str[i + 3 ];
119+ if ((c2 & 0xC0 ) == 0x80 && (c3 & 0xC0 ) == 0x80 &&
120+ (c4 & 0xC0 ) == 0x80 ) {
121+ result += c;
122+ result += c2;
123+ result += c3;
124+ result += c4;
125+ i += 4 ;
126+ } else {
127+ i++;
128+ }
129+ } else {
130+ i++;
131+ }
132+ }
133+
134+ return result;
135+ }
136+
13137IPC ::IPC (Server *server, std::string sock_path)
14138 : server(server), path(sock_path) {
15139 // create file descriptor
@@ -309,7 +433,7 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
309433 };
310434
311435 // outputs are distinguished by name
312- j[o->name ] = {
436+ j[sanitize_for_json ( o->name ) ] = {
313437 {" enabled" , o->enabled },
314438 {" focused" , output == server->focused_output ()},
315439 {" workspace" , output->get_active ()->num },
@@ -342,16 +466,20 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
342466 // below values may be null (e.g. virtual outputs)
343467 // they are generally set on physical displays though
344468 if (o->description )
345- j[o->name ][" description" ] = o->description ;
469+ j[sanitize_for_json (o->name )][" description" ] =
470+ sanitize_for_json (o->description );
346471
347472 if (o->make )
348- j[o->name ][" make" ] = o->make ;
473+ j[sanitize_for_json (o->name )][" make" ] =
474+ sanitize_for_json (o->make );
349475
350476 if (o->model )
351- j[o->name ][" model" ] = o->model ;
477+ j[sanitize_for_json (o->name )][" model" ] =
478+ sanitize_for_json (o->model );
352479
353480 if (o->serial )
354- j[o->name ][" serial" ] = o->serial ;
481+ j[sanitize_for_json (o->name )][" serial" ] =
482+ sanitize_for_json (o->serial );
355483 }
356484 break ;
357485 }
@@ -361,11 +489,11 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
361489 wl_list_for_each_safe (workspace, tmp1,
362490 &server->workspace_manager ->workspaces , link) {
363491 int i = 0 ;
364- j[workspace->output ->wlr_output ->name ][workspace-> num ] =
365- json::array ();
492+ j[sanitize_for_json ( workspace->output ->wlr_output ->name )]
493+ [workspace-> num ] = json::array ();
366494 wl_list_for_each_safe (toplevel, tmp2, &workspace->toplevels , link)
367- j[workspace->output ->wlr_output ->name ][workspace-> num - 1 ]
368- [i++] = string_format (" %p" , toplevel);
495+ j[sanitize_for_json ( workspace->output ->wlr_output ->name ) ]
496+ [workspace-> num - 1 ][ i++] = string_format (" %p" , toplevel);
369497 }
370498 break ;
371499 }
@@ -379,7 +507,7 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
379507 link) {
380508 // output modes are defined as a size, refresh rate and
381509 // preferred status - the selected mode is the current mode
382- j[output->wlr_output ->name ][i++] = {
510+ j[sanitize_for_json ( output->wlr_output ->name ) ][i++] = {
383511 {" refresh" , mode->refresh / 1000.0 },
384512 {" width" , mode->width },
385513 {" height" , mode->height },
@@ -496,12 +624,13 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
496624 // toplevels are indexed by their pointer as title is
497625 // non-unique
498626 j[string_format (" %p" , t)] = {
499- {" title" , t->get_title ()},
500- {" class" , t->get_app_id ()},
501- {" tag" , t->tag },
502- {" foreign" , t->ext_foreign_handle
503- ? t->ext_foreign_handle ->identifier
504- : " None" },
627+ {" title" , sanitize_for_json (t->get_title ())},
628+ {" class" , sanitize_for_json (t->get_app_id ())},
629+ {" tag" , sanitize_for_json (t->tag )},
630+ {" foreign" ,
631+ t->ext_foreign_handle && t->ext_foreign_handle ->identifier
632+ ? sanitize_for_json (t->ext_foreign_handle ->identifier )
633+ : " None" },
505634 {" x" , t->geometry .x },
506635 {" y" , t->geometry .y },
507636 {" width" , t->geometry .width },
@@ -525,12 +654,13 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
525654
526655 if (const Toplevel *t = w->active_toplevel ) {
527656 j = {
528- {" title" , t->get_title ()},
529- {" class" , t->get_app_id ()},
530- {" tag" , t->tag },
531- {" foreign" , t->ext_foreign_handle
532- ? t->ext_foreign_handle ->identifier
533- : " None" },
657+ {" title" , sanitize_for_json (t->get_title ())},
658+ {" class" , sanitize_for_json (t->get_app_id ())},
659+ {" tag" , sanitize_for_json (t->tag )},
660+ {" foreign" ,
661+ t->ext_foreign_handle && t->ext_foreign_handle ->identifier
662+ ? sanitize_for_json (t->ext_foreign_handle ->identifier )
663+ : " None" },
534664 {" x" , t->geometry .x },
535665 {" y" , t->geometry .y },
536666 {" width" , t->geometry .width },
@@ -547,10 +677,10 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
547677 }
548678 case IPC_KEYBOARD_LIST : {
549679 // just return the keyboard config
550- j = {{" layout" , server->config ->keyboard_layout },
551- {" model" , server->config ->keyboard_model },
552- {" variant" , server->config ->keyboard_variant },
553- {" options" , server->config ->keyboard_options },
680+ j = {{" layout" , sanitize_for_json ( server->config ->keyboard_layout ) },
681+ {" model" , sanitize_for_json ( server->config ->keyboard_model ) },
682+ {" variant" , sanitize_for_json ( server->config ->keyboard_variant ) },
683+ {" options" , sanitize_for_json ( server->config ->keyboard_options ) },
554684 {" repeat_rate" , server->config ->repeat_rate },
555685 {" repeat_delay" , server->config ->repeat_delay }};
556686 break ;
@@ -586,7 +716,10 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
586716
587717 // add to list of devices
588718 j[i++] = {
589- {" name" , keyboard->wlr_keyboard ->base .name },
719+ {" name" ,
720+ keyboard->wlr_keyboard ->base .name
721+ ? sanitize_for_json (keyboard->wlr_keyboard ->base .name )
722+ : " Unknown" },
590723 {" type" , type},
591724 };
592725 }
@@ -607,15 +740,15 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
607740 layout_idx++) {
608741
609742 // this long name is nicer for clients
610- std::string layout_name =
611- xkb_keymap_layout_get_name (keymap, layout_idx);
743+ std::string layout_name = sanitize_for_json (
744+ xkb_keymap_layout_get_name (keymap, layout_idx)) ;
612745
613746 // can be -1 if invalid
614747 int layout_enabled = xkb_state_layout_index_is_active (
615748 state, layout_idx, XKB_STATE_LAYOUT_EFFECTIVE );
616749
617750 j[keyboard->wlr_keyboard ->base .name
618- ? keyboard->wlr_keyboard ->base .name
751+ ? sanitize_for_json ( keyboard->wlr_keyboard ->base .name )
619752 : " default" ][layout_idx] = {
620753 {" enabled" , layout_enabled},
621754 {" layout" , layout_name},
@@ -637,7 +770,7 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
637770 // get the name of the keysym
638771 char buffer[255 ];
639772 xkb_keysym_get_name (bind.sym , buffer, 255 );
640- std::string name (buffer);
773+ std::string name = sanitize_for_json (buffer);
641774
642775 // handle mouse binds
643776 if (bind.sym >= 0x20000000 + 272 && bind.sym <= 0x20000000 + 276 )
@@ -708,8 +841,9 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
708841 modifiers += MODIFIERS [j] + " " ;
709842
710843 // get the name of the keysym
711- std::string name;
712- xkb_keysym_get_name (bind.sym , name.data (), sizeof (name));
844+ char buffer[255 ];
845+ xkb_keysym_get_name (bind.sym , buffer, 255 );
846+ std::string name = sanitize_for_json (buffer);
713847
714848 // display
715849 j = {
@@ -728,15 +862,15 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
728862 case IPC_RULE_LIST : {
729863 std::vector<WindowRule *> rules = server->config ->window_rules ;
730864 for (unsigned long i = 0 ; i != rules.size (); ++i) {
731- json res = {{" title" , rules[i]->title },
732- {" class" , rules[i]->class_ },
733- {" tag" , rules[i]->tag }};
865+ json res = {{" title" , sanitize_for_json ( rules[i]->title ) },
866+ {" class" , sanitize_for_json ( rules[i]->class_ ) },
867+ {" tag" , sanitize_for_json ( rules[i]->tag ) }};
734868
735869 if (rules[i]->workspace )
736870 res[" workspace" ] = rules[i]->workspace ;
737871
738872 if (!rules[i]->output .empty ())
739- res[" output" ] = rules[i]->output ;
873+ res[" output" ] = sanitize_for_json ( rules[i]->output ) ;
740874
741875 if (rules[i]->toplevel_state )
742876 switch (*rules[i]->toplevel_state ) {
0 commit comments