Exemple #1
0
/* Set *PBUF to a string of length LEN, allocated in POOL, read from STREAM.
   Also read a newline from STREAM and increase *ACTUAL_LEN by the total
   number of bytes read from STREAM.  */
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';

    /* Suck up extra newline after key data */
    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;
}
Exemple #2
0
svn_error_t *
svn_string_from_stream(svn_string_t **result,
                       svn_stream_t *stream,
                       apr_pool_t *result_pool,
                       apr_pool_t *scratch_pool)
{
  svn_stringbuf_t *work = svn_stringbuf_create_ensure(SVN__STREAM_CHUNK_SIZE,
                                                      result_pool);
  char *buffer = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);

  while (1)
    {
      apr_size_t len = SVN__STREAM_CHUNK_SIZE;

      SVN_ERR(svn_stream_read(stream, buffer, &len));
      svn_stringbuf_appendbytes(work, buffer, len);

      if (len < SVN__STREAM_CHUNK_SIZE)
        break;
    }

  SVN_ERR(svn_stream_close(stream));

  *result = apr_palloc(result_pool, sizeof(**result));
  (*result)->data = work->data;
  (*result)->len = work->len;

  return SVN_NO_ERROR;
}
Exemple #3
0
static svn_error_t *
parse_spool_file(svn_ra_neon__session_t *ras,
                 const char *spool_file_name,
                 ne_xml_parser *success_parser,
                 apr_pool_t *pool)
{
  svn_stream_t *spool_stream;
  char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
  apr_size_t len;

  SVN_ERR(svn_stream_open_readonly(&spool_stream, spool_file_name, pool, pool));
  while (1)
    {
      if (ras->callbacks &&
          ras->callbacks->cancel_func)
        SVN_ERR((ras->callbacks->cancel_func)(ras->callback_baton));

      len = SVN__STREAM_CHUNK_SIZE;
      SVN_ERR(svn_stream_read(spool_stream, buf, &len));
      if (len > 0)
        if (ne_xml_parse(success_parser, buf, len) != 0)
          /* The parse encountered an error or
             was aborted by a user defined callback */
          break;

      if (len != SVN__STREAM_CHUNK_SIZE)
        break;
    }
  return svn_stream_close(spool_stream);
}
Exemple #4
0
static svn_error_t *
test_spillbuf_stream(apr_pool_t *pool)
{
  svn_stream_t *stream = svn_stream__from_spillbuf(8 /* blocksize */,
                                                   15 /* maxsize */,
                                                   pool);
  char readbuf[256];
  apr_size_t readlen;
  apr_size_t writelen;

  writelen = 6;
  SVN_ERR(svn_stream_write(stream, "abcdef", &writelen));
  SVN_ERR(svn_stream_write(stream, "ghijkl", &writelen));
  /* now: two blocks: 8 and 4 bytes  */

  readlen = 8;
  SVN_ERR(svn_stream_read(stream, readbuf, &readlen));
  SVN_TEST_ASSERT(readlen == 8
                  && memcmp(readbuf, "abcdefgh", 8) == 0);
  /* now: one block: 4 bytes  */

  SVN_ERR(svn_stream_write(stream, "mnopqr", &writelen));
  /* now: two blocks: 8 and 2 bytes  */

  SVN_ERR(svn_stream_read(stream, readbuf, &readlen));
  SVN_TEST_ASSERT(readlen == 8
                  && memcmp(readbuf, "ijklmnop", 8) == 0);
  /* now: one block: 2 bytes  */

  SVN_ERR(svn_stream_write(stream, "stuvwx", &writelen));
  SVN_ERR(svn_stream_write(stream, "ABCDEF", &writelen));
  SVN_ERR(svn_stream_write(stream, "GHIJKL", &writelen));
  /* now: two blocks: 8 and 6 bytes, and 6 bytes spilled to a file  */

  SVN_ERR(svn_stream_read(stream, readbuf, &readlen));
  SVN_TEST_ASSERT(readlen == 8
                  && memcmp(readbuf, "qrstuvwx", 8) == 0);
  readlen = 6;
  SVN_ERR(svn_stream_read(stream, readbuf, &readlen));
  SVN_TEST_ASSERT(readlen == 6
                  && memcmp(readbuf, "ABCDEF", 6) == 0);
  SVN_ERR(svn_stream_read(stream, readbuf, &readlen));
  SVN_TEST_ASSERT(readlen == 6
                  && memcmp(readbuf, "GHIJKL", 6) == 0);

  return SVN_NO_ERROR;
}
static svn_error_t *
txdelta_next_window(svn_txdelta_window_t **window,
                    void *baton,
                    apr_pool_t *pool)
{
  struct txdelta_baton *b = baton;
  apr_size_t source_len = SVN_DELTA_WINDOW_SIZE;
  apr_size_t target_len = SVN_DELTA_WINDOW_SIZE;

  /* Read the source stream. */
  if (b->more_source)
    {
      SVN_ERR(svn_stream_read(b->source, b->buf, &source_len));
      b->more_source = (source_len == SVN_DELTA_WINDOW_SIZE);
    }
  else
    source_len = 0;

  /* Read the target stream. */
  SVN_ERR(svn_stream_read(b->target, b->buf + source_len, &target_len));
  b->pos += source_len;

  if (target_len == 0)
    {
      /* No target data?  We're done; return the final window. */
      if (b->context != NULL)
        SVN_ERR(svn_checksum_final(&b->checksum, b->context, b->result_pool));

      *window = NULL;
      b->more = FALSE;
      return SVN_NO_ERROR;
    }
  else if (b->context != NULL)
    SVN_ERR(svn_checksum_update(b->context, b->buf + source_len, target_len));

  *window = compute_window(b->buf, source_len, target_len,
                           b->pos - source_len, pool);

  /* That's it. */
  return SVN_NO_ERROR;
}
Exemple #6
0
/* Implements svn_read_fn_t */
static svn_error_t *
read_handler_lazyopen(void *baton,
                      char *buffer,
                      apr_size_t *len)
{
  lazyopen_baton_t *b = baton;

  SVN_ERR(lazyopen_if_unopened(b));
  SVN_ERR(svn_stream_read(b->real_stream, buffer, len));

  return SVN_NO_ERROR;
}
static svn_error_t *
test_stream_seek_stringbuf(apr_pool_t *pool)
{
  svn_stream_t *stream;
  svn_stringbuf_t *stringbuf;
  char buf[4];
  apr_size_t len;
  svn_stream_mark_t *mark;

  stringbuf = svn_stringbuf_create("OneTwo", pool);
  stream = svn_stream_from_stringbuf(stringbuf, pool);
  len = 3;
  SVN_ERR(svn_stream_read(stream, buf, &len));
  buf[3] = '\0';
  SVN_TEST_STRING_ASSERT(buf, "One");
  SVN_ERR(svn_stream_mark(stream, &mark, pool));
  len = 3;
  SVN_ERR(svn_stream_read(stream, buf, &len));
  buf[3] = '\0';
  SVN_TEST_STRING_ASSERT(buf, "Two");
  SVN_ERR(svn_stream_seek(stream, mark));
  len = 3;
  SVN_ERR(svn_stream_read(stream, buf, &len));
  buf[3] = '\0';
  SVN_TEST_STRING_ASSERT(buf, "Two");

  /* Go back to the begin of last word and try to skip some of it */
  SVN_ERR(svn_stream_seek(stream, mark));
  SVN_ERR(svn_stream_skip(stream, 2));
  /* The remaining line should be empty */
  len = 3;
  SVN_ERR(svn_stream_read(stream, buf, &len));
  buf[len] = '\0';
  SVN_TEST_ASSERT(len == 1);
  SVN_TEST_STRING_ASSERT(buf, "o");

  SVN_ERR(svn_stream_close(stream));

  return SVN_NO_ERROR;
}
Exemple #8
0
svn_error_t *
svn_stream_contents_same2(svn_boolean_t *same,
                          svn_stream_t *stream1,
                          svn_stream_t *stream2,
                          apr_pool_t *pool)
{
  char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
  char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
  apr_size_t bytes_read1 = SVN__STREAM_CHUNK_SIZE;
  apr_size_t bytes_read2 = SVN__STREAM_CHUNK_SIZE;
  svn_error_t *err = NULL;

  *same = TRUE;  /* assume TRUE, until disproved below */
  while (bytes_read1 == SVN__STREAM_CHUNK_SIZE
         && bytes_read2 == SVN__STREAM_CHUNK_SIZE)
    {
      err = svn_stream_read(stream1, buf1, &bytes_read1);
      if (err)
        break;
      err = svn_stream_read(stream2, buf2, &bytes_read2);
      if (err)
        break;

      if ((bytes_read1 != bytes_read2)
          || (memcmp(buf1, buf2, bytes_read1)))
        {
          *same = FALSE;
          break;
        }
    }

  return svn_error_compose_create(err,
                                  svn_error_compose_create(
                                    svn_stream_close(stream1),
                                    svn_stream_close(stream2)));
}
Exemple #9
0
static svn_error_t *
read_handler_checksum(void *baton, char *buffer, apr_size_t *len)
{
  struct checksum_stream_baton *btn = baton;
  apr_size_t saved_len = *len;

  SVN_ERR(svn_stream_read(btn->proxy, buffer, len));

  if (btn->read_checksum)
    SVN_ERR(svn_checksum_update(btn->read_ctx, buffer, *len));

  if (saved_len != *len)
    btn->read_more = FALSE;

  return SVN_NO_ERROR;
}
Exemple #10
0
/* Guts of svn_stream_readline().
 * Returns the line read from STREAM in *STRINGBUF, and indicates
 * end-of-file in *EOF.  If DETECT_EOL is TRUE, the end-of-line indicator
 * is detected automatically and returned in *EOL.
 * If DETECT_EOL is FALSE, *EOL must point to the desired end-of-line
 * indicator.  STRINGBUF is allocated in POOL. */
