]> git.neil.brown.name Git - edlib.git/blob - lib-qprint.c
TODO: clean out done items.
[edlib.git] / lib-qprint.c
1 /*
2  * Copyright Neil Brown ©2016-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * Filter a view on a document to make quoted-printable look like the
6  * decoded bytes.  A UTF-8 filter would be needed if the text
7  * is actually utf-8.
8  *
9  * Chars are passed through except for '=' and following.
10  * =HH decodes the hex
11  * =\r\n disappears
12  * \r\n -> \n
13  * space/tab at eol ignored
14  *
15  * So when stepping backward if we see a \n or hex char we need
16  * to look further to see what is really there.
17  * When stepping forwards, we need only check for '=' or white space.
18  */
19
20 #include <unistd.h>
21 #include <stdlib.h>
22
23 #define DOC_NEXT qp_next
24 #define DOC_PREV qp_prev
25 #define PANE_DATA_VOID
26 #include "core.h"
27
28 static struct map *qp_map safe;
29 DEF_LOOKUP_CMD(qp_handle, qp_map);
30
31 static int hex(wint_t c)
32 {
33         if (c >= '0' && c <= '9')
34                 return c - '0';
35         if (c >= 'A' && c <= 'F')
36                 return c - 'A' + 10;
37         if (c >= 'a' && c <= 'f')
38                 return c - 'a' + 10;
39         return -1;
40 }
41
42 static inline wint_t qp_next(struct pane *home safe, struct mark *mark safe,
43                              struct doc_ref *r safe, bool byte)
44 {
45         int move = r == &mark->ref;
46         struct pane *p = home->parent;
47         wint_t ch, c2, c3;
48         struct mark *m = mark;
49
50 retry:
51         if (move)
52                 ch = doc_next(p, m);
53         else
54                 ch = doc_following(p, m);
55         if (ch != '=' && ch != ' ' && ch != '\t' && ch != '\r') {
56                 if (m != mark) {
57                         if (move)
58                                 mark_to_mark(mark, m);
59                         mark_free(m);
60                 }
61                 goto normalize;
62         }
63         if (ch == '\r') {
64                 /* assume CR-LF - skip an extra char */
65                 if (move)
66                         doc_next(p, m);
67                 if (m != mark) {
68                         if (move)
69                                 mark_to_mark(mark, m);
70                         mark_free(m);
71                 }
72                 ch = '\n';
73                 goto normalize;
74         }
75         if (m == mark)
76                 m = mark_dup(m);
77         if (!move)
78                 doc_next(p, m);
79         if (ch == '=') {
80                 /* CRLF or HexHex expected. */
81                 c2 = doc_next(p, m);
82                 if (c2 == '\n')
83                         goto retry;
84                 c3 = doc_next(p, m);
85                 if (c2 == '\r' && c3 == '\n')
86                         goto retry;
87                 if (hex(c2) >= 0 && hex(c3) >= 0) {
88                         ch = hex(c2)*16 + hex(c3);
89                         if (move)
90                                 mark_to_mark(mark, m);
91                 }
92                 mark_free(m);
93                 goto normalize;
94         }
95         /* Whitespace, ignore if at eol */
96         if (move)
97                 mark_to_mark(mark, m);
98         while ((c2 = doc_next(p, m)) == ' ' || c2 == '\t')
99                 ;
100         if (c2 == '\r')
101                 /* Found the white-space, retry from here and see the '\n' */
102                 goto retry;
103         if (c2 == '\n') {
104                 /* No \r, just \n.  Step back to see it */
105                 doc_prev(p, m);
106                 goto retry;
107         }
108         /* Just normal white space */
109         mark_free(m);
110 normalize:
111         if (!move)
112                 return ch;
113 normalize_more:
114         m = mark;
115         /* If next is "=\n" we need to skip over it. */
116         if (doc_following(p, m) != '=')
117                 return ch;
118         m = mark_dup(mark);
119         doc_next(p, m);
120         while ((c2 = doc_next(p, m)) == ' ' ||
121                c2 == '\t' || c2 == '\r')
122                 ;
123         if (c2 != '\n') {
124                 /* Don't need to skip this */
125                 mark_free(m);
126                 return ch;
127         }
128         mark_to_mark(mark, m);
129         mark_free(m);
130         goto normalize_more;
131 }
132
133 static inline wint_t qp_prev(struct pane *home safe, struct mark *mark safe,
134                              struct doc_ref *r safe, bool byte)
135 {
136         int move = r == &mark->ref;
137         struct pane *p = home->parent;
138         wint_t ch, c2, c3;
139         struct mark *m = mark;
140
141 retry:
142         if (move)
143                 ch = doc_prev(p, m);
144         else
145                 ch = doc_prior(p, m);
146         if (ch == '\n') {
147                 if (m == mark)
148                         m = mark_dup(m);
149                 if (!move)
150                         doc_prev(p, m);
151                 /* '\n', skip '\r' and white space */
152                 while ((ch = doc_prior(p, m)) == '\r' ||
153                        ch == ' ' || ch == '\t')
154                         doc_prev(p, m);
155                 if (ch == '=') {
156                         doc_prev(p, m);
157                         goto retry;
158                 }
159                 if (move)
160                         mark_to_mark(mark, m);
161                 mark_free(m);
162                 return '\n';
163         }
164         if (hex(ch) < 0) {
165                 if (m != mark) {
166                         if (move)
167                                 mark_to_mark(mark, m);
168                         mark_free(m);
169                 }
170                 return ch;
171         }
172         if (m == mark)
173                 m = mark_dup(m);
174         else if (move)
175                 mark_to_mark(mark, m);
176         if (!move)
177                 doc_prev(p, m);
178
179         /* Maybe =HH */
180         c3 = ch;
181         c2 = doc_prev(p, m);
182         if (hex(c2) >= 0) {
183                 wint_t ceq = doc_prev(p, m);
184                 if (ceq == '=') {
185                         /* =HH */
186                         ch = hex(c2)*16 + hex(c3);
187                         if (move)
188                                 mark_to_mark(mark, m);
189                         mark_free(m);
190                         return ch;
191                 }
192         }
193         mark_free(m);
194         return ch;
195 }
196
197 DEF_CMD(qp_char)
198 {
199         return do_char_byte(ci);
200 }
201
202 struct qpcb {
203         struct command c;
204         struct command *cb safe;
205         struct pane *p safe;
206         char state; /* \0 or '=' or hexit */
207         int size;
208         struct buf lws;
209         struct mark *lws_start; /* after first lws char */
210 };
211
212 static int qpflush(struct qpcb *c safe, const struct cmd_info *ci, wint_t ch,
213                    const char *remainder, int rlen)
214 {
215         char *lws = buf_final(&c->lws);
216         int lws_len = c->lws.len;
217         int ret = 1;
218         int i;
219
220         while (ret > 0 && lws_len > 0 && c->lws_start) {
221                 ret = comm_call(c->cb, ci->key, c->p, *lws, c->lws_start, lws+1,
222                                 lws_len - 1, NULL, NULL, c->size, 0);
223                 doc_next(ci->home, c->lws_start);
224                 c->size = 0;
225                 if (ret > 0) {
226                         lws += ret;
227                         lws_len -= ret;
228                         ret = 1;
229                 }
230         }
231         buf_reinit(&c->lws);
232         mark_free(c->lws_start);
233         c->lws_start = NULL;
234         if (!ch)
235                 return ret;
236         for (i = 0; remainder && i < rlen; i++)
237                 if (strchr("=\r\n", remainder[i])) {
238                         rlen = i;
239                         if (remainder[i] == '=')
240                                 break;
241                         /* ignore trailing white space */
242                         while (i > 0 && remainder[i] <= ' ')
243                                 i -= 1;
244                         rlen = i;
245                 }
246         if (ret > 0)
247                 ret = comm_call(c->cb, ci->key, c->p, ch, ci->mark, remainder,
248                                 rlen, NULL, NULL, c->size, 0);
249         c->size = 0;
250         return ret;
251 }
252
253 DEF_CMD(qp_content_cb)
254 {
255         struct qpcb *c = container_of(ci->comm, struct qpcb, c);
256         wint_t wc = ci->num;
257         int ret = 1;
258
259         if (!ci->mark)
260                 return Enoarg;
261         if (ci->x)
262                 c->size = ci->x;
263
264         if (c->state && c->state != '=') {
265                 /* Must see a hexit */
266                 int h = hex(wc);
267                 if (h >= 0) {
268                         ret = qpflush(c, ci,  (hex(c->state) << 4) | h, NULL, 0);
269                         c->state = 0;
270                         return ret;
271                 }
272                 /* Pass first 2 literally */
273                 ret = qpflush(c, ci, '=', NULL, 0);
274                 if (ret > 0)
275                         ret = qpflush(c, ci, c->state, NULL, 0);
276                 c->state = 0;
277         }
278
279         if (wc == '\r')
280                 /* Always skip \r */
281                 return ret;
282         if (!c->state) {
283                 if (wc == '=') {
284                         /* flush lws even if this turns out to be "=\n    \n" */
285                         if (ret)
286                                 ret = qpflush(c, ci, 0, NULL, 0);
287                         c->state = wc;
288                         return ret;
289                 }
290                 if (wc == ' ' || wc == '\t') {
291                         if (!c->lws_start)
292                                 c->lws_start = mark_dup(ci->mark);
293                         buf_append(&c->lws, wc);
294                         return ret;
295                 }
296                 if (wc == '\n') {
297                         /* drop any trailing space */
298                         buf_reinit(&c->lws);
299                         mark_free(c->lws_start);
300                         c->lws_start = NULL;
301                 }
302                 if (ret > 0)
303                         ret = qpflush(c, ci, wc, ci->str, ci->num2);
304                 return ret;
305         }
306         /* Previous was '='. */
307         if (hex(wc) >= 0) {
308                 c->state = wc;
309                 return ret;
310         }
311         if (wc == ' ' || wc == '\t')
312                 /* Ignore space after =, incase at eol */
313                 return ret;
314         c->state = 0;
315         if (wc == '\n')
316                 /* The '=' was hiding the \n */
317                 return ret;
318         if (ret > 0)
319                 ret = qpflush(c, ci, '=', NULL, 0);
320         if (ret > 0)
321                 ret = qpflush(c, ci, wc, NULL, 0);
322         return ret;
323 }
324
325 DEF_CMD(qp_content)
326 {
327         struct qpcb c;
328         int ret;
329
330         if (!ci->comm2 || !ci->mark)
331                 return Enoarg;
332         /* No need to check ->key as providing bytes as chars
333          * is close enough.
334          */
335
336         c.c = qp_content_cb;
337         c.cb = ci->comm2;
338         c.p = ci->focus;
339         c.size = 0;
340         c.state = 0;
341         buf_init(&c.lws);
342         c.lws_start = NULL;
343         ret = home_call_comm(ci->home->parent, ci->key, ci->home,
344                              &c.c, 0, ci->mark, NULL, 0, ci->mark2);
345         free(c.lws.b);
346         mark_free(c.lws_start);
347         return ret;
348 }
349
350 DEF_CMD(qp_attach)
351 {
352         struct pane *p;
353
354         p = pane_register(ci->focus, 0, &qp_handle.c);
355         if (!p)
356                 return Efail;
357
358         return comm_call(ci->comm2, "callback:attach", p);
359 }
360
361 void edlib_init(struct pane *ed safe)
362 {
363
364         qp_map = key_alloc();
365
366         key_add(qp_map, "doc:char", &qp_char);
367         key_add(qp_map, "doc:byte", &qp_char);
368         key_add(qp_map, "doc:content", &qp_content);
369         key_add(qp_map, "doc:content-bytes", &qp_content);
370
371         call_comm("global-set-command", ed, &qp_attach, 0, NULL, "attach-quoted_printable");
372         call_comm("global-set-command", ed, &qp_attach, 0, NULL, "attach-qprint");
373 }