Binary-safe substitution result (preserve embedded NUL)
ober
79132fd5551061fc10cf7a2483990cb51fd5b0f7
--- a/jerboa_pcre2_shim.c +++ b/jerboa_pcre2_shim.c @@ -270,6 +270,17 @@ size_t jerboa_pcre2_substitute_result_length( return result ? result->length : 0; } +int jerboa_pcre2_substitute_result_copy( + const jerboa_pcre2_substitute_result_t *result, + char *buffer, size_t buffer_length) +{ + if (!result || !result->data) return PCRE2_ERROR_NULL; + if (!buffer && result->length > 0) return PCRE2_ERROR_NULL; + if (buffer_length < result->length) return PCRE2_ERROR_NOMEMORY; + memcpy(buffer, result->data, result->length); + return 0; +} + int jerboa_pcre2_substitute_free(jerboa_pcre2_substitute_result_t *result) { if (result) { --- a/src/jerboa-pcre2/ffi.ss +++ b/src/jerboa-pcre2/ffi.ss @@ -40,6 +40,7 @@ ffi-pcre2-substitute-code ffi-pcre2-substitute-result ffi-pcre2-substitute-result-length + ffi-pcre2-substitute-result-bytes ffi-pcre2-substitute-free ;; Named groups & pattern info @@ -172,6 +173,7 @@ (def c-substitute-code #f) (def c-substitute-result #f) (def c-substitute-result-length #f) + (def c-substitute-result-copy #f) (def c-substitute-free #f) (def c-substring-number-from-name #f) (def c-capture-count #f) @@ -224,6 +226,9 @@ (c-lambda (void*) string "jerboa_pcre2_substitute_result")) (set! c-substitute-result-length (c-lambda (void*) size_t "jerboa_pcre2_substitute_result_length")) + (set! c-substitute-result-copy + (foreign-procedure __collect_safe "jerboa_pcre2_substitute_result_copy" + (void* u8* size_t) integer-32)) (set! c-substitute-free (c-lambda (void*) int "jerboa_pcre2_substitute_free")) (set! c-substring-number-from-name @@ -402,6 +407,18 @@ (need-native 'ffi-pcre2-substitute-result-length) (c-substitute-result-length (ensure-match-data 'ffi-pcre2-substitute-result-length result))) + ;; Binary-safe result extraction: copy exactly result-length bytes (which may + ;; contain embedded NUL) into a fresh bytevector instead of relying on the + ;; NUL-terminated string return, which truncates at the first NUL byte. + (def (ffi-pcre2-substitute-result-bytes result) + (let* ([len (ffi-pcre2-substitute-result-length result)] + [bv (make-bytevector len 0)] + [rc (c-substitute-result-copy result bv len)]) + (unless (zero? rc) + (error 'ffi-pcre2-substitute-result-bytes + "PCRE2 substitute result copy failed" rc)) + bv)) + (def (ffi-pcre2-substitute-free result) (need-native 'ffi-pcre2-substitute-free) (c-substitute-free (ensure-match-data 'ffi-pcre2-substitute-free result)) --- a/src/jerboa-pcre2/pcre2.ss +++ b/src/jerboa-pcre2/pcre2.ss @@ -504,7 +504,9 @@ (lambda () (let ([rc (ffi-pcre2-substitute-code result)]) (cond - [(>= rc 0) (ffi-pcre2-substitute-result result)] + [(>= rc 0) + (utf8->string + (ffi-pcre2-substitute-result-bytes result))] [(= rc PCRE2_ERROR_NOMATCH) subject] [else (error who (ffi-pcre2-get-error-message rc) rc)]))) --- a/tests/pcre2-test.ss +++ b/tests/pcre2-test.ss @@ -231,7 +231,18 @@ (pcre2-replace-all rx (make-string 1000 #\a) (make-string 100 #\x))) (check (string-length result) => 100000) (check (string-ref result 0) => #\x) - (check (string-ref result 99999) => #\x))) + (check (string-ref result 99999) => #\x)) + + (test-case "substitution preserves embedded NUL bytes (binary-safe)" + (define repl (string #\Y (integer->char 0) #\Z)) + (define result (pcre2-replace-all "X" "aXbXc" repl)) + (check (string-length result) => 9) + (check (string-ref result 0) => #\a) + (check (string-ref result 1) => #\Y) + (check (char->integer (string-ref result 2)) => 0) + (check (string-ref result 3) => #\Z) + (check (string-ref result 4) => #\b) + (check (string-ref result 8) => #\c))) ;; ----------------------------------------------------------------- (test-group "concurrency and lifecycle"