Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_delta / svndiff.c
blob03b1e02b51581fb7eba18424980647ecfec6ba11
1 /*
2 * svndiff.c -- Encoding and decoding svndiff-format deltas.
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
20 #include <assert.h>
21 #include <string.h>
22 #include "svn_delta.h"
23 #include "svn_io.h"
24 #include "delta.h"
25 #include "svn_pools.h"
26 #include "svn_private_config.h"
27 #include <zlib.h>
29 /* This macro is taken from zlib, and was originally the function
30 compressBound. It shouldn't ever change, but once every millenium,
31 it may be useful for someone to make sure. */
32 #define svnCompressBound(LEN) ((LEN) + ((LEN) >> 12) + ((LEN) >> 14) + 11)
34 /* For svndiff1, address/instruction/new data under this size will not
35 be compressed using zlib as a secondary compressor. */
36 #define MIN_COMPRESS_SIZE 512
38 /* For svndiff, this is the compression level we pass to zlib. It
39 should be between 0 and 9, with higher numbers being greater
40 compression. */
41 #define SVNDIFF1_COMPRESS_LEVEL 5
42 #define NORMAL_BITS 7
43 #define LENGTH_BITS 5
46 /* ----- Text delta to svndiff ----- */
48 /* We make one of these and get it passed back to us in calls to the
49 window handler. We only use it to record the write function and
50 baton passed to svn_txdelta_to_svndiff (). */
51 struct encoder_baton {
52 svn_stream_t *output;
53 svn_boolean_t header_done;
54 int version;
55 apr_pool_t *pool;
59 /* Encode VAL into the buffer P using the variable-length svndiff
60 integer format. Return the incremented value of P after the
61 encoded bytes have been written.
63 This encoding uses the high bit of each byte as a continuation bit
64 and the other seven bits as data bits. High-order data bits are
65 encoded first, followed by lower-order bits, so the value can be
66 reconstructed by concatenating the data bits from left to right and
67 interpreting the result as a binary number. Examples (brackets
68 denote byte boundaries, spaces are for clarity only):
70 1 encodes as [0 0000001]
71 33 encodes as [0 0100001]
72 129 encodes as [1 0000001] [0 0000001]
73 2000 encodes as [1 0001111] [0 1010000]
76 static char *
77 encode_int(char *p, svn_filesize_t val)
79 int n;
80 svn_filesize_t v;
81 unsigned char cont;
83 assert(val >= 0);
85 /* Figure out how many bytes we'll need. */
86 v = val >> 7;
87 n = 1;
88 while (v > 0)
90 v = v >> 7;
91 n++;
94 /* Encode the remaining bytes; n is always the number of bytes
95 coming after the one we're encoding. */
96 while (--n >= 0)
98 cont = ((n > 0) ? 0x1 : 0x0) << 7;
99 *p++ = (char)(((val >> (n * 7)) & 0x7f) | cont);
102 return p;
106 /* Append an encoded integer to a string. */
107 static void
108 append_encoded_int(svn_stringbuf_t *header, svn_filesize_t val)
110 char buf[128], *p;
112 p = encode_int(buf, val);
113 svn_stringbuf_appendbytes(header, buf, p - buf);
116 /* If IN is a string that is >= MIN_COMPRESS_SIZE, zlib compress it and
117 place the result in OUT, with an integer prepended specifying the
118 original size. If IN is < MIN_COMPRESS_SIZE, or if the compressed
119 version of IN was no smaller than the original IN, OUT will be a copy
120 of IN with the size prepended as an integer. */
121 static svn_error_t *
122 zlib_encode(const char *data, apr_size_t len, svn_stringbuf_t *out)
124 unsigned long endlen;
125 unsigned int intlen;
127 append_encoded_int(out, len);
128 intlen = out->len;
130 if (len < MIN_COMPRESS_SIZE)
132 svn_stringbuf_appendbytes(out, data, len);
134 else
136 svn_stringbuf_ensure(out, svnCompressBound(len) + intlen);
137 endlen = out->blocksize;
139 if (compress2((unsigned char *)out->data + intlen, &endlen,
140 (const unsigned char *)data, len,
141 SVNDIFF1_COMPRESS_LEVEL) != Z_OK)
142 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA,
143 NULL,
144 _("Compression of svndiff data failed"));
146 /* Compression didn't help :(, just append the original text */
147 if (endlen >= len)
149 svn_stringbuf_appendbytes(out, data, len);
150 return SVN_NO_ERROR;
152 out->len = endlen + intlen;
154 return SVN_NO_ERROR;
157 static svn_error_t *
158 window_handler(svn_txdelta_window_t *window, void *baton)
160 struct encoder_baton *eb = baton;
161 apr_pool_t *pool = svn_pool_create(eb->pool);
162 svn_stringbuf_t *instructions = svn_stringbuf_create("", pool);
163 svn_stringbuf_t *i1 = svn_stringbuf_create("", pool);
164 svn_stringbuf_t *header = svn_stringbuf_create("", pool);
165 const svn_string_t *newdata;
166 char ibuf[128], *ip;
167 const svn_txdelta_op_t *op;
168 apr_size_t len;
170 /* Make sure we write the header. */
171 if (eb->header_done == FALSE)
173 char svnver[4] = "SVN\0";
174 len = 4;
175 svnver[3] = eb->version;
176 SVN_ERR(svn_stream_write(eb->output, svnver, &len));
177 eb->header_done = TRUE;
180 if (window == NULL)
182 svn_stream_t *output = eb->output;
184 /* We're done; clean up.
186 We clean our pool first. Given that the output stream was passed
187 TO us, we'll assume it has a longer lifetime, and that it will not
188 be affected by our pool destruction.
190 The contrary point of view (close the stream first): that could
191 tell our user that everything related to the output stream is done,
192 and a cleanup of the user pool should occur. However, that user
193 pool could include the subpool we created for our work (eb->pool),
194 which would then make our call to svn_pool_destroy() puke.
196 svn_pool_destroy(eb->pool);
198 return svn_stream_close(output);
201 /* Encode the instructions. */
202 for (op = window->ops; op < window->ops + window->num_ops; op++)
204 /* Encode the action code and length. */
205 ip = ibuf;
206 switch (op->action_code)
208 case svn_txdelta_source: *ip = (char)0; break;
209 case svn_txdelta_target: *ip = (char)(0x1 << 6); break;
210 case svn_txdelta_new: *ip = (char)(0x2 << 6); break;
212 if (op->length >> 6 == 0)
213 *ip++ |= op->length;
214 else
215 ip = encode_int(ip + 1, op->length);
216 if (op->action_code != svn_txdelta_new)
217 ip = encode_int(ip, op->offset);
218 svn_stringbuf_appendbytes(instructions, ibuf, ip - ibuf);
221 /* Encode the header. */
222 append_encoded_int(header, window->sview_offset);
223 append_encoded_int(header, window->sview_len);
224 append_encoded_int(header, window->tview_len);
225 if (eb->version == 1)
227 SVN_ERR(zlib_encode(instructions->data, instructions->len, i1));
228 instructions = i1;
230 append_encoded_int(header, instructions->len);
231 if (eb->version == 1)
233 svn_stringbuf_t *temp = svn_stringbuf_create("", pool);
234 svn_string_t *tempstr = svn_string_create("", pool);
235 SVN_ERR(zlib_encode(window->new_data->data, window->new_data->len,
236 temp));
237 tempstr->data = temp->data;
238 tempstr->len = temp->len;
239 newdata = tempstr;
241 else
242 newdata = window->new_data;
244 append_encoded_int(header, newdata->len);
246 /* Write out the window. */
247 len = header->len;
248 SVN_ERR(svn_stream_write(eb->output, header->data, &len));
249 if (instructions->len > 0)
251 len = instructions->len;
252 SVN_ERR(svn_stream_write(eb->output, instructions->data, &len));
254 if (newdata->len > 0)
256 len = newdata->len;
257 SVN_ERR(svn_stream_write(eb->output, newdata->data, &len));
260 svn_pool_destroy(pool);
261 return SVN_NO_ERROR;
264 void
265 svn_txdelta_to_svndiff2(svn_txdelta_window_handler_t *handler,
266 void **handler_baton,
267 svn_stream_t *output,
268 int svndiff_version,
269 apr_pool_t *pool)
271 apr_pool_t *subpool = svn_pool_create(pool);
272 struct encoder_baton *eb;
274 eb = apr_palloc(subpool, sizeof(*eb));
275 eb->output = output;
276 eb->header_done = FALSE;
277 eb->pool = subpool;
278 eb->version = svndiff_version;
280 *handler = window_handler;
281 *handler_baton = eb;
284 void
285 svn_txdelta_to_svndiff(svn_stream_t *output,
286 apr_pool_t *pool,
287 svn_txdelta_window_handler_t *handler,
288 void **handler_baton)
290 svn_txdelta_to_svndiff2(handler, handler_baton, output, 0, pool);
294 /* ----- svndiff to text delta ----- */
296 /* An svndiff parser object. */
297 struct decode_baton
299 /* Once the svndiff parser has enough data buffered to create a
300 "window", it passes this window to the caller's consumer routine. */
301 svn_txdelta_window_handler_t consumer_func;
302 void *consumer_baton;
304 /* Pool to create subpools from; each developing window will be a
305 subpool. */
306 apr_pool_t *pool;
308 /* The current subpool which contains our current window-buffer. */
309 apr_pool_t *subpool;
311 /* The actual svndiff data buffer, living within subpool. */
312 svn_stringbuf_t *buffer;
314 /* The offset and size of the last source view, so that we can check
315 to make sure the next one isn't sliding backwards. */
316 svn_filesize_t last_sview_offset;
317 apr_size_t last_sview_len;
319 /* We have to discard four bytes at the beginning for the header.
320 This field keeps track of how many of those bytes we have read. */
321 int header_bytes;
323 /* Do we want an error to occur when we close the stream that
324 indicates we didn't send the whole svndiff data? If you plan to
325 not transmit the whole svndiff data stream, you will want this to
326 be FALSE. */
327 svn_boolean_t error_on_early_close;
329 /* svndiff version in use by delta. */
330 unsigned char version;
334 /* Decode an svndiff-encoded integer into VAL and return a pointer to
335 the byte after the integer. The bytes to be decoded live in the
336 range [P..END-1]. See the comment for encode_int earlier in this
337 file for more detail on the encoding format. */
339 static const unsigned char *
340 decode_file_offset(svn_filesize_t *val,
341 const unsigned char *p,
342 const unsigned char *end)
344 /* Decode bytes until we're done. */
345 *val = 0;
346 while (p < end)
348 *val = (*val << 7) | (*p & 0x7f);
349 if (((*p++ >> 7) & 0x1) == 0)
350 return p;
352 return NULL;
356 /* Same as above, only decide into a size variable. */
358 static const unsigned char *
359 decode_size(apr_size_t *val,
360 const unsigned char *p,
361 const unsigned char *end)
363 /* Decode bytes until we're done. */
364 *val = 0;
365 while (p < end)
367 *val = (*val << 7) | (*p & 0x7f);
368 if (((*p++ >> 7) & 0x1) == 0)
369 return p;
371 return NULL;
374 /* Decode the possibly-zlib compressed string that is in IN, into OUT.
375 We expect an integer is prepended to IN that specifies the original
376 size, and that if encoded size == original size, that the remaining
377 data is not compressed. */
379 static svn_error_t *
380 zlib_decode(svn_stringbuf_t *in, svn_stringbuf_t *out)
382 apr_size_t len;
383 char *oldplace = in->data;
385 /* First thing in the string is the original length. */
386 in->data = (char *)decode_size(&len, (unsigned char *)in->data,
387 (unsigned char *)in->data+in->len);
388 /* We need to subtract the size of the encoded original length off the
389 * still remaining input length. */
390 in->len -= (in->data - oldplace);
391 if (in->len == len)
393 svn_stringbuf_appendstr(out, in);
394 return SVN_NO_ERROR;
396 else
398 unsigned long zliblen;
400 svn_stringbuf_ensure(out, len);
402 zliblen = len;
403 if (uncompress ((unsigned char *)out->data, &zliblen,
404 (const unsigned char *)in->data, in->len) != Z_OK)
405 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA,
406 NULL,
407 _("Decompression of svndiff data failed"));
409 /* Zlib should not produce something that has a different size than the
410 original length we stored. */
411 if (zliblen != len)
412 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA,
413 NULL,
414 _("Size of uncompressed data "
415 "does not match stored original length"));
416 out->len = zliblen;
418 return SVN_NO_ERROR;
421 /* Decode an instruction into OP, returning a pointer to the text
422 after the instruction. Note that if the action code is
423 svn_txdelta_new, the offset field of *OP will not be set. */
425 static const unsigned char *
426 decode_instruction(svn_txdelta_op_t *op,
427 const unsigned char *p,
428 const unsigned char *end)
430 if (p == end)
431 return NULL;
433 /* Decode the instruction selector. */
434 switch ((*p >> 6) & 0x3)
436 case 0x0: op->action_code = svn_txdelta_source; break;
437 case 0x1: op->action_code = svn_txdelta_target; break;
438 case 0x2: op->action_code = svn_txdelta_new; break;
439 case 0x3: return NULL;
442 /* Decode the length and offset. */
443 op->length = *p++ & 0x3f;
444 if (op->length == 0)
446 p = decode_size(&op->length, p, end);
447 if (p == NULL)
448 return NULL;
450 if (op->action_code != svn_txdelta_new)
452 p = decode_size(&op->offset, p, end);
453 if (p == NULL)
454 return NULL;
457 return p;
460 /* Count the instructions in the range [P..END-1] and make sure they
461 are valid for the given window lengths. Return an error if the
462 instructions are invalid; otherwise set *NINST to the number of
463 instructions. */
464 static svn_error_t *
465 count_and_verify_instructions(int *ninst,
466 const unsigned char *p,
467 const unsigned char *end,
468 apr_size_t sview_len,
469 apr_size_t tview_len,
470 apr_size_t new_len)
472 int n = 0;
473 svn_txdelta_op_t op;
474 apr_size_t tpos = 0, npos = 0;
476 while (p < end)
478 p = decode_instruction(&op, p, end);
480 /* Detect any malformed operations from the instruction stream. */
481 if (p == NULL)
482 return svn_error_createf
483 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
484 _("Invalid diff stream: insn %d cannot be decoded"), n);
485 else if (op.length <= 0)
486 return svn_error_createf
487 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
488 _("Invalid diff stream: insn %d has non-positive length"), n);
489 else if (op.length > tview_len - tpos)
490 return svn_error_createf
491 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
492 _("Invalid diff stream: insn %d overflows the target view"), n);
494 switch (op.action_code)
496 case svn_txdelta_source:
497 if (op.length > sview_len - op.offset)
498 return svn_error_createf
499 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
500 _("Invalid diff stream: "
501 "[src] insn %d overflows the source view"), n);
502 break;
503 case svn_txdelta_target:
504 if (op.offset >= tpos)
505 return svn_error_createf
506 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
507 _("Invalid diff stream: "
508 "[tgt] insn %d starts beyond the target view position"), n);
509 break;
510 case svn_txdelta_new:
511 if (op.length > new_len - npos)
512 return svn_error_createf
513 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
514 _("Invalid diff stream: "
515 "[new] insn %d overflows the new data section"), n);
516 npos += op.length;
517 break;
519 tpos += op.length;
520 n++;
522 if (tpos != tview_len)
523 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
524 _("Delta does not fill the target window"));
525 if (npos != new_len)
526 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
527 _("Delta does not contain enough new data"));
529 *ninst = n;
530 return SVN_NO_ERROR;
533 /* Given the five integer fields of a window header and a pointer to
534 the remainder of the window contents, fill in a delta window
535 structure *WINDOW. New allocations will be performed in POOL;
536 the new_data field of *WINDOW will refer directly to memory pointed
537 to by DATA. */
538 static svn_error_t *
539 decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset,
540 apr_size_t sview_len, apr_size_t tview_len, apr_size_t inslen,
541 apr_size_t newlen, const unsigned char *data, apr_pool_t *pool,
542 unsigned int version)
544 const unsigned char *insend;
545 int ninst;
546 apr_size_t npos;
547 svn_txdelta_op_t *ops, *op;
548 svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data));
550 window->sview_offset = sview_offset;
551 window->sview_len = sview_len;
552 window->tview_len = tview_len;
554 insend = data + inslen;
556 if (version == 1)
558 svn_stringbuf_t *instin, *ndin;
559 svn_stringbuf_t *instout, *ndout;
561 instin = svn_stringbuf_ncreate((const char *)data, insend - data, pool);
562 instout = svn_stringbuf_create("", pool);
563 SVN_ERR(zlib_decode(instin, instout));
565 ndin = svn_stringbuf_ncreate((const char *)insend, newlen, pool);
566 ndout = svn_stringbuf_create("", pool);
567 SVN_ERR(zlib_decode(ndin, ndout));
569 newlen = ndout->len;
570 data = (unsigned char *)instout->data;
571 insend = (unsigned char *)instout->data + instout->len;
573 new_data->data = (const char *) ndout->data;
574 new_data->len = newlen;
576 else
578 new_data->data = (const char *) insend;
579 new_data->len = newlen;
582 /* Count the instructions and make sure they are all valid. */
583 SVN_ERR(count_and_verify_instructions(&ninst, data, insend,
584 sview_len, tview_len, newlen));
586 /* Allocate a buffer for the instructions and decode them. */
587 ops = apr_palloc(pool, ninst * sizeof(*ops));
588 npos = 0;
589 window->src_ops = 0;
590 for (op = ops; op < ops + ninst; op++)
592 data = decode_instruction(op, data, insend);
593 if (op->action_code == svn_txdelta_source)
594 ++window->src_ops;
595 else if (op->action_code == svn_txdelta_new)
597 op->offset = npos;
598 npos += op->length;
601 assert(data == insend);
603 window->ops = ops;
604 window->num_ops = ninst;
605 window->new_data = new_data;
607 return SVN_NO_ERROR;
610 static svn_error_t *
611 write_handler(void *baton,
612 const char *buffer,
613 apr_size_t *len)
615 struct decode_baton *db = (struct decode_baton *) baton;
616 const unsigned char *p, *end;
617 svn_filesize_t sview_offset;
618 apr_size_t sview_len, tview_len, inslen, newlen, remaining;
619 apr_size_t buflen = *len;
621 /* Chew up four bytes at the beginning for the header. */
622 if (db->header_bytes < 4)
624 apr_size_t nheader = 4 - db->header_bytes;
625 if (nheader > buflen)
626 nheader = buflen;
627 if (memcmp(buffer, "SVN\0" + db->header_bytes, nheader) == 0)
628 db->version = 0;
629 else if (memcmp(buffer, "SVN\1" + db->header_bytes, nheader) == 0)
630 db->version = 1;
631 else
632 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_HEADER, NULL,
633 _("Svndiff has invalid header"));
634 buflen -= nheader;
635 buffer += nheader;
636 db->header_bytes += nheader;
639 /* Concatenate the old with the new. */
640 svn_stringbuf_appendbytes(db->buffer, buffer, buflen);
642 /* We have a buffer of svndiff data that might be good for:
644 a) an integral number of windows' worth of data - this is a
645 trivial case. Make windows from our data and ship them off.
647 b) a non-integral number of windows' worth of data - we shall
648 consume the integral portion of the window data, and then
649 somewhere in the following loop the decoding of the svndiff
650 data will run out of stuff to decode, and will simply return
651 SVN_NO_ERROR, anxiously awaiting more data.
654 while (1)
656 apr_pool_t *newpool;
657 svn_txdelta_window_t window;
659 /* Read the header, if we have enough bytes for that. */
660 p = (const unsigned char *) db->buffer->data;
661 end = (const unsigned char *) db->buffer->data + db->buffer->len;
663 p = decode_file_offset(&sview_offset, p, end);
664 if (p == NULL)
665 return SVN_NO_ERROR;
667 p = decode_size(&sview_len, p, end);
668 if (p == NULL)
669 return SVN_NO_ERROR;
671 p = decode_size(&tview_len, p, end);
672 if (p == NULL)
673 return SVN_NO_ERROR;
675 p = decode_size(&inslen, p, end);
676 if (p == NULL)
677 return SVN_NO_ERROR;
679 p = decode_size(&newlen, p, end);
680 if (p == NULL)
681 return SVN_NO_ERROR;
683 /* Check for integer overflow. */
684 if (sview_offset < 0 || inslen + newlen < inslen
685 || sview_len + tview_len < sview_len
686 || sview_offset + sview_len < sview_offset)
687 return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
688 _("Svndiff contains corrupt window header"));
690 /* Check for source windows which slide backwards. */
691 if (sview_len > 0
692 && (sview_offset < db->last_sview_offset
693 || (sview_offset + sview_len
694 < db->last_sview_offset + db->last_sview_len)))
695 return svn_error_create
696 (SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL,
697 _("Svndiff has backwards-sliding source views"));
699 /* Wait for more data if we don't have enough bytes for the
700 whole window. */
701 if ((apr_size_t) (end - p) < inslen + newlen)
702 return SVN_NO_ERROR;
704 /* Decode the window and send it off. */
705 SVN_ERR(decode_window(&window, sview_offset, sview_len, tview_len,
706 inslen, newlen, p, db->subpool,
707 db->version));
708 SVN_ERR(db->consumer_func(&window, db->consumer_baton));
710 /* Make a new subpool and buffer, saving aside the remaining
711 data in the old buffer. */
712 newpool = svn_pool_create(db->pool);
713 p += inslen + newlen;
714 remaining = db->buffer->data + db->buffer->len - (const char *) p;
715 db->buffer =
716 svn_stringbuf_ncreate((const char *) p, remaining, newpool);
718 /* Remember the offset and length of the source view for next time. */
719 db->last_sview_offset = sview_offset;
720 db->last_sview_len = sview_len;
722 /* We've copied stuff out of the old pool. Toss that pool and use
723 our new pool.
724 ### might be nice to avoid the copy and just use svn_pool_clear
725 ### to get rid of whatever the "other stuff" is. future project...
727 svn_pool_destroy(db->subpool);
728 db->subpool = newpool;
731 /* NOTREACHED */
735 static svn_error_t *
736 close_handler(void *baton)
738 struct decode_baton *db = (struct decode_baton *) baton;
739 svn_error_t *err;
741 /* Make sure that we're at a plausible end of stream, returning an
742 error if we are expected to do so. */
743 if ((db->error_on_early_close)
744 && (db->header_bytes < 4 || db->buffer->len != 0))
745 return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
746 _("Unexpected end of svndiff input"));
748 /* Tell the window consumer that we're done, and clean up. */
749 err = db->consumer_func(NULL, db->consumer_baton);
750 svn_pool_destroy(db->pool);
751 return err;
755 svn_stream_t *
756 svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler,
757 void *handler_baton,
758 svn_boolean_t error_on_early_close,
759 apr_pool_t *pool)
761 apr_pool_t *subpool = svn_pool_create(pool);
762 struct decode_baton *db = apr_palloc(pool, sizeof(*db));
763 svn_stream_t *stream;
765 db->consumer_func = handler;
766 db->consumer_baton = handler_baton;
767 db->pool = subpool;
768 db->subpool = svn_pool_create(subpool);
769 db->buffer = svn_stringbuf_create("", db->subpool);
770 db->last_sview_offset = 0;
771 db->last_sview_len = 0;
772 db->header_bytes = 0;
773 db->error_on_early_close = error_on_early_close;
774 stream = svn_stream_create(db, pool);
775 svn_stream_set_write(stream, write_handler);
776 svn_stream_set_close(stream, close_handler);
777 return stream;
781 /* Routines for reading one svndiff window at a time. */
783 /* Read one byte from STREAM into *BYTE. */
784 static svn_error_t *
785 read_one_byte(unsigned char *byte, svn_stream_t *stream)
787 char c;
788 apr_size_t len = 1;
790 SVN_ERR(svn_stream_read(stream, &c, &len));
791 if (len == 0)
792 return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
793 _("Unexpected end of svndiff input"));
794 *byte = (unsigned char) c;
795 return SVN_NO_ERROR;
798 /* Read and decode one integer from STREAM into *SIZE. */
799 static svn_error_t *
800 read_one_size(apr_size_t *size, svn_stream_t *stream)
802 unsigned char c;
804 *size = 0;
805 while (1)
807 SVN_ERR(read_one_byte(&c, stream));
808 *size = (*size << 7) | (c & 0x7f);
809 if (!(c & 0x80))
810 break;
812 return SVN_NO_ERROR;
815 /* Read a window header from STREAM and check it for integer overflow. */
816 static svn_error_t *
817 read_window_header(svn_stream_t *stream, svn_filesize_t *sview_offset,
818 apr_size_t *sview_len, apr_size_t *tview_len,
819 apr_size_t *inslen, apr_size_t *newlen)
821 unsigned char c;
823 /* Read the source view offset by hand, since it's not an apr_size_t. */
824 *sview_offset = 0;
825 while (1)
827 SVN_ERR(read_one_byte(&c, stream));
828 *sview_offset = (*sview_offset << 7) | (c & 0x7f);
829 if (!(c & 0x80))
830 break;
833 /* Read the four size fields. */
834 SVN_ERR(read_one_size(sview_len, stream));
835 SVN_ERR(read_one_size(tview_len, stream));
836 SVN_ERR(read_one_size(inslen, stream));
837 SVN_ERR(read_one_size(newlen, stream));
839 /* Check for integer overflow. */
840 if (*sview_offset < 0 || *inslen + *newlen < *inslen
841 || *sview_len + *tview_len < *sview_len
842 || *sview_offset + *sview_len < *sview_offset)
843 return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
844 _("Svndiff contains corrupt window header"));
846 return SVN_NO_ERROR;
849 svn_error_t *
850 svn_txdelta_read_svndiff_window(svn_txdelta_window_t **window,
851 svn_stream_t *stream,
852 int svndiff_version,
853 apr_pool_t *pool)
855 svn_filesize_t sview_offset;
856 apr_size_t sview_len, tview_len, inslen, newlen, len;
857 unsigned char *buf;
859 SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
860 &inslen, &newlen));
861 len = inslen + newlen;
862 buf = apr_palloc(pool, len);
863 SVN_ERR(svn_stream_read(stream, (char*)buf, &len));
864 if (len < inslen + newlen)
865 return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
866 _("Unexpected end of svndiff input"));
867 *window = apr_palloc(pool, sizeof(**window));
868 SVN_ERR(decode_window(*window, sview_offset, sview_len, tview_len, inslen,
869 newlen, buf, pool, svndiff_version));
870 return SVN_NO_ERROR;
874 svn_error_t *
875 svn_txdelta_skip_svndiff_window(apr_file_t *file,
876 int svndiff_version,
877 apr_pool_t *pool)
879 svn_stream_t *stream = svn_stream_from_aprfile(file, pool);
880 svn_filesize_t sview_offset;
881 apr_size_t sview_len, tview_len, inslen, newlen;
882 apr_off_t offset;
884 SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
885 &inslen, &newlen));
887 offset = inslen + newlen;
888 return svn_io_file_seek(file, APR_CUR, &offset, pool);