| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
/* |
|
2
|
|
|
|
|
|
|
* Hash.xs - File::Raw plugin bindings for File::Raw::Hash. |
|
3
|
|
|
|
|
|
|
* |
|
4
|
|
|
|
|
|
|
* file_slurp($p, plugin => 'hash', algo => 'sha256', into => \my $d); |
|
5
|
|
|
|
|
|
|
* file_slurp($p, plugin => 'hash', algos => [qw(sha256 md5)], |
|
6
|
|
|
|
|
|
|
* into => \my %digests); |
|
7
|
|
|
|
|
|
|
*/ |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
#define PERL_NO_GET_CONTEXT |
|
10
|
|
|
|
|
|
|
#include "EXTERN.h" |
|
11
|
|
|
|
|
|
|
#include "perl.h" |
|
12
|
|
|
|
|
|
|
#include "XSUB.h" |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
#include "file_plugin.h" |
|
15
|
|
|
|
|
|
|
#include "hashx.h" |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
#include |
|
18
|
|
|
|
|
|
|
#include |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
/* ============================================================ |
|
21
|
|
|
|
|
|
|
* Resolved options for a single plugin call. |
|
22
|
|
|
|
|
|
|
* ============================================================ */ |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
#define MAX_ALGOS 8 |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
typedef struct { |
|
27
|
|
|
|
|
|
|
hash_algo_id_t ids[MAX_ALGOS]; |
|
28
|
|
|
|
|
|
|
int n_ids; |
|
29
|
|
|
|
|
|
|
int multi; /* 0 = `algo`, 1 = `algos` */ |
|
30
|
|
|
|
|
|
|
hash_format_t format; |
|
31
|
|
|
|
|
|
|
SV *into; /* the user's ref SV; not refcount-bumped here */ |
|
32
|
|
|
|
|
|
|
SV *hmac_key_sv; /* NULL if not set */ |
|
33
|
|
|
|
|
|
|
int xxh64_seed_set; |
|
34
|
|
|
|
|
|
|
uint64_t xxh64_seed; |
|
35
|
|
|
|
|
|
|
} hash_opts_t; |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
/* Phase context for `into` validation. RECORD wants an arrayref |
|
38
|
|
|
|
|
|
|
* because we push one entry per record; READ/WRITE/STREAM want a |
|
39
|
|
|
|
|
|
|
* scalar (single algo) or hash (multi-algo) ref. */ |
|
40
|
|
|
|
|
|
|
typedef enum { |
|
41
|
|
|
|
|
|
|
PHASE_ONE_SHOT = 0, |
|
42
|
|
|
|
|
|
|
PHASE_RECORD = 1 |
|
43
|
|
|
|
|
|
|
} decode_phase_t; |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
static int |
|
46
|
2800
|
|
|
|
|
|
str_eq(const char *a, STRLEN alen, const char *b) |
|
47
|
|
|
|
|
|
|
{ |
|
48
|
2800
|
100
|
|
|
|
|
return alen == strlen(b) && memcmp(a, b, alen) == 0; |
|
|
|
100
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
static const char *VALID_OPT_KEYS[] = { |
|
52
|
|
|
|
|
|
|
"algo", "algos", "into", "format", |
|
53
|
|
|
|
|
|
|
"hmac_key", "xxh64_seed", |
|
54
|
|
|
|
|
|
|
"plugin", /* always present from the dispatcher */ |
|
55
|
|
|
|
|
|
|
NULL |
|
56
|
|
|
|
|
|
|
}; |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
static int |
|
59
|
382
|
|
|
|
|
|
known_opt(const char *key, STRLEN klen) |
|
60
|
|
|
|
|
|
|
{ |
|
61
|
|
|
|
|
|
|
const char *const *p; |
|
62
|
1463
|
100
|
|
|
|
|
for (p = VALID_OPT_KEYS; *p; p++) { |
|
63
|
1462
|
100
|
|
|
|
|
if (str_eq(key, klen, *p)) return 1; |
|
64
|
|
|
|
|
|
|
} |
|
65
|
1
|
|
|
|
|
|
return 0; |
|
66
|
|
|
|
|
|
|
} |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
/* Parse one algo name SV, croak on unknown. */ |
|
69
|
|
|
|
|
|
|
static hash_algo_id_t |
|
70
|
134
|
|
|
|
|
|
parse_algo_sv(pTHX_ SV *sv) |
|
71
|
|
|
|
|
|
|
{ |
|
72
|
|
|
|
|
|
|
STRLEN alen; |
|
73
|
|
|
|
|
|
|
const char *ap; |
|
74
|
|
|
|
|
|
|
const hash_algo_info_t *info; |
|
75
|
|
|
|
|
|
|
|
|
76
|
134
|
50
|
|
|
|
|
if (!SvOK(sv)) |
|
77
|
0
|
|
|
|
|
|
croak("File::Raw::Hash: algo name must not be undef"); |
|
78
|
134
|
50
|
|
|
|
|
if (SvROK(sv)) |
|
79
|
0
|
|
|
|
|
|
croak("File::Raw::Hash: algo name must be a string, not a reference"); |
|
80
|
134
|
|
|
|
|
|
ap = SvPV(sv, alen); |
|
81
|
134
|
|
|
|
|
|
info = hash_algo_lookup(ap, alen); |
|
82
|
134
|
100
|
|
|
|
|
if (!info) |
|
83
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: unknown algo '%.*s' " |
|
84
|
|
|
|
|
|
|
"(known: sha256 sha512 sha1 md5 crc32 xxh64 blake3)", |
|
85
|
|
|
|
|
|
|
(int)alen, ap); |
|
86
|
133
|
|
|
|
|
|
return info->id; |
|
87
|
|
|
|
|
|
|
} |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
/* Decode the per-call options HV into a hash_opts_t. Croaks on any |
|
90
|
|
|
|
|
|
|
* validation failure. Caller passes a zeroed struct. */ |
|
91
|
|
|
|
|
|
|
static void |
|
92
|
117
|
|
|
|
|
|
decode_opts(pTHX_ HV *opts_hv, hash_opts_t *opts, decode_phase_t phase) |
|
93
|
|
|
|
|
|
|
{ |
|
94
|
|
|
|
|
|
|
HE *he; |
|
95
|
117
|
|
|
|
|
|
SV *algo_sv = NULL; |
|
96
|
117
|
|
|
|
|
|
SV *algos_sv = NULL; |
|
97
|
117
|
|
|
|
|
|
SV *fmt_sv = NULL; |
|
98
|
117
|
|
|
|
|
|
SV *seed_sv = NULL; |
|
99
|
|
|
|
|
|
|
|
|
100
|
117
|
50
|
|
|
|
|
if (!opts_hv) croak("File::Raw::Hash: missing options"); |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
/* First pass: validate keys + grab the ones we care about. */ |
|
103
|
117
|
|
|
|
|
|
hv_iterinit(opts_hv); |
|
104
|
498
|
100
|
|
|
|
|
while ((he = hv_iternext(opts_hv))) { |
|
105
|
|
|
|
|
|
|
I32 klen_i; |
|
106
|
382
|
|
|
|
|
|
const char *key = hv_iterkey(he, &klen_i); |
|
107
|
382
|
|
|
|
|
|
STRLEN klen = (STRLEN)klen_i; |
|
108
|
382
|
|
|
|
|
|
SV *val = hv_iterval(opts_hv, he); |
|
109
|
|
|
|
|
|
|
|
|
110
|
382
|
100
|
|
|
|
|
if (!known_opt(key, klen)) { |
|
111
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: unknown option '%.*s' (known: algo, " |
|
112
|
|
|
|
|
|
|
"algos, into, format, hmac_key, xxh64_seed)", |
|
113
|
|
|
|
|
|
|
(int)klen, key); |
|
114
|
|
|
|
|
|
|
} |
|
115
|
381
|
100
|
|
|
|
|
if (str_eq(key, klen, "algo")) algo_sv = val; |
|
116
|
280
|
100
|
|
|
|
|
else if (str_eq(key, klen, "algos")) algos_sv = val; |
|
117
|
264
|
100
|
|
|
|
|
else if (str_eq(key, klen, "into")) opts->into = val; |
|
118
|
149
|
100
|
|
|
|
|
else if (str_eq(key, klen, "format")) fmt_sv = val; |
|
119
|
140
|
100
|
|
|
|
|
else if (str_eq(key, klen, "hmac_key")) opts->hmac_key_sv = val; |
|
120
|
124
|
100
|
|
|
|
|
else if (str_eq(key, klen, "xxh64_seed")) seed_sv = val; |
|
121
|
|
|
|
|
|
|
/* "plugin" key: silently ignored. */ |
|
122
|
|
|
|
|
|
|
} |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
/* Mutual exclusion. */ |
|
125
|
116
|
100
|
|
|
|
|
if (algo_sv && SvOK(algo_sv) && algos_sv && SvOK(algos_sv)) |
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
126
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: 'algo' and 'algos' are mutually exclusive"); |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
/* Resolve algorithm list. */ |
|
129
|
115
|
100
|
|
|
|
|
if (algos_sv && SvOK(algos_sv)) { |
|
|
|
50
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
AV *av; |
|
131
|
|
|
|
|
|
|
SSize_t n, i; |
|
132
|
15
|
100
|
|
|
|
|
if (!SvROK(algos_sv) || SvTYPE(SvRV(algos_sv)) != SVt_PVAV) |
|
|
|
50
|
|
|
|
|
|
|
133
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: 'algos' must be an arrayref"); |
|
134
|
14
|
|
|
|
|
|
av = (AV *)SvRV(algos_sv); |
|
135
|
14
|
|
|
|
|
|
n = av_len(av) + 1; |
|
136
|
14
|
100
|
|
|
|
|
if (n < 1) |
|
137
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: 'algos' arrayref is empty"); |
|
138
|
13
|
50
|
|
|
|
|
if (n > MAX_ALGOS) |
|
139
|
0
|
|
|
|
|
|
croak("File::Raw::Hash: too many algos (%ld); max %d", |
|
140
|
|
|
|
|
|
|
(long)n, MAX_ALGOS); |
|
141
|
47
|
100
|
|
|
|
|
for (i = 0; i < n; i++) { |
|
142
|
34
|
|
|
|
|
|
SV **slot = av_fetch(av, i, 0); |
|
143
|
34
|
50
|
|
|
|
|
if (!slot || !*slot) |
|
|
|
50
|
|
|
|
|
|
|
144
|
0
|
|
|
|
|
|
croak("File::Raw::Hash: undef entry in 'algos' at index %ld", |
|
145
|
|
|
|
|
|
|
(long)i); |
|
146
|
34
|
|
|
|
|
|
opts->ids[i] = parse_algo_sv(aTHX_ *slot); |
|
147
|
|
|
|
|
|
|
} |
|
148
|
13
|
|
|
|
|
|
opts->n_ids = (int)n; |
|
149
|
13
|
|
|
|
|
|
opts->multi = 1; |
|
150
|
100
|
50
|
|
|
|
|
} else if (algo_sv && SvOK(algo_sv)) { |
|
|
|
50
|
|
|
|
|
|
|
151
|
100
|
|
|
|
|
|
opts->ids[0] = parse_algo_sv(aTHX_ algo_sv); |
|
152
|
99
|
|
|
|
|
|
opts->n_ids = 1; |
|
153
|
99
|
|
|
|
|
|
opts->multi = 0; |
|
154
|
|
|
|
|
|
|
} else { |
|
155
|
|
|
|
|
|
|
/* Default: single sha256. */ |
|
156
|
0
|
|
|
|
|
|
opts->ids[0] = HA_SHA256; |
|
157
|
0
|
|
|
|
|
|
opts->n_ids = 1; |
|
158
|
0
|
|
|
|
|
|
opts->multi = 0; |
|
159
|
|
|
|
|
|
|
} |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
/* Resolve format. */ |
|
162
|
120
|
100
|
|
|
|
|
if (fmt_sv && SvOK(fmt_sv)) { |
|
|
|
50
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
STRLEN flen; |
|
164
|
|
|
|
|
|
|
const char *fp; |
|
165
|
9
|
50
|
|
|
|
|
if (SvROK(fmt_sv)) |
|
166
|
0
|
|
|
|
|
|
croak("File::Raw::Hash: 'format' must be a string"); |
|
167
|
9
|
|
|
|
|
|
fp = SvPV(fmt_sv, flen); |
|
168
|
9
|
100
|
|
|
|
|
if (hash_format_parse(fp, flen, &opts->format) != 0) |
|
169
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: unknown format '%.*s' " |
|
170
|
|
|
|
|
|
|
"(known: hex, HEX, base64, base64url, raw)", |
|
171
|
|
|
|
|
|
|
(int)flen, fp); |
|
172
|
|
|
|
|
|
|
} else { |
|
173
|
103
|
|
|
|
|
|
opts->format = HF_HEX; |
|
174
|
|
|
|
|
|
|
} |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
/* Resolve xxh64_seed. */ |
|
177
|
111
|
100
|
|
|
|
|
if (seed_sv && SvOK(seed_sv)) { |
|
|
|
50
|
|
|
|
|
|
|
178
|
7
|
100
|
|
|
|
|
if (SvROK(seed_sv)) |
|
179
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: 'xxh64_seed' must be an integer"); |
|
180
|
6
|
|
|
|
|
|
opts->xxh64_seed = (uint64_t)SvUV(seed_sv); |
|
181
|
6
|
|
|
|
|
|
opts->xxh64_seed_set = 1; |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
/* HMAC key validation. The key itself can be any byte string |
|
185
|
|
|
|
|
|
|
* including binary / empty. Only the value's *type* is checked |
|
186
|
|
|
|
|
|
|
* here; per-algo HMAC-able-ness is checked when set_hmac runs. */ |
|
187
|
120
|
100
|
|
|
|
|
if (opts->hmac_key_sv && SvOK(opts->hmac_key_sv)) { |
|
|
|
50
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
int j; |
|
189
|
16
|
100
|
|
|
|
|
if (SvROK(opts->hmac_key_sv)) |
|
190
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: 'hmac_key' must be a byte string, " |
|
191
|
|
|
|
|
|
|
"not a reference"); |
|
192
|
28
|
100
|
|
|
|
|
for (j = 0; j < opts->n_ids; j++) { |
|
193
|
18
|
|
|
|
|
|
const hash_algo_info_t *info = hash_algo_by_id(opts->ids[j]); |
|
194
|
18
|
100
|
|
|
|
|
if (!info->hmac_able) |
|
195
|
5
|
|
|
|
|
|
croak("File::Raw::Hash: HMAC is not defined for algo " |
|
196
|
|
|
|
|
|
|
"'%s' (HMAC-able: sha256, sha512, sha1, md5)", |
|
197
|
|
|
|
|
|
|
info->name); |
|
198
|
|
|
|
|
|
|
} |
|
199
|
|
|
|
|
|
|
} else { |
|
200
|
94
|
|
|
|
|
|
opts->hmac_key_sv = NULL; |
|
201
|
|
|
|
|
|
|
} |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
/* Validate `into`. Required; shape depends on phase. */ |
|
204
|
104
|
100
|
|
|
|
|
if (!opts->into || !SvOK(opts->into)) |
|
|
|
100
|
|
|
|
|
|
|
205
|
2
|
|
|
|
|
|
croak("File::Raw::Hash: 'into' is required"); |
|
206
|
102
|
100
|
|
|
|
|
if (!SvROK(opts->into)) |
|
207
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: 'into' must be a reference"); |
|
208
|
|
|
|
|
|
|
|
|
209
|
101
|
100
|
|
|
|
|
if (phase == PHASE_RECORD) { |
|
210
|
10
|
100
|
|
|
|
|
if (SvTYPE(SvRV(opts->into)) != SVt_PVAV) |
|
211
|
2
|
|
|
|
|
|
croak("File::Raw::Hash: in record phase, 'into' must be an " |
|
212
|
|
|
|
|
|
|
"ARRAY ref (one entry pushed per record)"); |
|
213
|
8
|
|
|
|
|
|
return; |
|
214
|
|
|
|
|
|
|
} |
|
215
|
|
|
|
|
|
|
|
|
216
|
91
|
100
|
|
|
|
|
if (opts->multi) { |
|
217
|
9
|
100
|
|
|
|
|
if (SvTYPE(SvRV(opts->into)) != SVt_PVHV) |
|
218
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: 'into' must be a hash ref when " |
|
219
|
|
|
|
|
|
|
"'algos' is used"); |
|
220
|
|
|
|
|
|
|
} else { |
|
221
|
82
|
|
|
|
|
|
SV *referent = SvRV(opts->into); |
|
222
|
82
|
|
|
|
|
|
svtype t = SvTYPE(referent); |
|
223
|
82
|
50
|
|
|
|
|
if (t == SVt_PVAV || t == SVt_PVHV || t == SVt_PVCV |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
224
|
81
|
50
|
|
|
|
|
|| t == SVt_PVGV || t == SVt_PVFM || t == SVt_PVIO) |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
225
|
1
|
|
|
|
|
|
croak("File::Raw::Hash: 'into' must be a SCALAR ref for " |
|
226
|
|
|
|
|
|
|
"single-algo (got %s ref)", sv_reftype(referent, 0)); |
|
227
|
|
|
|
|
|
|
} |
|
228
|
|
|
|
|
|
|
} |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
/* Helper: build, run and finalise a runner over the given bytes, |
|
231
|
|
|
|
|
|
|
* applying HMAC if a key is present. Returns 0 on success, croaks on |
|
232
|
|
|
|
|
|
|
* setup error. results[*] is owned by the runner and lives until |
|
233
|
|
|
|
|
|
|
* hash_runner_free. */ |
|
234
|
|
|
|
|
|
|
static void |
|
235
|
90
|
|
|
|
|
|
run_full(pTHX_ const hash_opts_t *opts, |
|
236
|
|
|
|
|
|
|
const char *data, size_t len, |
|
237
|
|
|
|
|
|
|
hash_runner_t *runner, const hash_result_t **out_results) |
|
238
|
|
|
|
|
|
|
{ |
|
239
|
90
|
50
|
|
|
|
|
if (hash_runner_init(runner, opts->ids, opts->n_ids, opts->format, |
|
240
|
90
|
|
|
|
|
|
opts->xxh64_seed) != 0) |
|
241
|
0
|
|
|
|
|
|
croak("File::Raw::Hash: out of memory initialising runner"); |
|
242
|
|
|
|
|
|
|
|
|
243
|
90
|
100
|
|
|
|
|
if (opts->hmac_key_sv) { |
|
244
|
|
|
|
|
|
|
STRLEN klen; |
|
245
|
|
|
|
|
|
|
const unsigned char *kp = |
|
246
|
9
|
|
|
|
|
|
(const unsigned char *)SvPV(opts->hmac_key_sv, klen); |
|
247
|
9
|
50
|
|
|
|
|
if (hash_runner_set_hmac(runner, kp, (size_t)klen) != 0) { |
|
248
|
0
|
|
|
|
|
|
hash_runner_free(runner); |
|
249
|
|
|
|
|
|
|
/* Only reachable if a non-HMAC-able algo slipped past |
|
250
|
|
|
|
|
|
|
* decode_opts; defensive. */ |
|
251
|
0
|
|
|
|
|
|
croak("File::Raw::Hash: HMAC mode rejected for the requested " |
|
252
|
|
|
|
|
|
|
"algorithm set"); |
|
253
|
|
|
|
|
|
|
} |
|
254
|
|
|
|
|
|
|
} |
|
255
|
|
|
|
|
|
|
|
|
256
|
90
|
50
|
|
|
|
|
if (data && len) hash_runner_update(runner, data, len); |
|
|
|
100
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
|
|
258
|
90
|
50
|
|
|
|
|
if (hash_runner_finish(runner, out_results) != 0) { |
|
259
|
0
|
|
|
|
|
|
hash_runner_free(runner); |
|
260
|
0
|
|
|
|
|
|
croak("File::Raw::Hash: out of memory finalising runner"); |
|
261
|
|
|
|
|
|
|
} |
|
262
|
90
|
|
|
|
|
|
} |
|
263
|
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
/* Write digest results into the user's `into` target (READ/WRITE/STREAM |
|
265
|
|
|
|
|
|
|
* shape). For RECORD phase use append_record_results. */ |
|
266
|
|
|
|
|
|
|
static void |
|
267
|
89
|
|
|
|
|
|
emit_results(pTHX_ const hash_opts_t *opts, const hash_result_t *results) |
|
268
|
|
|
|
|
|
|
{ |
|
269
|
|
|
|
|
|
|
int i; |
|
270
|
89
|
100
|
|
|
|
|
if (opts->multi) { |
|
271
|
8
|
|
|
|
|
|
HV *h = (HV *)SvRV(opts->into); |
|
272
|
30
|
100
|
|
|
|
|
for (i = 0; i < opts->n_ids; i++) { |
|
273
|
22
|
|
|
|
|
|
const hash_result_t *r = &results[i]; |
|
274
|
22
|
|
|
|
|
|
SV *val = newSVpvn(r->out, r->out_len); |
|
275
|
22
|
50
|
|
|
|
|
if (opts->format != HF_RAW) SvUTF8_off(val); |
|
276
|
22
|
|
|
|
|
|
(void)hv_store(h, r->name, (I32)strlen(r->name), val, 0); |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
} else { |
|
279
|
81
|
|
|
|
|
|
SV *target = SvRV(opts->into); |
|
280
|
81
|
|
|
|
|
|
const hash_result_t *r = &results[0]; |
|
281
|
81
|
|
|
|
|
|
sv_setpvn(target, r->out, r->out_len); |
|
282
|
81
|
100
|
|
|
|
|
if (opts->format != HF_RAW) SvUTF8_off(target); |
|
283
|
|
|
|
|
|
|
} |
|
284
|
89
|
|
|
|
|
|
} |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
/* RECORD-phase emission: push one element into the user's arrayref. |
|
287
|
|
|
|
|
|
|
* Element shape mirrors the READ/WRITE convention: |
|
288
|
|
|
|
|
|
|
* single algo -> a scalar (the digest) |
|
289
|
|
|
|
|
|
|
* multi algos -> a hashref (algo => digest, ...) |
|
290
|
|
|
|
|
|
|
*/ |
|
291
|
|
|
|
|
|
|
static void |
|
292
|
8
|
|
|
|
|
|
append_record_results(pTHX_ const hash_opts_t *opts, |
|
293
|
|
|
|
|
|
|
const hash_result_t *results) |
|
294
|
|
|
|
|
|
|
{ |
|
295
|
8
|
|
|
|
|
|
AV *av = (AV *)SvRV(opts->into); |
|
296
|
|
|
|
|
|
|
int i; |
|
297
|
8
|
100
|
|
|
|
|
if (opts->multi) { |
|
298
|
2
|
|
|
|
|
|
HV *h = newHV(); |
|
299
|
8
|
100
|
|
|
|
|
for (i = 0; i < opts->n_ids; i++) { |
|
300
|
6
|
|
|
|
|
|
const hash_result_t *r = &results[i]; |
|
301
|
6
|
|
|
|
|
|
SV *val = newSVpvn(r->out, r->out_len); |
|
302
|
6
|
50
|
|
|
|
|
if (opts->format != HF_RAW) SvUTF8_off(val); |
|
303
|
6
|
|
|
|
|
|
(void)hv_store(h, r->name, (I32)strlen(r->name), val, 0); |
|
304
|
|
|
|
|
|
|
} |
|
305
|
2
|
|
|
|
|
|
av_push(av, newRV_noinc((SV *)h)); |
|
306
|
|
|
|
|
|
|
} else { |
|
307
|
6
|
|
|
|
|
|
const hash_result_t *r = &results[0]; |
|
308
|
6
|
|
|
|
|
|
SV *val = newSVpvn(r->out, r->out_len); |
|
309
|
6
|
100
|
|
|
|
|
if (opts->format != HF_RAW) SvUTF8_off(val); |
|
310
|
6
|
|
|
|
|
|
av_push(av, val); |
|
311
|
|
|
|
|
|
|
} |
|
312
|
8
|
|
|
|
|
|
} |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
/* ============================================================ |
|
315
|
|
|
|
|
|
|
* READ / WRITE callbacks (passthrough + side-channel digest). |
|
316
|
|
|
|
|
|
|
* ============================================================ */ |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
static SV * |
|
319
|
100
|
|
|
|
|
|
hash_one_shot(pTHX_ FilePluginContext *ctx) |
|
320
|
|
|
|
|
|
|
{ |
|
321
|
|
|
|
|
|
|
hash_opts_t opts; |
|
322
|
|
|
|
|
|
|
hash_runner_t runner; |
|
323
|
100
|
|
|
|
|
|
const hash_result_t *results = NULL; |
|
324
|
100
|
|
|
|
|
|
STRLEN dlen = 0; |
|
325
|
100
|
|
|
|
|
|
const char *dp = NULL; |
|
326
|
|
|
|
|
|
|
|
|
327
|
100
|
|
|
|
|
|
memset(&opts, 0, sizeof opts); |
|
328
|
100
|
|
|
|
|
|
memset(&runner, 0, sizeof runner); |
|
329
|
|
|
|
|
|
|
|
|
330
|
100
|
|
|
|
|
|
decode_opts(aTHX_ ctx->options, &opts, PHASE_ONE_SHOT); |
|
331
|
|
|
|
|
|
|
|
|
332
|
82
|
50
|
|
|
|
|
if (ctx->data && SvOK(ctx->data)) dp = SvPV(ctx->data, dlen); |
|
|
|
50
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
|
|
334
|
82
|
|
|
|
|
|
run_full(aTHX_ &opts, dp, (size_t)dlen, &runner, &results); |
|
335
|
82
|
|
|
|
|
|
emit_results(aTHX_ &opts, results); |
|
336
|
82
|
|
|
|
|
|
hash_runner_free(&runner); |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
/* Passthrough. */ |
|
339
|
82
|
50
|
|
|
|
|
if (!ctx->data) return newSVpvn("", 0); |
|
340
|
82
|
|
|
|
|
|
return SvREFCNT_inc_simple_NN(ctx->data); |
|
341
|
|
|
|
|
|
|
} |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
static SV * |
|
344
|
94
|
|
|
|
|
|
hash_read_cb(pTHX_ FilePluginContext *ctx) |
|
345
|
|
|
|
|
|
|
{ |
|
346
|
94
|
|
|
|
|
|
return hash_one_shot(aTHX_ ctx); |
|
347
|
|
|
|
|
|
|
} |
|
348
|
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
static SV * |
|
350
|
6
|
|
|
|
|
|
hash_write_cb(pTHX_ FilePluginContext *ctx) |
|
351
|
|
|
|
|
|
|
{ |
|
352
|
6
|
|
|
|
|
|
return hash_one_shot(aTHX_ ctx); |
|
353
|
|
|
|
|
|
|
} |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
/* ============================================================ |
|
356
|
|
|
|
|
|
|
* RECORD callback (one digest per record, pushed into arrayref). |
|
357
|
|
|
|
|
|
|
* ============================================================ */ |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
static SV * |
|
360
|
10
|
|
|
|
|
|
hash_record_cb(pTHX_ FilePluginContext *ctx, SV *record) |
|
361
|
|
|
|
|
|
|
{ |
|
362
|
|
|
|
|
|
|
hash_opts_t opts; |
|
363
|
|
|
|
|
|
|
hash_runner_t runner; |
|
364
|
10
|
|
|
|
|
|
const hash_result_t *results = NULL; |
|
365
|
10
|
|
|
|
|
|
STRLEN dlen = 0; |
|
366
|
10
|
|
|
|
|
|
const char *dp = NULL; |
|
367
|
|
|
|
|
|
|
|
|
368
|
10
|
|
|
|
|
|
memset(&opts, 0, sizeof opts); |
|
369
|
10
|
|
|
|
|
|
memset(&runner, 0, sizeof runner); |
|
370
|
|
|
|
|
|
|
|
|
371
|
10
|
|
|
|
|
|
decode_opts(aTHX_ ctx->options, &opts, PHASE_RECORD); |
|
372
|
|
|
|
|
|
|
|
|
373
|
8
|
50
|
|
|
|
|
if (record && SvOK(record)) dp = SvPV(record, dlen); |
|
|
|
50
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
|
|
375
|
8
|
|
|
|
|
|
run_full(aTHX_ &opts, dp, (size_t)dlen, &runner, &results); |
|
376
|
8
|
|
|
|
|
|
append_record_results(aTHX_ &opts, results); |
|
377
|
8
|
|
|
|
|
|
hash_runner_free(&runner); |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
/* Passthrough the record so downstream filters / map_lines see it |
|
380
|
|
|
|
|
|
|
* unchanged. The dispatcher mortalises on its way out. */ |
|
381
|
8
|
50
|
|
|
|
|
if (!record) return &PL_sv_undef; |
|
382
|
8
|
|
|
|
|
|
return SvREFCNT_inc_simple_NN(record); |
|
383
|
|
|
|
|
|
|
} |
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
/* ============================================================ |
|
386
|
|
|
|
|
|
|
* STREAM callback. |
|
387
|
|
|
|
|
|
|
* ============================================================ */ |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
typedef struct { |
|
390
|
|
|
|
|
|
|
hash_runner_t runner; |
|
391
|
|
|
|
|
|
|
hash_opts_t opts; |
|
392
|
|
|
|
|
|
|
SV *into_ref; /* +1 refcount */ |
|
393
|
|
|
|
|
|
|
} hash_stream_state_t; |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
static int |
|
396
|
102
|
|
|
|
|
|
hash_stream_cb(pTHX_ FilePluginContext *ctx, |
|
397
|
|
|
|
|
|
|
const char *chunk, size_t len, int eof) |
|
398
|
|
|
|
|
|
|
{ |
|
399
|
102
|
|
|
|
|
|
hash_stream_state_t *st = (hash_stream_state_t *)ctx->call_state; |
|
400
|
|
|
|
|
|
|
|
|
401
|
102
|
100
|
|
|
|
|
if (!st) { |
|
402
|
7
|
|
|
|
|
|
st = (hash_stream_state_t *)calloc(1, sizeof *st); |
|
403
|
7
|
50
|
|
|
|
|
if (!st) { |
|
404
|
0
|
|
|
|
|
|
warn("File::Raw::Hash: stream alloc failed"); |
|
405
|
0
|
|
|
|
|
|
ctx->cancel = 1; |
|
406
|
0
|
|
|
|
|
|
return 1; |
|
407
|
|
|
|
|
|
|
} |
|
408
|
7
|
|
|
|
|
|
decode_opts(aTHX_ ctx->options, &st->opts, PHASE_ONE_SHOT); |
|
409
|
7
|
50
|
|
|
|
|
if (hash_runner_init(&st->runner, st->opts.ids, st->opts.n_ids, |
|
410
|
|
|
|
|
|
|
st->opts.format, st->opts.xxh64_seed) != 0) { |
|
411
|
0
|
|
|
|
|
|
free(st); |
|
412
|
0
|
|
|
|
|
|
warn("File::Raw::Hash: stream runner init failed"); |
|
413
|
0
|
|
|
|
|
|
ctx->cancel = 1; |
|
414
|
0
|
|
|
|
|
|
return 1; |
|
415
|
|
|
|
|
|
|
} |
|
416
|
7
|
100
|
|
|
|
|
if (st->opts.hmac_key_sv) { |
|
417
|
|
|
|
|
|
|
STRLEN klen; |
|
418
|
|
|
|
|
|
|
const unsigned char *kp = |
|
419
|
1
|
|
|
|
|
|
(const unsigned char *)SvPV(st->opts.hmac_key_sv, klen); |
|
420
|
1
|
50
|
|
|
|
|
if (hash_runner_set_hmac(&st->runner, kp, (size_t)klen) != 0) { |
|
421
|
0
|
|
|
|
|
|
hash_runner_free(&st->runner); |
|
422
|
0
|
|
|
|
|
|
free(st); |
|
423
|
0
|
|
|
|
|
|
warn("File::Raw::Hash: stream HMAC setup failed"); |
|
424
|
0
|
|
|
|
|
|
ctx->cancel = 1; |
|
425
|
0
|
|
|
|
|
|
return 1; |
|
426
|
|
|
|
|
|
|
} |
|
427
|
|
|
|
|
|
|
} |
|
428
|
7
|
|
|
|
|
|
st->into_ref = SvREFCNT_inc_simple_NN(st->opts.into); |
|
429
|
7
|
|
|
|
|
|
ctx->call_state = st; |
|
430
|
|
|
|
|
|
|
} |
|
431
|
|
|
|
|
|
|
|
|
432
|
102
|
100
|
|
|
|
|
if (chunk && len) { |
|
|
|
50
|
|
|
|
|
|
|
433
|
95
|
|
|
|
|
|
hash_runner_update(&st->runner, chunk, len); |
|
434
|
|
|
|
|
|
|
} |
|
435
|
|
|
|
|
|
|
|
|
436
|
102
|
100
|
|
|
|
|
if (eof) { |
|
437
|
7
|
|
|
|
|
|
const hash_result_t *results = NULL; |
|
438
|
7
|
50
|
|
|
|
|
if (hash_runner_finish(&st->runner, &results) != 0) { |
|
439
|
0
|
|
|
|
|
|
hash_runner_free(&st->runner); |
|
440
|
0
|
|
|
|
|
|
SvREFCNT_dec(st->into_ref); |
|
441
|
0
|
|
|
|
|
|
free(st); |
|
442
|
0
|
|
|
|
|
|
ctx->call_state = NULL; |
|
443
|
0
|
|
|
|
|
|
warn("File::Raw::Hash: stream finish failed"); |
|
444
|
0
|
|
|
|
|
|
ctx->cancel = 1; |
|
445
|
0
|
|
|
|
|
|
return 1; |
|
446
|
|
|
|
|
|
|
} |
|
447
|
7
|
|
|
|
|
|
emit_results(aTHX_ &st->opts, results); |
|
448
|
7
|
|
|
|
|
|
hash_runner_free(&st->runner); |
|
449
|
7
|
|
|
|
|
|
SvREFCNT_dec(st->into_ref); |
|
450
|
7
|
|
|
|
|
|
free(st); |
|
451
|
7
|
|
|
|
|
|
ctx->call_state = NULL; |
|
452
|
|
|
|
|
|
|
} |
|
453
|
|
|
|
|
|
|
|
|
454
|
102
|
|
|
|
|
|
return 0; /* continue */ |
|
455
|
|
|
|
|
|
|
} |
|
456
|
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
/* ============================================================ */ |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
static FilePlugin hash_plugin; |
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
MODULE = File::Raw::Hash PACKAGE = File::Raw::Hash |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
PROTOTYPES: DISABLE |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
BOOT: |
|
466
|
14
|
|
|
|
|
|
memset(&hash_plugin, 0, sizeof hash_plugin); |
|
467
|
14
|
|
|
|
|
|
hash_plugin.name = "hash"; |
|
468
|
14
|
|
|
|
|
|
hash_plugin.read_fn = hash_read_cb; |
|
469
|
14
|
|
|
|
|
|
hash_plugin.write_fn = hash_write_cb; |
|
470
|
14
|
|
|
|
|
|
hash_plugin.record_fn = hash_record_cb; |
|
471
|
14
|
|
|
|
|
|
hash_plugin.stream_fn = hash_stream_cb; |
|
472
|
14
|
|
|
|
|
|
file_register_plugin(aTHX_ &hash_plugin); |
|
473
|
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
# ============================================================ |
|
475
|
|
|
|
|
|
|
# Test helper: invoke the hash plugin's record_fn through File::Raw's |
|
476
|
|
|
|
|
|
|
# dispatch_record entry point. Public name has a leading underscore to |
|
477
|
|
|
|
|
|
|
# signal "not part of the supported API" - it exists so the test suite |
|
478
|
|
|
|
|
|
|
# can exercise RECORD phase end-to-end before File::Raw exposes a |
|
479
|
|
|
|
|
|
|
# user-facing per-record iterator. |
|
480
|
|
|
|
|
|
|
# ============================================================ |
|
481
|
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
SV* |
|
483
|
|
|
|
|
|
|
_test_record_one(record_sv, ...) |
|
484
|
|
|
|
|
|
|
SV *record_sv |
|
485
|
|
|
|
|
|
|
PREINIT: |
|
486
|
|
|
|
|
|
|
HV *opts; |
|
487
|
|
|
|
|
|
|
SV *result; |
|
488
|
|
|
|
|
|
|
int i; |
|
489
|
|
|
|
|
|
|
CODE: |
|
490
|
10
|
50
|
|
|
|
|
if ((items - 1) % 2 != 0) |
|
491
|
0
|
|
|
|
|
|
croak("File::Raw::Hash::_test_record_one: odd number of " |
|
492
|
|
|
|
|
|
|
"key/value option args"); |
|
493
|
10
|
|
|
|
|
|
opts = newHV(); |
|
494
|
|
|
|
|
|
|
/* Default plugin to "hash" so the caller can omit it. */ |
|
495
|
10
|
|
|
|
|
|
(void)hv_stores(opts, "plugin", newSVpvs("hash")); |
|
496
|
32
|
100
|
|
|
|
|
for (i = 1; i < items; i += 2) { |
|
497
|
|
|
|
|
|
|
STRLEN klen; |
|
498
|
22
|
|
|
|
|
|
const char *kp = SvPV(ST(i), klen); |
|
499
|
22
|
|
|
|
|
|
SV *vp = SvREFCNT_inc(ST(i + 1)); |
|
500
|
22
|
|
|
|
|
|
(void)hv_store(opts, kp, (I32)klen, vp, 0); |
|
501
|
|
|
|
|
|
|
} |
|
502
|
10
|
|
|
|
|
|
result = file_plugin_dispatch_record(aTHX_ opts, NULL, record_sv); |
|
503
|
8
|
|
|
|
|
|
SvREFCNT_dec((SV *)opts); |
|
504
|
8
|
50
|
|
|
|
|
if (!result) { |
|
505
|
0
|
|
|
|
|
|
RETVAL = &PL_sv_undef; |
|
506
|
0
|
|
|
|
|
|
SvREFCNT_inc(RETVAL); |
|
507
|
|
|
|
|
|
|
} else { |
|
508
|
8
|
|
|
|
|
|
RETVAL = result; |
|
509
|
|
|
|
|
|
|
} |
|
510
|
|
|
|
|
|
|
OUTPUT: |
|
511
|
|
|
|
|
|
|
RETVAL |