static svn_error_t *
stream_readline_bytewise(svn_stringbuf_t **stringbuf,
                         svn_boolean_t *eof,
                         const char *eol,
                         svn_stream_t *stream,
                         apr_pool_t *pool)
{
  svn_stringbuf_t *str;
  apr_size_t numbytes;
  const char *match;
  char c;

  /* Since we're reading one character at a time, let's at least
     optimize for the 90% case.  90% of the time, we can avoid the
     stringbuf ever having to realloc() itself if we start it out at
     80 chars.  */
  str = svn_stringbuf_create_ensure(LINE_CHUNK_SIZE, pool);

  /* Read into STR up to and including the next EOL sequence. */
  match = eol;
  while (*match)
    {
      numbytes = 1;
      SVN_ERR(svn_stream_read(stream, &c, &numbytes));
      if (numbytes != 1)
        {
          /* a 'short' read means the stream has run out. */
          *eof = TRUE;
          *stringbuf = str;
          return SVN_NO_ERROR;
        }

      if (c == *match)
        match++;
      else
        match = eol;

      svn_stringbuf_appendbyte(str, c);
    }

  *eof = FALSE;
  svn_stringbuf_chop(str, match - eol);
  *stringbuf = str;

  return SVN_NO_ERROR;
}
/* This is the write handler for a target-push delta stream.  It reads
 * source data, buffers target data, and fires off delta windows when
 * the target data buffer is full. */
