2 * svndiff.c -- Encoding and decoding svndiff-format deltas.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
27 #include "svn_delta.h"
30 #include "svn_pools.h"
31 #include "svn_private_config.h"
33 #include "private/svn_error_private.h"
34 #include "private/svn_delta_private.h"
35 #include "private/svn_subr_private.h"
36 #include "private/svn_string_private.h"
37 #include "private/svn_dep_compat.h"
39 static const char SVNDIFF_V0[] = { 'S', 'V', 'N', 0 };
40 static const char SVNDIFF_V1[] = { 'S', 'V', 'N', 1 };
41 static const char SVNDIFF_V2[] = { 'S', 'V', 'N', 2 };
43 #define SVNDIFF_HEADER_SIZE (sizeof(SVNDIFF_V0))
46 get_svndiff_header(int version)
50 else if (version == 1)
56 /* ----- Text delta to svndiff ----- */
58 /* We make one of these and get it passed back to us in calls to the
59 window handler. We only use it to record the write function and
60 baton passed to svn_txdelta_to_svndiff3(). */
61 struct encoder_baton {
63 svn_boolean_t header_done;
65 int compression_level;
66 /* Pool for temporary allocations, will be cleared periodically. */
67 apr_pool_t *scratch_pool;
70 /* This is at least as big as the largest size for a single instruction. */
71 #define MAX_INSTRUCTION_LEN (2*SVN__MAX_ENCODED_UINT_LEN+1)
72 /* This is at least as big as the largest possible instructions
73 section: in theory, the instructions could be SVN_DELTA_WINDOW_SIZE
74 1-byte copy-from-source instructions (though this is very unlikely). */
75 #define MAX_INSTRUCTION_SECTION_LEN (SVN_DELTA_WINDOW_SIZE*MAX_INSTRUCTION_LEN)
78 /* Append an encoded integer to a string. */
80 append_encoded_int(svn_stringbuf_t *header, svn_filesize_t val)
82 unsigned char buf[SVN__MAX_ENCODED_UINT_LEN], *p;
84 SVN_ERR_ASSERT_NO_RETURN(val >= 0);
85 p = svn__encode_uint(buf, (apr_uint64_t)val);
86 svn_stringbuf_appendbytes(header, (const char *)buf, p - buf);
90 send_simple_insertion_window(svn_txdelta_window_t *window,
91 struct encoder_baton *eb)
93 unsigned char headers[SVNDIFF_HEADER_SIZE + 5 * SVN__MAX_ENCODED_UINT_LEN
94 + MAX_INSTRUCTION_LEN];
95 unsigned char ibuf[MAX_INSTRUCTION_LEN];
96 unsigned char *header_current;
97 apr_size_t header_len;
99 apr_size_t len = window->new_data->len;
101 /* there is only one target copy op. It must span the whole window */
102 assert(window->ops[0].action_code == svn_txdelta_new);
103 assert(window->ops[0].length == window->tview_len);
104 assert(window->ops[0].offset == 0);
106 /* write stream header if necessary */
107 if (!eb->header_done)
109 eb->header_done = TRUE;
110 memcpy(headers, get_svndiff_header(eb->version), SVNDIFF_HEADER_SIZE);
111 header_current = headers + SVNDIFF_HEADER_SIZE;
115 header_current = headers;
118 /* Encode the action code and length. */
119 if (window->tview_len >> 6 == 0)
121 ibuf[0] = (unsigned char)(window->tview_len + (0x2 << 6));
126 ibuf[0] = (0x2 << 6);
127 ip_len = svn__encode_uint(ibuf + 1, window->tview_len) - ibuf;
130 /* encode the window header. Please note that the source window may
131 * have content despite not being used for deltification. */
132 header_current = svn__encode_uint(header_current,
133 (apr_uint64_t)window->sview_offset);
134 header_current = svn__encode_uint(header_current, window->sview_len);
135 header_current = svn__encode_uint(header_current, window->tview_len);
136 header_current[0] = (unsigned char)ip_len; /* 1 instruction */
137 header_current = svn__encode_uint(&header_current[1], len);
139 /* append instructions (1 to a handful of bytes) */
140 for (i = 0; i < ip_len; ++i)
141 header_current[i] = ibuf[i];
143 header_len = header_current - headers + ip_len;
145 /* Write out the window. */
146 SVN_ERR(svn_stream_write(eb->output, (const char *)headers, &header_len));
148 SVN_ERR(svn_stream_write(eb->output, window->new_data->data, &len));
153 /* Encodes delta window WINDOW to svndiff-format.
154 The svndiff version is VERSION. COMPRESSION_LEVEL is the
155 compression level to use.
156 Returned values will be allocated in POOL or refer to *WINDOW
159 encode_window(svn_stringbuf_t **instructions_p,
160 svn_stringbuf_t **header_p,
161 const svn_string_t **newdata_p,
162 svn_txdelta_window_t *window,
164 int compression_level,
167 svn_stringbuf_t *instructions;
168 svn_stringbuf_t *header;
169 const svn_string_t *newdata;
170 unsigned char ibuf[MAX_INSTRUCTION_LEN], *ip;
171 const svn_txdelta_op_t *op;
173 /* create the necessary data buffers */
174 instructions = svn_stringbuf_create_empty(pool);
175 header = svn_stringbuf_create_empty(pool);
177 /* Encode the instructions. */
178 for (op = window->ops; op < window->ops + window->num_ops; op++)
180 /* Encode the action code and length. */
182 switch (op->action_code)
184 case svn_txdelta_source: *ip = 0; break;
185 case svn_txdelta_target: *ip = (0x1 << 6); break;
186 case svn_txdelta_new: *ip = (0x2 << 6); break;
188 if (op->length >> 6 == 0)
189 *ip++ |= (unsigned char)op->length;
191 ip = svn__encode_uint(ip + 1, op->length);
192 if (op->action_code != svn_txdelta_new)
193 ip = svn__encode_uint(ip, op->offset);
194 svn_stringbuf_appendbytes(instructions, (const char *)ibuf, ip - ibuf);
197 /* Encode the header. */
198 append_encoded_int(header, window->sview_offset);
199 append_encoded_int(header, window->sview_len);
200 append_encoded_int(header, window->tview_len);
203 svn_stringbuf_t *compressed_instructions;
204 compressed_instructions = svn_stringbuf_create_empty(pool);
205 SVN_ERR(svn__compress_lz4(instructions->data, instructions->len,
206 compressed_instructions));
207 instructions = compressed_instructions;
209 else if (version == 1)
211 svn_stringbuf_t *compressed_instructions;
212 compressed_instructions = svn_stringbuf_create_empty(pool);
213 SVN_ERR(svn__compress_zlib(instructions->data, instructions->len,
214 compressed_instructions, compression_level));
215 instructions = compressed_instructions;
217 append_encoded_int(header, instructions->len);
219 /* Encode the data. */
222 svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
224 SVN_ERR(svn__compress_lz4(window->new_data->data, window->new_data->len,
226 newdata = svn_stringbuf__morph_into_string(compressed);
228 else if (version == 1)
230 svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
232 SVN_ERR(svn__compress_zlib(window->new_data->data, window->new_data->len,
233 compressed, compression_level));
234 newdata = svn_stringbuf__morph_into_string(compressed);
237 newdata = window->new_data;
239 append_encoded_int(header, newdata->len);
241 *instructions_p = instructions;
243 *newdata_p = newdata;
248 /* Note: When changing things here, check the related comment in
249 the svn_txdelta_to_svndiff_stream() function. */
251 window_handler(svn_txdelta_window_t *window, void *baton)
253 struct encoder_baton *eb = baton;
255 svn_stringbuf_t *instructions;
256 svn_stringbuf_t *header;
257 const svn_string_t *newdata;
259 /* use specialized code if there is no source */
260 if (window && !window->src_ops && window->num_ops == 1 && !eb->version)
261 return svn_error_trace(send_simple_insertion_window(window, eb));
263 /* Make sure we write the header. */
264 if (!eb->header_done)
266 len = SVNDIFF_HEADER_SIZE;
267 SVN_ERR(svn_stream_write(eb->output, get_svndiff_header(eb->version),
269 eb->header_done = TRUE;
274 /* We're done; clean up. */
275 SVN_ERR(svn_stream_close(eb->output));
277 svn_pool_destroy(eb->scratch_pool);
282 svn_pool_clear(eb->scratch_pool);
284 SVN_ERR(encode_window(&instructions, &header, &newdata, window,
285 eb->version, eb->compression_level,
288 /* Write out the window. */
290 SVN_ERR(svn_stream_write(eb->output, header->data, &len));
291 if (instructions->len > 0)
293 len = instructions->len;
294 SVN_ERR(svn_stream_write(eb->output, instructions->data, &len));
296 if (newdata->len > 0)
299 SVN_ERR(svn_stream_write(eb->output, newdata->data, &len));
306 svn_txdelta_to_svndiff3(svn_txdelta_window_handler_t *handler,
307 void **handler_baton,
308 svn_stream_t *output,
310 int compression_level,
313 struct encoder_baton *eb;
315 eb = apr_palloc(pool, sizeof(*eb));
317 eb->header_done = FALSE;
318 eb->scratch_pool = svn_pool_create(pool);
319 eb->version = svndiff_version;
320 eb->compression_level = compression_level;
322 *handler = window_handler;
327 svn_txdelta_to_svndiff2(svn_txdelta_window_handler_t *handler,
328 void **handler_baton,
329 svn_stream_t *output,
333 svn_txdelta_to_svndiff3(handler, handler_baton, output, svndiff_version,
334 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
338 svn_txdelta_to_svndiff(svn_stream_t *output,
340 svn_txdelta_window_handler_t *handler,
341 void **handler_baton)
343 svn_txdelta_to_svndiff3(handler, handler_baton, output, 0,
344 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
348 /* ----- svndiff to text delta ----- */
350 /* An svndiff parser object. */
353 /* Once the svndiff parser has enough data buffered to create a
354 "window", it passes this window to the caller's consumer routine. */
355 svn_txdelta_window_handler_t consumer_func;
356 void *consumer_baton;
358 /* Pool to create subpools from; each developing window will be a
362 /* The current subpool which contains our current window-buffer. */
365 /* The actual svndiff data buffer, living within subpool. */
366 svn_stringbuf_t *buffer;
368 /* The offset and size of the last source view, so that we can check
369 to make sure the next one isn't sliding backwards. */
370 svn_filesize_t last_sview_offset;
371 apr_size_t last_sview_len;
373 /* We have to discard four bytes at the beginning for the header.
374 This field keeps track of how many of those bytes we have read. */
375 apr_size_t header_bytes;
377 /* Do we want an error to occur when we close the stream that
378 indicates we didn't send the whole svndiff data? If you plan to
379 not transmit the whole svndiff data stream, you will want this to
381 svn_boolean_t error_on_early_close;
383 /* svndiff version in use by delta. */
384 unsigned char version;
386 /* Length of parsed delta window header. 0 if window is not parsed yet. */
387 apr_size_t window_header_len;
389 /* Five integer fields of parsed delta window header. Valid only if
390 WINDOW_HEADER_LEN > 0 */
391 svn_filesize_t sview_offset;
392 apr_size_t sview_len;
393 apr_size_t tview_len;
399 /* Wrapper aroung svn__deencode_uint taking a file size as *VAL. */
400 static const unsigned char *
401 decode_file_offset(svn_filesize_t *val,
402 const unsigned char *p,
403 const unsigned char *end)
405 apr_uint64_t temp = 0;
406 const unsigned char *result = svn__decode_uint(&temp, p, end);
407 *val = (svn_filesize_t)temp;
412 /* Same as above, only decode into a size variable. */
413 static const unsigned char *
414 decode_size(apr_size_t *val,
415 const unsigned char *p,
416 const unsigned char *end)
418 apr_uint64_t temp = 0;
419 const unsigned char *result = svn__decode_uint(&temp, p, end);
420 if (temp > APR_SIZE_MAX)
423 *val = (apr_size_t)temp;
427 /* Decode an instruction into OP, returning a pointer to the text
428 after the instruction. Note that if the action code is
429 svn_txdelta_new, the offset field of *OP will not be set. */
430 static const unsigned char *
431 decode_instruction(svn_txdelta_op_t *op,
432 const unsigned char *p,
433 const unsigned char *end)
441 /* We need this more than once */
444 /* Decode the instruction selector. */
445 action = (c >> 6) & 0x3;
449 /* This relies on enum svn_delta_action values to match and never to be
451 op->action_code = (enum svn_delta_action)(action);
453 /* Decode the length and offset. */
454 op->length = c & 0x3f;
457 p = decode_size(&op->length, p, end);
461 if (action != svn_txdelta_new)
463 p = decode_size(&op->offset, p, end);
471 /* Count the instructions in the range [P..END-1] and make sure they
472 are valid for the given window lengths. Return an error if the
473 instructions are invalid; otherwise set *NINST to the number of
476 count_and_verify_instructions(int *ninst,
477 const unsigned char *p,
478 const unsigned char *end,
479 apr_size_t sview_len,
480 apr_size_t tview_len,
485 apr_size_t tpos = 0, npos = 0;
489 p = decode_instruction(&op, p, end);
491 /* Detect any malformed operations from the instruction stream. */
493 return svn_error_createf
494 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
495 _("Invalid diff stream: insn %d cannot be decoded"), n);
496 else if (op.length == 0)
497 return svn_error_createf
498 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
499 _("Invalid diff stream: insn %d has length zero"), n);
500 else if (op.length > tview_len - tpos)
501 return svn_error_createf
502 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
503 _("Invalid diff stream: insn %d overflows the target view"), n);
505 switch (op.action_code)
507 case svn_txdelta_source:
508 if (op.length > sview_len - op.offset ||
509 op.offset > sview_len)
510 return svn_error_createf
511 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
512 _("Invalid diff stream: "
513 "[src] insn %d overflows the source view"), n);
515 case svn_txdelta_target:
516 if (op.offset >= tpos)
517 return svn_error_createf
518 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
519 _("Invalid diff stream: "
520 "[tgt] insn %d starts beyond the target view position"), n);
522 case svn_txdelta_new:
523 if (op.length > new_len - npos)
524 return svn_error_createf
525 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
526 _("Invalid diff stream: "
527 "[new] insn %d overflows the new data section"), n);
534 if (tpos != tview_len)
535 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
536 _("Delta does not fill the target window"));
538 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
539 _("Delta does not contain enough new data"));
545 /* Given the five integer fields of a window header and a pointer to
546 the remainder of the window contents, fill in a delta window
547 structure *WINDOW. New allocations will be performed in POOL;
548 the new_data field of *WINDOW will refer directly to memory pointed
551 decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset,
552 apr_size_t sview_len, apr_size_t tview_len, apr_size_t inslen,
553 apr_size_t newlen, const unsigned char *data, apr_pool_t *pool,
554 unsigned int version)
556 const unsigned char *insend;
559 svn_txdelta_op_t *ops, *op;
560 svn_string_t *new_data;
562 window->sview_offset = sview_offset;
563 window->sview_len = sview_len;
564 window->tview_len = tview_len;
566 insend = data + inslen;
570 svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool);
571 svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool);
573 SVN_ERR(svn__decompress_lz4(insend, newlen, ndout,
574 SVN_DELTA_WINDOW_SIZE));
575 SVN_ERR(svn__decompress_lz4(data, insend - data, instout,
576 MAX_INSTRUCTION_SECTION_LEN));
579 data = (unsigned char *)instout->data;
580 insend = (unsigned char *)instout->data + instout->len;
582 new_data = svn_stringbuf__morph_into_string(ndout);
584 else if (version == 1)
586 svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool);
587 svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool);
589 SVN_ERR(svn__decompress_zlib(insend, newlen, ndout,
590 SVN_DELTA_WINDOW_SIZE));
591 SVN_ERR(svn__decompress_zlib(data, insend - data, instout,
592 MAX_INSTRUCTION_SECTION_LEN));
595 data = (unsigned char *)instout->data;
596 insend = (unsigned char *)instout->data + instout->len;
598 new_data = svn_stringbuf__morph_into_string(ndout);
602 /* Copy the data because an svn_string_t must have the invariant
604 new_data = svn_string_ncreate((const char*)insend, newlen, pool);
607 /* Count the instructions and make sure they are all valid. */
608 SVN_ERR(count_and_verify_instructions(&ninst, data, insend,
609 sview_len, tview_len, newlen));
611 /* Allocate a buffer for the instructions and decode them. */
612 ops = apr_palloc(pool, ninst * sizeof(*ops));
615 for (op = ops; op < ops + ninst; op++)
617 data = decode_instruction(op, data, insend);
618 if (op->action_code == svn_txdelta_source)
620 else if (op->action_code == svn_txdelta_new)
626 SVN_ERR_ASSERT(data == insend);
629 window->num_ops = ninst;
630 window->new_data = new_data;
636 write_handler(void *baton,
640 struct decode_baton *db = (struct decode_baton *) baton;
641 const unsigned char *p, *end;
642 apr_size_t buflen = *len;
644 /* Chew up four bytes at the beginning for the header. */
645 if (db->header_bytes < SVNDIFF_HEADER_SIZE)
647 apr_size_t nheader = SVNDIFF_HEADER_SIZE - db->header_bytes;
648 if (nheader > buflen)
650 if (memcmp(buffer, SVNDIFF_V0 + db->header_bytes, nheader) == 0)
652 else if (memcmp(buffer, SVNDIFF_V1 + db->header_bytes, nheader) == 0)
654 else if (memcmp(buffer, SVNDIFF_V2 + db->header_bytes, nheader) == 0)
657 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_HEADER, NULL,
658 _("Svndiff has invalid header"));
661 db->header_bytes += nheader;
664 /* Concatenate the old with the new. */
665 svn_stringbuf_appendbytes(db->buffer, buffer, buflen);
667 /* We have a buffer of svndiff data that might be good for:
669 a) an integral number of windows' worth of data - this is a
670 trivial case. Make windows from our data and ship them off.
672 b) a non-integral number of windows' worth of data - we shall
673 consume the integral portion of the window data, and then
674 somewhere in the following loop the decoding of the svndiff
675 data will run out of stuff to decode, and will simply return
676 SVN_NO_ERROR, anxiously awaiting more data.
681 svn_txdelta_window_t window;
683 /* Read the header, if we have enough bytes for that. */
684 p = (const unsigned char *) db->buffer->data;
685 end = (const unsigned char *) db->buffer->data + db->buffer->len;
687 if (db->window_header_len == 0)
689 svn_filesize_t sview_offset;
690 apr_size_t sview_len, tview_len, inslen, newlen;
691 const unsigned char *hdr_start = p;
693 p = decode_file_offset(&sview_offset, p, end);
697 p = decode_size(&sview_len, p, end);
701 p = decode_size(&tview_len, p, end);
705 p = decode_size(&inslen, p, end);
709 p = decode_size(&newlen, p, end);
713 if (tview_len > SVN_DELTA_WINDOW_SIZE ||
714 sview_len > SVN_DELTA_WINDOW_SIZE ||
715 /* for svndiff1, newlen includes the original length */
716 newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN ||
717 inslen > MAX_INSTRUCTION_SECTION_LEN)
718 return svn_error_create(
719 SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
720 _("Svndiff contains a too-large window"));
722 /* Check for integer overflow. */
723 if (sview_offset < 0 || inslen + newlen < inslen
724 || sview_len + tview_len < sview_len
725 || (apr_size_t)sview_offset + sview_len < (apr_size_t)sview_offset)
726 return svn_error_create(
727 SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
728 _("Svndiff contains corrupt window header"));
730 /* Check for source windows which slide backwards. */
732 && (sview_offset < db->last_sview_offset
733 || (sview_offset + sview_len
734 < db->last_sview_offset + db->last_sview_len)))
735 return svn_error_create(
736 SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL,
737 _("Svndiff has backwards-sliding source views"));
739 /* Remember parsed window header. */
740 db->window_header_len = p - hdr_start;
741 db->sview_offset = sview_offset;
742 db->sview_len = sview_len;
743 db->tview_len = tview_len;
749 /* Skip already parsed window header. */
750 p += db->window_header_len;
753 /* Wait for more data if we don't have enough bytes for the
755 if ((apr_size_t) (end - p) < db->inslen + db->newlen)
758 /* Decode the window and send it off. */
759 SVN_ERR(decode_window(&window, db->sview_offset, db->sview_len,
760 db->tview_len, db->inslen, db->newlen, p,
761 db->subpool, db->version));
762 SVN_ERR(db->consumer_func(&window, db->consumer_baton));
764 p += db->inslen + db->newlen;
766 /* Remove processed data from the buffer. */
767 svn_stringbuf_remove(db->buffer, 0, db->buffer->len - (end - p));
769 /* Reset window header length. */
770 db->window_header_len = 0;
772 /* Remember the offset and length of the source view for next time. */
773 db->last_sview_offset = db->sview_offset;
774 db->last_sview_len = db->sview_len;
777 svn_pool_clear(db->subpool);
780 /* At this point we processed all integral windows and DB->BUFFER is empty
781 or contains partially read window header.
782 Check that unprocessed data is not larger than theoretical maximum
783 window header size. */
784 if (db->buffer->len > 5 * SVN__MAX_ENCODED_UINT_LEN)
785 return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
786 _("Svndiff contains a too-large window header"));
791 /* Minimal svn_stream_t write handler, doing nothing */
793 noop_write_handler(void *baton,
801 close_handler(void *baton)
803 struct decode_baton *db = (struct decode_baton *) baton;
806 /* Make sure that we're at a plausible end of stream, returning an
807 error if we are expected to do so. */
808 if ((db->error_on_early_close)
809 && (db->header_bytes < 4 || db->buffer->len != 0))
810 return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
811 _("Unexpected end of svndiff input"));
813 /* Tell the window consumer that we're done, and clean up. */
814 err = db->consumer_func(NULL, db->consumer_baton);
815 svn_pool_destroy(db->pool);
821 svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler,
823 svn_boolean_t error_on_early_close,
826 svn_stream_t *stream;
828 if (handler != svn_delta_noop_window_handler)
830 apr_pool_t *subpool = svn_pool_create(pool);
831 struct decode_baton *db = apr_palloc(pool, sizeof(*db));
833 db->consumer_func = handler;
834 db->consumer_baton = handler_baton;
836 db->subpool = svn_pool_create(subpool);
837 db->buffer = svn_stringbuf_create_empty(db->pool);
838 db->last_sview_offset = 0;
839 db->last_sview_len = 0;
840 db->header_bytes = 0;
841 db->error_on_early_close = error_on_early_close;
842 db->window_header_len = 0;
843 stream = svn_stream_create(db, pool);
845 svn_stream_set_write(stream, write_handler);
846 svn_stream_set_close(stream, close_handler);
850 /* And else we just ignore everything as efficiently as we can.
851 by only hooking a no-op handler */
852 stream = svn_stream_create(NULL, pool);
853 svn_stream_set_write(stream, noop_write_handler);
859 /* Routines for reading one svndiff window at a time. */
861 /* Read one byte from STREAM into *BYTE. */
863 read_one_byte(unsigned char *byte, svn_stream_t *stream)
868 SVN_ERR(svn_stream_read_full(stream, &c, &len));
870 return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
871 _("Unexpected end of svndiff input"));
872 *byte = (unsigned char) c;
876 /* Read and decode one integer from STREAM into *SIZE.
877 Increment *BYTE_COUNTER by the number of chars we have read. */
879 read_one_size(apr_size_t *size,
880 apr_size_t *byte_counter,
881 svn_stream_t *stream)
888 SVN_ERR(read_one_byte(&c, stream));
890 *size = (*size << 7) | (c & 0x7f);
897 /* Read a window header from STREAM and check it for integer overflow. */
899 read_window_header(svn_stream_t *stream, svn_filesize_t *sview_offset,
900 apr_size_t *sview_len, apr_size_t *tview_len,
901 apr_size_t *inslen, apr_size_t *newlen,
902 apr_size_t *header_len)
906 /* Read the source view offset by hand, since it's not an apr_size_t. */
911 SVN_ERR(read_one_byte(&c, stream));
913 *sview_offset = (*sview_offset << 7) | (c & 0x7f);
918 /* Read the four size fields. */
919 SVN_ERR(read_one_size(sview_len, header_len, stream));
920 SVN_ERR(read_one_size(tview_len, header_len, stream));
921 SVN_ERR(read_one_size(inslen, header_len, stream));
922 SVN_ERR(read_one_size(newlen, header_len, stream));
924 if (*tview_len > SVN_DELTA_WINDOW_SIZE ||
925 *sview_len > SVN_DELTA_WINDOW_SIZE ||
926 /* for svndiff1, newlen includes the original length */
927 *newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN ||
928 *inslen > MAX_INSTRUCTION_SECTION_LEN)
929 return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
930 _("Svndiff contains a too-large window"));
932 /* Check for integer overflow. */
933 if (*sview_offset < 0 || *inslen + *newlen < *inslen
934 || *sview_len + *tview_len < *sview_len
935 || (apr_size_t)*sview_offset + *sview_len < (apr_size_t)*sview_offset)
936 return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
937 _("Svndiff contains corrupt window header"));
943 svn_txdelta_read_svndiff_window(svn_txdelta_window_t **window,
944 svn_stream_t *stream,
948 svn_filesize_t sview_offset;
949 apr_size_t sview_len, tview_len, inslen, newlen, len, header_len;
952 SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
953 &inslen, &newlen, &header_len));
954 len = inslen + newlen;
955 buf = apr_palloc(pool, len);
956 SVN_ERR(svn_stream_read_full(stream, (char*)buf, &len));
957 if (len < inslen + newlen)
958 return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
959 _("Unexpected end of svndiff input"));
960 *window = apr_palloc(pool, sizeof(**window));
961 return decode_window(*window, sview_offset, sview_len, tview_len, inslen,
962 newlen, buf, pool, svndiff_version);
967 svn_txdelta_skip_svndiff_window(apr_file_t *file,
971 svn_stream_t *stream = svn_stream_from_aprfile2(file, TRUE, pool);
972 svn_filesize_t sview_offset;
973 apr_size_t sview_len, tview_len, inslen, newlen, header_len;
976 SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
977 &inslen, &newlen, &header_len));
979 offset = inslen + newlen;
980 return svn_io_file_seek(file, APR_CUR, &offset, pool);
984 svn_txdelta__read_raw_window_len(apr_size_t *window_len,
985 svn_stream_t *stream,
988 svn_filesize_t sview_offset;
989 apr_size_t sview_len, tview_len, inslen, newlen, header_len;
991 SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
992 &inslen, &newlen, &header_len));
994 *window_len = inslen + newlen + header_len;
998 typedef struct svndiff_stream_baton_t
1000 apr_pool_t *scratch_pool;
1001 svn_txdelta_stream_t *txstream;
1002 svn_txdelta_window_handler_t handler;
1003 void *handler_baton;
1004 svn_stringbuf_t *window_buffer;
1005 apr_size_t read_pos;
1006 svn_boolean_t hit_eof;
1007 } svndiff_stream_baton_t;
1009 static svn_error_t *
1010 svndiff_stream_write_fn(void *baton, const char *data, apr_size_t *len)
1012 svndiff_stream_baton_t *b = baton;
1014 /* The memory usage here is limited, as this buffer doesn't grow
1015 beyond the (header size + max window size in svndiff format).
1016 See the comment in svn_txdelta_to_svndiff_stream(). */
1017 svn_stringbuf_appendbytes(b->window_buffer, data, *len);
1019 return SVN_NO_ERROR;
1022 static svn_error_t *
1023 svndiff_stream_read_fn(void *baton, char *buffer, apr_size_t *len)
1025 svndiff_stream_baton_t *b = baton;
1026 apr_size_t left = *len;
1027 apr_size_t read = 0;
1031 apr_size_t chunk_size;
1033 if (b->read_pos == b->window_buffer->len && !b->hit_eof)
1035 svn_txdelta_window_t *window;
1037 svn_pool_clear(b->scratch_pool);
1038 svn_stringbuf_setempty(b->window_buffer);
1039 SVN_ERR(svn_txdelta_next_window(&window, b->txstream,
1041 SVN_ERR(b->handler(window, b->handler_baton));
1048 if (left > b->window_buffer->len - b->read_pos)
1049 chunk_size = b->window_buffer->len - b->read_pos;
1056 memcpy(buffer, b->window_buffer->data + b->read_pos, chunk_size);
1057 b->read_pos += chunk_size;
1058 buffer += chunk_size;
1064 return SVN_NO_ERROR;
1068 svn_txdelta_to_svndiff_stream(svn_txdelta_stream_t *txstream,
1069 int svndiff_version,
1070 int compression_level,
1073 svndiff_stream_baton_t *baton;
1074 svn_stream_t *push_stream;
1075 svn_stream_t *pull_stream;
1077 baton = apr_pcalloc(pool, sizeof(*baton));
1078 baton->scratch_pool = svn_pool_create(pool);
1079 baton->txstream = txstream;
1080 baton->window_buffer = svn_stringbuf_create_empty(pool);
1081 baton->hit_eof = FALSE;
1082 baton->read_pos = 0;
1084 push_stream = svn_stream_create(baton, pool);
1085 svn_stream_set_write(push_stream, svndiff_stream_write_fn);
1087 /* We rely on the implementation detail of the svn_txdelta_to_svndiff3()
1088 function, namely, on how the window_handler() function behaves.
1089 As long as it writes one svndiff window at a time to the target
1090 stream, the memory usage of this function (in other words, how
1091 much data can be accumulated in the internal 'window_buffer')
1093 svn_txdelta_to_svndiff3(&baton->handler, &baton->handler_baton,
1094 push_stream, svndiff_version,
1095 compression_level, pool);
1097 pull_stream = svn_stream_create(baton, pool);
1098 svn_stream_set_read2(pull_stream, NULL, svndiff_stream_read_fn);