]> git.sesse.net Git - plocate/blob - serializer.cpp
Escape unprintable characters when outputting filenames to a terminal.
[plocate] / serializer.cpp
1 #include "serializer.h"
2
3 #include "dprintf.h"
4
5 #include <chrono>
6 #include <inttypes.h>
7 #include <memory>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <utility>
11
12 using namespace std;
13 using namespace std::chrono;
14
15 extern steady_clock::time_point start;
16
17 void apply_limit()
18 {
19         if (--limit_left > 0) {
20                 return;
21         }
22         dprintf("Done in %.1f ms, found %" PRId64 " matches.\n",
23                 1e3 * duration<float>(steady_clock::now() - start).count(), limit_matches);
24         if (only_count) {
25                 printf("%" PRId64 "\n", limit_matches);
26         }
27         exit(0);
28 }
29
30 void print_possibly_escaped(const string &str)
31 {
32         if (print_nul) {
33                 printf("%s%c", str.c_str(), 0);
34                 return;
35         } else if (!stdout_is_tty) {
36                 printf("%s\n", str.c_str());
37                 return;
38         }
39
40         // stdout is a terminal, so we should protect the user against
41         // escapes, stray newlines and the likes. First of all, check if
42         // all the characters are safe; we consider everything safe that
43         // isn't a control character, ', " or \. People could make
44         // filenames like "$(rm -rf)", but that's out-of-scope.
45         const char *ptr = str.data();
46         size_t len = str.size();
47
48         mbtowc(nullptr, 0, 0);
49         wchar_t pwc;
50         bool all_safe = true;
51         do {
52                 int ret = mbtowc(&pwc, ptr, len);
53                 if (ret == -1) {
54                         all_safe = false;  // Malformed data.
55                 } else if (ret == 0) {
56                         break;  // EOF.
57                 } else if (pwc < 32 || pwc == '\'' || pwc == '"' || pwc == '\\') {
58                         all_safe = false;
59                 } else {
60                         ptr += ret;
61                         len -= ret;
62                 }
63         } while (all_safe);
64
65         if (all_safe) {
66                 printf("%s\n", str.c_str());
67                 return;
68         }
69
70         // Print escaped, but in such a way that the user can easily take the
71         // escaped output and paste into the shell. We print much like GNU ls does,
72         // ie., using the shell $'foo' construct whenever we need to print something
73         // escaped.
74         bool in_escaped_mode = false;
75         printf("'");
76
77         mbtowc(nullptr, 0, 0);
78         ptr = str.data();
79         len = str.size();
80         for (;;) {
81                 int ret = mbtowc(nullptr, ptr, len);
82                 if (ret == -1) {
83                         // Malformed data.
84                         printf("?");
85                         ++ptr;
86                         --len;
87                 } else if (ret == 0) {
88                         break;  // EOF.
89                 }
90                 if (*ptr < 32 || *ptr == '\'' || *ptr == '"' || *ptr == '\\') {
91                         if (!in_escaped_mode) {
92                                 printf("'$'");
93                                 in_escaped_mode = true;
94                         }
95
96                         // The list of allowed escapes is from bash(1).
97                         switch (*ptr) {
98                         case '\a':
99                                 printf("\\a");
100                                 break;
101                         case '\b':
102                                 printf("\\b");
103                                 break;
104                         case '\f':
105                                 printf("\\f");
106                                 break;
107                         case '\n':
108                                 printf("\\n");
109                                 break;
110                         case '\r':
111                                 printf("\\r");
112                                 break;
113                         case '\t':
114                                 printf("\\t");
115                                 break;
116                         case '\v':
117                                 printf("\\v");
118                                 break;
119                         case '\\':
120                                 printf("\\\\");
121                                 break;
122                         case '\'':
123                                 printf("\\'");
124                                 break;
125                         case '"':
126                                 printf("\\\"");
127                                 break;
128                         default:
129                                 printf("\\%03o", *ptr);
130                                 break;
131                         }
132                 } else {
133                         if (in_escaped_mode) {
134                                 printf("''");
135                                 in_escaped_mode = false;
136                         }
137                         fwrite(ptr, ret, 1, stdout);
138                 }
139                 ptr += ret;
140                 len -= ret;
141         }
142         printf("'\n");
143 }
144
145 void Serializer::print(uint64_t seq, uint64_t skip, const string msg)
146 {
147         if (only_count) {
148                 if (!msg.empty()) {
149                         apply_limit();
150                 }
151                 return;
152         }
153
154         if (next_seq != seq) {
155                 pending.push(Element{ seq, skip, move(msg) });
156                 return;
157         }
158
159         if (!msg.empty()) {
160                 print_possibly_escaped(msg);
161                 apply_limit();
162         }
163         next_seq += skip;
164
165         // See if any delayed prints can now be dealt with.
166         while (!pending.empty() && pending.top().seq == next_seq) {
167                 if (!pending.top().msg.empty()) {
168                         print_possibly_escaped(pending.top().msg);
169                         apply_limit();
170                 }
171                 next_seq += pending.top().skip;
172                 pending.pop();
173         }
174 }