static svn_error_t *
tpush_write_handler(void *baton, const char *data, apr_size_t *len)
{
  struct tpush_baton *tb = baton;
  apr_size_t chunk_len, data_len = *len;
  apr_pool_t *pool = svn_pool_create(tb->pool);
  svn_txdelta_window_t *window;

  while (data_len > 0)
    {
      svn_pool_clear(pool);

      /* Make sure we're all full up on source data, if possible. */
      if (tb->source_len == 0 && !tb->source_done)
        {
          tb->source_len = SVN_DELTA_WINDOW_SIZE;
          SVN_ERR(svn_stream_read(tb->source, tb->buf, &tb->source_len));
          if (tb->source_len < SVN_DELTA_WINDOW_SIZE)
            tb->source_done = TRUE;
        }

      /* Copy in the target data, up to SVN_DELTA_WINDOW_SIZE. */
      chunk_len = SVN_DELTA_WINDOW_SIZE - tb->target_len;
      if (chunk_len > data_len)
        chunk_len = data_len;
      memcpy(tb->buf + tb->source_len + tb->target_len, data, chunk_len);
      data += chunk_len;
      data_len -= chunk_len;
      tb->target_len += chunk_len;

      /* If we're full of target data, compute and fire off a window. */
      if (tb->target_len == SVN_DELTA_WINDOW_SIZE)
        {
          window = compute_window(tb->buf, tb->source_len, tb->target_len,
                                  tb->source_offset, pool);
          SVN_ERR(tb->wh(window, tb->whb));
          tb->source_offset += tb->source_len;
          tb->source_len = 0;
          tb->target_len = 0;
        }
    }

  svn_pool_destroy(pool);
  return SVN_NO_ERROR;
}
Exemple #12
0
svn_error_t *svn_stream_copy3(svn_stream_t *from, svn_stream_t *to,
                              svn_cancel_func_t cancel_func,
                              void *cancel_baton,
                              apr_pool_t *scratch_pool)
{
  char *buf = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
  svn_error_t *err;
  svn_error_t *err2;

  /* Read and write chunks until we get a short read, indicating the
     end of the stream.  (We can't get a short write without an
     associated error.) */
  while (1)
    {
      apr_size_t len = SVN__STREAM_CHUNK_SIZE;

      if (cancel_func)
        {
          err = cancel_func(cancel_baton);
          if (err)
             break;
        }

      err = svn_stream_read(from, buf, &len);
      if (err)
         break;

      if (len > 0)
        err = svn_stream_write(to, buf, &len);

      if (err || (len != SVN__STREAM_CHUNK_SIZE))
          break;
    }

  err2 = svn_error_compose_create(svn_stream_close(from),
                                  svn_stream_close(to));

  return svn_error_compose_create(err, err2);
}
static svn_error_t *
test_stream_compressed_empty_file(apr_pool_t *pool)
{
  svn_stream_t *stream, *empty_file_stream;
  char buf[1];
  apr_size_t len;

  /* Reading an empty file with a compressed stream should not error. */
  SVN_ERR(svn_stream_open_unique(&empty_file_stream, NULL, NULL,
                                 svn_io_file_del_on_pool_cleanup,
                                 pool, pool));
  stream = svn_stream_compressed(empty_file_stream, pool);
  len = sizeof(buf);
  SVN_ERR(svn_stream_read(stream, buf, &len));
  if (len > 0)
    return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
                            "Got unexpected result.");

  SVN_ERR(svn_stream_close(stream));

  return SVN_NO_ERROR;
}
Exemple #14
0
svn_error_t *
svn_ra_svn__stream_read(svn_ra_svn__stream_t *stream, char *data,
                        apr_size_t *len)
{
  return svn_stream_read(stream->stream, data, len);
}
/* Implements svn_hash_read2 and svn_hash_read_incremental. */
static svn_error_t *
hash_read(apr_hash_t *hash, svn_stream_t *stream, const char *terminator,
          svn_boolean_t incremental, apr_pool_t *pool)
{
  svn_stringbuf_t *buf;
  svn_boolean_t eof;
  apr_size_t len, keylen, vallen;
  char c, *end, *keybuf, *valbuf;
  apr_pool_t *iterpool = svn_pool_create(pool);

  while (1)
    {
      svn_pool_clear(iterpool);

      /* Read a key length line.  Might be END, though. */
      SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));

      /* Check for the end of the hash. */
      if ((!terminator && eof && buf->len == 0)
          || (terminator && (strcmp(buf->data, terminator) == 0)))
        break;

      /* Check for unexpected end of stream */
      if (eof)
        return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
                                _("Serialized hash missing terminator"));

      if ((buf->len >= 3) && (buf->data[0] == 'K') && (buf->data[1] == ' '))
        {
          /* Get the length of the key */
          keylen = (size_t) strtoul(buf->data + 2, &end, 10);
          if (keylen == (size_t) ULONG_MAX || *end != '\0')
            return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
                                    _("Serialized hash malformed"));

          /* Now read that much into a buffer. */
          keybuf = apr_palloc(pool, keylen + 1);
          SVN_ERR(svn_stream_read(stream, keybuf, &keylen));
          keybuf[keylen] = '\0';

          /* Suck up extra newline after key data */
          len = 1;
          SVN_ERR(svn_stream_read(stream, &c, &len));
          if (c != '\n')
            return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
                                    _("Serialized hash malformed"));

          /* Read a val length line */
          SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));

          if ((buf->data[0] == 'V') && (buf->data[1] == ' '))
            {
              vallen = (size_t) strtoul(buf->data + 2, &end, 10);
              if (vallen == (size_t) ULONG_MAX || *end != '\0')
                return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
                                        _("Serialized hash malformed"));

              valbuf = apr_palloc(iterpool, vallen + 1);
              SVN_ERR(svn_stream_read(stream, valbuf, &vallen));
              valbuf[vallen] = '\0';

              /* Suck up extra newline after val data */
              len = 1;
              SVN_ERR(svn_stream_read(stream, &c, &len));
              if (c != '\n')
                return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
                                        _("Serialized hash malformed"));

              /* Add a new hash entry. */
              apr_hash_set(hash, keybuf, keylen,
                           svn_string_ncreate(valbuf, vallen, pool));
            }
          else
            return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
                                    _("Serialized hash malformed"));
        }
      else if (incremental && (buf->len >= 3)
               && (buf->data[0] == 'D') && (buf->data[1] == ' '))
        {
          /* Get the length of the key */
          keylen = (size_t) strtoul(buf->data + 2, &end, 10);
          if (keylen == (size_t) ULONG_MAX || *end != '\0')
            return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
                                    _("Serialized hash malformed"));

          /* Now read that much into a buffer. */
          keybuf = apr_palloc(iterpool, keylen + 1);
          SVN_ERR(svn_stream_read(stream, keybuf, &keylen));
          keybuf[keylen] = '\0';

          /* Suck up extra newline after key data */
          len = 1;
          SVN_ERR(svn_stream_read(stream, &c, &len));
          if (c != '\n')
            return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
                                    _("Serialized hash malformed"));

          /* Remove this hash entry. */
          apr_hash_set(hash, keybuf, keylen, NULL);
        }
      else
        {
          return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
                                  _("Serialized hash malformed"));
        }
    }

  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}
