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