]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_repos/load.c
Update Subversion and dependencies to 1.14.0 LTS.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_repos / load.c
1 /* load.c --- parsing a 'dumpfile'-formatted stream.
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23
24 #include <apr.h>
25
26 #include "svn_hash.h"
27 #include "svn_pools.h"
28 #include "svn_error.h"
29 #include "svn_repos.h"
30 #include "svn_string.h"
31 #include "repos.h"
32 #include "svn_private_config.h"
33 #include "svn_ctype.h"
34
35 #include "private/svn_dep_compat.h"
36
37 /*----------------------------------------------------------------------*/
38 \f
39 /** The parser and related helper funcs **/
40
41
42 static svn_error_t *
43 stream_ran_dry(void)
44 {
45   return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
46                           _("Premature end of content data in dumpstream"));
47 }
48
49 static svn_error_t *
50 stream_malformed(void)
51 {
52   return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
53                           _("Dumpstream data appears to be malformed"));
54 }
55
56 /* Allocate a new hash *HEADERS in POOL, and read a series of
57    RFC822-style headers from STREAM.  Duplicate each header's name and
58    value into POOL and store in hash as a const char * ==> const char *.
59
60    The headers are assumed to be terminated by a single blank line,
61    which will be permanently sucked from the stream and tossed.
62
63    If the caller has already read in the first header line, it should
64    be passed in as FIRST_HEADER.  If not, pass NULL instead.
65  */
66 static svn_error_t *
67 read_header_block(svn_stream_t *stream,
68                   svn_stringbuf_t *first_header,
69                   apr_hash_t **headers,
70                   apr_pool_t *pool)
71 {
72   *headers = apr_hash_make(pool);
73
74   while (1)
75     {
76       svn_stringbuf_t *header_str;
77       const char *name, *value;
78       svn_boolean_t eof;
79       apr_size_t i = 0;
80
81       if (first_header != NULL)
82         {
83           header_str = first_header;
84           first_header = NULL;  /* so we never visit this block again. */
85           eof = FALSE;
86         }
87
88       else
89         /* Read the next line into a stringbuf. */
90         SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
91
92       if (svn_stringbuf_isempty(header_str))
93         break;    /* end of header block */
94       else if (eof)
95         return stream_ran_dry();
96
97       /* Find the next colon in the stringbuf. */
98       while (header_str->data[i] != ':')
99         {
100           if (header_str->data[i] == '\0')
101             return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
102                                      _("Dump stream contains a malformed "
103                                        "header (with no ':') at '%.20s'"),
104                                      header_str->data);
105           i++;
106         }
107       /* Create a 'name' string and point to it. */
108       header_str->data[i] = '\0';
109       name = header_str->data;
110
111       /* Skip over the NULL byte and the space following it.  */
112       i += 2;
113       if (i > header_str->len)
114         return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
115                                  _("Dump stream contains a malformed "
116                                    "header (with no value) at '%.20s'"),
117                                  header_str->data);
118
119       /* Point to the 'value' string. */
120       value = header_str->data + i;
121
122       /* Store name/value in hash. */
123       svn_hash_sets(*headers, name, value);
124     }
125
126   return SVN_NO_ERROR;
127 }
128
129
130 /* Set *PBUF to a string of length LEN, allocated in POOL, read from STREAM.
131    Also read a newline from STREAM and increase *ACTUAL_LEN by the total
132    number of bytes read from STREAM.  */
133 static svn_error_t *
134 read_key_or_val(char **pbuf,
135                 svn_filesize_t *actual_length,
136                 svn_stream_t *stream,
137                 apr_size_t len,
138                 apr_pool_t *pool)
139 {
140   char *buf = apr_pcalloc(pool, len + 1);
141   apr_size_t numread;
142   char c;
143
144   numread = len;
145   SVN_ERR(svn_stream_read_full(stream, buf, &numread));
146   *actual_length += numread;
147   if (numread != len)
148     return svn_error_trace(stream_ran_dry());
149   buf[len] = '\0';
150
151   /* Suck up extra newline after key data */
152   numread = 1;
153   SVN_ERR(svn_stream_read_full(stream, &c, &numread));
154   *actual_length += numread;
155   if (numread != 1)
156     return svn_error_trace(stream_ran_dry());
157   if (c != '\n')
158     return svn_error_trace(stream_malformed());
159
160   *pbuf = buf;
161   return SVN_NO_ERROR;
162 }
163
164
165 /* Read CONTENT_LENGTH bytes from STREAM, parsing the bytes as an
166    encoded Subversion properties hash, and making multiple calls to
167    PARSE_FNS->set_*_property on RECORD_BATON (depending on the value
168    of IS_NODE.)
169
170    Set *ACTUAL_LENGTH to the number of bytes consumed from STREAM.
171    If an error is returned, the value of *ACTUAL_LENGTH is undefined.
172
173    Use POOL for all allocations.  */
174 static svn_error_t *
175 parse_property_block(svn_stream_t *stream,
176                      svn_filesize_t content_length,
177                      const svn_repos_parse_fns3_t *parse_fns,
178                      void *record_baton,
179                      void *parse_baton,
180                      svn_boolean_t is_node,
181                      svn_filesize_t *actual_length,
182                      apr_pool_t *pool)
183 {
184   svn_stringbuf_t *strbuf;
185   apr_pool_t *proppool = svn_pool_create(pool);
186
187   *actual_length = 0;
188   while (content_length != *actual_length)
189     {
190       char *buf;  /* a pointer into the stringbuf's data */
191       svn_boolean_t eof;
192
193       svn_pool_clear(proppool);
194
195       /* Read a key length line.  (Actually, it might be PROPS_END). */
196       SVN_ERR(svn_stream_readline(stream, &strbuf, "\n", &eof, proppool));
197
198       if (eof)
199         {
200           /* We could just use stream_ran_dry() or stream_malformed(),
201              but better to give a non-generic property block error. */
202           return svn_error_create
203             (SVN_ERR_STREAM_MALFORMED_DATA, NULL,
204              _("Incomplete or unterminated property block"));
205         }
206
207       *actual_length += (strbuf->len + 1); /* +1 because we read a \n too. */
208       buf = strbuf->data;
209
210       if (! strcmp(buf, "PROPS-END"))
211         break; /* no more properties. */
212
213       else if ((buf[0] == 'K') && (buf[1] == ' '))
214         {
215           char *keybuf;
216           apr_uint64_t len;
217
218           SVN_ERR(svn_cstring_strtoui64(&len, buf + 2, 0, APR_SIZE_MAX, 10));
219           SVN_ERR(read_key_or_val(&keybuf, actual_length,
220                                   stream, (apr_size_t)len, proppool));
221
222           /* Read a val length line */
223           SVN_ERR(svn_stream_readline(stream, &strbuf, "\n", &eof, proppool));
224           if (eof)
225             return stream_ran_dry();
226
227           *actual_length += (strbuf->len + 1); /* +1 because we read \n too */
228           buf = strbuf->data;
229
230           if ((buf[0] == 'V') && (buf[1] == ' '))
231             {
232               svn_string_t propstring;
233               char *valbuf;
234               apr_int64_t val;
235
236               SVN_ERR(svn_cstring_atoi64(&val, buf + 2));
237               propstring.len = (apr_size_t)val;
238               SVN_ERR(read_key_or_val(&valbuf, actual_length,
239                                       stream, propstring.len, proppool));
240               propstring.data = valbuf;
241
242               /* Now, send the property pair to the vtable! */
243               if (is_node)
244                 {
245                   SVN_ERR(parse_fns->set_node_property(record_baton,
246                                                        keybuf,
247                                                        &propstring));
248                 }
249               else
250                 {
251                   SVN_ERR(parse_fns->set_revision_property(record_baton,
252                                                            keybuf,
253                                                            &propstring));
254                 }
255             }
256           else
257             return stream_malformed(); /* didn't find expected 'V' line */
258         }
259       else if ((buf[0] == 'D') && (buf[1] == ' '))
260         {
261           char *keybuf;
262           apr_uint64_t len;
263
264           SVN_ERR(svn_cstring_strtoui64(&len, buf + 2, 0, APR_SIZE_MAX, 10));
265           SVN_ERR(read_key_or_val(&keybuf, actual_length,
266                                   stream, (apr_size_t)len, proppool));
267
268           /* We don't expect these in revision properties, and if we see
269              one when we don't have a delete_node_property callback,
270              then we're seeing a v3 feature in a v2 dump. */
271           if (!is_node || !parse_fns->delete_node_property)
272             return stream_malformed();
273
274           SVN_ERR(parse_fns->delete_node_property(record_baton, keybuf));
275         }
276       else
277         return stream_malformed(); /* didn't find expected 'K' line */
278
279     } /* while (1) */
280
281   svn_pool_destroy(proppool);
282   return SVN_NO_ERROR;
283 }
284
285
286 /* Read CONTENT_LENGTH bytes from STREAM. If IS_DELTA is true, use
287    PARSE_FNS->apply_textdelta to push a text delta, otherwise use
288    PARSE_FNS->set_fulltext to push those bytes as replace fulltext for
289    a node.  Use BUFFER/BUFLEN to push the fulltext in "chunks".
290
291    Use POOL for all allocations.  */
292 static svn_error_t *
293 parse_text_block(svn_stream_t *stream,
294                  svn_filesize_t content_length,
295                  svn_boolean_t is_delta,
296                  const svn_repos_parse_fns3_t *parse_fns,
297                  void *record_baton,
298                  char *buffer,
299                  apr_size_t buflen,
300                  apr_pool_t *pool)
301 {
302   svn_stream_t *text_stream = NULL;
303   apr_size_t num_to_read, rlen, wlen;
304
305   if (is_delta)
306     {
307       svn_txdelta_window_handler_t wh;
308       void *whb;
309
310       SVN_ERR(parse_fns->apply_textdelta(&wh, &whb, record_baton));
311       if (wh)
312         text_stream = svn_txdelta_parse_svndiff(wh, whb, TRUE, pool);
313     }
314   else
315     {
316       /* Get a stream to which we can push the data. */
317       SVN_ERR(parse_fns->set_fulltext(&text_stream, record_baton));
318     }
319
320   /* Regardless of whether or not we have a sink for our data, we
321      need to read it. */
322   while (content_length)
323     {
324       if (content_length >= (svn_filesize_t)buflen)
325         rlen = buflen;
326       else
327         rlen = (apr_size_t) content_length;
328
329       num_to_read = rlen;
330       SVN_ERR(svn_stream_read_full(stream, buffer, &rlen));
331       content_length -= rlen;
332       if (rlen != num_to_read)
333         return stream_ran_dry();
334
335       if (text_stream)
336         {
337           /* write however many bytes you read. */
338           wlen = rlen;
339           SVN_ERR(svn_stream_write(text_stream, buffer, &wlen));
340           if (wlen != rlen)
341             {
342               /* Uh oh, didn't write as many bytes as we read. */
343               return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
344                                       _("Unexpected EOF writing contents"));
345             }
346         }
347     }
348
349   /* If we opened a stream, we must close it. */
350   if (text_stream)
351     SVN_ERR(svn_stream_close(text_stream));
352
353   return SVN_NO_ERROR;
354 }
355
356
357
358 /* Parse VERSIONSTRING from STREAM and verify that we support the dumpfile
359    format version number, setting *VERSION appropriately. */
360 static svn_error_t *
361 parse_format_version(int *version,
362                      svn_stream_t *stream,
363                      apr_pool_t *scratch_pool)
364 {
365   static const int magic_len = sizeof(SVN_REPOS_DUMPFILE_MAGIC_HEADER) - 1;
366   svn_stringbuf_t *linebuf;
367   const char *p;
368   int value;
369
370   /* No svn_stream_readline() here, because malformed streams may not have
371      the EOL at all, and currently svn_stream_readline() keeps loading the
372      whole thing into memory until it encounters an EOL or the stream ends.
373      This is particularly troublesome, because users may incorrectly attempt
374      to load arbitrary large files instread of proper dump files.
375
376      As a workaround, parse the first line with a length limit.  While this
377      is not a complete solution, doing so handles the common case described
378      above.  For a complete solution, svn_stream_readline() may need to grow
379      a `limit` argument that would allow us to safely use it everywhere within
380      this parser.
381    */
382   linebuf = svn_stringbuf_create_empty(scratch_pool);
383   while (1)
384     {
385       apr_size_t len;
386       char c;
387
388       len = 1;
389       SVN_ERR(svn_stream_read_full(stream, &c, &len));
390       if (len != 1)
391         return stream_ran_dry();
392
393       if (c == '\n')
394         break;
395
396       if (linebuf->len + 1 > 80)
397         return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
398                                  _("Malformed dumpfile header '%s'"),
399                                  linebuf->data);
400
401       svn_stringbuf_appendbyte(linebuf, c);
402     }
403
404   p = strchr(linebuf->data, ':');
405
406   if (p == NULL
407       || p != (linebuf->data + magic_len)
408       || strncmp(linebuf->data,
409                  SVN_REPOS_DUMPFILE_MAGIC_HEADER,
410                  magic_len))
411     return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
412                              _("Malformed dumpfile header '%s'"),
413                              linebuf->data);
414
415   SVN_ERR(svn_cstring_atoi(&value, p + 1));
416
417   if (value > SVN_REPOS_DUMPFILE_FORMAT_VERSION)
418     return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
419                              _("Unsupported dumpfile version: %d"),
420                              value);
421
422   *version = value;
423   return SVN_NO_ERROR;
424 }
425
426 /*----------------------------------------------------------------------*/
427 \f
428 /** Dummy callback implementations for functions not provided by the user **/
429
430 static svn_error_t *
431 dummy_handler_magic_header_record(int version,
432                                   void *parse_baton,
433                                   apr_pool_t *pool)
434 {
435   return SVN_NO_ERROR;
436 }
437
438 static svn_error_t *
439 dummy_handler_uuid_record(const char *uuid,
440                           void *parse_baton,
441                           apr_pool_t *pool)
442 {
443   return SVN_NO_ERROR;
444 }
445
446 static svn_error_t *
447 dummy_handler_new_revision_record(void **revision_baton,
448                                   apr_hash_t *headers,
449                                   void *parse_baton,
450                                   apr_pool_t *pool)
451 {
452   *revision_baton = NULL;
453   return SVN_NO_ERROR;
454 }
455
456 static svn_error_t *
457 dummy_handler_new_node_record(void **node_baton,
458                               apr_hash_t *headers,
459                               void *revision_baton,
460                               apr_pool_t *pool)
461 {
462   *node_baton = NULL;
463   return SVN_NO_ERROR;
464 }
465
466 static svn_error_t *
467 dummy_handler_set_revision_property(void *revision_baton,
468                                     const char *name,
469                                     const svn_string_t *value)
470 {
471   return SVN_NO_ERROR;
472 }
473
474 static svn_error_t *
475 dummy_handler_set_node_property(void *node_baton,
476                                 const char *name,
477                                 const svn_string_t *value)
478 {
479   return SVN_NO_ERROR;
480 }
481
482 static svn_error_t *
483 dummy_handler_delete_node_property(void *node_baton,
484                                    const char *name)
485 {
486   return SVN_NO_ERROR;
487 }
488
489 static svn_error_t *
490 dummy_handler_remove_node_props(void *node_baton)
491 {
492   return SVN_NO_ERROR;
493 }
494
495 static svn_error_t *
496 dummy_handler_set_fulltext(svn_stream_t **stream,
497                                void *node_baton)
498 {
499   return SVN_NO_ERROR;
500 }
501
502 static svn_error_t *
503 dummy_handler_apply_textdelta(svn_txdelta_window_handler_t *handler,
504                               void **handler_baton,
505                               void *node_baton)
506 {
507   /* Only called by parse_text_block() and that tests for NULL handlers. */
508   *handler = NULL;
509   *handler_baton = NULL;
510   return SVN_NO_ERROR;
511 }
512
513 static svn_error_t *
514 dummy_handler_close_node(void *node_baton)
515 {
516   return SVN_NO_ERROR;
517 }
518
519 static svn_error_t *
520 dummy_handler_close_revision(void *revision_baton)
521 {
522   return SVN_NO_ERROR;
523 }
524
525 /* Helper macro to copy the function pointer SOURCE->NAME to DEST->NAME.
526  * If the source pointer is NULL, pick the corresponding dummy handler
527  * instead. */
528 #define SET_VTABLE_ENTRY(dest, source, name) \
529   dest->name = provided->name ? provided->name : dummy_handler_##name
530
531 /* Return a copy of PROVIDED with all NULL callbacks replaced by a dummy
532  * handler.  Allocate the result in RESULT_POOL. */
533 static const svn_repos_parse_fns3_t *
534 complete_vtable(const svn_repos_parse_fns3_t *provided,
535                 apr_pool_t *result_pool)
536 {
537   svn_repos_parse_fns3_t *completed = apr_pcalloc(result_pool,
538                                                   sizeof(*completed));
539
540   SET_VTABLE_ENTRY(completed, provided, magic_header_record);
541   SET_VTABLE_ENTRY(completed, provided, uuid_record);
542   SET_VTABLE_ENTRY(completed, provided, new_revision_record);
543   SET_VTABLE_ENTRY(completed, provided, new_node_record);
544   SET_VTABLE_ENTRY(completed, provided, set_revision_property);
545   SET_VTABLE_ENTRY(completed, provided, set_node_property);
546   SET_VTABLE_ENTRY(completed, provided, delete_node_property);
547   SET_VTABLE_ENTRY(completed, provided, remove_node_props);
548   SET_VTABLE_ENTRY(completed, provided, set_fulltext);
549   SET_VTABLE_ENTRY(completed, provided, apply_textdelta);
550   SET_VTABLE_ENTRY(completed, provided, close_node);
551   SET_VTABLE_ENTRY(completed, provided, close_revision);
552
553   return completed;
554 }
555
556 /*----------------------------------------------------------------------*/
557 \f
558 /** The public routines **/
559
560 svn_error_t *
561 svn_repos_parse_dumpstream3(svn_stream_t *stream,
562                             const svn_repos_parse_fns3_t *parse_fns,
563                             void *parse_baton,
564                             svn_boolean_t deltas_are_text,
565                             svn_cancel_func_t cancel_func,
566                             void *cancel_baton,
567                             apr_pool_t *pool)
568 {
569   svn_boolean_t eof;
570   svn_stringbuf_t *linebuf;
571   void *rev_baton = NULL;
572   char *buffer = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
573   apr_size_t buflen = SVN__STREAM_CHUNK_SIZE;
574   apr_pool_t *linepool = svn_pool_create(pool);
575   apr_pool_t *revpool = svn_pool_create(pool);
576   apr_pool_t *nodepool = svn_pool_create(pool);
577   int version;
578
579   /* Make sure we can blindly invoke callbacks. */
580   parse_fns = complete_vtable(parse_fns, pool);
581
582   /* Start parsing process. */
583   /* The first two lines of the stream are the dumpfile-format version
584      number, and a blank line.  To preserve backward compatibility,
585      don't assume the existence of newer parser-vtable functions. */
586   SVN_ERR(parse_format_version(&version, stream, linepool));
587   if (parse_fns->magic_header_record != NULL)
588     SVN_ERR(parse_fns->magic_header_record(version, parse_baton, pool));
589
590   /* A dumpfile "record" is defined to be a header-block of
591      rfc822-style headers, possibly followed by a content-block.
592
593        - A header-block is always terminated by a single blank line (\n\n)
594
595        - We know whether the record has a content-block by looking for
596          a 'Content-length:' header.  The content-block will always be
597          of a specific length, plus an extra newline.
598
599      Once a record is fully sucked from the stream, an indeterminate
600      number of blank lines (or lines that begin with whitespace) may
601      follow before the next record (or the end of the stream.)
602   */
603
604   while (1)
605     {
606       apr_hash_t *headers;
607       void *node_baton;
608       svn_boolean_t found_node = FALSE;
609       svn_boolean_t old_v1_with_cl = FALSE;
610       const char *content_length;
611       const char *prop_cl;
612       const char *text_cl;
613       const char *value;
614       svn_filesize_t actual_prop_length;
615
616       /* Clear our per-line pool. */
617       svn_pool_clear(linepool);
618
619       /* Check for cancellation. */
620       if (cancel_func)
621         SVN_ERR(cancel_func(cancel_baton));
622
623       /* Keep reading blank lines until we discover a new record, or until
624          the stream runs out. */
625       SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));
626
627       if (eof)
628         {
629           if (svn_stringbuf_isempty(linebuf))
630             break;   /* end of stream, go home. */
631           else
632             return stream_ran_dry();
633         }
634
635       if ((linebuf->len == 0) || (svn_ctype_isspace(linebuf->data[0])))
636         continue; /* empty line ... loop */
637
638       /*** Found the beginning of a new record. ***/
639
640       /* The last line we read better be a header of some sort.
641          Read the whole header-block into a hash. */
642       SVN_ERR(read_header_block(stream, linebuf, &headers, linepool));
643
644       /*** Handle the various header blocks. ***/
645
646       /* Is this a revision record? */
647       if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER))
648         {
649           /* If we already have a rev_baton open, we need to close it
650              and clear the per-revision subpool. */
651           if (rev_baton != NULL)
652             {
653               SVN_ERR(parse_fns->close_revision(rev_baton));
654               svn_pool_clear(revpool);
655             }
656
657           SVN_ERR(parse_fns->new_revision_record(&rev_baton,
658                                                  headers, parse_baton,
659                                                  revpool));
660         }
661       /* Or is this, perhaps, a node record? */
662       else if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH))
663         {
664           SVN_ERR(parse_fns->new_node_record(&node_baton,
665                                              headers,
666                                              rev_baton,
667                                              nodepool));
668           found_node = TRUE;
669         }
670       /* Or is this the repos UUID? */
671       else if ((value = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_UUID)))
672         {
673           SVN_ERR(parse_fns->uuid_record(value, parse_baton, pool));
674         }
675       /* Or perhaps a dumpfile format? */
676       /* ### TODO: use parse_format_version */
677       else if ((value = svn_hash_gets(headers,
678                                       SVN_REPOS_DUMPFILE_MAGIC_HEADER)))
679         {
680           /* ### someday, switch modes of operation here. */
681           SVN_ERR(svn_cstring_atoi(&version, value));
682         }
683       /* Or is this bogosity?! */
684       else
685         {
686           /* What the heck is this record?!? */
687           return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
688                                   _("Unrecognized record type in stream"));
689         }
690
691       /* Need 3 values below to determine v1 dump type
692
693          Old (pre 0.14?) v1 dumps don't have Prop-content-length
694          and Text-content-length fields, but always have a properties
695          block in a block with Content-Length > 0 */
696
697       content_length = svn_hash_gets(headers,
698                                      SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
699       prop_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
700       text_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
701       old_v1_with_cl =
702         version == 1 && content_length && ! prop_cl && ! text_cl;
703
704       /* Is there a props content-block to parse? */
705       if (prop_cl || old_v1_with_cl)
706         {
707           const char *delta = svn_hash_gets(headers,
708                                             SVN_REPOS_DUMPFILE_PROP_DELTA);
709           svn_boolean_t is_delta = (delta && strcmp(delta, "true") == 0);
710
711           /* First, remove all node properties, unless this is a delta
712              property block. */
713           if (found_node && !is_delta)
714             SVN_ERR(parse_fns->remove_node_props(node_baton));
715
716           SVN_ERR(parse_property_block
717                   (stream,
718                    svn__atoui64(prop_cl ? prop_cl : content_length),
719                    parse_fns,
720                    found_node ? node_baton : rev_baton,
721                    parse_baton,
722                    found_node,
723                    &actual_prop_length,
724                    found_node ? nodepool : revpool));
725         }
726
727       /* Is there a text content-block to parse? */
728       if (text_cl)
729         {
730           const char *delta = svn_hash_gets(headers,
731                                             SVN_REPOS_DUMPFILE_TEXT_DELTA);
732           svn_boolean_t is_delta = FALSE;
733           if (! deltas_are_text)
734             is_delta = (delta && strcmp(delta, "true") == 0);
735
736           SVN_ERR(parse_text_block(stream,
737                                    svn__atoui64(text_cl),
738                                    is_delta,
739                                    parse_fns,
740                                    found_node ? node_baton : rev_baton,
741                                    buffer,
742                                    buflen,
743                                    found_node ? nodepool : revpool));
744         }
745       else if (old_v1_with_cl)
746         {
747           /* An old-v1 block with a Content-length might have a text block.
748              If the property block did not consume all the bytes of the
749              Content-length, then it clearly does have a text block.
750              If not, then we must deduce whether we have an *empty* text
751              block or an *absent* text block.  The rules are:
752              - "Node-kind: file" blocks have an empty (i.e. present, but
753                zero-length) text block, since they represent a file
754                modification.  Note that file-copied-text-unmodified blocks
755                have no Content-length - even if they should have contained
756                a modified property block, the pre-0.14 dumper forgets to
757                dump the modified properties.
758              - If it is not a file node, then it is a revision or directory,
759                and so has an absent text block.
760           */
761           const char *node_kind;
762           svn_filesize_t cl_value = svn__atoui64(content_length)
763                                     - actual_prop_length;
764
765           if (cl_value ||
766               ((node_kind = svn_hash_gets(headers,
767                                           SVN_REPOS_DUMPFILE_NODE_KIND))
768                && strcmp(node_kind, "file") == 0)
769              )
770             SVN_ERR(parse_text_block(stream,
771                                      cl_value,
772                                      FALSE,
773                                      parse_fns,
774                                      found_node ? node_baton : rev_baton,
775                                      buffer,
776                                      buflen,
777                                      found_node ? nodepool : revpool));
778         }
779
780       /* if we have a content-length header, did we read all of it?
781          in case of an old v1, we *always* read all of it, because
782          text-content-length == content-length - prop-content-length
783       */
784       if (content_length && ! old_v1_with_cl)
785         {
786           apr_size_t rlen, num_to_read;
787           svn_filesize_t remaining =
788             svn__atoui64(content_length) -
789             (prop_cl ? svn__atoui64(prop_cl) : 0) -
790             (text_cl ? svn__atoui64(text_cl) : 0);
791
792
793           if (remaining < 0)
794             return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
795                                     _("Sum of subblock sizes larger than "
796                                       "total block content length"));
797
798           /* Consume remaining bytes in this content block */
799           while (remaining > 0)
800             {
801               if (remaining >= (svn_filesize_t)buflen)
802                 rlen = buflen;
803               else
804                 rlen = (apr_size_t) remaining;
805
806               num_to_read = rlen;
807               SVN_ERR(svn_stream_read_full(stream, buffer, &rlen));
808               remaining -= rlen;
809               if (rlen != num_to_read)
810                 return stream_ran_dry();
811             }
812         }
813
814       /* If we just finished processing a node record, we need to
815          close the node record and clear the per-node subpool. */
816       if (found_node)
817         {
818           SVN_ERR(parse_fns->close_node(node_baton));
819           svn_pool_clear(nodepool);
820         }
821
822       /*** End of processing for one record. ***/
823
824     } /* end of stream */
825
826   /* Close out whatever revision we're in. */
827   if (rev_baton != NULL)
828     SVN_ERR(parse_fns->close_revision(rev_baton));
829
830   svn_pool_destroy(linepool);
831   svn_pool_destroy(revpool);
832   svn_pool_destroy(nodepool);
833   return SVN_NO_ERROR;
834 }