]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_repos/load.c
Import DTS files from Linux 5.3
[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 and verify that we support the dumpfile format
359    version number, setting *VERSION appropriately. */
360 static svn_error_t *
361 parse_format_version(int *version,
362                      const char *versionstring)
363 {
364   static const int magic_len = sizeof(SVN_REPOS_DUMPFILE_MAGIC_HEADER) - 1;
365   const char *p = strchr(versionstring, ':');
366   int value;
367
368   if (p == NULL
369       || p != (versionstring + magic_len)
370       || strncmp(versionstring,
371                  SVN_REPOS_DUMPFILE_MAGIC_HEADER,
372                  magic_len))
373     return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
374                              _("Malformed dumpfile header '%s'"),
375                              versionstring);
376
377   SVN_ERR(svn_cstring_atoi(&value, p + 1));
378
379   if (value > SVN_REPOS_DUMPFILE_FORMAT_VERSION)
380     return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
381                              _("Unsupported dumpfile version: %d"),
382                              value);
383
384   *version = value;
385   return SVN_NO_ERROR;
386 }
387
388 /*----------------------------------------------------------------------*/
389 \f
390 /** Dummy callback implementations for functions not provided by the user **/
391
392 static svn_error_t *
393 dummy_handler_magic_header_record(int version,
394                                   void *parse_baton,
395                                   apr_pool_t *pool)
396 {
397   return SVN_NO_ERROR;
398 }
399
400 static svn_error_t *
401 dummy_handler_uuid_record(const char *uuid,
402                           void *parse_baton,
403                           apr_pool_t *pool)
404 {
405   return SVN_NO_ERROR;
406 }
407
408 static svn_error_t *
409 dummy_handler_new_revision_record(void **revision_baton,
410                                   apr_hash_t *headers,
411                                   void *parse_baton,
412                                   apr_pool_t *pool)
413 {
414   *revision_baton = NULL;
415   return SVN_NO_ERROR;
416 }
417
418 static svn_error_t *
419 dummy_handler_new_node_record(void **node_baton,
420                               apr_hash_t *headers,
421                               void *revision_baton,
422                               apr_pool_t *pool)
423 {
424   *node_baton = NULL;
425   return SVN_NO_ERROR;
426 }
427
428 static svn_error_t *
429 dummy_handler_set_revision_property(void *revision_baton,
430                                     const char *name,
431                                     const svn_string_t *value)
432 {
433   return SVN_NO_ERROR;
434 }
435
436 static svn_error_t *
437 dummy_handler_set_node_property(void *node_baton,
438                                 const char *name,
439                                 const svn_string_t *value)
440 {
441   return SVN_NO_ERROR;
442 }
443
444 static svn_error_t *
445 dummy_handler_delete_node_property(void *node_baton,
446                                    const char *name)
447 {
448   return SVN_NO_ERROR;
449 }
450
451 static svn_error_t *
452 dummy_handler_remove_node_props(void *node_baton)
453 {
454   return SVN_NO_ERROR;
455 }
456
457 static svn_error_t *
458 dummy_handler_set_fulltext(svn_stream_t **stream,
459                                void *node_baton)
460 {
461   return SVN_NO_ERROR;
462 }
463
464 static svn_error_t *
465 dummy_handler_apply_textdelta(svn_txdelta_window_handler_t *handler,
466                               void **handler_baton,
467                               void *node_baton)
468 {
469   /* Only called by parse_text_block() and that tests for NULL handlers. */
470   *handler = NULL;
471   *handler_baton = NULL;
472   return SVN_NO_ERROR;
473 }
474
475 static svn_error_t *
476 dummy_handler_close_node(void *node_baton)
477 {
478   return SVN_NO_ERROR;
479 }
480
481 static svn_error_t *
482 dummy_handler_close_revision(void *revision_baton)
483 {
484   return SVN_NO_ERROR;
485 }
486
487 /* Helper macro to copy the function pointer SOURCE->NAME to DEST->NAME.
488  * If the source pointer is NULL, pick the corresponding dummy handler
489  * instead. */
490 #define SET_VTABLE_ENTRY(dest, source, name) \
491   dest->name = provided->name ? provided->name : dummy_handler_##name
492
493 /* Return a copy of PROVIDED with all NULL callbacks replaced by a dummy
494  * handler.  Allocate the result in RESULT_POOL. */
495 static const svn_repos_parse_fns3_t *
496 complete_vtable(const svn_repos_parse_fns3_t *provided,
497                 apr_pool_t *result_pool)
498 {
499   svn_repos_parse_fns3_t *completed = apr_pcalloc(result_pool,
500                                                   sizeof(*completed));
501
502   SET_VTABLE_ENTRY(completed, provided, magic_header_record);
503   SET_VTABLE_ENTRY(completed, provided, uuid_record);
504   SET_VTABLE_ENTRY(completed, provided, new_revision_record);
505   SET_VTABLE_ENTRY(completed, provided, new_node_record);
506   SET_VTABLE_ENTRY(completed, provided, set_revision_property);
507   SET_VTABLE_ENTRY(completed, provided, set_node_property);
508   SET_VTABLE_ENTRY(completed, provided, delete_node_property);
509   SET_VTABLE_ENTRY(completed, provided, remove_node_props);
510   SET_VTABLE_ENTRY(completed, provided, set_fulltext);
511   SET_VTABLE_ENTRY(completed, provided, apply_textdelta);
512   SET_VTABLE_ENTRY(completed, provided, close_node);
513   SET_VTABLE_ENTRY(completed, provided, close_revision);
514
515   return completed;
516 }
517
518 /*----------------------------------------------------------------------*/
519 \f
520 /** The public routines **/
521
522 svn_error_t *
523 svn_repos_parse_dumpstream3(svn_stream_t *stream,
524                             const svn_repos_parse_fns3_t *parse_fns,
525                             void *parse_baton,
526                             svn_boolean_t deltas_are_text,
527                             svn_cancel_func_t cancel_func,
528                             void *cancel_baton,
529                             apr_pool_t *pool)
530 {
531   svn_boolean_t eof;
532   svn_stringbuf_t *linebuf;
533   void *rev_baton = NULL;
534   char *buffer = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
535   apr_size_t buflen = SVN__STREAM_CHUNK_SIZE;
536   apr_pool_t *linepool = svn_pool_create(pool);
537   apr_pool_t *revpool = svn_pool_create(pool);
538   apr_pool_t *nodepool = svn_pool_create(pool);
539   int version;
540
541   /* Make sure we can blindly invoke callbacks. */
542   parse_fns = complete_vtable(parse_fns, pool);
543
544   /* Start parsing process. */
545   SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));
546   if (eof)
547     return stream_ran_dry();
548
549   /* The first two lines of the stream are the dumpfile-format version
550      number, and a blank line.  To preserve backward compatibility,
551      don't assume the existence of newer parser-vtable functions. */
552   SVN_ERR(parse_format_version(&version, linebuf->data));
553   if (parse_fns->magic_header_record != NULL)
554     SVN_ERR(parse_fns->magic_header_record(version, parse_baton, pool));
555
556   /* A dumpfile "record" is defined to be a header-block of
557      rfc822-style headers, possibly followed by a content-block.
558
559        - A header-block is always terminated by a single blank line (\n\n)
560
561        - We know whether the record has a content-block by looking for
562          a 'Content-length:' header.  The content-block will always be
563          of a specific length, plus an extra newline.
564
565      Once a record is fully sucked from the stream, an indeterminate
566      number of blank lines (or lines that begin with whitespace) may
567      follow before the next record (or the end of the stream.)
568   */
569
570   while (1)
571     {
572       apr_hash_t *headers;
573       void *node_baton;
574       svn_boolean_t found_node = FALSE;
575       svn_boolean_t old_v1_with_cl = FALSE;
576       const char *content_length;
577       const char *prop_cl;
578       const char *text_cl;
579       const char *value;
580       svn_filesize_t actual_prop_length;
581
582       /* Clear our per-line pool. */
583       svn_pool_clear(linepool);
584
585       /* Check for cancellation. */
586       if (cancel_func)
587         SVN_ERR(cancel_func(cancel_baton));
588
589       /* Keep reading blank lines until we discover a new record, or until
590          the stream runs out. */
591       SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));
592
593       if (eof)
594         {
595           if (svn_stringbuf_isempty(linebuf))
596             break;   /* end of stream, go home. */
597           else
598             return stream_ran_dry();
599         }
600
601       if ((linebuf->len == 0) || (svn_ctype_isspace(linebuf->data[0])))
602         continue; /* empty line ... loop */
603
604       /*** Found the beginning of a new record. ***/
605
606       /* The last line we read better be a header of some sort.
607          Read the whole header-block into a hash. */
608       SVN_ERR(read_header_block(stream, linebuf, &headers, linepool));
609
610       /*** Handle the various header blocks. ***/
611
612       /* Is this a revision record? */
613       if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER))
614         {
615           /* If we already have a rev_baton open, we need to close it
616              and clear the per-revision subpool. */
617           if (rev_baton != NULL)
618             {
619               SVN_ERR(parse_fns->close_revision(rev_baton));
620               svn_pool_clear(revpool);
621             }
622
623           SVN_ERR(parse_fns->new_revision_record(&rev_baton,
624                                                  headers, parse_baton,
625                                                  revpool));
626         }
627       /* Or is this, perhaps, a node record? */
628       else if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH))
629         {
630           SVN_ERR(parse_fns->new_node_record(&node_baton,
631                                              headers,
632                                              rev_baton,
633                                              nodepool));
634           found_node = TRUE;
635         }
636       /* Or is this the repos UUID? */
637       else if ((value = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_UUID)))
638         {
639           SVN_ERR(parse_fns->uuid_record(value, parse_baton, pool));
640         }
641       /* Or perhaps a dumpfile format? */
642       /* ### TODO: use parse_format_version */
643       else if ((value = svn_hash_gets(headers,
644                                       SVN_REPOS_DUMPFILE_MAGIC_HEADER)))
645         {
646           /* ### someday, switch modes of operation here. */
647           SVN_ERR(svn_cstring_atoi(&version, value));
648         }
649       /* Or is this bogosity?! */
650       else
651         {
652           /* What the heck is this record?!? */
653           return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
654                                   _("Unrecognized record type in stream"));
655         }
656
657       /* Need 3 values below to determine v1 dump type
658
659          Old (pre 0.14?) v1 dumps don't have Prop-content-length
660          and Text-content-length fields, but always have a properties
661          block in a block with Content-Length > 0 */
662
663       content_length = svn_hash_gets(headers,
664                                      SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
665       prop_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
666       text_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
667       old_v1_with_cl =
668         version == 1 && content_length && ! prop_cl && ! text_cl;
669
670       /* Is there a props content-block to parse? */
671       if (prop_cl || old_v1_with_cl)
672         {
673           const char *delta = svn_hash_gets(headers,
674                                             SVN_REPOS_DUMPFILE_PROP_DELTA);
675           svn_boolean_t is_delta = (delta && strcmp(delta, "true") == 0);
676
677           /* First, remove all node properties, unless this is a delta
678              property block. */
679           if (found_node && !is_delta)
680             SVN_ERR(parse_fns->remove_node_props(node_baton));
681
682           SVN_ERR(parse_property_block
683                   (stream,
684                    svn__atoui64(prop_cl ? prop_cl : content_length),
685                    parse_fns,
686                    found_node ? node_baton : rev_baton,
687                    parse_baton,
688                    found_node,
689                    &actual_prop_length,
690                    found_node ? nodepool : revpool));
691         }
692
693       /* Is there a text content-block to parse? */
694       if (text_cl)
695         {
696           const char *delta = svn_hash_gets(headers,
697                                             SVN_REPOS_DUMPFILE_TEXT_DELTA);
698           svn_boolean_t is_delta = FALSE;
699           if (! deltas_are_text)
700             is_delta = (delta && strcmp(delta, "true") == 0);
701
702           SVN_ERR(parse_text_block(stream,
703                                    svn__atoui64(text_cl),
704                                    is_delta,
705                                    parse_fns,
706                                    found_node ? node_baton : rev_baton,
707                                    buffer,
708                                    buflen,
709                                    found_node ? nodepool : revpool));
710         }
711       else if (old_v1_with_cl)
712         {
713           /* An old-v1 block with a Content-length might have a text block.
714              If the property block did not consume all the bytes of the
715              Content-length, then it clearly does have a text block.
716              If not, then we must deduce whether we have an *empty* text
717              block or an *absent* text block.  The rules are:
718              - "Node-kind: file" blocks have an empty (i.e. present, but
719                zero-length) text block, since they represent a file
720                modification.  Note that file-copied-text-unmodified blocks
721                have no Content-length - even if they should have contained
722                a modified property block, the pre-0.14 dumper forgets to
723                dump the modified properties.
724              - If it is not a file node, then it is a revision or directory,
725                and so has an absent text block.
726           */
727           const char *node_kind;
728           svn_filesize_t cl_value = svn__atoui64(content_length)
729                                     - actual_prop_length;
730
731           if (cl_value ||
732               ((node_kind = svn_hash_gets(headers,
733                                           SVN_REPOS_DUMPFILE_NODE_KIND))
734                && strcmp(node_kind, "file") == 0)
735              )
736             SVN_ERR(parse_text_block(stream,
737                                      cl_value,
738                                      FALSE,
739                                      parse_fns,
740                                      found_node ? node_baton : rev_baton,
741                                      buffer,
742                                      buflen,
743                                      found_node ? nodepool : revpool));
744         }
745
746       /* if we have a content-length header, did we read all of it?
747          in case of an old v1, we *always* read all of it, because
748          text-content-length == content-length - prop-content-length
749       */
750       if (content_length && ! old_v1_with_cl)
751         {
752           apr_size_t rlen, num_to_read;
753           svn_filesize_t remaining =
754             svn__atoui64(content_length) -
755             (prop_cl ? svn__atoui64(prop_cl) : 0) -
756             (text_cl ? svn__atoui64(text_cl) : 0);
757
758
759           if (remaining < 0)
760             return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
761                                     _("Sum of subblock sizes larger than "
762                                       "total block content length"));
763
764           /* Consume remaining bytes in this content block */
765           while (remaining > 0)
766             {
767               if (remaining >= (svn_filesize_t)buflen)
768                 rlen = buflen;
769               else
770                 rlen = (apr_size_t) remaining;
771
772               num_to_read = rlen;
773               SVN_ERR(svn_stream_read_full(stream, buffer, &rlen));
774               remaining -= rlen;
775               if (rlen != num_to_read)
776                 return stream_ran_dry();
777             }
778         }
779
780       /* If we just finished processing a node record, we need to
781          close the node record and clear the per-node subpool. */
782       if (found_node)
783         {
784           SVN_ERR(parse_fns->close_node(node_baton));
785           svn_pool_clear(nodepool);
786         }
787
788       /*** End of processing for one record. ***/
789
790     } /* end of stream */
791
792   /* Close out whatever revision we're in. */
793   if (rev_baton != NULL)
794     SVN_ERR(parse_fns->close_revision(rev_baton));
795
796   svn_pool_destroy(linepool);
797   svn_pool_destroy(revpool);
798   svn_pool_destroy(nodepool);
799   return SVN_NO_ERROR;
800 }