]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_delta/svndiff.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_delta / svndiff.c
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().  */
44 struct 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.  */
61 static void
62 append_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
71 static svn_error_t *
72 send_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
138 static svn_error_t *
139 window_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
256 void
257 svn_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
278 void
279 svn_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
289 void
290 svn_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 \f
300 /* ----- svndiff to text delta ----- */
301
302 /* An svndiff parser object.  */
303 struct 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. */
341 static const unsigned char *
342 decode_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. */
354 static const unsigned char *
355 decode_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.  */
371 static const unsigned char *
372 decode_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.  */
416 static svn_error_t *
417 count_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
486 static svn_error_t *
487 zlib_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. */
506 static svn_error_t *
507 decode_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
581 static const char SVNDIFF_V0[] = { 'S', 'V', 'N', 0 };
582 static const char SVNDIFF_V1[] = { 'S', 'V', 'N', 1 };
583 #define SVNDIFF_HEADER_SIZE (sizeof(SVNDIFF_V0))
584
585 static svn_error_t *
586 write_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 */
726 static svn_error_t *
727 noop_write_handler(void *baton,
728                    const char *buffer,
729                    apr_size_t *len)
730 {
731   return SVN_NO_ERROR;
732 }
733
734 static svn_error_t *
735 close_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
754 svn_stream_t *
755 svn_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. */
793 static svn_error_t *
794 read_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. */
809 static svn_error_t *
810 read_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. */
829 static svn_error_t *
830 read_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
873 svn_error_t *
874 svn_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
897 svn_error_t *
898 svn_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
914 svn_error_t *
915 svn_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