svndiff.c revision 299742
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/* ----- Text delta to svndiff ----- */ 40 41/* We make one of these and get it passed back to us in calls to the 42 window handler. We only use it to record the write function and 43 baton passed to svn_txdelta_to_svndiff3(). */ 44struct encoder_baton { 45 svn_stream_t *output; 46 svn_boolean_t header_done; 47 int version; 48 int compression_level; 49 apr_pool_t *pool; 50}; 51 52/* This is at least as big as the largest size for a single instruction. */ 53#define MAX_INSTRUCTION_LEN (2*SVN__MAX_ENCODED_UINT_LEN+1) 54/* This is at least as big as the largest possible instructions 55 section: in theory, the instructions could be SVN_DELTA_WINDOW_SIZE 56 1-byte copy-from-source instructions (though this is very unlikely). */ 57#define MAX_INSTRUCTION_SECTION_LEN (SVN_DELTA_WINDOW_SIZE*MAX_INSTRUCTION_LEN) 58 59 60/* Append an encoded integer to a string. */ 61static void 62append_encoded_int(svn_stringbuf_t *header, svn_filesize_t val) 63{ 64 unsigned char buf[SVN__MAX_ENCODED_UINT_LEN], *p; 65 66 SVN_ERR_ASSERT_NO_RETURN(val >= 0); 67 p = svn__encode_uint(buf, (apr_uint64_t)val); 68 svn_stringbuf_appendbytes(header, (const char *)buf, p - buf); 69} 70 71static svn_error_t * 72send_simple_insertion_window(svn_txdelta_window_t *window, 73 struct encoder_baton *eb) 74{ 75 unsigned char headers[4 + 5 * SVN__MAX_ENCODED_UINT_LEN 76 + MAX_INSTRUCTION_LEN]; 77 unsigned char ibuf[MAX_INSTRUCTION_LEN]; 78 unsigned char *header_current; 79 apr_size_t header_len; 80 apr_size_t ip_len, i; 81 apr_size_t len = window->new_data->len; 82 83 /* there is only one target copy op. It must span the whole window */ 84 assert(window->ops[0].action_code == svn_txdelta_new); 85 assert(window->ops[0].length == window->tview_len); 86 assert(window->ops[0].offset == 0); 87 88 /* write stream header if necessary */ 89 if (!eb->header_done) 90 { 91 eb->header_done = TRUE; 92 headers[0] = 'S'; 93 headers[1] = 'V'; 94 headers[2] = 'N'; 95 headers[3] = (unsigned char)eb->version; 96 header_current = headers + 4; 97 } 98 else 99 { 100 header_current = headers; 101 } 102 103 /* Encode the action code and length. */ 104 if (window->tview_len >> 6 == 0) 105 { 106 ibuf[0] = (unsigned char)(window->tview_len + (0x2 << 6)); 107 ip_len = 1; 108 } 109 else 110 { 111 ibuf[0] = (0x2 << 6); 112 ip_len = svn__encode_uint(ibuf + 1, window->tview_len) - ibuf; 113 } 114 115 /* encode the window header. Please note that the source window may 116 * have content despite not being used for deltification. */ 117 header_current = svn__encode_uint(header_current, 118 (apr_uint64_t)window->sview_offset); 119 header_current = svn__encode_uint(header_current, window->sview_len); 120 header_current = svn__encode_uint(header_current, window->tview_len); 121 header_current[0] = (unsigned char)ip_len; /* 1 instruction */ 122 header_current = svn__encode_uint(&header_current[1], len); 123 124 /* append instructions (1 to a handful of bytes) */ 125 for (i = 0; i < ip_len; ++i) 126 header_current[i] = ibuf[i]; 127 128 header_len = header_current - headers + ip_len; 129 130 /* Write out the window. */ 131 SVN_ERR(svn_stream_write(eb->output, (const char *)headers, &header_len)); 132 if (len) 133 SVN_ERR(svn_stream_write(eb->output, window->new_data->data, &len)); 134 135 return SVN_NO_ERROR; 136} 137 138static svn_error_t * 139window_handler(svn_txdelta_window_t *window, void *baton) 140{ 141 struct encoder_baton *eb = baton; 142 apr_pool_t *pool; 143 svn_stringbuf_t *instructions; 144 svn_stringbuf_t *i1; 145 svn_stringbuf_t *header; 146 const svn_string_t *newdata; 147 unsigned char ibuf[MAX_INSTRUCTION_LEN], *ip; 148 const svn_txdelta_op_t *op; 149 apr_size_t len; 150 151 /* use specialized code if there is no source */ 152 if (window && !window->src_ops && window->num_ops == 1 && !eb->version) 153 return svn_error_trace(send_simple_insertion_window(window, eb)); 154 155 /* Make sure we write the header. */ 156 if (!eb->header_done) 157 { 158 char svnver[4] = {'S','V','N','\0'}; 159 len = 4; 160 svnver[3] = (char)eb->version; 161 SVN_ERR(svn_stream_write(eb->output, svnver, &len)); 162 eb->header_done = TRUE; 163 } 164 165 if (window == NULL) 166 { 167 svn_stream_t *output = eb->output; 168 169 /* We're done; clean up. 170 171 We clean our pool first. Given that the output stream was passed 172 TO us, we'll assume it has a longer lifetime, and that it will not 173 be affected by our pool destruction. 174 175 The contrary point of view (close the stream first): that could 176 tell our user that everything related to the output stream is done, 177 and a cleanup of the user pool should occur. However, that user 178 pool could include the subpool we created for our work (eb->pool), 179 which would then make our call to svn_pool_destroy() puke. 180 */ 181 svn_pool_destroy(eb->pool); 182 183 return svn_stream_close(output); 184 } 185 186 /* create the necessary data buffers */ 187 pool = svn_pool_create(eb->pool); 188 instructions = svn_stringbuf_create_empty(pool); 189 i1 = svn_stringbuf_create_empty(pool); 190 header = svn_stringbuf_create_empty(pool); 191 192 /* Encode the instructions. */ 193 for (op = window->ops; op < window->ops + window->num_ops; op++) 194 { 195 /* Encode the action code and length. */ 196 ip = ibuf; 197 switch (op->action_code) 198 { 199 case svn_txdelta_source: *ip = 0; break; 200 case svn_txdelta_target: *ip = (0x1 << 6); break; 201 case svn_txdelta_new: *ip = (0x2 << 6); break; 202 } 203 if (op->length >> 6 == 0) 204 *ip++ |= (unsigned char)op->length; 205 else 206 ip = svn__encode_uint(ip + 1, op->length); 207 if (op->action_code != svn_txdelta_new) 208 ip = svn__encode_uint(ip, op->offset); 209 svn_stringbuf_appendbytes(instructions, (const char *)ibuf, ip - ibuf); 210 } 211 212 /* Encode the header. */ 213 append_encoded_int(header, window->sview_offset); 214 append_encoded_int(header, window->sview_len); 215 append_encoded_int(header, window->tview_len); 216 if (eb->version == 1) 217 { 218 SVN_ERR(svn__compress(instructions, i1, eb->compression_level)); 219 instructions = i1; 220 } 221 append_encoded_int(header, instructions->len); 222 if (eb->version == 1) 223 { 224 svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool); 225 svn_stringbuf_t *original = svn_stringbuf_create_empty(pool); 226 original->data = (char *)window->new_data->data; /* won't be modified */ 227 original->len = window->new_data->len; 228 original->blocksize = window->new_data->len + 1; 229 230 SVN_ERR(svn__compress(original, compressed, eb->compression_level)); 231 newdata = svn_stringbuf__morph_into_string(compressed); 232 } 233 else 234 newdata = window->new_data; 235 236 append_encoded_int(header, newdata->len); 237 238 /* Write out the window. */ 239 len = header->len; 240 SVN_ERR(svn_stream_write(eb->output, header->data, &len)); 241 if (instructions->len > 0) 242 { 243 len = instructions->len; 244 SVN_ERR(svn_stream_write(eb->output, instructions->data, &len)); 245 } 246 if (newdata->len > 0) 247 { 248 len = newdata->len; 249 SVN_ERR(svn_stream_write(eb->output, newdata->data, &len)); 250 } 251 252 svn_pool_destroy(pool); 253 return SVN_NO_ERROR; 254} 255 256void 257svn_txdelta_to_svndiff3(svn_txdelta_window_handler_t *handler, 258 void **handler_baton, 259 svn_stream_t *output, 260 int svndiff_version, 261 int compression_level, 262 apr_pool_t *pool) 263{ 264 apr_pool_t *subpool = svn_pool_create(pool); 265 struct encoder_baton *eb; 266 267 eb = apr_palloc(subpool, sizeof(*eb)); 268 eb->output = output; 269 eb->header_done = FALSE; 270 eb->pool = subpool; 271 eb->version = svndiff_version; 272 eb->compression_level = compression_level; 273 274 *handler = window_handler; 275 *handler_baton = eb; 276} 277 278void 279svn_txdelta_to_svndiff2(svn_txdelta_window_handler_t *handler, 280 void **handler_baton, 281 svn_stream_t *output, 282 int svndiff_version, 283 apr_pool_t *pool) 284{ 285 svn_txdelta_to_svndiff3(handler, handler_baton, output, svndiff_version, 286 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); 287} 288 289void 290svn_txdelta_to_svndiff(svn_stream_t *output, 291 apr_pool_t *pool, 292 svn_txdelta_window_handler_t *handler, 293 void **handler_baton) 294{ 295 svn_txdelta_to_svndiff3(handler, handler_baton, output, 0, 296 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); 297} 298 299 300/* ----- svndiff to text delta ----- */ 301 302/* An svndiff parser object. */ 303struct decode_baton 304{ 305 /* Once the svndiff parser has enough data buffered to create a 306 "window", it passes this window to the caller's consumer routine. */ 307 svn_txdelta_window_handler_t consumer_func; 308 void *consumer_baton; 309 310 /* Pool to create subpools from; each developing window will be a 311 subpool. */ 312 apr_pool_t *pool; 313 314 /* The current subpool which contains our current window-buffer. */ 315 apr_pool_t *subpool; 316 317 /* The actual svndiff data buffer, living within subpool. */ 318 svn_stringbuf_t *buffer; 319 320 /* The offset and size of the last source view, so that we can check 321 to make sure the next one isn't sliding backwards. */ 322 svn_filesize_t last_sview_offset; 323 apr_size_t last_sview_len; 324 325 /* We have to discard four bytes at the beginning for the header. 326 This field keeps track of how many of those bytes we have read. */ 327 apr_size_t header_bytes; 328 329 /* Do we want an error to occur when we close the stream that 330 indicates we didn't send the whole svndiff data? If you plan to 331 not transmit the whole svndiff data stream, you will want this to 332 be FALSE. */ 333 svn_boolean_t error_on_early_close; 334 335 /* svndiff version in use by delta. */ 336 unsigned char version; 337}; 338 339 340/* Wrapper aroung svn__deencode_uint taking a file size as *VAL. */ 341static const unsigned char * 342decode_file_offset(svn_filesize_t *val, 343 const unsigned char *p, 344 const unsigned char *end) 345{ 346 apr_uint64_t temp = 0; 347 const unsigned char *result = svn__decode_uint(&temp, p, end); 348 *val = (svn_filesize_t)temp; 349 350 return result; 351} 352 353/* Same as above, only decode into a size variable. */ 354static const unsigned char * 355decode_size(apr_size_t *val, 356 const unsigned char *p, 357 const unsigned char *end) 358{ 359 apr_uint64_t temp = 0; 360 const unsigned char *result = svn__decode_uint(&temp, p, end); 361 if (temp > APR_SIZE_MAX) 362 return NULL; 363 364 *val = (apr_size_t)temp; 365 return result; 366} 367 368/* Decode an instruction into OP, returning a pointer to the text 369 after the instruction. Note that if the action code is 370 svn_txdelta_new, the offset field of *OP will not be set. */ 371static const unsigned char * 372decode_instruction(svn_txdelta_op_t *op, 373 const unsigned char *p, 374 const unsigned char *end) 375{ 376 apr_size_t c; 377 apr_size_t action; 378 379 if (p == end) 380 return NULL; 381 382 /* We need this more than once */ 383 c = *p++; 384 385 /* Decode the instruction selector. */ 386 action = (c >> 6) & 0x3; 387 if (action >= 0x3) 388 return NULL; 389 390 /* This relies on enum svn_delta_action values to match and never to be 391 redefined. */ 392 op->action_code = (enum svn_delta_action)(action); 393 394 /* Decode the length and offset. */ 395 op->length = c & 0x3f; 396 if (op->length == 0) 397 { 398 p = decode_size(&op->length, p, end); 399 if (p == NULL) 400 return NULL; 401 } 402 if (action != svn_txdelta_new) 403 { 404 p = decode_size(&op->offset, p, end); 405 if (p == NULL) 406 return NULL; 407 } 408 409 return p; 410} 411 412/* Count the instructions in the range [P..END-1] and make sure they 413 are valid for the given window lengths. Return an error if the 414 instructions are invalid; otherwise set *NINST to the number of 415 instructions. */ 416static svn_error_t * 417count_and_verify_instructions(int *ninst, 418 const unsigned char *p, 419 const unsigned char *end, 420 apr_size_t sview_len, 421 apr_size_t tview_len, 422 apr_size_t new_len) 423{ 424 int n = 0; 425 svn_txdelta_op_t op; 426 apr_size_t tpos = 0, npos = 0; 427 428 while (p < end) 429 { 430 p = decode_instruction(&op, p, end); 431 432 /* Detect any malformed operations from the instruction stream. */ 433 if (p == NULL) 434 return svn_error_createf 435 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, 436 _("Invalid diff stream: insn %d cannot be decoded"), n); 437 else if (op.length == 0) 438 return svn_error_createf 439 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, 440 _("Invalid diff stream: insn %d has length zero"), n); 441 else if (op.length > tview_len - tpos) 442 return svn_error_createf 443 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, 444 _("Invalid diff stream: insn %d overflows the target view"), n); 445 446 switch (op.action_code) 447 { 448 case svn_txdelta_source: 449 if (op.length > sview_len - op.offset || 450 op.offset > sview_len) 451 return svn_error_createf 452 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, 453 _("Invalid diff stream: " 454 "[src] insn %d overflows the source view"), n); 455 break; 456 case svn_txdelta_target: 457 if (op.offset >= tpos) 458 return svn_error_createf 459 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, 460 _("Invalid diff stream: " 461 "[tgt] insn %d starts beyond the target view position"), n); 462 break; 463 case svn_txdelta_new: 464 if (op.length > new_len - npos) 465 return svn_error_createf 466 (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, 467 _("Invalid diff stream: " 468 "[new] insn %d overflows the new data section"), n); 469 npos += op.length; 470 break; 471 } 472 tpos += op.length; 473 n++; 474 } 475 if (tpos != tview_len) 476 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL, 477 _("Delta does not fill the target window")); 478 if (npos != new_len) 479 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL, 480 _("Delta does not contain enough new data")); 481 482 *ninst = n; 483 return SVN_NO_ERROR; 484} 485 486static svn_error_t * 487zlib_decode(const unsigned char *in, apr_size_t inLen, svn_stringbuf_t *out, 488 apr_size_t limit) 489{ 490 /* construct a fake string buffer as parameter to svn__decompress. 491 This is fine as that function never writes to it. */ 492 svn_stringbuf_t compressed; 493 compressed.pool = NULL; 494 compressed.data = (char *)in; 495 compressed.len = inLen; 496 compressed.blocksize = inLen + 1; 497 498 return svn__decompress(&compressed, out, limit); 499} 500 501/* Given the five integer fields of a window header and a pointer to 502 the remainder of the window contents, fill in a delta window 503 structure *WINDOW. New allocations will be performed in POOL; 504 the new_data field of *WINDOW will refer directly to memory pointed 505 to by DATA. */ 506static svn_error_t * 507decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset, 508 apr_size_t sview_len, apr_size_t tview_len, apr_size_t inslen, 509 apr_size_t newlen, const unsigned char *data, apr_pool_t *pool, 510 unsigned int version) 511{ 512 const unsigned char *insend; 513 int ninst; 514 apr_size_t npos; 515 svn_txdelta_op_t *ops, *op; 516 svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data)); 517 518 window->sview_offset = sview_offset; 519 window->sview_len = sview_len; 520 window->tview_len = tview_len; 521 522 insend = data + inslen; 523 524 if (version == 1) 525 { 526 svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool); 527 svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool); 528 529 SVN_ERR(zlib_decode(insend, newlen, ndout, 530 SVN_DELTA_WINDOW_SIZE)); 531 SVN_ERR(zlib_decode(data, insend - data, instout, 532 MAX_INSTRUCTION_SECTION_LEN)); 533 534 newlen = ndout->len; 535 data = (unsigned char *)instout->data; 536 insend = (unsigned char *)instout->data + instout->len; 537 538 new_data->data = (const char *) ndout->data; 539 new_data->len = newlen; 540 } 541 else 542 { 543 /* Copy the data because an svn_string_t must have the invariant 544 data[len]=='\0'. */ 545 char *buf = apr_palloc(pool, newlen + 1); 546 547 memcpy(buf, insend, newlen); 548 buf[newlen] = '\0'; 549 new_data->data = buf; 550 new_data->len = newlen; 551 } 552 553 /* Count the instructions and make sure they are all valid. */ 554 SVN_ERR(count_and_verify_instructions(&ninst, data, insend, 555 sview_len, tview_len, newlen)); 556 557 /* Allocate a buffer for the instructions and decode them. */ 558 ops = apr_palloc(pool, ninst * sizeof(*ops)); 559 npos = 0; 560 window->src_ops = 0; 561 for (op = ops; op < ops + ninst; op++) 562 { 563 data = decode_instruction(op, data, insend); 564 if (op->action_code == svn_txdelta_source) 565 ++window->src_ops; 566 else if (op->action_code == svn_txdelta_new) 567 { 568 op->offset = npos; 569 npos += op->length; 570 } 571 } 572 SVN_ERR_ASSERT(data == insend); 573 574 window->ops = ops; 575 window->num_ops = ninst; 576 window->new_data = new_data; 577 578 return SVN_NO_ERROR; 579} 580 581static const char SVNDIFF_V0[] = { 'S', 'V', 'N', 0 }; 582static const char SVNDIFF_V1[] = { 'S', 'V', 'N', 1 }; 583#define SVNDIFF_HEADER_SIZE (sizeof(SVNDIFF_V0)) 584 585static svn_error_t * 586write_handler(void *baton, 587 const char *buffer, 588 apr_size_t *len) 589{ 590 struct decode_baton *db = (struct decode_baton *) baton; 591 const unsigned char *p, *end; 592 svn_filesize_t sview_offset; 593 apr_size_t sview_len, tview_len, inslen, newlen, remaining; 594 apr_size_t buflen = *len; 595 596 /* Chew up four bytes at the beginning for the header. */ 597 if (db->header_bytes < SVNDIFF_HEADER_SIZE) 598 { 599 apr_size_t nheader = SVNDIFF_HEADER_SIZE - db->header_bytes; 600 if (nheader > buflen) 601 nheader = buflen; 602 if (memcmp(buffer, SVNDIFF_V0 + db->header_bytes, nheader) == 0) 603 db->version = 0; 604 else if (memcmp(buffer, SVNDIFF_V1 + db->header_bytes, nheader) == 0) 605 db->version = 1; 606 else 607 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_HEADER, NULL, 608 _("Svndiff has invalid header")); 609 buflen -= nheader; 610 buffer += nheader; 611 db->header_bytes += nheader; 612 } 613 614 /* Concatenate the old with the new. */ 615 svn_stringbuf_appendbytes(db->buffer, buffer, buflen); 616 617 /* We have a buffer of svndiff data that might be good for: 618 619 a) an integral number of windows' worth of data - this is a 620 trivial case. Make windows from our data and ship them off. 621 622 b) a non-integral number of windows' worth of data - we shall 623 consume the integral portion of the window data, and then 624 somewhere in the following loop the decoding of the svndiff 625 data will run out of stuff to decode, and will simply return 626 SVN_NO_ERROR, anxiously awaiting more data. 627 */ 628 629 while (1) 630 { 631 apr_pool_t *newpool; 632 svn_txdelta_window_t window; 633 634 /* Read the header, if we have enough bytes for that. */ 635 p = (const unsigned char *) db->buffer->data; 636 end = (const unsigned char *) db->buffer->data + db->buffer->len; 637 638 p = decode_file_offset(&sview_offset, p, end); 639 if (p == NULL) 640 break; 641 642 p = decode_size(&sview_len, p, end); 643 if (p == NULL) 644 break; 645 646 p = decode_size(&tview_len, p, end); 647 if (p == NULL) 648 break; 649 650 p = decode_size(&inslen, p, end); 651 if (p == NULL) 652 break; 653 654 p = decode_size(&newlen, p, end); 655 if (p == NULL) 656 break; 657 658 if (tview_len > SVN_DELTA_WINDOW_SIZE || 659 sview_len > SVN_DELTA_WINDOW_SIZE || 660 /* for svndiff1, newlen includes the original length */ 661 newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN || 662 inslen > MAX_INSTRUCTION_SECTION_LEN) 663 return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, 664 _("Svndiff contains a too-large window")); 665 666 /* Check for integer overflow. */ 667 if (sview_offset < 0 || inslen + newlen < inslen 668 || sview_len + tview_len < sview_len 669 || (apr_size_t)sview_offset + sview_len < (apr_size_t)sview_offset) 670 return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, 671 _("Svndiff contains corrupt window header")); 672 673 /* Check for source windows which slide backwards. */ 674 if (sview_len > 0 675 && (sview_offset < db->last_sview_offset 676 || (sview_offset + sview_len 677 < db->last_sview_offset + db->last_sview_len))) 678 return svn_error_create 679 (SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL, 680 _("Svndiff has backwards-sliding source views")); 681 682 /* Wait for more data if we don't have enough bytes for the 683 whole window. */ 684 if ((apr_size_t) (end - p) < inslen + newlen) 685 return SVN_NO_ERROR; 686 687 /* Decode the window and send it off. */ 688 SVN_ERR(decode_window(&window, sview_offset, sview_len, tview_len, 689 inslen, newlen, p, db->subpool, 690 db->version)); 691 SVN_ERR(db->consumer_func(&window, db->consumer_baton)); 692 693 /* Make a new subpool and buffer, saving aside the remaining 694 data in the old buffer. */ 695 newpool = svn_pool_create(db->pool); 696 p += inslen + newlen; 697 remaining = db->buffer->data + db->buffer->len - (const char *) p; 698 db->buffer = 699 svn_stringbuf_ncreate((const char *) p, remaining, newpool); 700 701 /* Remember the offset and length of the source view for next time. */ 702 db->last_sview_offset = sview_offset; 703 db->last_sview_len = sview_len; 704 705 /* We've copied stuff out of the old pool. Toss that pool and use 706 our new pool. 707 ### might be nice to avoid the copy and just use svn_pool_clear 708 ### to get rid of whatever the "other stuff" is. future project... 709 */ 710 svn_pool_destroy(db->subpool); 711 db->subpool = newpool; 712 } 713 714 /* At this point we processed all integral windows and DB->BUFFER is empty 715 or contains partially read window header. 716 Check that unprocessed data is not larger that theoretical maximum 717 window header size. */ 718 if (db->buffer->len > 5 * SVN__MAX_ENCODED_UINT_LEN) 719 return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, 720 _("Svndiff contains a too-large window header")); 721 722 return SVN_NO_ERROR; 723} 724 725/* Minimal svn_stream_t write handler, doing nothing */ 726static svn_error_t * 727noop_write_handler(void *baton, 728 const char *buffer, 729 apr_size_t *len) 730{ 731 return SVN_NO_ERROR; 732} 733 734static svn_error_t * 735close_handler(void *baton) 736{ 737 struct decode_baton *db = (struct decode_baton *) baton; 738 svn_error_t *err; 739 740 /* Make sure that we're at a plausible end of stream, returning an 741 error if we are expected to do so. */ 742 if ((db->error_on_early_close) 743 && (db->header_bytes < 4 || db->buffer->len != 0)) 744 return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL, 745 _("Unexpected end of svndiff input")); 746 747 /* Tell the window consumer that we're done, and clean up. */ 748 err = db->consumer_func(NULL, db->consumer_baton); 749 svn_pool_destroy(db->pool); 750 return err; 751} 752 753 754svn_stream_t * 755svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler, 756 void *handler_baton, 757 svn_boolean_t error_on_early_close, 758 apr_pool_t *pool) 759{ 760 apr_pool_t *subpool = svn_pool_create(pool); 761 struct decode_baton *db = apr_palloc(pool, sizeof(*db)); 762 svn_stream_t *stream; 763 764 db->consumer_func = handler; 765 db->consumer_baton = handler_baton; 766 db->pool = subpool; 767 db->subpool = svn_pool_create(subpool); 768 db->buffer = svn_stringbuf_create_empty(db->subpool); 769 db->last_sview_offset = 0; 770 db->last_sview_len = 0; 771 db->header_bytes = 0; 772 db->error_on_early_close = error_on_early_close; 773 stream = svn_stream_create(db, pool); 774 775 if (handler != svn_delta_noop_window_handler) 776 { 777 svn_stream_set_write(stream, write_handler); 778 svn_stream_set_close(stream, close_handler); 779 } 780 else 781 { 782 /* And else we just ignore everything as efficiently as we can. 783 by only hooking a no-op handler */ 784 svn_stream_set_write(stream, noop_write_handler); 785 } 786 return stream; 787} 788 789 790/* Routines for reading one svndiff window at a time. */ 791 792/* Read one byte from STREAM into *BYTE. */ 793static svn_error_t * 794read_one_byte(unsigned char *byte, svn_stream_t *stream) 795{ 796 char c; 797 apr_size_t len = 1; 798 799 SVN_ERR(svn_stream_read_full(stream, &c, &len)); 800 if (len == 0) 801 return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL, 802 _("Unexpected end of svndiff input")); 803 *byte = (unsigned char) c; 804 return SVN_NO_ERROR; 805} 806 807/* Read and decode one integer from STREAM into *SIZE. 808 Increment *BYTE_COUNTER by the number of chars we have read. */ 809static svn_error_t * 810read_one_size(apr_size_t *size, 811 apr_size_t *byte_counter, 812 svn_stream_t *stream) 813{ 814 unsigned char c; 815 816 *size = 0; 817 while (1) 818 { 819 SVN_ERR(read_one_byte(&c, stream)); 820 ++*byte_counter; 821 *size = (*size << 7) | (c & 0x7f); 822 if (!(c & 0x80)) 823 break; 824 } 825 return SVN_NO_ERROR; 826} 827 828/* Read a window header from STREAM and check it for integer overflow. */ 829static svn_error_t * 830read_window_header(svn_stream_t *stream, svn_filesize_t *sview_offset, 831 apr_size_t *sview_len, apr_size_t *tview_len, 832 apr_size_t *inslen, apr_size_t *newlen, 833 apr_size_t *header_len) 834{ 835 unsigned char c; 836 837 /* Read the source view offset by hand, since it's not an apr_size_t. */ 838 *header_len = 0; 839 *sview_offset = 0; 840 while (1) 841 { 842 SVN_ERR(read_one_byte(&c, stream)); 843 ++*header_len; 844 *sview_offset = (*sview_offset << 7) | (c & 0x7f); 845 if (!(c & 0x80)) 846 break; 847 } 848 849 /* Read the four size fields. */ 850 SVN_ERR(read_one_size(sview_len, header_len, stream)); 851 SVN_ERR(read_one_size(tview_len, header_len, stream)); 852 SVN_ERR(read_one_size(inslen, header_len, stream)); 853 SVN_ERR(read_one_size(newlen, header_len, stream)); 854 855 if (*tview_len > SVN_DELTA_WINDOW_SIZE || 856 *sview_len > SVN_DELTA_WINDOW_SIZE || 857 /* for svndiff1, newlen includes the original length */ 858 *newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN || 859 *inslen > MAX_INSTRUCTION_SECTION_LEN) 860 return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, 861 _("Svndiff contains a too-large window")); 862 863 /* Check for integer overflow. */ 864 if (*sview_offset < 0 || *inslen + *newlen < *inslen 865 || *sview_len + *tview_len < *sview_len 866 || (apr_size_t)*sview_offset + *sview_len < (apr_size_t)*sview_offset) 867 return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, 868 _("Svndiff contains corrupt window header")); 869 870 return SVN_NO_ERROR; 871} 872 873svn_error_t * 874svn_txdelta_read_svndiff_window(svn_txdelta_window_t **window, 875 svn_stream_t *stream, 876 int svndiff_version, 877 apr_pool_t *pool) 878{ 879 svn_filesize_t sview_offset; 880 apr_size_t sview_len, tview_len, inslen, newlen, len, header_len; 881 unsigned char *buf; 882 883 SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len, 884 &inslen, &newlen, &header_len)); 885 len = inslen + newlen; 886 buf = apr_palloc(pool, len); 887 SVN_ERR(svn_stream_read_full(stream, (char*)buf, &len)); 888 if (len < inslen + newlen) 889 return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL, 890 _("Unexpected end of svndiff input")); 891 *window = apr_palloc(pool, sizeof(**window)); 892 return decode_window(*window, sview_offset, sview_len, tview_len, inslen, 893 newlen, buf, pool, svndiff_version); 894} 895 896 897svn_error_t * 898svn_txdelta_skip_svndiff_window(apr_file_t *file, 899 int svndiff_version, 900 apr_pool_t *pool) 901{ 902 svn_stream_t *stream = svn_stream_from_aprfile2(file, TRUE, pool); 903 svn_filesize_t sview_offset; 904 apr_size_t sview_len, tview_len, inslen, newlen, header_len; 905 apr_off_t offset; 906 907 SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len, 908 &inslen, &newlen, &header_len)); 909 910 offset = inslen + newlen; 911 return svn_io_file_seek(file, APR_CUR, &offset, pool); 912} 913 914svn_error_t * 915svn_txdelta__read_raw_window_len(apr_size_t *window_len, 916 svn_stream_t *stream, 917 apr_pool_t *pool) 918{ 919 svn_filesize_t sview_offset; 920 apr_size_t sview_len, tview_len, inslen, newlen, header_len; 921 922 SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len, 923 &inslen, &newlen, &header_len)); 924 925 *window_len = inslen + newlen + header_len; 926 return SVN_NO_ERROR; 927} 928 929