]> git.neil.brown.name Git - edlib.git/blob - lib-base64.c
TODO: clean out done items.
[edlib.git] / lib-base64.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 base64 look like the
6  * decoded bytes.  A UTF-8 filter would be needed if the base64
7  * is actually utf-8.
8  *
9  * Each mark needs not just a location in the b64, but also
10  * which byte of a QUAD (4 b64 characters) it is at.
11  * We store this in the mark as attribute "b64-pos", which makes
12  * stacking b64 impossible - but who would want to?
13  * This can have a value "0", "1", "2".  A mark is never on the
14  * 4th char of a QUAD.
15  * doc:set-ref initialises this as does a mark:arrived notification
16  * which references another mark.  doc:char and doc:byte will use
17  * the pos to know how to interpret, and will update it after any
18  * movement as will doc:content.
19  */
20
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <wctype.h>
24
25 #define DOC_NEXT base64_next
26 #define DOC_PREV base64_prev
27
28 #define PANE_DATA_VOID
29 #include "core.h"
30
31 static struct map *b64_map safe;
32 DEF_LOOKUP_CMD(b64_handle, b64_map);
33
34 static int is_b64(wint_t c)
35 {
36         return (c >= 'A' && c <= 'Z') ||
37                 (c >= 'a' && c <= 'z') ||
38                 (c >= '0' && c <= '9') ||
39                 c == '+' || c == '/' || c == '=';
40 }
41
42 static wint_t from_b64(char c)
43 {
44         /* This assumes that 'c' is_b64() */
45         if (c <= '+')
46                 return 62;
47         else if (c == '/')
48                 return 63;
49         else if (c <= '9')
50                 return (c - '0') + 52;
51         else if (c == '=')
52                 return 64;
53         else if (c <= 'Z')
54                 return (c - 'A') + 0;
55         else
56                 return (c - 'a') + 26;
57 }
58
59 static wint_t get_b64(struct pane *p safe, struct mark *m safe)
60 {
61         wint_t c;
62         wint_t ret;
63
64         do {
65                 c = doc_next(p, m);
66         } while (c != WEOF && !is_b64(c));
67         if (c == WEOF)
68                 return WEOF;
69         ret = from_b64(c);
70         while ((c = doc_following(p, m)) != WEOF && !is_b64(c))
71                 doc_next(p, m);
72         return ret;
73 }
74
75 static wint_t get_b64_rev(struct pane *p safe, struct mark *m safe)
76 {
77         wint_t c;
78
79         do {
80                 c = doc_prev(p, m);
81         } while (c != WEOF && !is_b64(c));
82         if (c == WEOF)
83                 return WEOF;
84         return from_b64(c);
85 }
86
87 static void set_pos(struct mark *m safe, int pos)
88 {
89         char ps[2] = "0";
90
91         while (pos < 0)
92                 pos += 4;
93         while (pos >= 4)
94                 pos -= 4;
95         ps[0] += pos;
96         attr_set_str(&m->attrs, "b64-pos", ps);
97         mark_watch(m);
98 }
99
100 static int get_pos(struct mark *m safe)
101 {
102         char *ps = attr_find(m->attrs, "b64-pos");
103         if (ps && strlen(ps) == 1 &&
104             ps[0] >= '0' && ps[1] <= '2')
105                 return ps[0] - '0';
106         return -1;
107 }
108
109 static inline wint_t base64_next(struct pane *home safe, struct mark *mark safe,
110                                  struct doc_ref *r safe, bool bytes)
111 {
112         int pos = 0;
113         int move = r == &mark->ref;
114         struct mark *m;
115         struct pane *p = home->parent;
116         wint_t c1, c2, b;
117
118         pos = get_pos(mark);
119         if (pos < 0)
120                 /* bug? */
121                 pos = 0;
122
123         m = mark_dup(mark);
124 retry:
125         c1 = get_b64(p, m);
126         c2 = get_b64(p, m);
127         /* If we found '=', there is no more to find */
128         if (c1 == 64 || c2 == 64) {
129                 while (pos < 2 && c2 != WEOF) {
130                         c2 = get_b64(p, m);
131                         pos += 1;
132                 }
133                 pos = 0;
134                 if (c2 != WEOF)
135                         /* hopefully it was 64 aka '=' */
136                         goto retry;
137         }
138         if (c1 == WEOF || c2 == WEOF) {
139                 mark_free(m);
140                 return CHAR_RET(WEOF);
141         }
142
143         switch(pos) {
144         case 0:
145                 b = (c1 << 2) | (c2 >> 4);
146                 break;
147         case 1:
148                 b = ((c1 << 4) & 0xF0) | (c2 >> 2);
149                 break;
150         case 2:
151                 b = ((c1 << 6) & 0xC0) | c2;
152                 break;
153         default:
154                 b = 0;
155         }
156         if (move) {
157                 if (pos < 2)
158                         /* Step back to look at the last char read */
159                         doc_prev(p, m);
160                 else
161                         pos += 1;
162                 mark_to_mark(mark, m);
163                 set_pos(mark, pos+1);
164         }
165
166         mark_free(m);
167         return b;
168 }
169
170 static inline wint_t base64_prev(struct pane *home safe, struct mark *mark safe,
171                                  struct doc_ref *r safe, bool bytes)
172 {
173         int pos = 0;
174         int move = r == &mark->ref;
175         struct mark *m;
176         struct pane *p = home->parent;
177         wint_t c1, c2, b;
178
179         pos = get_pos(mark);
180         if (pos < 0)
181                 /* bug? */
182                 pos = 0;
183
184         m = mark_dup(mark);
185
186         if (pos)
187                 if (get_b64(p, m) == WEOF)
188                         pos = 0;
189         c2 = get_b64_rev(p, m);
190         c1 = get_b64_rev(p, m);
191         if (pos <= 0)
192                 pos = 2;
193         else
194                 pos -= 1;
195         while (c2 == 64) {
196                 c2 = c1;
197                 c1 = get_b64_rev(p, m);
198                 if (pos <= 0)
199                         pos = 2;
200                 else
201                         pos -= 1;
202         }
203
204         if (c1 == WEOF || c2 == WEOF) {
205                 mark_free(m);
206                 return CHAR_RET(WEOF);
207         }
208
209         switch(pos) {
210         case 0:
211                 b = (c1 << 2) | (c2 >> 4);
212                 break;
213         case 1:
214                 b = ((c1 << 4) & 0xF0) | (c2 >> 2);
215                 break;
216         case 2:
217                 b = ((c1 << 6) & 0xC0) | c2;
218                 break;
219         default:
220                 b = 0;
221         }
222         if (move) {
223                 mark_to_mark(mark, m);
224                 set_pos(mark, pos);
225         }
226
227         mark_free(m);
228         return b;
229 }
230
231 DEF_CMD(base64_char)
232 {
233         return do_char_byte(ci);
234 }
235
236 DEF_CMD(base64_setref)
237 {
238         if (ci->mark)
239                 /* Start and end are always pos 0 */
240                 set_pos(ci->mark, 0);
241         return Efallthrough;
242 }
243
244 DEF_CMD(base64_arrived)
245 {
246         struct mark *m = ci->mark;
247         struct mark *ref = ci->mark2;
248         int pos;
249
250         if (!m)
251                 return 1;
252
253         if (get_pos(m) >= 0)
254                 /* Interesting mark, keep tracking it */
255                 mark_watch(m);
256         if (!ref)
257                 return 1;
258         pos = get_pos(ref);
259         if (pos >= 0)
260                 set_pos(m, pos);
261         return 1;
262 }
263
264 struct b64c {
265         struct command c;
266         struct command *cb;
267         struct pane *p safe;
268         struct pane *home safe;
269         struct mark *m safe; /* trails 1 b64 char behind main mark */
270         int pos;
271         int size;
272         char c1;
273         bool nobulk;
274 };
275
276 static int b64_bulk(struct b64c *c safe, wchar_t first, const char *s safe, int len)
277 {
278         /* Parse out 4char->3byte section of 's' and then
279          * pass them to c->cb.
280          * Return the number of chars processed.
281          */
282         int ret = 0;
283         char *out = malloc((len+1)*3/4);
284         int b[4];
285         int in_pos = 0, out_pos = 0, buf_pos = 0;
286         int i;
287
288         if (!out)
289                 return ret;
290         if (is_b64(first))
291                 b[buf_pos++] = from_b64(first);
292         while (len > 0) {
293                 wint_t wc = (unsigned char)s[in_pos++];
294                 len -= 1;
295                 if (!is_b64(wc))
296                         continue;
297                 b[buf_pos++] = from_b64(wc);
298                 if (b[buf_pos-1] == 64)
299                         break;
300                 if (buf_pos < 4)
301                         continue;
302                 out[out_pos++] = ((b[0] << 2) & 0xFC) | (b[1] >> 4);
303                 out[out_pos++] = ((b[1] << 4) & 0xF0) | (b[2] >> 2);
304                 out[out_pos++] = ((b[2] << 6) & 0xC0) | (b[3] >> 0);
305                 ret = in_pos+1;
306                 buf_pos = 0;
307         }
308
309         /* Now send 'out' to callback */
310         i = 0;
311         while (i < out_pos) {
312                 int rv = comm_call(c->cb, "cb", c->p, out[i], c->m,
313                                    out+i+1, out_pos-i-1, NULL, NULL,
314                                    c->size, 0);
315                 c->size = 0;
316                 if (rv <= 0 || rv > (out_pos - i) + 1) {
317                         if (rv <= 0)
318                                 ret = rv;
319                         c->nobulk = True;
320                         break;
321                 }
322                 i += rv;
323                 if (i < out_pos)
324                         /* Only some was consumed, so need to
325                          * advance c->m by the amount of chars that
326                          * were consumed - in 'home'.
327                          */
328                         call("doc:char", c->home, rv, c->m);
329         }
330         free(out);
331         return ret;
332 }
333
334 DEF_CMD(base64_content_cb)
335 {
336         struct b64c *c = container_of(ci->comm, struct b64c, c);
337         wint_t wc = ci->num;
338         char c2;
339         wint_t b;
340         int ret;
341
342         if (!ci->mark)
343                 return Enoarg;
344         if (ci->x)
345                 c->size = ci->x * 3 / 4;
346
347         if (!is_b64(wc))
348                 return 1;
349         /* Mark as advances down in the doc so we didn't see it.
350          * Need to explicitly set pos
351          */
352         set_pos(ci->mark, (c->pos+1)%4);
353         if (!c->nobulk && wc != '=' && (c->pos % 4) == 0 &&
354             ci->str && ci->num2 >= 4) {
355                 mark_to_mark(c->m, ci->mark);
356                 ret = b64_bulk(c, wc, ci->str, ci->num2);
357                 if (ret > 0)
358                         return ret;
359         }
360
361         c2 = from_b64(wc);
362         if (c2 == 64) {
363                 /* We've found a padding '=', that's all folks. */
364                 c->c1 = 64;
365                 return Efalse;
366         }
367         if (c->pos <= 0 || c->pos > 3) {
368                 c->c1 = c2;
369                 c->pos = 1;
370                 mark_to_mark(c->m, ci->mark);
371                 return 1;
372         }
373         if (c->c1 == 64) {
374                 /* This is first b64 */
375                 c->c1 = c2;
376                 c->pos = (c->pos + 1) % 4;
377                 mark_to_mark(c->m, ci->mark);
378                 return 1;
379         }
380         /* Have 2 b64 chars, can report one char */
381         switch(c->pos) {
382         case 1:
383                 b = (c->c1 << 2) | (c2 >> 4);
384                 break;
385         case 2:
386                 b = ((c->c1 << 4) & 0xF0) | (c2 >> 2);
387                 break;
388         case 3:
389                 b = ((c->c1 << 6) & 0xC0) | c2;
390                 break;
391         default:
392                 b = 0;
393         }
394         c->pos += 1;
395         c->c1 = c2;
396         if (c->pos == 4)
397                 mark_to_mark(c->m, ci->mark);
398         ret = comm_call(c->cb, ci->key, c->p, b, c->m, NULL,
399                         0, NULL, NULL, c->size, 0);
400         if (c->pos != 4)
401                 mark_to_mark(c->m, ci->mark);
402         c->size = 0;
403         if (ret == Efalse)
404                 c->c1 = 64;
405         return ret;
406 }
407
408 DEF_CMD(base64_content)
409 {
410         struct b64c c;
411         int ret;
412
413         if (!ci->comm2 || !ci->mark)
414                 return Enoarg;
415         /* No need to check ->key as providing bytes as chars
416          * is close enough.
417          */
418
419         c.c = base64_content_cb;
420         c.nobulk = False;
421         c.cb = ci->comm2;
422         c.p = ci->focus;
423         c.home = ci->home;
424         c.pos = get_pos(ci->mark);
425         c.size = 0;
426         c.m = mark_dup(ci->mark);
427         c.c1 = 64;
428         ret = home_call_comm(ci->home->parent, ci->key, ci->home, &c.c,
429                              0, ci->mark, NULL, 0, ci->mark2);
430         if (c.c1 != 64 && (c.pos % 4) > 0 && ci->mark2) {
431                 /* We must have reached mark2, but need one more
432                  * b64 char.  Skip space if needed to find it.
433                  */
434                 wint_t c2;
435                 while ((c2 = doc_next(ci->home->parent, c.m)) != WEOF &&
436                        iswspace(c2))
437                         ;
438                 if (c2 != WEOF)
439                         comm_call(&c.c, "cb", ci->home->parent,
440                                   c2, ci->mark2);
441         }
442         mark_free(c.m);
443         return ret;
444 }
445
446 DEF_CMD(b64_attach)
447 {
448         struct pane *p;
449
450         p = pane_register(ci->focus, 0, &b64_handle.c);
451         if (!p)
452                 return Efail;
453         call("doc:request:mark:arrived", p);
454
455         return comm_call(ci->comm2, "callback:attach", p);
456 }
457
458 void edlib_init(struct pane *ed safe)
459 {
460
461         b64_map = key_alloc();
462
463         key_add(b64_map, "doc:char", &base64_char);
464         key_add(b64_map, "doc:byte", &base64_char);
465         key_add(b64_map, "doc:content", &base64_content);
466         key_add(b64_map, "doc:content-bytes", &base64_content);
467         key_add(b64_map, "doc:set-ref", &base64_setref);
468         key_add(b64_map, "mark:arrived", &base64_arrived);
469
470         call_comm("global-set-command", ed, &b64_attach, 0, NULL, "attach-base64");
471 }