#include "svn_private_config.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_fs.h"
#include "svn_repos.h"
#include "svn_string.h"
#include "svn_path.h"
#include "svn_props.h"
#include "repos.h"
#include "svn_private_config.h"
#include "svn_mergeinfo.h"
#include "svn_checksum.h"
#include "svn_subst.h"
#include "svn_ctype.h"
#include <apr_lib.h>
#include "private/svn_dep_compat.h"
#include "private/svn_mergeinfo_private.h"
static svn_error_t *
stream_ran_dry(void)
{
return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
_("Premature end of content data in dumpstream"));
}
static svn_error_t *
stream_malformed(void)
{
return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
_("Dumpstream data appears to be malformed"));
}
static svn_error_t *
read_header_block(svn_stream_t *stream,
svn_stringbuf_t *first_header,
apr_hash_t **headers,
apr_pool_t *pool)
{
*headers = apr_hash_make(pool);
while (1)
{
svn_stringbuf_t *header_str;
const char *name, *value;
svn_boolean_t eof;
apr_size_t i = 0;
if (first_header != NULL)
{
header_str = first_header;
first_header = NULL;
eof = FALSE;
}
else
SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
if (svn_stringbuf_isempty(header_str))
break;
else if (eof)
return stream_ran_dry();
while (header_str->data[i] != ':')
{
if (header_str->data[i] == '\0')
return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
_("Dump stream contains a malformed "
"header (with no ':') at '%.20s'"),
header_str->data);
i++;
}
header_str->data[i] = '\0';
name = header_str->data;
i += 2;
if (i > header_str->len)
return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
_("Dump stream contains a malformed "
"header (with no value) at '%.20s'"),
header_str->data);
value = header_str->data + i;
apr_hash_set(*headers, name, APR_HASH_KEY_STRING, value);
}
return SVN_NO_ERROR;
}
static svn_error_t *
read_key_or_val(char **pbuf,
svn_filesize_t *actual_length,
svn_stream_t *stream,
apr_size_t len,
apr_pool_t *pool)
{
char *buf = apr_pcalloc(pool, len + 1);
apr_size_t numread;
char c;
numread = len;
SVN_ERR(svn_stream_read(stream, buf, &numread));
*actual_length += numread;
if (numread != len)
return svn_error_trace(stream_ran_dry());
buf[len] = '\0';
numread = 1;
SVN_ERR(svn_stream_read(stream, &c, &numread));
*actual_length += numread;
if (numread != 1)
return svn_error_trace(stream_ran_dry());
if (c != '\n')
return svn_error_trace(stream_malformed());
*pbuf = buf;
return SVN_NO_ERROR;
}
static svn_error_t *
parse_property_block(svn_stream_t *stream,
svn_filesize_t content_length,
const svn_repos_parse_fns2_t *parse_fns,
void *record_baton,
void *parse_baton,
svn_boolean_t is_node,
svn_filesize_t *actual_length,
apr_pool_t *pool)
{
svn_stringbuf_t *strbuf;
apr_pool_t *proppool = svn_pool_create(pool);
*actual_length = 0;
while (content_length != *actual_length)
{
char *buf;
svn_boolean_t eof;
svn_pool_clear(proppool);
SVN_ERR(svn_stream_readline(stream, &strbuf, "\n", &eof, proppool));
if (eof)
{
return svn_error_create
(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
_("Incomplete or unterminated property block"));
}
*actual_length += (strbuf->len + 1);
buf = strbuf->data;
if (! strcmp(buf, "PROPS-END"))
break;
else if ((buf[0] == 'K') && (buf[1] == ' '))
{
char *keybuf;
apr_uint64_t len;
SVN_ERR(svn_cstring_strtoui64(&len, buf + 2, 0, APR_SIZE_MAX, 10));
SVN_ERR(read_key_or_val(&keybuf, actual_length,
stream, (apr_size_t)len, proppool));
SVN_ERR(svn_stream_readline(stream, &strbuf, "\n", &eof, proppool));
if (eof)
return stream_ran_dry();
*actual_length += (strbuf->len + 1);
buf = strbuf->data;
if ((buf[0] == 'V') && (buf[1] == ' '))
{
svn_string_t propstring;
char *valbuf;
apr_int64_t val;
SVN_ERR(svn_cstring_atoi64(&val, buf + 2));
propstring.len = (apr_size_t)val;
SVN_ERR(read_key_or_val(&valbuf, actual_length,
stream, propstring.len, proppool));
propstring.data = valbuf;
if (is_node)
{
SVN_ERR(parse_fns->set_node_property(record_baton,
keybuf,
&propstring));
}
else
{
SVN_ERR(parse_fns->set_revision_property(record_baton,
keybuf,
&propstring));
}
}
else
return stream_malformed();
}
else if ((buf[0] == 'D') && (buf[1] == ' '))
{
char *keybuf;
apr_uint64_t len;
SVN_ERR(svn_cstring_strtoui64(&len, buf + 2, 0, APR_SIZE_MAX, 10));
SVN_ERR(read_key_or_val(&keybuf, actual_length,
stream, (apr_size_t)len, proppool));
if (!is_node || !parse_fns->delete_node_property)
return stream_malformed();
SVN_ERR(parse_fns->delete_node_property(record_baton, keybuf));
}
else
return stream_malformed();
}
svn_pool_destroy(proppool);
return SVN_NO_ERROR;
}
static svn_error_t *
parse_text_block(svn_stream_t *stream,
svn_filesize_t content_length,
svn_boolean_t is_delta,
const svn_repos_parse_fns2_t *parse_fns,
void *record_baton,
char *buffer,
apr_size_t buflen,
apr_pool_t *pool)
{
svn_stream_t *text_stream = NULL;
apr_size_t num_to_read, rlen, wlen;
if (is_delta)
{
svn_txdelta_window_handler_t wh;
void *whb;
SVN_ERR(parse_fns->apply_textdelta(&wh, &whb, record_baton));
if (wh)
text_stream = svn_txdelta_parse_svndiff(wh, whb, TRUE, pool);
}
else
{
SVN_ERR(parse_fns->set_fulltext(&text_stream, record_baton));
}
if (content_length == 0)
{
wlen = 0;
if (text_stream)
SVN_ERR(svn_stream_write(text_stream, "", &wlen));
}
while (content_length)
{
if (content_length >= buflen)
rlen = buflen;
else
rlen = (apr_size_t) content_length;
num_to_read = rlen;
SVN_ERR(svn_stream_read(stream, buffer, &rlen));
content_length -= rlen;
if (rlen != num_to_read)
return stream_ran_dry();
if (text_stream)
{
wlen = rlen;
SVN_ERR(svn_stream_write(text_stream, buffer, &wlen));
if (wlen != rlen)
{
return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
_("Unexpected EOF writing contents"));
}
}
}
if (text_stream)
SVN_ERR(svn_stream_close(text_stream));
return SVN_NO_ERROR;
}
static svn_error_t *
parse_format_version(const char *versionstring, int *version)
{
static const int magic_len = sizeof(SVN_REPOS_DUMPFILE_MAGIC_HEADER) - 1;
const char *p = strchr(versionstring, ':');
int value;
if (p == NULL
|| p != (versionstring + magic_len)
|| strncmp(versionstring,
SVN_REPOS_DUMPFILE_MAGIC_HEADER,
magic_len))
return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
_("Malformed dumpfile header '%s'"),
versionstring);
SVN_ERR(svn_cstring_atoi(&value, p + 1));
if (value > SVN_REPOS_DUMPFILE_FORMAT_VERSION)
return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
_("Unsupported dumpfile version: %d"),
value);
*version = value;
return SVN_NO_ERROR;
}
svn_error_t *
svn_repos_parse_dumpstream2(svn_stream_t *stream,
const svn_repos_parse_fns2_t *parse_fns,
void *parse_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
{
svn_boolean_t eof;
svn_stringbuf_t *linebuf;
void *rev_baton = NULL;
char *buffer = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
apr_size_t buflen = SVN__STREAM_CHUNK_SIZE;
apr_pool_t *linepool = svn_pool_create(pool);
apr_pool_t *revpool = svn_pool_create(pool);
apr_pool_t *nodepool = svn_pool_create(pool);
int version;
SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));
if (eof)
return stream_ran_dry();
SVN_ERR(parse_format_version(linebuf->data, &version));
if (version == SVN_REPOS_DUMPFILE_FORMAT_VERSION
&& (!parse_fns->delete_node_property || !parse_fns->apply_textdelta))
return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
_("Unsupported dumpfile version: %d"), version);
while (1)
{
apr_hash_t *headers;
void *node_baton;
svn_boolean_t found_node = FALSE;
svn_boolean_t old_v1_with_cl = FALSE;
const char *content_length;
const char *prop_cl;
const char *text_cl;
const char *value;
svn_filesize_t actual_prop_length;
svn_pool_clear(linepool);
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));
if (eof)
{
if (svn_stringbuf_isempty(linebuf))
break;
else
return stream_ran_dry();
}
if ((linebuf->len == 0) || (svn_ctype_isspace(linebuf->data[0])))
continue;
SVN_ERR(read_header_block(stream, linebuf, &headers, linepool));
if (apr_hash_get(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER,
APR_HASH_KEY_STRING))
{
if (rev_baton != NULL)
{
SVN_ERR(parse_fns->close_revision(rev_baton));
svn_pool_clear(revpool);
}
SVN_ERR(parse_fns->new_revision_record(&rev_baton,
headers, parse_baton,
revpool));
}
else if (apr_hash_get(headers, SVN_REPOS_DUMPFILE_NODE_PATH,
APR_HASH_KEY_STRING))
{
SVN_ERR(parse_fns->new_node_record(&node_baton,
headers,
rev_baton,
nodepool));
found_node = TRUE;
}
else if ((value = apr_hash_get(headers, SVN_REPOS_DUMPFILE_UUID,
APR_HASH_KEY_STRING)))
{
SVN_ERR(parse_fns->uuid_record(value, parse_baton, pool));
}
else if ((value = apr_hash_get(headers,
SVN_REPOS_DUMPFILE_MAGIC_HEADER,
APR_HASH_KEY_STRING)))
{
SVN_ERR(svn_cstring_atoi(&version, value));
}
else
{
return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
_("Unrecognized record type in stream"));
}
content_length = apr_hash_get(headers,
SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
APR_HASH_KEY_STRING);
prop_cl = apr_hash_get(headers,
SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
APR_HASH_KEY_STRING);
text_cl = apr_hash_get(headers,
SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH,
APR_HASH_KEY_STRING);
old_v1_with_cl =
version == 1 && content_length && ! prop_cl && ! text_cl;
if (prop_cl || old_v1_with_cl)
{
const char *delta = apr_hash_get(headers,
SVN_REPOS_DUMPFILE_PROP_DELTA,
APR_HASH_KEY_STRING);
svn_boolean_t is_delta = (delta && strcmp(delta, "true") == 0);
if (found_node && !is_delta)
SVN_ERR(parse_fns->remove_node_props(node_baton));
SVN_ERR(parse_property_block
(stream,
svn__atoui64(prop_cl ? prop_cl : content_length),
parse_fns,
found_node ? node_baton : rev_baton,
parse_baton,
found_node,
&actual_prop_length,
found_node ? nodepool : revpool));
}
if (text_cl)
{
const char *delta = apr_hash_get(headers,
SVN_REPOS_DUMPFILE_TEXT_DELTA,
APR_HASH_KEY_STRING);
svn_boolean_t is_delta = (delta && strcmp(delta, "true") == 0);
SVN_ERR(parse_text_block(stream,
svn__atoui64(text_cl),
is_delta,
parse_fns,
found_node ? node_baton : rev_baton,
buffer,
buflen,
found_node ? nodepool : revpool));
}
else if (old_v1_with_cl)
{
const char *node_kind;
svn_filesize_t cl_value = svn__atoui64(content_length)
- actual_prop_length;
if (cl_value ||
((node_kind = apr_hash_get(headers,
SVN_REPOS_DUMPFILE_NODE_KIND,
APR_HASH_KEY_STRING))
&& strcmp(node_kind, "file") == 0)
)
SVN_ERR(parse_text_block(stream,
cl_value,
FALSE,
parse_fns,
found_node ? node_baton : rev_baton,
buffer,
buflen,
found_node ? nodepool : revpool));
}
if (content_length && ! old_v1_with_cl)
{
apr_size_t rlen, num_to_read;
svn_filesize_t remaining =
svn__atoui64(content_length) -
(prop_cl ? svn__atoui64(prop_cl) : 0) -
(text_cl ? svn__atoui64(text_cl) : 0);
if (remaining < 0)
return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
_("Sum of subblock sizes larger than "
"total block content length"));
while (remaining > 0)
{
if (remaining >= buflen)
rlen = buflen;
else
rlen = (apr_size_t) remaining;
num_to_read = rlen;
SVN_ERR(svn_stream_read(stream, buffer, &rlen));
remaining -= rlen;
if (rlen != num_to_read)
return stream_ran_dry();
}
}
if (found_node)
{
SVN_ERR(parse_fns->close_node(node_baton));
svn_pool_clear(nodepool);
}
}
if (rev_baton != NULL)
SVN_ERR(parse_fns->close_revision(rev_baton));
svn_pool_destroy(linepool);
svn_pool_destroy(revpool);
svn_pool_destroy(nodepool);
return SVN_NO_ERROR;
}