2 * Copyright Neil Brown ©2016-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
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
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
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.
25 #define DOC_NEXT base64_next
26 #define DOC_PREV base64_prev
28 #define PANE_DATA_VOID
31 static struct map *b64_map safe;
32 DEF_LOOKUP_CMD(b64_handle, b64_map);
34 static int is_b64(wint_t c)
36 return (c >= 'A' && c <= 'Z') ||
37 (c >= 'a' && c <= 'z') ||
38 (c >= '0' && c <= '9') ||
39 c == '+' || c == '/' || c == '=';
42 static wint_t from_b64(char c)
44 /* This assumes that 'c' is_b64() */
50 return (c - '0') + 52;
56 return (c - 'a') + 26;
59 static wint_t get_b64(struct pane *p safe, struct mark *m safe)
66 } while (c != WEOF && !is_b64(c));
70 while ((c = doc_following(p, m)) != WEOF && !is_b64(c))
75 static wint_t get_b64_rev(struct pane *p safe, struct mark *m safe)
81 } while (c != WEOF && !is_b64(c));
87 static void set_pos(struct mark *m safe, int pos)
96 attr_set_str(&m->attrs, "b64-pos", ps);
100 static int get_pos(struct mark *m safe)
102 char *ps = attr_find(m->attrs, "b64-pos");
103 if (ps && strlen(ps) == 1 &&
104 ps[0] >= '0' && ps[1] <= '2')
109 static inline wint_t base64_next(struct pane *home safe, struct mark *mark safe,
110 struct doc_ref *r safe, bool bytes)
113 int move = r == &mark->ref;
115 struct pane *p = home->parent;
127 /* If we found '=', there is no more to find */
128 if (c1 == 64 || c2 == 64) {
129 while (pos < 2 && c2 != WEOF) {
135 /* hopefully it was 64 aka '=' */
138 if (c1 == WEOF || c2 == WEOF) {
140 return CHAR_RET(WEOF);
145 b = (c1 << 2) | (c2 >> 4);
148 b = ((c1 << 4) & 0xF0) | (c2 >> 2);
151 b = ((c1 << 6) & 0xC0) | c2;
158 /* Step back to look at the last char read */
162 mark_to_mark(mark, m);
163 set_pos(mark, pos+1);
170 static inline wint_t base64_prev(struct pane *home safe, struct mark *mark safe,
171 struct doc_ref *r safe, bool bytes)
174 int move = r == &mark->ref;
176 struct pane *p = home->parent;
187 if (get_b64(p, m) == WEOF)
189 c2 = get_b64_rev(p, m);
190 c1 = get_b64_rev(p, m);
197 c1 = get_b64_rev(p, m);
204 if (c1 == WEOF || c2 == WEOF) {
206 return CHAR_RET(WEOF);
211 b = (c1 << 2) | (c2 >> 4);
214 b = ((c1 << 4) & 0xF0) | (c2 >> 2);
217 b = ((c1 << 6) & 0xC0) | c2;
223 mark_to_mark(mark, m);
233 return do_char_byte(ci);
236 DEF_CMD(base64_setref)
239 /* Start and end are always pos 0 */
240 set_pos(ci->mark, 0);
244 DEF_CMD(base64_arrived)
246 struct mark *m = ci->mark;
247 struct mark *ref = ci->mark2;
254 /* Interesting mark, keep tracking it */
268 struct pane *home safe;
269 struct mark *m safe; /* trails 1 b64 char behind main mark */
276 static int b64_bulk(struct b64c *c safe, wchar_t first, const char *s safe, int len)
278 /* Parse out 4char->3byte section of 's' and then
279 * pass them to c->cb.
280 * Return the number of chars processed.
283 char *out = malloc((len+1)*3/4);
285 int in_pos = 0, out_pos = 0, buf_pos = 0;
291 b[buf_pos++] = from_b64(first);
293 wint_t wc = (unsigned char)s[in_pos++];
297 b[buf_pos++] = from_b64(wc);
298 if (b[buf_pos-1] == 64)
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);
309 /* Now send 'out' to callback */
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,
316 if (rv <= 0 || rv > (out_pos - i) + 1) {
324 /* Only some was consumed, so need to
325 * advance c->m by the amount of chars that
326 * were consumed - in 'home'.
328 call("doc:char", c->home, rv, c->m);
334 DEF_CMD(base64_content_cb)
336 struct b64c *c = container_of(ci->comm, struct b64c, c);
345 c->size = ci->x * 3 / 4;
349 /* Mark as advances down in the doc so we didn't see it.
350 * Need to explicitly set pos
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);
363 /* We've found a padding '=', that's all folks. */
367 if (c->pos <= 0 || c->pos > 3) {
370 mark_to_mark(c->m, ci->mark);
374 /* This is first b64 */
376 c->pos = (c->pos + 1) % 4;
377 mark_to_mark(c->m, ci->mark);
380 /* Have 2 b64 chars, can report one char */
383 b = (c->c1 << 2) | (c2 >> 4);
386 b = ((c->c1 << 4) & 0xF0) | (c2 >> 2);
389 b = ((c->c1 << 6) & 0xC0) | c2;
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);
401 mark_to_mark(c->m, ci->mark);
408 DEF_CMD(base64_content)
413 if (!ci->comm2 || !ci->mark)
415 /* No need to check ->key as providing bytes as chars
419 c.c = base64_content_cb;
424 c.pos = get_pos(ci->mark);
426 c.m = mark_dup(ci->mark);
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.
435 while ((c2 = doc_next(ci->home->parent, c.m)) != WEOF &&
439 comm_call(&c.c, "cb", ci->home->parent,
450 p = pane_register(ci->focus, 0, &b64_handle.c);
453 call("doc:request:mark:arrived", p);
455 return comm_call(ci->comm2, "callback:attach", p);
458 void edlib_init(struct pane *ed safe)
461 b64_map = key_alloc();
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);
470 call_comm("global-set-command", ed, &b64_attach, 0, NULL, "attach-base64");