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