Module: BrotliSplice

Defined in:
lib/brotli_splice/version.rb,
ext/brotli_splice/brotli_splice.c

Defined Under Namespace

Classes: Error

Constant Summary collapse

VERSION =
"0.1.1"

Class Method Summary collapse

Class Method Details

.encode(html, secret_offset, secret_length, quality: 11) ⇒ Hash

Brotli-encode html with a spliceable slot at the given position.

The slot’s last 2 bytes are reserved as a fixed context suffix (“rn”) and cannot be replaced. The replaceable portion is secret_length - 2 bytes.

Returns:

{ data:           String,   # the Brotli-encoded stream
  secret_offset:  Integer,  # byte offset of the replaceable data in the stream
  secret_length:  Integer,  # replaceable byte count (secret_length - 2)
  context_suffix: String }  # the 2 fixed context bytes ("\r\n")

Returns:

  • (Hash)


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'ext/brotli_splice/brotli_splice.c', line 69

static VALUE rb_brotli_splice_encode(int argc, VALUE *argv, VALUE self) {
    VALUE rb_html, rb_sec_off, rb_sec_len, rb_opts;
    rb_scan_args(argc, argv, "3:", &rb_html, &rb_sec_off, &rb_sec_len, &rb_opts);

    Check_Type(rb_html, T_STRING);
    const uint8_t *html = (const uint8_t *)RSTRING_PTR(rb_html);
    size_t html_len = RSTRING_LEN(rb_html);
    size_t sec_off = NUM2SIZET(rb_sec_off);
    size_t sec_total = NUM2SIZET(rb_sec_len);

    if (sec_total <= CTX_LEN)
        rb_raise(eBrotliSpliceError, "secret_length must be > %d", CTX_LEN);
    if (sec_off + sec_total > html_len)
        rb_raise(eBrotliSpliceError, "secret_offset + secret_length exceeds html size");

    int quality = 11;
    if (!NIL_P(rb_opts)) {
        VALUE q = rb_hash_aref(rb_opts, ID2SYM(rb_intern("quality")));
        if (!NIL_P(q)) quality = NUM2INT(q);
    }

    /* Split into part1 / secret_body / context / part3 */
    const uint8_t *part1 = html;
    size_t p1_len = sec_off;

    size_t sec_body = sec_total - CTX_LEN;
    const uint8_t *secret = html + sec_off;          /* secret_body bytes */

    const uint8_t *part3 = html + sec_off + sec_total;
    size_t p3_len = html_len - sec_off - sec_total;

    /* Allocate output buffer */
    size_t out_cap = html_len + 65536;
    uint8_t *out = xmalloc(out_cap);
    size_t pos = 0;

    /* ── Chunk 1: compress part1 with FLUSH ──────────────────────── */
    BrotliEncoderState *enc1 = BrotliEncoderCreateInstance(NULL, NULL, NULL);
    if (!enc1) { xfree(out); rb_raise(eBrotliSpliceError, "encoder creation failed"); }
    BrotliEncoderSetParameter(enc1, BROTLI_PARAM_QUALITY, quality);
    BrotliEncoderSetParameter(enc1, BROTLI_PARAM_LGWIN, 22);

    size_t c1 = encoder_feed(enc1, BROTLI_OPERATION_FLUSH,
                              part1, p1_len, out + pos, out_cap - pos);
    BrotliEncoderDestroyInstance(enc1);
    if (c1 == 0 && p1_len > 0) {
        xfree(out);
        rb_raise(eBrotliSpliceError, "chunk1 encoding failed");
    }
    pos += c1;

    /* ── Chunk 2: uncompressed meta-block for secret body ────────── */
    size_t sec_data_offset = pos + 3;  /* 3-byte header for ≤64KB */
    size_t c2 = make_uncompressed_block(out + pos, secret, sec_body);
    if (c2 == 0) { xfree(out); rb_raise(eBrotliSpliceError, "chunk2 failed"); }
    pos += c2;

    /* ── Chunk 3: context bytes + part3, STREAM_OFFSET ───────────── */
    BrotliEncoderState *enc2 = BrotliEncoderCreateInstance(NULL, NULL, NULL);
    if (!enc2) { xfree(out); rb_raise(eBrotliSpliceError, "encoder2 creation failed"); }
    BrotliEncoderSetParameter(enc2, BROTLI_PARAM_QUALITY, quality);
    BrotliEncoderSetParameter(enc2, BROTLI_PARAM_LGWIN, 22);
    BrotliEncoderSetParameter(enc2, BROTLI_PARAM_STREAM_OFFSET,
                               (uint32_t)(p1_len + sec_body));

    size_t c3_in_len = CTX_LEN + p3_len;
    uint8_t *c3_in = xmalloc(c3_in_len);
    memcpy(c3_in, CTX_BYTES, CTX_LEN);
    memcpy(c3_in + CTX_LEN, part3, p3_len);

    size_t c3 = encoder_feed(enc2, BROTLI_OPERATION_FINISH,
                              c3_in, c3_in_len, out + pos, out_cap - pos);
    BrotliEncoderDestroyInstance(enc2);
    xfree(c3_in);
    if (c3 == 0) { xfree(out); rb_raise(eBrotliSpliceError, "chunk3 encoding failed"); }
    pos += c3;

    /* Build result */
    VALUE data = rb_str_new((char *)out, pos);
    rb_enc_associate(data, rb_ascii8bit_encoding());
    xfree(out);

    VALUE result = rb_hash_new();
    rb_hash_aset(result, ID2SYM(rb_intern("data")), data);
    rb_hash_aset(result, ID2SYM(rb_intern("secret_offset")), SIZET2NUM(sec_data_offset));
    rb_hash_aset(result, ID2SYM(rb_intern("secret_length")), SIZET2NUM(sec_body));
    rb_hash_aset(result, ID2SYM(rb_intern("context_suffix")),
                 rb_str_new((const char *)CTX_BYTES, CTX_LEN));
    return result;
}

.replace(compressed_data, new_secret, secret_offset, secret_length) ⇒ String

Replace the secret in a compressed stream. new_secret must be exactly secret_length bytes. Returns a new string.

Returns:

  • (String)


167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'ext/brotli_splice/brotli_splice.c', line 167

static VALUE rb_brotli_splice_replace(VALUE self,
                                       VALUE rb_data, VALUE rb_secret,
                                       VALUE rb_offset, VALUE rb_length) {
    Check_Type(rb_data, T_STRING);
    Check_Type(rb_secret, T_STRING);
    size_t offset = NUM2SIZET(rb_offset);
    size_t length = NUM2SIZET(rb_length);

    if ((size_t)RSTRING_LEN(rb_secret) != length)
        rb_raise(eBrotliSpliceError,
                 "secret length %ld != expected %zu",
                 RSTRING_LEN(rb_secret), length);

    if (offset + length > (size_t)RSTRING_LEN(rb_data))
        rb_raise(eBrotliSpliceError, "offset+length exceeds data size");

    VALUE result = rb_str_dup(rb_data);
    rb_str_modify(result);
    memcpy(RSTRING_PTR(result) + offset, RSTRING_PTR(rb_secret), length);
    return result;
}