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
30 static struct map *b64_map safe;
31 DEF_LOOKUP_CMD(b64_handle, b64_map);
33 static int is_b64(wint_t c)
35 return (c >= 'A' && c <= 'Z') ||
36 (c >= 'a' && c <= 'z') ||
37 (c >= '0' && c <= '9') ||
38 c == '+' || c == '/' || c == '=';
41 static wint_t from_b64(char c)
43 /* This assumes that 'c' is_b64() */
49 return (c - '0') + 52;
55 return (c - 'a') + 26;
58 static wint_t get_b64(struct pane *p safe, struct mark *m safe)
65 } while (c != WEOF && !is_b64(c));
69 while ((c = doc_following(p, m)) != WEOF && !is_b64(c))
74 static wint_t get_b64_rev(struct pane *p safe, struct mark *m safe)
80 } while (c != WEOF && !is_b64(c));
86 static void set_pos(struct mark *m safe, int pos)
95 attr_set_str(&m->attrs, "b64-pos", ps);
99 static int get_pos(struct mark *m safe)
101 char *ps = attr_find(m->attrs, "b64-pos");
102 if (ps && strlen(ps) == 1 &&
103 ps[0] >= '0' && ps[1] <= '2')
108 static inline wint_t base64_next(struct pane *home safe, struct mark *mark safe,
109 struct doc_ref *r safe, bool bytes)
112 int move = r == &mark->ref;
114 struct pane *p = home->parent;
126 /* If we found '=', there is no more to find */
127 if (c1 == 64 || c2 == 64) {
128 while (pos < 2 && c2 != WEOF) {
134 /* hopefully it was 64 aka '=' */
137 if (c1 == WEOF || c2 == WEOF) {
139 return CHAR_RET(WEOF);
144 b = (c1 << 2) | (c2 >> 4);
147 b = ((c1 << 4) & 0xF0) | (c2 >> 2);
150 b = ((c1 << 6) & 0xC0) | c2;
157 /* Step back to look at the last char read */
161 mark_to_mark(mark, m);
162 set_pos(mark, pos+1);
169 static inline wint_t base64_prev(struct pane *home safe, struct mark *mark safe,
170 struct doc_ref *r safe, bool bytes)
173 int move = r == &mark->ref;
175 struct pane *p = home->parent;
186 if (get_b64(p, m) == WEOF)
188 c2 = get_b64_rev(p, m);
189 c1 = get_b64_rev(p, m);
196 c1 = get_b64_rev(p, m);
203 if (c1 == WEOF || c2 == WEOF) {
205 return CHAR_RET(WEOF);
210 b = (c1 << 2) | (c2 >> 4);
213 b = ((c1 << 4) & 0xF0) | (c2 >> 2);
216 b = ((c1 << 6) & 0xC0) | c2;
222 mark_to_mark(mark, m);
232 return do_char_byte(ci);
235 DEF_CMD(base64_setref)
238 /* Start and end are always pos 0 */
239 set_pos(ci->mark, 0);
243 DEF_CMD(base64_arrived)
245 struct mark *m = ci->mark;
246 struct mark *ref = ci->mark2;
253 /* Interesting mark, keep tracking it */
267 struct pane *home safe;
268 struct mark *m safe; /* trails 1 b64 char behind main mark */
275 static int b64_bulk(struct b64c *c safe, wchar_t first, const char *s safe, int len)
277 /* Parse out 4char->3byte section of 's' and then
278 * pass them to c->cb.
279 * Return the number of chars processed.
282 char *out = malloc((len+1)*3/4);
284 int in_pos = 0, out_pos = 0, buf_pos = 0;
290 b[buf_pos++] = from_b64(first);
292 wint_t wc = (unsigned char)s[in_pos++];
296 b[buf_pos++] = from_b64(wc);
297 if (b[buf_pos-1] == 64)
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);
308 /* Now send 'out' to callback */
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,
315 if (rv <= 0 || rv > (out_pos - i) + 1) {
323 /* Only some was consumed, so need to
324 * advance c->m by the amount of chars that
325 * were consumed - in 'home'.
327 call("doc:char", c->home, rv, c->m);
333 DEF_CMD(base64_content_cb)
335 struct b64c *c = container_of(ci->comm, struct b64c, c);
344 c->size = ci->x * 3 / 4;
348 /* Mark as advances down in the doc so we didn't see it.
349 * Need to explicitly set pos
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);
362 /* We've found a padding '=', that's all folks. */
366 if (c->pos <= 0 || c->pos > 3) {
369 mark_to_mark(c->m, ci->mark);
373 /* This is first b64 */
375 c->pos = (c->pos + 1) % 4;
376 mark_to_mark(c->m, ci->mark);
379 /* Have 2 b64 chars, can report one char */
382 b = (c->c1 << 2) | (c2 >> 4);
385 b = ((c->c1 << 4) & 0xF0) | (c2 >> 2);
388 b = ((c->c1 << 6) & 0xC0) | c2;
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);
400 mark_to_mark(c->m, ci->mark);
407 DEF_CMD(base64_content)
412 if (!ci->comm2 || !ci->mark)
414 /* No need to check ->key as providing bytes as chars
418 c.c = base64_content_cb;
423 c.pos = get_pos(ci->mark);
425 c.m = mark_dup(ci->mark);
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.
434 while ((c2 = doc_next(ci->home->parent, c.m)) != WEOF &&
438 comm_call(&c.c, "cb", ci->home->parent,
449 p = pane_register(ci->focus, 0, &b64_handle.c);
452 call("doc:request:mark:arrived", p);
454 return comm_call(ci->comm2, "callback:attach", p);
457 void edlib_init(struct pane *ed safe)
460 b64_map = key_alloc();
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);
469 call_comm("global-set-command", ed, &b64_attach, 0, NULL, "attach-base64");