/* Apply WINDOW to the streams given by APPL.  */
static svn_error_t *
apply_window(svn_txdelta_window_t *window, void *baton)
{
  struct apply_baton *ab = (struct apply_baton *) baton;
  apr_size_t len;
  svn_error_t *err;

  if (window == NULL)
    {
      /* We're done; just clean up.  */
      if (ab->result_digest)
        apr_md5_final(ab->result_digest, &(ab->md5_context));

      err = svn_stream_close(ab->target);
      svn_pool_destroy(ab->pool);

      return err;
    }

  /* Make sure the source view didn't slide backwards.  */
  SVN_ERR_ASSERT(window->sview_len == 0
                 || (window->sview_offset >= ab->sbuf_offset
                     && (window->sview_offset + window->sview_len
                         >= ab->sbuf_offset + ab->sbuf_len)));

  /* Make sure there's enough room in the target buffer.  */
  SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool));

  /* Prepare the source buffer for reading from the input stream.  */
  if (window->sview_offset != ab->sbuf_offset
      || window->sview_len > ab->sbuf_size)
    {
      char *old_sbuf = ab->sbuf;

      /* Make sure there's enough room.  */
      SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len,
              ab->pool));

      /* If the existing view overlaps with the new view, copy the
       * overlap to the beginning of the new buffer.  */
      if (ab->sbuf_offset + ab->sbuf_len > window->sview_offset)
        {
          apr_size_t start =
            (apr_size_t)(window->sview_offset - ab->sbuf_offset);
          memmove(ab->sbuf, old_sbuf + start, ab->sbuf_len - start);
          ab->sbuf_len -= start;
        }
      else
        ab->sbuf_len = 0;
      ab->sbuf_offset = window->sview_offset;
    }

  /* Read the remainder of the source view into the buffer.  */
  if (ab->sbuf_len < window->sview_len)
    {
      len = window->sview_len - ab->sbuf_len;
      err = svn_stream_read(ab->source, ab->sbuf + ab->sbuf_len, &len);
      if (err == SVN_NO_ERROR && len != window->sview_len - ab->sbuf_len)
        err = svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
                               "Delta source ended unexpectedly");
      if (err != SVN_NO_ERROR)
        return err;
      ab->sbuf_len = window->sview_len;
    }

  /* Apply the window instructions to the source view to generate
     the target view.  */
  len = window->tview_len;
  svn_txdelta_apply_instructions(window, ab->sbuf, ab->tbuf, &len);
  SVN_ERR_ASSERT(len == window->tview_len);

  /* Write out the output. */

  /* ### We've also considered just adding two (optionally null)
     arguments to svn_stream_create(): read_checksum and
     write_checksum.  Then instead of every caller updating an md5
     context when it calls svn_stream_write() or svn_stream_read(),
     streams would do it automatically, and verify the checksum in
     svn_stream_closed().  But this might be overkill for issue #689;
     so for now we just update the context here. */
  if (ab->result_digest)
    apr_md5_update(&(ab->md5_context), ab->tbuf, len);

  return svn_stream_write(ab->target, ab->tbuf, &len);
}
Exemple #17
0
static svn_error_t *
read_handler_md5(void *baton, char *buffer, apr_size_t *len)
{
  struct md5_stream_baton *btn = baton;
  return svn_error_trace(svn_stream_read(btn->proxy, buffer, len));
}
static svn_error_t *
test_stream_seek_translated(apr_pool_t *pool)
{
  svn_stream_t *stream, *translated_stream;
  svn_stringbuf_t *stringbuf;
  char buf[44]; /* strlen("One$MyKeyword: my keyword was expanded $Two") + \0 */
  apr_size_t len;
  svn_stream_mark_t *mark;
  apr_hash_t *keywords;
  svn_string_t *keyword_val;

  keywords = apr_hash_make(pool);
  keyword_val = svn_string_create("my keyword was expanded", pool);
  apr_hash_set(keywords, "MyKeyword", APR_HASH_KEY_STRING, keyword_val);
  stringbuf = svn_stringbuf_create("One$MyKeyword$Two", pool);
  stream = svn_stream_from_stringbuf(stringbuf, pool);
  translated_stream = svn_subst_stream_translated(stream, APR_EOL_STR,
                                                  FALSE, keywords, TRUE, pool);
  /* Seek from outside of keyword to inside of keyword. */
  len = 25;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 25);
  buf[25] = '\0';
  SVN_TEST_STRING_ASSERT(buf, "One$MyKeyword: my keyword");
  SVN_ERR(svn_stream_mark(translated_stream, &mark, pool));
  SVN_ERR(svn_stream_reset(translated_stream));
  SVN_ERR(svn_stream_seek(translated_stream, mark));
  len = 4;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 4);
  buf[4] = '\0';
  SVN_TEST_STRING_ASSERT(buf, " was");

  SVN_ERR(svn_stream_seek(translated_stream, mark));
  SVN_ERR(svn_stream_skip(translated_stream, 2));
  len = 2;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 2);
  buf[len] = '\0';
  SVN_TEST_STRING_ASSERT(buf, "as");

  /* Seek from inside of keyword to inside of keyword. */
  SVN_ERR(svn_stream_mark(translated_stream, &mark, pool));
  len = 9;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 9);
  buf[9] = '\0';
  SVN_TEST_STRING_ASSERT(buf, " expanded");
  SVN_ERR(svn_stream_seek(translated_stream, mark));
  len = 9;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 9);
  buf[9] = '\0';
  SVN_TEST_STRING_ASSERT(buf, " expanded");

  SVN_ERR(svn_stream_seek(translated_stream, mark));
  SVN_ERR(svn_stream_skip(translated_stream, 6));
  len = 3;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 3);
  buf[len] = '\0';
  SVN_TEST_STRING_ASSERT(buf, "ded");

  /* Seek from inside of keyword to outside of keyword. */
  SVN_ERR(svn_stream_mark(translated_stream, &mark, pool));
  len = 4;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 4);
  buf[4] = '\0';
  SVN_TEST_STRING_ASSERT(buf, " $Tw");
  SVN_ERR(svn_stream_seek(translated_stream, mark));
  len = 4;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 4);
  buf[4] = '\0';
  SVN_TEST_STRING_ASSERT(buf, " $Tw");

  SVN_ERR(svn_stream_seek(translated_stream, mark));
  SVN_ERR(svn_stream_skip(translated_stream, 2));
  len = 2;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 2);
  buf[len] = '\0';
  SVN_TEST_STRING_ASSERT(buf, "Tw");

  /* Seek from outside of keyword to outside of keyword. */
  SVN_ERR(svn_stream_mark(translated_stream, &mark, pool));
  len = 1;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 1);
  buf[1] = '\0';
  SVN_TEST_STRING_ASSERT(buf, "o");
  SVN_ERR(svn_stream_seek(translated_stream, mark));
  len = 1;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 1);
  buf[1] = '\0';
  SVN_TEST_STRING_ASSERT(buf, "o");

  SVN_ERR(svn_stream_seek(translated_stream, mark));
  SVN_ERR(svn_stream_skip(translated_stream, 2));
  len = 1;
  SVN_ERR(svn_stream_read(translated_stream, buf, &len));
  SVN_TEST_ASSERT(len == 0);
  buf[len] = '\0';
  SVN_TEST_STRING_ASSERT(buf, "");

  SVN_ERR(svn_stream_close(stream));

  return SVN_NO_ERROR;
}
static svn_error_t *
test_stream_from_string(apr_pool_t *pool)
{
  int i;
  apr_pool_t *subpool = svn_pool_create(pool);

#define NUM_TEST_STRINGS 4
#define TEST_BUF_SIZE 10

  static const char * const strings[NUM_TEST_STRINGS] = {
    /* 0 */
    "",
    /* 1 */
    "This is a string.",
    /* 2 */
    "This is, by comparison to the previous string, a much longer string.",
    /* 3 */
    "And if you thought that last string was long, you just wait until "
    "I'm finished here.  I mean, how can a string really claim to be long "
    "when it fits on a single line of 80-columns?  Give me a break. "
    "Now, I'm not saying that I'm the longest string out there--far from "
    "it--but I feel that it is safe to assume that I'm far longer than my "
    "peers.  And that demands some amount of respect, wouldn't you say?"
  };

  /* Test svn_stream_from_stringbuf() as a readable stream. */
  for (i = 0; i < NUM_TEST_STRINGS; i++)
    {
      svn_stream_t *stream;
      char buffer[TEST_BUF_SIZE];
      svn_stringbuf_t *inbuf, *outbuf;
      apr_size_t len;

      inbuf = svn_stringbuf_create(strings[i], subpool);
      outbuf = svn_stringbuf_create("", subpool);
      stream = svn_stream_from_stringbuf(inbuf, subpool);
      len = TEST_BUF_SIZE;
      while (len == TEST_BUF_SIZE)
        {
          /* Read a chunk ... */
          SVN_ERR(svn_stream_read(stream, buffer, &len));

          /* ... and append the chunk to the stringbuf. */
          svn_stringbuf_appendbytes(outbuf, buffer, len);
        }

      if (! svn_stringbuf_compare(inbuf, outbuf))
        return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
                                "Got unexpected result.");

      svn_pool_clear(subpool);
    }

  /* Test svn_stream_from_stringbuf() as a writable stream. */
  for (i = 0; i < NUM_TEST_STRINGS; i++)
    {
      svn_stream_t *stream;
      svn_stringbuf_t *inbuf, *outbuf;
      apr_size_t amt_read, len;

      inbuf = svn_stringbuf_create(strings[i], subpool);
      outbuf = svn_stringbuf_create("", subpool);
      stream = svn_stream_from_stringbuf(outbuf, subpool);
      amt_read = 0;
      while (amt_read < inbuf->len)
        {
          /* Write a chunk ... */
          len = TEST_BUF_SIZE < (inbuf->len - amt_read)
                  ? TEST_BUF_SIZE
                  : inbuf->len - amt_read;
          SVN_ERR(svn_stream_write(stream, inbuf->data + amt_read, &len));
          amt_read += len;
        }

      if (! svn_stringbuf_compare(inbuf, outbuf))
        return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
                                "Got unexpected result.");

      svn_pool_clear(subpool);
    }

