File Coverage

Base64.xs
Criterion Covered Total %
statement 81 83 97.5
branch 58 70 82.8
condition n/a
subroutine n/a
pod n/a
total 139 153 90.8


line stmt bran cond sub pod time code
1             /*
2             * Base64.xs - File::Raw plugin bindings for File::Raw::Base64.
3             *
4             * Two plugins registered at BOOT:
5             * "base64" - standard alphabet (RFC 4648 §4)
6             * "base64url" - URL-safe alphabet (RFC 4648 §5)
7             *
8             * Both share the same C codec (b64.c). The plugin's `state` slot
9             * carries an integer flag (0 / 1) telling the read/write callbacks
10             * which alphabet to seed; per-call options merge on top via the
11             * standard FilePluginContext::options HV.
12             */
13              
14             #define PERL_NO_GET_CONTEXT
15             #include "EXTERN.h"
16             #include "perl.h"
17             #include "XSUB.h"
18              
19             /* file_plugin.h comes from File::Raw via ExtUtils::Depends -- the
20             consumer Makefile.PL adds the right -I to find it. */
21             #include "file_plugin.h"
22             #include "b64.h"
23              
24             #include
25              
26             /* ============================================================
27             * Option decoding
28             * ============================================================ */
29              
30             static const char *VALID_OPT_KEYS[] = {
31             "wrap", "urlsafe", "padding", "pem", "pem_label", "strict", "eol",
32             /* present in the HV File::Raw built for our dispatch call; we
33             * recognise and ignore it so unknown-key detection doesn't fire. */
34             "plugin",
35             NULL
36             };
37              
38             static int
39 100           known_opt(const char *key, STRLEN klen)
40             {
41             const char *const *p;
42 678 100         for (p = VALID_OPT_KEYS; *p; p++) {
43 677 100         if (strlen(*p) == klen && memcmp(*p, key, klen) == 0) return 1;
    100          
44             }
45 1           return 0;
46             }
47              
48             /* Holds string SVs we extract from the options HV so the C strings
49             * outlive the call into b64_*. The HV itself is mortal in File::Raw's
50             * dispatch, so the string PVs are valid for the call duration anyway —
51             * but we copy into the struct's own slots to keep the lifetime
52             * obviously local. */
53             typedef struct {
54             b64_options_t opts;
55             /* Backing storage for opts.pem_label and opts.eol. The caller
56             * passes these through the options HV; we point at the SV's PV
57             * since the HV stays mortal until after the b64 call returns. */
58             } decode_state_t;
59              
60             static void
61 73           decode_opts(pTHX_ HV *opts_hv, b64_options_t *opts)
62             {
63             HE *he;
64              
65 73 50         if (!opts_hv) return;
66              
67 73           hv_iterinit(opts_hv);
68 167 100         while ((he = hv_iternext(opts_hv))) {
69             I32 klen_i;
70             const char *key;
71             STRLEN klen;
72             SV *val;
73              
74 100           key = hv_iterkey(he, &klen_i);
75 100           klen = (STRLEN)klen_i;
76 100           val = hv_iterval(opts_hv, he);
77              
78 100 100         if (!known_opt(key, klen)) {
79 1           croak("File::Raw::Base64: unknown option '%.*s'",
80             (int)klen, key);
81             }
82 99 50         if (!SvOK(val)) continue;
83              
84 99 100         if (klen == 4 && memcmp(key, "wrap", 4) == 0) {
    50          
85 8           IV n = SvIV(val);
86 8 100         if (n < 0) croak("File::Raw::Base64: wrap must be >= 0");
87 7           opts->wrap = (int)n;
88 91 100         } else if (klen == 7 && memcmp(key, "urlsafe", 7) == 0) {
    100          
89 1           opts->urlsafe = SvTRUE(val) ? 1 : 0;
90 90 100         } else if (klen == 7 && memcmp(key, "padding", 7) == 0) {
    50          
91 2           opts->padding = SvTRUE(val) ? 1 : 0;
92 88 100         } else if (klen == 3 && memcmp(key, "pem", 3) == 0) {
    100          
93 8           opts->pem = SvTRUE(val) ? 1 : 0;
94 80 100         } else if (klen == 9 && memcmp(key, "pem_label", 9) == 0) {
    50          
95             STRLEN llen;
96             const char *lp;
97 4           lp = SvPV(val, llen);
98 4 100         if (llen == 0)
99 1           croak("File::Raw::Base64: pem_label must be non-empty");
100             /* memchr for NUL: PEM markers can't contain NULs. */
101 3 100         if (memchr(lp, '\0', llen) != NULL)
102 1           croak("File::Raw::Base64: pem_label must not contain NUL");
103 2           opts->pem_label = lp;
104 76 100         } else if (klen == 6 && memcmp(key, "strict", 6) == 0) {
    100          
105 2           opts->strict = SvTRUE(val) ? 1 : 0;
106 74 100         } else if (klen == 3 && memcmp(key, "eol", 3) == 0) {
    50          
107             STRLEN elen;
108             const char *ep;
109 3           ep = SvPV(val, elen);
110 3 100         if (elen == 0 || elen > 2)
    100          
111 2           croak("File::Raw::Base64: eol must be 1 or 2 bytes");
112 1           opts->eol = ep;
113             }
114             /* "plugin" key: ignored. */
115             }
116             }
117              
118             /* ============================================================
119             * Plugin callbacks
120             * ============================================================ */
121              
122             /* Plugin state is a tiny `int` (0 = standard alphabet, 1 = URL-safe).
123             * We use a static int per plugin and stash its address in
124             * FilePlugin.state. */
125             static int alphabet_std = 0;
126             static int alphabet_url = 1;
127              
128             static void
129 73           seed_from_state(b64_options_t *opts, void *state)
130             {
131 73           b64_options_init(opts);
132 73 50         if (state) {
133 73           opts->urlsafe = *(int *)state;
134             }
135 73           }
136              
137             static SV *
138 34           b64_read_cb(pTHX_ FilePluginContext *ctx)
139             {
140             b64_options_t opts;
141             STRLEN ilen;
142             const char *ipv;
143 34           unsigned char *out = NULL;
144 34           size_t out_cap = 0, out_len = 0, err_off = 0;
145             int rc;
146             SV *result;
147              
148 34           seed_from_state(&opts, ctx->plugin_state);
149 34 50         if (ctx->options) decode_opts(aTHX_ ctx->options, &opts);
150              
151 34 50         if (!ctx->data) return &PL_sv_undef;
152 34           ipv = SvPV(ctx->data, ilen);
153              
154 34           rc = b64_decode(ipv, ilen, &opts, &out, &out_cap, &out_len, &err_off);
155 34 100         if (rc != B64_OK) {
156 5           free(out);
157 5           croak("File::Raw::Base64: %s at byte offset %lu",
158             b64_strerror(rc), (unsigned long)err_off);
159             }
160              
161 29 100         result = newSVpvn(out_len ? (const char *)out : "", out_len);
162 29           free(out);
163 29           return result;
164             }
165              
166             static SV *
167 39           b64_write_cb(pTHX_ FilePluginContext *ctx)
168             {
169             b64_options_t opts;
170             STRLEN ilen;
171             const char *ipv;
172 39           char *out = NULL;
173 39           size_t out_cap = 0, out_len = 0, err_off = 0;
174             int rc;
175             SV *result;
176              
177 39           seed_from_state(&opts, ctx->plugin_state);
178 39 50         if (ctx->options) decode_opts(aTHX_ ctx->options, &opts);
179              
180 33 50         if (!ctx->data) return &PL_sv_undef;
181 33           ipv = SvPV(ctx->data, ilen);
182              
183 33           rc = b64_encode((const unsigned char *)ipv, ilen, &opts,
184             &out, &out_cap, &out_len, &err_off);
185 33 50         if (rc != B64_OK) {
186 0           free(out);
187 0           croak("File::Raw::Base64: %s at byte offset %lu",
188             b64_strerror(rc), (unsigned long)err_off);
189             }
190              
191 33 100         result = newSVpvn(out_len ? out : "", out_len);
192 33           free(out);
193 33           return result;
194             }
195              
196             /* Plugin descriptors. Static-storage lifetime so the registry's
197             * non-owning pointer stays valid for the life of the process. */
198             static FilePlugin base64_plugin;
199             static FilePlugin base64url_plugin;
200              
201             /* ============================================================ */
202              
203             MODULE = File::Raw::Base64 PACKAGE = File::Raw::Base64
204              
205             PROTOTYPES: DISABLE
206              
207             BOOT:
208 10           memset(&base64_plugin, 0, sizeof base64_plugin);
209 10           memset(&base64url_plugin, 0, sizeof base64url_plugin);
210 10           base64_plugin.name = "base64";
211 10           base64_plugin.read_fn = b64_read_cb;
212 10           base64_plugin.write_fn = b64_write_cb;
213 10           base64_plugin.state = &alphabet_std;
214 10           base64url_plugin.name = "base64url";
215 10           base64url_plugin.read_fn = b64_read_cb;
216 10           base64url_plugin.write_fn = b64_write_cb;
217 10           base64url_plugin.state = &alphabet_url;
218 10           file_register_plugin(aTHX_ &base64_plugin);
219 10           file_register_plugin(aTHX_ &base64url_plugin);