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