#undef NUM_TEST_STRINGS
#undef TEST_BUF_SIZE

  svn_pool_destroy(subpool);
  return SVN_NO_ERROR;
}
static svn_error_t *
test_stream_compressed(apr_pool_t *pool)
{
#define NUM_TEST_STRINGS 5
#define TEST_BUF_SIZE 10
#define GENERATED_SIZE 20000

  int i;
  svn_stringbuf_t *bufs[NUM_TEST_STRINGS];
  apr_pool_t *subpool = svn_pool_create(pool);

  static const char * const strings[NUM_TEST_STRINGS - 1] = {
    /* 0 */
    "",
    /* 1 */
    "This is a string.",
    /* 2 */
    "This is, by comparison to the previous string, a much longer string.",
    /* 3 */
    "And if you thought that last string was long, you just wait until "
    "I'm finished here.  I mean, how can a string really claim to be long "
    "when it fits on a single line of 80-columns?  Give me a break. "
    "Now, I'm not saying that I'm the longest string out there--far from "
    "it--but I feel that it is safe to assume that I'm far longer than my "
    "peers.  And that demands some amount of respect, wouldn't you say?"
  };


  for (i = 0; i < (NUM_TEST_STRINGS - 1); i++)
    bufs[i] = svn_stringbuf_create(strings[i], pool);

  /* the last buffer is for the generated data */
  bufs[NUM_TEST_STRINGS - 1] = generate_test_bytes(GENERATED_SIZE, pool);

  for (i = 0; i < NUM_TEST_STRINGS; i++)
    {
      svn_stream_t *stream;
      svn_stringbuf_t *origbuf, *inbuf, *outbuf;
      char buf[TEST_BUF_SIZE];
      apr_size_t len;

      origbuf = bufs[i];
      inbuf = svn_stringbuf_create("", subpool);
      outbuf = svn_stringbuf_create("", subpool);

      stream = svn_stream_compressed(svn_stream_from_stringbuf(outbuf,
                                                               subpool),
                                     subpool);
      len = origbuf->len;
      SVN_ERR(svn_stream_write(stream, origbuf->data, &len));
      SVN_ERR(svn_stream_close(stream));

      stream = svn_stream_compressed(svn_stream_from_stringbuf(outbuf,
                                                               subpool),
                                     subpool);
      len = TEST_BUF_SIZE;
      while (len >= TEST_BUF_SIZE)
        {
          len = TEST_BUF_SIZE;
          SVN_ERR(svn_stream_read(stream, buf, &len));
          if (len > 0)
            svn_stringbuf_appendbytes(inbuf, buf, len);
        }

      if (! svn_stringbuf_compare(inbuf, origbuf))
        return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
                                "Got unexpected result.");

      SVN_ERR(svn_stream_close(stream));

      svn_pool_clear(subpool);
    }

