1 /*
2 * svndiff.c -- Encoding and decoding svndiff-format deltas.
3 *
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
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 * under the License.
21 * ====================================================================
22 */
23
24
25 #include <assert.h>
26 #include <string.h>
27 #include "svn_delta.h"
28 #include "svn_io.h"
29 #include "delta.h"
30 #include "svn_pools.h"
31 #include "svn_private_config.h"
32
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"
38
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 };
42
43 #define SVNDIFF_HEADER_SIZE (sizeof(SVNDIFF_V0))
44
45 static const char *
get_svndiff_header(int version)46 get_svndiff_header(int version)
47 {
48 if (version == 2)
49 return SVNDIFF_V2;
50 else if (version == 1)
51 return SVNDIFF_V1;
52 else
53 return SVNDIFF_V0;
54 }
55
56 /* ----- Text delta to svndiff ----- */
57
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 {
62 svn_stream_t *output;
63 svn_boolean_t header_done;
64 int version;
65 int compression_level;
66 /* Pool for temporary allocations, will be cleared periodically. */
67 apr_pool_t *scratch_pool;
68 };
69
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)
76
77
78 /* Append an encoded integer to a string. */
79 static void
append_encoded_int(svn_stringbuf_t * header,svn_filesize_t val)80 append_encoded_int(svn_stringbuf_t *header, svn_filesize_t val)
81 {
82 unsigned char buf[SVN__MAX_ENCODED_UINT_LEN], *p;
83
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);
87 }
88
89 static svn_error_t *
send_simple_insertion_window(svn_txdelta_window_t * window,struct encoder_baton * eb)90 send_simple_insertion_window(svn_txdelta_window_t *window,
91 struct encoder_baton *eb)
92 {
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;
98 apr_size_t ip_len, i;
99 apr_size_t len = window->new_data->len;
100
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);
105
106 /* write stream header if necessary */
107 if (!eb->header_done)
108 {
109 eb->header_done = TRUE;
110 memcpy(headers, get_svndiff_header(eb->version), SVNDIFF_HEADER_SIZE);
111 header_current = headers + SVNDIFF_HEADER_SIZE;
112 }
113 else
114 {
115 header_current = headers;
116 }
117
118 /* Encode the action code and length. */
119 if (window->tview_len >> 6 == 0)
120 {
121 ibuf[0] = (unsigned char)(window->tview_len + (0x2 << 6));
122 ip_len = 1;
123 }
124 else
125 {
126 ibuf[0] = (0x2 << 6);
127 ip_len = svn__encode_uint(ibuf + 1, window->tview_len) - ibuf;
128 }
129
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);
138
139 /* append instructions (1 to a handful of bytes) */
140 for (i = 0; i < ip_len; ++i)
141 header_current[i] = ibuf[i];
142
143 header_len = header_current - headers + ip_len;
144
145 /* Write out the window. */
146 SVN_ERR(svn_stream_write(eb->output, (const char *)headers, &header_len));
147 if (len)
148 SVN_ERR(svn_stream_write(eb->output, window->new_data->data, &len));
149
150 return SVN_NO_ERROR;
151 }
152
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
157 fields. */
158 static svn_error_t *
encode_window(svn_stringbuf_t ** instructions_p,svn_stringbuf_t ** header_p,const svn_string_t ** newdata_p,svn_txdelta_window_t * window,int version,int compression_level,apr_pool_t * pool)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,
163 int version,
164 int compression_level,
165 apr_pool_t *pool)
166 {
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;
172
173 /* create the necessary data buffers */
174 instructions = svn_stringbuf_create_empty(pool);
175 header = svn_stringbuf_create_empty(pool);
176
177 /* Encode the instructions. */
178 for (op = window->ops; op < window->ops + window->num_ops; op++)
179 {
180 /* Encode the action code and length. */
181 ip = ibuf;
182 switch (op->action_code)
183 {
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;
187 }
188 if (op->length >> 6 == 0)
189 *ip++ |= (unsigned char)op->length;
190 else
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);
195 }
196
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);
201 if (version == 2)
202 {
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;
208 }
209 else if (version == 1)
210 {
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;
216 }
217 append_encoded_int(header, instructions->len);
218
219 /* Encode the data. */
220 if (version == 2)
221 {
222 svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
223
224 SVN_ERR(svn__compress_lz4(window->new_data->data, window->new_data->len,
225 compressed));
226 newdata = svn_stringbuf__morph_into_string(compressed);
227 }
228 else if (version == 1)
229 {
230 svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
231
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);
235 }
236 else
237 newdata = window->new_data;
238
239 append_encoded_int(header, newdata->len);
240
241 *instructions_p = instructions;
242 *header_p = header;
243 *newdata_p = newdata;
244
245 return SVN_NO_ERROR;
246 }
247
248 /* Note: When changing things here, check the related comment in
249 the svn_txdelta_to_svndiff_stream() function. */
250 static svn_error_t *
window_handler(svn_txdelta_window_t * window,void * baton)251 window_handler(svn_txdelta_window_t *window, void *baton)
252 {
253 struct encoder_baton *eb = baton;
254 apr_size_t len;
255 svn_stringbuf_t *instructions;
256 svn_stringbuf_t *header;
257 const svn_string_t *newdata;
258
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));
262
263 /* Make sure we write the header. */
264 if (!eb->header_done)
265 {
266 len = SVNDIFF_HEADER_SIZE;
267 SVN_ERR(svn_stream_write(eb->output, get_svndiff_header(eb->version),
268 &len));
269 eb->header_done = TRUE;
270 }
271
272 if (window == NULL)
273 {
274 /* We're done; clean up. */
275 SVN_ERR(svn_stream_close(eb->output));
276
277 svn_pool_destroy(eb->scratch_pool);
278
279 return SVN_NO_ERROR;
280 }
281
282 svn_pool_clear(eb->scratch_pool);
283
284 SVN_ERR(encode_window(&instructions, &header, &newdata, window,
285 eb->version, eb->compression_level,
286 eb->scratch_pool));
287
288 /* Write out the window. */
289 len = header->len;
290 SVN_ERR(svn_stream_write(eb->output, header->data, &len));
291 if (instructions->len > 0)
292 {
293 len = instructions->len;
294 SVN_ERR(svn_stream_write(eb->output, instructions->data, &len));
295 }
296 if (newdata->len > 0)
297 {
298 len = newdata->len;
299 SVN_ERR(svn_stream_write(eb->output, newdata->data, &len));
300 }
301
302 return SVN_NO_ERROR;
303 }
304
305 void
svn_txdelta_to_svndiff3(svn_txdelta_window_handler_t * handler,void ** handler_baton,svn_stream_t * output,int svndiff_version,int compression_level,apr_pool_t * pool)306 svn_txdelta_to_svndiff3(svn_txdelta_window_handler_t *handler,
307 void **handler_baton,
308 svn_stream_t *output,
309 int svndiff_version,
310 int compression_level,
311 apr_pool_t *pool)
312 {
313 struct encoder_baton *eb;
314
315 eb = apr_palloc(pool, sizeof(*eb));
316 eb->output = output;
317 eb->header_done = FALSE;
318 eb->scratch_pool = svn_pool_create(pool);
319 eb->version = svndiff_version;
320 eb->compression_level = compression_level;
321
322 *handler = window_handler;
323 *handler_baton = eb;
324 }
325
326 void
svn_txdelta_to_svndiff2(svn_txdelta_window_handler_t * handler,void ** handler_baton,svn_stream_t * output,int svndiff_version,apr_pool_t * pool)327 svn_txdelta_to_svndiff2(svn_txdelta_window_handler_t *handler,
328 void **handler_baton,
329 svn_stream_t *output,
330 int svndiff_version,
331 apr_pool_t *pool)
332 {
333 svn_txdelta_to_svndiff3(handler, handler_baton, output, svndiff_version,
334 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
335 }
336
337 void
svn_txdelta_to_svndiff(svn_stream_t * output,apr_pool_t * pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)338 svn_txdelta_to_svndiff(svn_stream_t *output,
339 apr_pool_t *pool,
340 svn_txdelta_window_handler_t *handler,
341 void **handler_baton)
342 {
343 svn_txdelta_to_svndiff3(handler, handler_baton, output, 0,
344 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
345 }
346
347
348 /* ----- svndiff to text delta ----- */
349
350 /* An svndiff parser object. */
351 struct decode_baton
352 {
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;
357
358 /* Pool to create subpools from; each developing window will be a
359 subpool. */
360 apr_pool_t *pool;
361
362 /* The current subpool which contains our current window-buffer. */
363 apr_pool_t *subpool;
364
365 /* The actual svndiff data buffer, living within subpool. */
366 svn_stringbuf_t *buffer;
367
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;
372
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;
376
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
380 be FALSE. */
381 svn_boolean_t error_on_early_close;
382
383 /* svndiff version in use by delta. */
384 unsigned char version;
385
386 /* Length of parsed delta window header. 0 if window is not parsed yet. */
387 apr_size_t window_header_len;
388
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;
394 apr_size_t inslen;
395 apr_size_t newlen;
396 };
397
398
399 /* Wrapper aroung svn__deencode_uint taking a file size as *VAL. */
400 static const unsigned char *
decode_file_offset(svn_filesize_t * val,const unsigned char * p,const unsigned char * end)401 decode_file_offset(svn_filesize_t *val,
402 const unsigned char *p,
403 const unsigned char *end)
404 {
405 apr_uint64_t temp = 0;
406 const unsigned char *result = svn__decode_uint(&temp, p, end);
407 *val = (svn_filesize_t)temp;
408
409 return result;
410 }
411
412 /* Same as above, only decode into a size variable. */
413 static const unsigned char *
decode_size(apr_size_t * val,const unsigned char * p,const unsigned char * end)414 decode_size(apr_size_t *val,
415 const unsigned char *p,
416 const unsigned char *end)
417 {
418 apr_uint64_t temp = 0;
419 const unsigned char *result = svn__decode_uint(&temp, p, end);
420 if (temp > APR_SIZE_MAX)
421 return NULL;
422
423 *val = (apr_size_t)temp;
424 return result;
425 }
426
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 *
decode_instruction(svn_txdelta_op_t * op,const unsigned char * p,const unsigned char * end)431 decode_instruction(svn_txdelta_op_t *op,
432 const unsigned char *p,
433 const unsigned char *end)
434 {
435 apr_size_t c;
436 apr_size_t action;
437
438 if (p == end)
439 return NULL;
440
441 /* We need this more than once */
442 c = *p++;
443
444 /* Decode the instruction selector. */
445 action = (c >> 6) & 0x3;
446 if (action >= 0x3)
447 return NULL;
448
449 /* This relies on enum svn_delta_action values to match and never to be
450 redefined. */
451 op->action_code = (enum svn_delta_action)(action);
452
453 /* Decode the length and offset. */
454 op->length = c & 0x3f;
455 if (op->length == 0)
456 {
457 p = decode_size(&op->length, p, end);
458 if (p == NULL)
459 return NULL;
460 }
461 if (action != svn_txdelta_new)
462 {
463 p = decode_size(&op->offset, p, end);
464 if (p == NULL)
465 return NULL;
466 }
467
468 return p;
469 }
470
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
474 instructions. */
475 static svn_error_t *
count_and_verify_instructions(int * ninst,const unsigned char * p,const unsigned char * end,apr_size_t sview_len,apr_size_t tview_len,apr_size_t new_len)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,
481 apr_size_t new_len)
482 {
483 int n = 0;
484 svn_txdelta_op_t op;
485 apr_size_t tpos = 0, npos = 0;
486
487 while (p < end)
488 {
489 p = decode_instruction(&op, p, end);
490
491 /* Detect any malformed operations from the instruction stream. */
492 if (p == NULL)
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);
504
505 switch (op.action_code)
506 {
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);
514 break;
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);
521 break;
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);
528 npos += op.length;
529 break;
530 }
531 tpos += op.length;
532 n++;
533 }
534 if (tpos != tview_len)
535 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
536 _("Delta does not fill the target window"));
537 if (npos != new_len)
538 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
539 _("Delta does not contain enough new data"));
540
541 *ninst = n;
542 return SVN_NO_ERROR;
543 }
544
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
549 to by DATA. */
550 static svn_error_t *
decode_window(svn_txdelta_window_t * window,svn_filesize_t sview_offset,apr_size_t sview_len,apr_size_t tview_len,apr_size_t inslen,apr_size_t newlen,const unsigned char * data,apr_pool_t * pool,unsigned int version)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)
555 {
556 const unsigned char *insend;
557 int ninst;
558 apr_size_t npos;
559 svn_txdelta_op_t *ops, *op;
560 svn_string_t *new_data;
561
562 window->sview_offset = sview_offset;
563 window->sview_len = sview_len;
564 window->tview_len = tview_len;
565
566 insend = data + inslen;
567
568 if (version == 2)
569 {
570 svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool);
571 svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool);
572
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));
577
578 newlen = ndout->len;
579 data = (unsigned char *)instout->data;
580 insend = (unsigned char *)instout->data + instout->len;
581
582 new_data = svn_stringbuf__morph_into_string(ndout);
583 }
584 else if (version == 1)
585 {
586 svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool);
587 svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool);
588
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));
593
594 newlen = ndout->len;
595 data = (unsigned char *)instout->data;
596 insend = (unsigned char *)instout->data + instout->len;
597
598 new_data = svn_stringbuf__morph_into_string(ndout);
599 }
600 else
601 {
602 /* Copy the data because an svn_string_t must have the invariant
603 data[len]=='\0'. */
604 new_data = svn_string_ncreate((const char*)insend, newlen, pool);
605 }
606
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));
610
611 /* Allocate a buffer for the instructions and decode them. */
612 ops = apr_palloc(pool, ninst * sizeof(*ops));
613 npos = 0;
614 window->src_ops = 0;
615 for (op = ops; op < ops + ninst; op++)
616 {
617 data = decode_instruction(op, data, insend);
618 if (op->action_code == svn_txdelta_source)
619 ++window->src_ops;
620 else if (op->action_code == svn_txdelta_new)
621 {
622 op->offset = npos;
623 npos += op->length;
624 }
625 }
626 SVN_ERR_ASSERT(data == insend);
627
628 window->ops = ops;
629 window->num_ops = ninst;
630 window->new_data = new_data;
631
632 return SVN_NO_ERROR;
633 }
634
635 static svn_error_t *
write_handler(void * baton,const char * buffer,apr_size_t * len)636 write_handler(void *baton,
637 const char *buffer,
638 apr_size_t *len)
639 {
640 struct decode_baton *db = (struct decode_baton *) baton;
641 const unsigned char *p, *end;
642 apr_size_t buflen = *len;
643
644 /* Chew up four bytes at the beginning for the header. */
645 if (db->header_bytes < SVNDIFF_HEADER_SIZE)
646 {
647 apr_size_t nheader = SVNDIFF_HEADER_SIZE - db->header_bytes;
648 if (nheader > buflen)
649 nheader = buflen;
650 if (memcmp(buffer, SVNDIFF_V0 + db->header_bytes, nheader) == 0)
651 db->version = 0;
652 else if (memcmp(buffer, SVNDIFF_V1 + db->header_bytes, nheader) == 0)
653 db->version = 1;
654 else if (memcmp(buffer, SVNDIFF_V2 + db->header_bytes, nheader) == 0)
655 db->version = 2;
656 else
657 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_HEADER, NULL,
658 _("Svndiff has invalid header"));
659 buflen -= nheader;
660 buffer += nheader;
661 db->header_bytes += nheader;
662 }
663
664 /* Concatenate the old with the new. */
665 svn_stringbuf_appendbytes(db->buffer, buffer, buflen);
666
667 /* We have a buffer of svndiff data that might be good for:
668
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.
671
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.
677 */
678
679 while (1)
680 {
681 svn_txdelta_window_t window;
682
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;
686
687 if (db->window_header_len == 0)
688 {
689 svn_filesize_t sview_offset;
690 apr_size_t sview_len, tview_len, inslen, newlen;
691 const unsigned char *hdr_start = p;
692
693 p = decode_file_offset(&sview_offset, p, end);
694 if (p == NULL)
695 break;
696
697 p = decode_size(&sview_len, p, end);
698 if (p == NULL)
699 break;
700
701 p = decode_size(&tview_len, p, end);
702 if (p == NULL)
703 break;
704
705 p = decode_size(&inslen, p, end);
706 if (p == NULL)
707 break;
708
709 p = decode_size(&newlen, p, end);
710 if (p == NULL)
711 break;
712
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"));
721
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"));
729
730 /* Check for source windows which slide backwards. */
731 if (sview_len > 0
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"));
738
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;
744 db->inslen = inslen;
745 db->newlen = newlen;
746 }
747 else
748 {
749 /* Skip already parsed window header. */
750 p += db->window_header_len;
751 }
752
753 /* Wait for more data if we don't have enough bytes for the
754 whole window. */
755 if ((apr_size_t) (end - p) < db->inslen + db->newlen)
756 return SVN_NO_ERROR;
757
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));
763
764 p += db->inslen + db->newlen;
765
766 /* Remove processed data from the buffer. */
767 svn_stringbuf_remove(db->buffer, 0, db->buffer->len - (end - p));
768
769 /* Reset window header length. */
770 db->window_header_len = 0;
771
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;
775
776 /* Clear subpool. */
777 svn_pool_clear(db->subpool);
778 }
779
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"));
787
788 return SVN_NO_ERROR;
789 }
790
791 /* Minimal svn_stream_t write handler, doing nothing */
792 static svn_error_t *
noop_write_handler(void * baton,const char * buffer,apr_size_t * len)793 noop_write_handler(void *baton,
794 const char *buffer,
795 apr_size_t *len)
796 {
797 return SVN_NO_ERROR;
798 }
799
800 static svn_error_t *
close_handler(void * baton)801 close_handler(void *baton)
802 {
803 struct decode_baton *db = (struct decode_baton *) baton;
804 svn_error_t *err;
805
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"));
812
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);
816 return err;
817 }
818
819
820 svn_stream_t *
svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler,void * handler_baton,svn_boolean_t error_on_early_close,apr_pool_t * pool)821 svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler,
822 void *handler_baton,
823 svn_boolean_t error_on_early_close,
824 apr_pool_t *pool)
825 {
826 svn_stream_t *stream;
827
828 if (handler != svn_delta_noop_window_handler)
829 {
830 apr_pool_t *subpool = svn_pool_create(pool);
831 struct decode_baton *db = apr_palloc(pool, sizeof(*db));
832
833 db->consumer_func = handler;
834 db->consumer_baton = handler_baton;
835 db->pool = subpool;
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);
844
845 svn_stream_set_write(stream, write_handler);
846 svn_stream_set_close(stream, close_handler);
847 }
848 else
849 {
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);
854 }
855 return stream;
856 }
857
858
859 /* Routines for reading one svndiff window at a time. */
860
861 /* Read one byte from STREAM into *BYTE. */
862 static svn_error_t *
read_one_byte(unsigned char * byte,svn_stream_t * stream)863 read_one_byte(unsigned char *byte, svn_stream_t *stream)
864 {
865 char c;
866 apr_size_t len = 1;
867
868 SVN_ERR(svn_stream_read_full(stream, &c, &len));
869 if (len == 0)
870 return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
871 _("Unexpected end of svndiff input"));
872 *byte = (unsigned char) c;
873 return SVN_NO_ERROR;
874 }
875
876 /* Read and decode one integer from STREAM into *SIZE.
877 Increment *BYTE_COUNTER by the number of chars we have read. */
878 static svn_error_t *
read_one_size(apr_size_t * size,apr_size_t * byte_counter,svn_stream_t * stream)879 read_one_size(apr_size_t *size,
880 apr_size_t *byte_counter,
881 svn_stream_t *stream)
882 {
883 unsigned char c;
884
885 *size = 0;
886 while (1)
887 {
888 SVN_ERR(read_one_byte(&c, stream));
889 ++*byte_counter;
890 *size = (*size << 7) | (c & 0x7f);
891 if (!(c & 0x80))
892 break;
893 }
894 return SVN_NO_ERROR;
895 }
896
897 /* Read a window header from STREAM and check it for integer overflow. */
898 static svn_error_t *
read_window_header(svn_stream_t * stream,svn_filesize_t * sview_offset,apr_size_t * sview_len,apr_size_t * tview_len,apr_size_t * inslen,apr_size_t * newlen,apr_size_t * header_len)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)
903 {
904 unsigned char c;
905
906 /* Read the source view offset by hand, since it's not an apr_size_t. */
907 *header_len = 0;
908 *sview_offset = 0;
909 while (1)
910 {
911 SVN_ERR(read_one_byte(&c, stream));
912 ++*header_len;
913 *sview_offset = (*sview_offset << 7) | (c & 0x7f);
914 if (!(c & 0x80))
915 break;
916 }
917
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));
923
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"));
931
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"));
938
939 return SVN_NO_ERROR;
940 }
941
942 svn_error_t *
svn_txdelta_read_svndiff_window(svn_txdelta_window_t ** window,svn_stream_t * stream,int svndiff_version,apr_pool_t * pool)943 svn_txdelta_read_svndiff_window(svn_txdelta_window_t **window,
944 svn_stream_t *stream,
945 int svndiff_version,
946 apr_pool_t *pool)
947 {
948 svn_filesize_t sview_offset;
949 apr_size_t sview_len, tview_len, inslen, newlen, len, header_len;
950 unsigned char *buf;
951
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);
963 }
964
965
966 svn_error_t *
svn_txdelta_skip_svndiff_window(apr_file_t * file,int svndiff_version,apr_pool_t * pool)967 svn_txdelta_skip_svndiff_window(apr_file_t *file,
968 int svndiff_version,
969 apr_pool_t *pool)
970 {
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;
974 apr_off_t offset;
975
976 SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
977 &inslen, &newlen, &header_len));
978
979 offset = inslen + newlen;
980 return svn_io_file_seek(file, APR_CUR, &offset, pool);
981 }
982
983 svn_error_t *
svn_txdelta__read_raw_window_len(apr_size_t * window_len,svn_stream_t * stream,apr_pool_t * pool)984 svn_txdelta__read_raw_window_len(apr_size_t *window_len,
985 svn_stream_t *stream,
986 apr_pool_t *pool)
987 {
988 svn_filesize_t sview_offset;
989 apr_size_t sview_len, tview_len, inslen, newlen, header_len;
990
991 SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
992 &inslen, &newlen, &header_len));
993
994 *window_len = inslen + newlen + header_len;
995 return SVN_NO_ERROR;
996 }
997
998 typedef struct svndiff_stream_baton_t
999 {
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;
1008
1009 static svn_error_t *
svndiff_stream_write_fn(void * baton,const char * data,apr_size_t * len)1010 svndiff_stream_write_fn(void *baton, const char *data, apr_size_t *len)
1011 {
1012 svndiff_stream_baton_t *b = baton;
1013
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);
1018
1019 return SVN_NO_ERROR;
1020 }
1021
1022 static svn_error_t *
svndiff_stream_read_fn(void * baton,char * buffer,apr_size_t * len)1023 svndiff_stream_read_fn(void *baton, char *buffer, apr_size_t *len)
1024 {
1025 svndiff_stream_baton_t *b = baton;
1026 apr_size_t left = *len;
1027 apr_size_t read = 0;
1028
1029 while (left)
1030 {
1031 apr_size_t chunk_size;
1032
1033 if (b->read_pos == b->window_buffer->len && !b->hit_eof)
1034 {
1035 svn_txdelta_window_t *window;
1036
1037 svn_pool_clear(b->scratch_pool);
1038 svn_stringbuf_setempty(b->window_buffer);
1039 SVN_ERR(svn_txdelta_next_window(&window, b->txstream,
1040 b->scratch_pool));
1041 SVN_ERR(b->handler(window, b->handler_baton));
1042 b->read_pos = 0;
1043
1044 if (!window)
1045 b->hit_eof = TRUE;
1046 }
1047
1048 if (left > b->window_buffer->len - b->read_pos)
1049 chunk_size = b->window_buffer->len - b->read_pos;
1050 else
1051 chunk_size = left;
1052
1053 if (!chunk_size)
1054 break;
1055
1056 memcpy(buffer, b->window_buffer->data + b->read_pos, chunk_size);
1057 b->read_pos += chunk_size;
1058 buffer += chunk_size;
1059 read += chunk_size;
1060 left -= chunk_size;
1061 }
1062
1063 *len = read;
1064 return SVN_NO_ERROR;
1065 }
1066
1067 svn_stream_t *
svn_txdelta_to_svndiff_stream(svn_txdelta_stream_t * txstream,int svndiff_version,int compression_level,apr_pool_t * pool)1068 svn_txdelta_to_svndiff_stream(svn_txdelta_stream_t *txstream,
1069 int svndiff_version,
1070 int compression_level,
1071 apr_pool_t *pool)
1072 {
1073 svndiff_stream_baton_t *baton;
1074 svn_stream_t *push_stream;
1075 svn_stream_t *pull_stream;
1076
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;
1083
1084 push_stream = svn_stream_create(baton, pool);
1085 svn_stream_set_write(push_stream, svndiff_stream_write_fn);
1086
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')
1092 is limited. */
1093 svn_txdelta_to_svndiff3(&baton->handler, &baton->handler_baton,
1094 push_stream, svndiff_version,
1095 compression_level, pool);
1096
1097 pull_stream = svn_stream_create(baton, pool);
1098 svn_stream_set_read2(pull_stream, NULL, svndiff_stream_read_fn);
1099
1100 return pull_stream;
1101 }
1102