#undef NUM_TEST_STRINGS
#undef TEST_BUF_SIZE
#undef GENERATED_SIZE

  svn_pool_destroy(subpool);
  return SVN_NO_ERROR;
}
Exemple #21
0
static svn_error_t *
read_handler_disown(void *baton, char *buffer, apr_size_t *len)
{
  return svn_error_trace(svn_stream_read(baton, buffer, len));
}
Exemple #22
0
/* Read CONTENT_LENGTH bytes from STREAM, and use
   PARSE_FNS->set_fulltext to push those bytes as replace fulltext for
   a node.  Use BUFFER/BUFLEN to push the fulltext in "chunks".

   Use POOL for all allocations.  */
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_fns3_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
    {
        /* Get a stream to which we can push the data. */
        SVN_ERR(parse_fns->set_fulltext(&text_stream, record_baton));
    }

    /* If there are no contents to read, just write an empty buffer
       through our callback. */
    if (content_length == 0)
    {
        wlen = 0;
        if (text_stream)
            SVN_ERR(svn_stream_write(text_stream, "", &wlen));
    }

    /* Regardless of whether or not we have a sink for our data, we
       need to read it. */
    while (content_length)
    {
        if (content_length >= (svn_filesize_t)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)
        {
            /* write however many bytes you read. */
            wlen = rlen;
            SVN_ERR(svn_stream_write(text_stream, buffer, &wlen));
            if (wlen != rlen)
            {
                /* Uh oh, didn't write as many bytes as we read. */
                return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
                                        _("Unexpected EOF writing contents"));
            }
        }
    }

    /* If we opened a stream, we must close it. */
    if (text_stream)
        SVN_ERR(svn_stream_close(text_stream));

    return SVN_NO_ERROR;
}
Exemple #23
0
svn_error_t *
svn_repos_parse_dumpstream3(svn_stream_t *stream,
                            const svn_repos_parse_fns3_t *parse_fns,
                            void *parse_baton,
                            svn_boolean_t deltas_are_text,
                            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();

    /* The first two lines of the stream are the dumpfile-format version
       number, and a blank line.  To preserve backward compatibility,
       don't assume the existence of newer parser-vtable functions. */
    SVN_ERR(parse_format_version(&version, linebuf->data));
    if (parse_fns->magic_header_record != NULL)
        SVN_ERR(parse_fns->magic_header_record(version, parse_baton, pool));

    /* A dumpfile "record" is defined to be a header-block of
       rfc822-style headers, possibly followed by a content-block.

         - A header-block is always terminated by a single blank line (\n\n)

         - We know whether the record has a content-block by looking for
           a 'Content-length:' header.  The content-block will always be
           of a specific length, plus an extra newline.

       Once a record is fully sucked from the stream, an indeterminate
       number of blank lines (or lines that begin with whitespace) may
       follow before the next record (or the end of the stream.)
    */

    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;

        /* Clear our per-line pool. */
        svn_pool_clear(linepool);

        /* Check for cancellation. */
        if (cancel_func)
            SVN_ERR(cancel_func(cancel_baton));

        /* Keep reading blank lines until we discover a new record, or until
           the stream runs out. */
        SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));

        if (eof)
        {
            if (svn_stringbuf_isempty(linebuf))
                break;   /* end of stream, go home. */
            else
                return stream_ran_dry();
        }

        if ((linebuf->len == 0) || (svn_ctype_isspace(linebuf->data[0])))
            continue; /* empty line ... loop */

        /*** Found the beginning of a new record. ***/

        /* The last line we read better be a header of some sort.
           Read the whole header-block into a hash. */
        SVN_ERR(read_header_block(stream, linebuf, &headers, linepool));

        /*** Handle the various header blocks. ***/

        /* Is this a revision record? */
        if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER))
        {
            /* If we already have a rev_baton open, we need to close it
               and clear the per-revision subpool. */
            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));
        }
        /* Or is this, perhaps, a node record? */
        else if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH))
        {
            SVN_ERR(parse_fns->new_node_record(&node_baton,
                                               headers,
                                               rev_baton,
                                               nodepool));
            found_node = TRUE;
        }
        /* Or is this the repos UUID? */
        else if ((value = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_UUID)))
        {
            SVN_ERR(parse_fns->uuid_record(value, parse_baton, pool));
        }
        /* Or perhaps a dumpfile format? */
        /* ### TODO: use parse_format_version */
        else if ((value = svn_hash_gets(headers,
                                        SVN_REPOS_DUMPFILE_MAGIC_HEADER)))
        {
            /* ### someday, switch modes of operation here. */
            SVN_ERR(svn_cstring_atoi(&version, value));
        }
        /* Or is this bogosity?! */
        else
        {
            /* What the heck is this record?!? */
            return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
                                    _("Unrecognized record type in stream"));
        }

        /* Need 3 values below to determine v1 dump type

           Old (pre 0.14?) v1 dumps don't have Prop-content-length
           and Text-content-length fields, but always have a properties
           block in a block with Content-Length > 0 */

        content_length = svn_hash_gets(headers,
                                       SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
        prop_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
        text_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
        old_v1_with_cl =
            version == 1 && content_length && ! prop_cl && ! text_cl;

        /* Is there a props content-block to parse? */
        if (prop_cl || old_v1_with_cl)
        {
            const char *delta = svn_hash_gets(headers,
                                              SVN_REPOS_DUMPFILE_PROP_DELTA);
            svn_boolean_t is_delta = (delta && strcmp(delta, "true") == 0);

            /* First, remove all node properties, unless this is a delta
               property block. */
            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));
        }

        /* Is there a text content-block to parse? */
        if (text_cl)
        {
            const char *delta = svn_hash_gets(headers,
                                              SVN_REPOS_DUMPFILE_TEXT_DELTA);
            svn_boolean_t is_delta = FALSE;
            if (! deltas_are_text)
                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)
        {
            /* An old-v1 block with a Content-length might have a text block.
               If the property block did not consume all the bytes of the
               Content-length, then it clearly does have a text block.
               If not, then we must deduce whether we have an *empty* text
               block or an *absent* text block.  The rules are:
               - "Node-kind: file" blocks have an empty (i.e. present, but
                 zero-length) text block, since they represent a file
                 modification.  Note that file-copied-text-unmodified blocks
                 have no Content-length - even if they should have contained
                 a modified property block, the pre-0.14 dumper forgets to
                 dump the modified properties.
               - If it is not a file node, then it is a revision or directory,
                 and so has an absent text block.
            */
            const char *node_kind;
            svn_filesize_t cl_value = svn__atoui64(content_length)
                                      - actual_prop_length;

            if (cl_value ||
                    ((node_kind = svn_hash_gets(headers,
                                                SVN_REPOS_DUMPFILE_NODE_KIND))
                     && 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 we have a content-length header, did we read all of it?
           in case of an old v1, we *always* read all of it, because
           text-content-length == content-length - prop-content-length
        */
        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"));

            /* Consume remaining bytes in this content block */
            while (remaining > 0)
            {
                if (remaining >= (svn_filesize_t)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 we just finished processing a node record, we need to
           close the node record and clear the per-node subpool. */
        if (found_node)
        {
            SVN_ERR(parse_fns->close_node(node_baton));
            svn_pool_clear(nodepool);
        }

        /*** End of processing for one record. ***/

    } /* end of stream */

    /* Close out whatever revision we're in. */
    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;
}
Exemple #24
0
static svn_error_t *
stream_readline_chunky(svn_stringbuf_t **stringbuf,
                       svn_boolean_t *eof,
                       const char *eol,
                       svn_stream_t *stream,
                       apr_pool_t *pool)
{
  /* Read larger chunks of data at once into this buffer and scan
   * that for EOL. A good chunk size should be about 80 chars since
   * most text lines will be shorter. However, don't use a much
   * larger value because filling the buffer from the stream takes
   * time as well.
   */
  char buffer[LINE_CHUNK_SIZE+1];

  /* variables */
  svn_stream_mark_t *mark;
  apr_size_t numbytes;
  const char *eol_pos;
  apr_size_t total_parsed = 0;

  /* invariant for this call */
  const size_t eol_len = strlen(eol);

  /* Remember the line start so this plus the line length will be
   * the position to move to at the end of this function.
   */
  SVN_ERR(svn_stream_mark(stream, &mark, pool));

  /* Read the first chunk. */
  numbytes = LINE_CHUNK_SIZE;
  SVN_ERR(svn_stream_read(stream, buffer, &numbytes));
  buffer[numbytes] = '\0';

  /* Look for the EOL in this first chunk. If we find it, we are done here.
   */
  eol_pos = strstr(buffer, eol);
  if (eol_pos != NULL)
    {
      *stringbuf = svn_stringbuf_ncreate(buffer, eol_pos - buffer, pool);
      total_parsed = eol_pos - buffer + eol_len;
    }
  else if (numbytes < LINE_CHUNK_SIZE)
    {
      /* We hit EOF but not EOL.
       */
      *stringbuf = svn_stringbuf_ncreate(buffer, numbytes, pool);
      *eof = TRUE;
      return SVN_NO_ERROR;
     }
  else
    {
      /* A larger buffer for the string is needed. */
      svn_stringbuf_t *str;
      str = svn_stringbuf_create_ensure(2*LINE_CHUNK_SIZE, pool);
      svn_stringbuf_appendbytes(str, buffer, numbytes);
      *stringbuf = str;

      /* Loop reading chunks until an EOL was found. If we hit EOF, fall
       * back to the standard implementation. */
      do
      {
        /* Append the next chunk to the string read so far.
         */
        svn_stringbuf_ensure(str, str->len + LINE_CHUNK_SIZE);
        numbytes = LINE_CHUNK_SIZE;
        SVN_ERR(svn_stream_read(stream, str->data + str->len, &numbytes));
        str->len += numbytes;
        str->data[str->len] = '\0';

        /* Look for the EOL in the new data plus the last part of the
         * previous chunk because the EOL may span over the boundary
         * between both chunks.
         */
        eol_pos = strstr(str->data + str->len - numbytes - (eol_len-1), eol);

        if ((numbytes < LINE_CHUNK_SIZE) && (eol_pos == NULL))
        {
          /* We hit EOF instead of EOL. */
          *eof = TRUE;
          return SVN_NO_ERROR;
        }
      }
      while (eol_pos == NULL);

      /* Number of bytes we actually consumed (i.e. line + EOF).
       * We need to "return" the rest to the stream by moving its
       * read pointer.
       */
      total_parsed = eol_pos - str->data + eol_len;

      /* Terminate the string at the EOL postion and return it. */
      str->len = eol_pos - str->data;
      str->data[str->len] = 0;
    }

  /* Move the stream read pointer to the first position behind the EOL.
   */
  SVN_ERR(svn_stream_seek(stream, mark));
  return svn_error_trace(svn_stream_skip(stream, total_parsed));
}