1131087Smarcel/*
2131087Smarcel * subst.c :  generic eol/keyword substitution routines
3161589Smarcel *
4131087Smarcel * ====================================================================
5131087Smarcel *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#define APR_WANT_STRFUNC
27#include <apr_want.h>
28
29#include <stdlib.h>
30#include <assert.h>
31#include <apr_pools.h>
32#include <apr_tables.h>
33#include <apr_file_io.h>
34#include <apr_strings.h>
35
36#include "svn_hash.h"
37#include "svn_cmdline.h"
38#include "svn_types.h"
39#include "svn_string.h"
40#include "svn_time.h"
41#include "svn_dirent_uri.h"
42#include "svn_path.h"
43#include "svn_error.h"
44#include "svn_utf.h"
45#include "svn_io.h"
46#include "svn_subst.h"
47#include "svn_pools.h"
48#include "private/svn_io_private.h"
49
50#include "svn_private_config.h"
51
52#include "private/svn_string_private.h"
53#include "private/svn_eol_private.h"
54
55/**
56 * The textual elements of a detranslated special file.  One of these
57 * strings must appear as the first element of any special file as it
58 * exists in the repository or the text base.
59 */
60#define SVN_SUBST__SPECIAL_LINK_STR "link"
61
62void
63svn_subst_eol_style_from_value(svn_subst_eol_style_t *style,
64                               const char **eol,
65                               const char *value)
66{
67  if (value == NULL)
68    {
69      /* property doesn't exist. */
70      *eol = NULL;
71      if (style)
72        *style = svn_subst_eol_style_none;
73    }
74  else if (! strcmp("native", value))
75    {
76      *eol = APR_EOL_STR;       /* whee, a portability library! */
77      if (style)
78        *style = svn_subst_eol_style_native;
79    }
80  else if (! strcmp("LF", value))
81    {
82      *eol = "\n";
83      if (style)
84        *style = svn_subst_eol_style_fixed;
85    }
86  else if (! strcmp("CR", value))
87    {
88      *eol = "\r";
89      if (style)
90        *style = svn_subst_eol_style_fixed;
91    }
92  else if (! strcmp("CRLF", value))
93    {
94      *eol = "\r\n";
95      if (style)
96        *style = svn_subst_eol_style_fixed;
97    }
98  else
99    {
100      *eol = NULL;
101      if (style)
102        *style = svn_subst_eol_style_unknown;
103    }
104}
105
106
107svn_boolean_t
108svn_subst_translation_required(svn_subst_eol_style_t style,
109                               const char *eol,
110                               apr_hash_t *keywords,
111                               svn_boolean_t special,
112                               svn_boolean_t force_eol_check)
113{
114  return (special || keywords
115          || (style != svn_subst_eol_style_none && force_eol_check)
116          || (style == svn_subst_eol_style_native &&
117              strcmp(APR_EOL_STR, SVN_SUBST_NATIVE_EOL_STR) != 0)
118          || (style == svn_subst_eol_style_fixed &&
119              strcmp(APR_EOL_STR, eol) != 0));
120}
121
122
123
124/* Helper function for svn_subst_build_keywords */
125
126/* Given a printf-like format string, return a string with proper
127 * information filled in.
128 *
129 * Important API note: This function is the core of the implementation of
130 * svn_subst_build_keywords (all versions), and as such must implement the
131 * tolerance of NULL and zero inputs that that function's documentation
132 * stipulates.
133 *
134 * The format codes:
135 *
136 * %a author of this revision
137 * %b basename of the URL of this file
138 * %d short format of date of this revision
139 * %D long format of date of this revision
140 * %P path relative to root of repos
141 * %r number of this revision
142 * %R root url of repository
143 * %u URL of this file
144 * %_ a space
145 * %% a literal %
146 *
147 * The following special format codes are also recognized:
148 *   %H is equivalent to %P%_%r%_%d%_%a
149 *   %I is equivalent to %b%_%r%_%d%_%a
150 *
151 * All memory is allocated out of @a pool.
152 */
153static svn_string_t *
154keyword_printf(const char *fmt,
155               const char *rev,
156               const char *url,
157               const char *repos_root_url,
158               apr_time_t date,
159               const char *author,
160               apr_pool_t *pool)
161{
162  svn_stringbuf_t *value = svn_stringbuf_create_empty(pool);
163  const char *cur;
164  size_t n;
165
166  for (;;)
167    {
168      cur = fmt;
169
170      while (*cur != '\0' && *cur != '%')
171        cur++;
172
173      if ((n = cur - fmt) > 0) /* Do we have an as-is string? */
174        svn_stringbuf_appendbytes(value, fmt, n);
175
176      if (*cur == '\0')
177        break;
178
179      switch (cur[1])
180        {
181        case 'a': /* author of this revision */
182          if (author)
183            svn_stringbuf_appendcstr(value, author);
184          break;
185        case 'b': /* basename of this file */
186          if (url && *url)
187            {
188              const char *base_name = svn_uri_basename(url, pool);
189              svn_stringbuf_appendcstr(value, base_name);
190            }
191          break;
192        case 'd': /* short format of date of this revision */
193          if (date)
194            {
195              apr_time_exp_t exploded_time;
196              const char *human;
197
198              apr_time_exp_gmt(&exploded_time, date);
199
200              human = apr_psprintf(pool, "%04d-%02d-%02d %02d:%02d:%02dZ",
201                                   exploded_time.tm_year + 1900,
202                                   exploded_time.tm_mon + 1,
203                                   exploded_time.tm_mday,
204                                   exploded_time.tm_hour,
205                                   exploded_time.tm_min,
206                                   exploded_time.tm_sec);
207
208              svn_stringbuf_appendcstr(value, human);
209            }
210          break;
211        case 'D': /* long format of date of this revision */
212          if (date)
213            svn_stringbuf_appendcstr(value,
214                                     svn_time_to_human_cstring(date, pool));
215          break;
216        case 'P': /* relative path of this file */
217          if (repos_root_url && *repos_root_url != '\0' && url && *url != '\0')
218            {
219              const char *repos_relpath;
220
221              repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, pool);
222              if (repos_relpath)
223                svn_stringbuf_appendcstr(value, repos_relpath);
224            }
225          break;
226        case 'R': /* root of repos */
227          if (repos_root_url && *repos_root_url != '\0')
228            svn_stringbuf_appendcstr(value, repos_root_url);
229          break;
230        case 'r': /* number of this revision */
231          if (rev)
232            svn_stringbuf_appendcstr(value, rev);
233          break;
234        case 'u': /* URL of this file */
235          if (url)
236            svn_stringbuf_appendcstr(value, url);
237          break;
238        case '_': /* '%_' => a space */
239          svn_stringbuf_appendbyte(value, ' ');
240          break;
241        case '%': /* '%%' => a literal % */
242          svn_stringbuf_appendbyte(value, *cur);
243          break;
244        case '\0': /* '%' as the last character of the string. */
245          svn_stringbuf_appendbyte(value, *cur);
246          /* Now go back one character, since this was just a one character
247           * sequence, whereas all others are two characters, and we do not
248           * want to skip the null terminator entirely and carry on
249           * formatting random memory contents. */
250          cur--;
251          break;
252        case 'H':
253          {
254            svn_string_t *s = keyword_printf("%P%_%r%_%d%_%a", rev, url,
255                                             repos_root_url, date, author,
256                                             pool);
257            svn_stringbuf_appendcstr(value, s->data);
258          }
259          break;
260        case 'I':
261          {
262            svn_string_t *s = keyword_printf("%b%_%r%_%d%_%a", rev, url,
263                                             repos_root_url, date, author,
264                                             pool);
265            svn_stringbuf_appendcstr(value, s->data);
266          }
267          break;
268        default: /* Unrecognized code, just print it literally. */
269          svn_stringbuf_appendbytes(value, cur, 2);
270          break;
271        }
272
273      /* Format code is processed - skip it, and get ready for next chunk. */
274      fmt = cur + 2;
275    }
276
277  return svn_stringbuf__morph_into_string(value);
278}
279
280static svn_error_t *
281build_keywords(apr_hash_t **kw,
282               svn_boolean_t expand_custom_keywords,
283               const char *keywords_val,
284               const char *rev,
285               const char *url,
286               const char *repos_root_url,
287               apr_time_t date,
288               const char *author,
289               apr_pool_t *pool)
290{
291  apr_array_header_t *keyword_tokens;
292  int i;
293  *kw = apr_hash_make(pool);
294
295  keyword_tokens = svn_cstring_split(keywords_val, " \t\v\n\b\r\f",
296                                     TRUE /* chop */, pool);
297
298  for (i = 0; i < keyword_tokens->nelts; ++i)
299    {
300      const char *keyword = APR_ARRAY_IDX(keyword_tokens, i, const char *);
301      const char *custom_fmt = NULL;
302
303      if (expand_custom_keywords)
304        {
305          char *sep;
306
307          /* Check if there is a custom keyword definition, started by '='. */
308          sep = strchr(keyword, '=');
309          if (sep)
310            {
311              *sep = '\0'; /* Split keyword's name from custom format. */
312              custom_fmt = sep + 1;
313            }
314        }
315
316      if (custom_fmt)
317        {
318          svn_string_t *custom_val;
319
320          /* Custom keywords must be allowed to match the name of an
321           * existing fixed keyword. This is for compatibility purposes,
322           * in case new fixed keywords are added to Subversion which
323           * happen to match a custom keyword defined somewhere.
324           * There is only one global namespace for keyword names. */
325          custom_val = keyword_printf(custom_fmt, rev, url, repos_root_url,
326                                      date, author, pool);
327          svn_hash_sets(*kw, keyword, custom_val);
328        }
329      else if ((! strcmp(keyword, SVN_KEYWORD_REVISION_LONG))
330               || (! strcmp(keyword, SVN_KEYWORD_REVISION_MEDIUM))
331               || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_REVISION_SHORT)))
332        {
333          svn_string_t *revision_val;
334
335          revision_val = keyword_printf("%r", rev, url, repos_root_url,
336                                        date, author, pool);
337          svn_hash_sets(*kw, SVN_KEYWORD_REVISION_LONG, revision_val);
338          svn_hash_sets(*kw, SVN_KEYWORD_REVISION_MEDIUM, revision_val);
339          svn_hash_sets(*kw, SVN_KEYWORD_REVISION_SHORT, revision_val);
340        }
341      else if ((! strcmp(keyword, SVN_KEYWORD_DATE_LONG))
342               || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_DATE_SHORT)))
343        {
344          svn_string_t *date_val;
345
346          date_val = keyword_printf("%D", rev, url, repos_root_url, date,
347                                    author, pool);
348          svn_hash_sets(*kw, SVN_KEYWORD_DATE_LONG, date_val);
349          svn_hash_sets(*kw, SVN_KEYWORD_DATE_SHORT, date_val);
350        }
351      else if ((! strcmp(keyword, SVN_KEYWORD_AUTHOR_LONG))
352               || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_AUTHOR_SHORT)))
353        {
354          svn_string_t *author_val;
355
356          author_val = keyword_printf("%a", rev, url, repos_root_url, date,
357                                      author, pool);
358          svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_LONG, author_val);
359          svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_SHORT, author_val);
360        }
361      else if ((! strcmp(keyword, SVN_KEYWORD_URL_LONG))
362               || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_URL_SHORT)))
363        {
364          svn_string_t *url_val;
365
366          url_val = keyword_printf("%u", rev, url, repos_root_url, date,
367                                   author, pool);
368          svn_hash_sets(*kw, SVN_KEYWORD_URL_LONG, url_val);
369          svn_hash_sets(*kw, SVN_KEYWORD_URL_SHORT, url_val);
370        }
371      else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_ID)))
372        {
373          svn_string_t *id_val;
374
375          id_val = keyword_printf("%b %r %d %a", rev, url, repos_root_url,
376                                  date, author, pool);
377          svn_hash_sets(*kw, SVN_KEYWORD_ID, id_val);
378        }
379      else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_HEADER)))
380        {
381          svn_string_t *header_val;
382
383          header_val = keyword_printf("%u %r %d %a", rev, url, repos_root_url,
384                                      date, author, pool);
385          svn_hash_sets(*kw, SVN_KEYWORD_HEADER, header_val);
386        }
387    }
388
389  return SVN_NO_ERROR;
390}
391
392svn_error_t *
393svn_subst_build_keywords2(apr_hash_t **kw,
394                          const char *keywords_val,
395                          const char *rev,
396                          const char *url,
397                          apr_time_t date,
398                          const char *author,
399                          apr_pool_t *pool)
400{
401  return svn_error_trace(build_keywords(kw, FALSE, keywords_val, rev, url,
402                                        NULL, date, author, pool));
403}
404
405
406svn_error_t *
407svn_subst_build_keywords3(apr_hash_t **kw,
408                          const char *keywords_val,
409                          const char *rev,
410                          const char *url,
411                          const char *repos_root_url,
412                          apr_time_t date,
413                          const char *author,
414                          apr_pool_t *pool)
415{
416  return svn_error_trace(build_keywords(kw, TRUE, keywords_val,
417                                        rev, url, repos_root_url,
418                                        date, author, pool));
419}
420
421
422/*** Helpers for svn_subst_translate_stream2 ***/
423
424
425/* Write out LEN bytes of BUF into STREAM. */
426/* ### TODO: 'stream_write()' would be a better name for this. */
427static svn_error_t *
428translate_write(svn_stream_t *stream,
429                const void *buf,
430                apr_size_t len)
431{
432  SVN_ERR(svn_stream_write(stream, buf, &len));
433  /* (No need to check LEN, as a short write always produces an error.) */
434  return SVN_NO_ERROR;
435}
436
437
438/* Perform the substitution of VALUE into keyword string BUF (with len
439   *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating
440   *LEN to the new size of the substituted result.  Return TRUE if all
441   goes well, FALSE otherwise.  If VALUE is NULL, keyword will be
442   contracted, else it will be expanded.  */
443static svn_boolean_t
444translate_keyword_subst(char *buf,
445                        apr_size_t *len,
446                        const char *keyword,
447                        apr_size_t keyword_len,
448                        const svn_string_t *value)
449{
450  char *buf_ptr;
451
452  /* Make sure we gotz good stuffs. */
453  assert(*len <= SVN_KEYWORD_MAX_LEN);
454  assert((buf[0] == '$') && (buf[*len - 1] == '$'));
455
456  /* Need at least a keyword and two $'s. */
457  if (*len < keyword_len + 2)
458    return FALSE;
459
460  /* Need at least space for two $'s, two spaces and a colon, and that
461     leaves zero space for the value itself. */
462  if (keyword_len > SVN_KEYWORD_MAX_LEN - 5)
463    return FALSE;
464
465  /* The keyword needs to match what we're looking for. */
466  if (strncmp(buf + 1, keyword, keyword_len))
467    return FALSE;
468
469  buf_ptr = buf + 1 + keyword_len;
470
471  /* Check for fixed-length expansion.
472   * The format of fixed length keyword and its data is
473   * Unexpanded keyword:         "$keyword::       $"
474   * Expanded keyword:           "$keyword:: value $"
475   * Expanded kw with filling:   "$keyword:: value   $"
476   * Truncated keyword:          "$keyword:: longval#$"
477   */
478  if ((buf_ptr[0] == ':') /* first char after keyword is ':' */
479      && (buf_ptr[1] == ':') /* second char after keyword is ':' */
480      && (buf_ptr[2] == ' ') /* third char after keyword is ' ' */
481      && ((buf[*len - 2] == ' ')  /* has ' ' for next to last character */
482          || (buf[*len - 2] == '#')) /* .. or has '#' for next to last
483                                        character */
484      && ((6 + keyword_len) < *len))  /* holds "$kw:: x $" at least */
485    {
486      /* This is fixed length keyword, so *len remains unchanged */
487      apr_size_t max_value_len = *len - (6 + keyword_len);
488
489      if (! value)
490        {
491          /* no value, so unexpand */
492          buf_ptr += 2;
493          while (*buf_ptr != '$')
494            *(buf_ptr++) = ' ';
495        }
496      else
497        {
498          if (value->len <= max_value_len)
499            { /* replacement not as long as template, pad with spaces */
500              strncpy(buf_ptr + 3, value->data, value->len);
501              buf_ptr += 3 + value->len;
502              while (*buf_ptr != '$')
503                *(buf_ptr++) = ' ';
504            }
505          else
506            {
507              /* replacement needs truncating */
508              strncpy(buf_ptr + 3, value->data, max_value_len);
509              buf[*len - 2] = '#';
510              buf[*len - 1] = '$';
511            }
512        }
513      return TRUE;
514    }
515
516  /* Check for unexpanded keyword. */
517  else if (buf_ptr[0] == '$')          /* "$keyword$" */
518    {
519      /* unexpanded... */
520      if (value)
521        {
522          /* ...so expand. */
523          buf_ptr[0] = ':';
524          buf_ptr[1] = ' ';
525          if (value->len)
526            {
527              apr_size_t vallen = value->len;
528
529              /* "$keyword: value $" */
530              if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
531                vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
532              strncpy(buf_ptr + 2, value->data, vallen);
533              buf_ptr[2 + vallen] = ' ';
534              buf_ptr[2 + vallen + 1] = '$';
535              *len = 5 + keyword_len + vallen;
536            }
537          else
538            {
539              /* "$keyword: $"  */
540              buf_ptr[2] = '$';
541              *len = 4 + keyword_len;
542            }
543        }
544      else
545        {
546          /* ...but do nothing. */
547        }
548      return TRUE;
549    }
550
551  /* Check for expanded keyword. */
552  else if (((*len >= 4 + keyword_len ) /* holds at least "$keyword: $" */
553           && (buf_ptr[0] == ':')      /* first char after keyword is ':' */
554           && (buf_ptr[1] == ' ')      /* second char after keyword is ' ' */
555           && (buf[*len - 2] == ' '))
556        || ((*len >= 3 + keyword_len ) /* holds at least "$keyword:$" */
557           && (buf_ptr[0] == ':')      /* first char after keyword is ':' */
558           && (buf_ptr[1] == '$')))    /* second char after keyword is '$' */
559    {
560      /* expanded... */
561      if (! value)
562        {
563          /* ...so unexpand. */
564          buf_ptr[0] = '$';
565          *len = 2 + keyword_len;
566        }
567      else
568        {
569          /* ...so re-expand. */
570          buf_ptr[0] = ':';
571          buf_ptr[1] = ' ';
572          if (value->len)
573            {
574              apr_size_t vallen = value->len;
575
576              /* "$keyword: value $" */
577              if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
578                vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
579              strncpy(buf_ptr + 2, value->data, vallen);
580              buf_ptr[2 + vallen] = ' ';
581              buf_ptr[2 + vallen + 1] = '$';
582              *len = 5 + keyword_len + vallen;
583            }
584          else
585            {
586              /* "$keyword: $"  */
587              buf_ptr[2] = '$';
588              *len = 4 + keyword_len;
589            }
590        }
591      return TRUE;
592    }
593
594  return FALSE;
595}
596
597/* Parse BUF (whose length is LEN, and which starts and ends with '$'),
598   trying to match one of the keyword names in KEYWORDS.  If such a
599   keyword is found, update *KEYWORD_NAME with the keyword name and
600   return TRUE. */
601static svn_boolean_t
602match_keyword(char *buf,
603              apr_size_t len,
604              char *keyword_name,
605              apr_hash_t *keywords)
606{
607  apr_size_t i;
608
609  /* Early return for ignored keywords */
610  if (! keywords)
611    return FALSE;
612
613  /* Extract the name of the keyword */
614  for (i = 0; i < len - 2 && buf[i + 1] != ':'; i++)
615    keyword_name[i] = buf[i + 1];
616  keyword_name[i] = '\0';
617
618  return svn_hash_gets(keywords, keyword_name) != NULL;
619}
620
621/* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN):
622   optionally perform the substitution in place, update *LEN with
623   the new length of the translated keyword string, and return TRUE.
624   If this buffer doesn't contain a known keyword pattern, leave BUF
625   and *LEN untouched and return FALSE.
626
627   See the docstring for svn_subst_copy_and_translate for how the
628   EXPAND and KEYWORDS parameters work.
629
630   NOTE: It is assumed that BUF has been allocated to be at least
631   SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less
632   than or equal SVN_KEYWORD_MAX_LEN in length.  Also, any expansions
633   which would result in a keyword string which is greater than
634   SVN_KEYWORD_MAX_LEN will have their values truncated in such a way
635   that the resultant keyword string is still valid (begins with
636   "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long).  */
637static svn_boolean_t
638translate_keyword(char *buf,
639                  apr_size_t *len,
640                  const char *keyword_name,
641                  svn_boolean_t expand,
642                  apr_hash_t *keywords)
643{
644  const svn_string_t *value;
645
646  /* Make sure we gotz good stuffs. */
647  assert(*len <= SVN_KEYWORD_MAX_LEN);
648  assert((buf[0] == '$') && (buf[*len - 1] == '$'));
649
650  /* Early return for ignored keywords */
651  if (! keywords)
652    return FALSE;
653
654  value = svn_hash_gets(keywords, keyword_name);
655
656  if (value)
657    {
658      return translate_keyword_subst(buf, len,
659                                     keyword_name, strlen(keyword_name),
660                                     expand ? value : NULL);
661    }
662
663  return FALSE;
664}
665
666/* A boolean expression that evaluates to true if the first STR_LEN characters
667   of the string STR are one of the end-of-line strings LF, CR, or CRLF;
668   to false otherwise.  */
669#define STRING_IS_EOL(str, str_len) \
670  (((str_len) == 2 &&  (str)[0] == '\r' && (str)[1] == '\n') || \
671   ((str_len) == 1 && ((str)[0] == '\n' || (str)[0] == '\r')))
672
673/* A boolean expression that evaluates to true if the end-of-line string EOL1,
674   having length EOL1_LEN, and the end-of-line string EOL2, having length
675   EOL2_LEN, are different, assuming that EOL1 and EOL2 are both from the
676   set {"\n", "\r", "\r\n"};  to false otherwise.
677
678   Given that EOL1 and EOL2 are either "\n", "\r", or "\r\n", then if
679   EOL1_LEN is not the same as EOL2_LEN, then EOL1 and EOL2 are of course
680   different. If EOL1_LEN and EOL2_LEN are both 2 then EOL1 and EOL2 are both
681   "\r\n" and *EOL1 == *EOL2. Otherwise, EOL1_LEN and EOL2_LEN are both 1.
682   We need only check the one character for equality to determine whether
683   EOL1 and EOL2 are different in that case. */
684#define DIFFERENT_EOL_STRINGS(eol1, eol1_len, eol2, eol2_len) \
685  (((eol1_len) != (eol2_len)) || (*(eol1) != *(eol2)))
686
687
688/* Translate the newline string NEWLINE_BUF (of length NEWLINE_LEN) to
689   the newline string EOL_STR (of length EOL_STR_LEN), writing the
690   result (which is always EOL_STR) to the stream DST.
691
692   This function assumes that NEWLINE_BUF is either "\n", "\r", or "\r\n".
693
694   Also check for consistency of the source newline strings across
695   multiple calls, using SRC_FORMAT (length *SRC_FORMAT_LEN) as a cache
696   of the first newline found.  If the current newline is not the same
697   as SRC_FORMAT, look to the REPAIR parameter.  If REPAIR is TRUE,
698   ignore the inconsistency, else return an SVN_ERR_IO_INCONSISTENT_EOL
699   error.  If *SRC_FORMAT_LEN is 0, assume we are examining the first
700   newline in the file, and copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to
701   use for later consistency checks.
702
703   If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if the
704   newline string that was written (EOL_STR) is not the same as the newline
705   string that was translated (NEWLINE_BUF), otherwise leave *TRANSLATED_EOL
706   untouched.
707
708   Note: all parameters are required even if REPAIR is TRUE.
709   ### We could require that REPAIR must not change across a sequence of
710       calls, and could then optimize by not using SRC_FORMAT at all if
711       REPAIR is TRUE.
712*/
713static svn_error_t *
714translate_newline(const char *eol_str,
715                  apr_size_t eol_str_len,
716                  char *src_format,
717                  apr_size_t *src_format_len,
718                  const char *newline_buf,
719                  apr_size_t newline_len,
720                  svn_stream_t *dst,
721                  svn_boolean_t *translated_eol,
722                  svn_boolean_t repair)
723{
724  SVN_ERR_ASSERT(STRING_IS_EOL(newline_buf, newline_len));
725
726  /* If we've seen a newline before, compare it with our cache to
727     check for consistency, else cache it for future comparisons. */
728  if (*src_format_len)
729    {
730      /* Comparing with cache.  If we are inconsistent and
731         we are NOT repairing the file, generate an error! */
732      if ((! repair) && DIFFERENT_EOL_STRINGS(src_format, *src_format_len,
733                                              newline_buf, newline_len))
734        return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL, NULL, NULL);
735    }
736  else
737    {
738      /* This is our first line ending, so cache it before
739         handling it. */
740      strncpy(src_format, newline_buf, newline_len);
741      *src_format_len = newline_len;
742    }
743
744  /* Write the desired newline */
745  SVN_ERR(translate_write(dst, eol_str, eol_str_len));
746
747  /* Report whether we translated it.  Note: Not using DIFFERENT_EOL_STRINGS()
748   * because EOL_STR may not be a valid EOL sequence. */
749  if (translated_eol != NULL &&
750      (eol_str_len != newline_len ||
751       memcmp(eol_str, newline_buf, eol_str_len) != 0))
752    *translated_eol = TRUE;
753
754  return SVN_NO_ERROR;
755}
756
757
758
759/*** Public interfaces. ***/
760
761svn_boolean_t
762svn_subst_keywords_differ(const svn_subst_keywords_t *a,
763                          const svn_subst_keywords_t *b,
764                          svn_boolean_t compare_values)
765{
766  if (((a == NULL) && (b == NULL)) /* no A or B */
767      /* no A, and B has no contents */
768      || ((a == NULL)
769          && (b->revision == NULL)
770          && (b->date == NULL)
771          && (b->author == NULL)
772          && (b->url == NULL))
773      /* no B, and A has no contents */
774      || ((b == NULL)           && (a->revision == NULL)
775          && (a->date == NULL)
776          && (a->author == NULL)
777          && (a->url == NULL))
778      /* neither A nor B has any contents */
779      || ((a != NULL) && (b != NULL)
780          && (b->revision == NULL)
781          && (b->date == NULL)
782          && (b->author == NULL)
783          && (b->url == NULL)
784          && (a->revision == NULL)
785          && (a->date == NULL)
786          && (a->author == NULL)
787          && (a->url == NULL)))
788    {
789      return FALSE;
790    }
791  else if ((a == NULL) || (b == NULL))
792    return TRUE;
793
794  /* Else both A and B have some keywords. */
795
796  if ((! a->revision) != (! b->revision))
797    return TRUE;
798  else if ((compare_values && (a->revision != NULL))
799           && (strcmp(a->revision->data, b->revision->data) != 0))
800    return TRUE;
801
802  if ((! a->date) != (! b->date))
803    return TRUE;
804  else if ((compare_values && (a->date != NULL))
805           && (strcmp(a->date->data, b->date->data) != 0))
806    return TRUE;
807
808  if ((! a->author) != (! b->author))
809    return TRUE;
810  else if ((compare_values && (a->author != NULL))
811           && (strcmp(a->author->data, b->author->data) != 0))
812    return TRUE;
813
814  if ((! a->url) != (! b->url))
815    return TRUE;
816  else if ((compare_values && (a->url != NULL))
817           && (strcmp(a->url->data, b->url->data) != 0))
818    return TRUE;
819
820  /* Else we never found a difference, so they must be the same. */
821
822  return FALSE;
823}
824
825svn_boolean_t
826svn_subst_keywords_differ2(apr_hash_t *a,
827                           apr_hash_t *b,
828                           svn_boolean_t compare_values,
829                           apr_pool_t *pool)
830{
831  apr_hash_index_t *hi;
832  unsigned int a_count, b_count;
833
834  /* An empty hash is logically equal to a NULL,
835   * as far as this API is concerned. */
836  a_count = (a == NULL) ? 0 : apr_hash_count(a);
837  b_count = (b == NULL) ? 0 : apr_hash_count(b);
838
839  if (a_count != b_count)
840    return TRUE;
841
842  if (a_count == 0)
843    return FALSE;
844
845  /* The hashes are both non-NULL, and have the same number of items.
846   * We must check that every item of A is present in B. */
847  for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi))
848    {
849      const void *key;
850      apr_ssize_t klen;
851      void *void_a_val;
852      svn_string_t *a_val, *b_val;
853
854      apr_hash_this(hi, &key, &klen, &void_a_val);
855      a_val = void_a_val;
856      b_val = apr_hash_get(b, key, klen);
857
858      if (!b_val || (compare_values && !svn_string_compare(a_val, b_val)))
859        return TRUE;
860    }
861
862  return FALSE;
863}
864
865
866/* Baton for translate_chunk() to store its state in. */
867struct translation_baton
868{
869  const char *eol_str;
870  svn_boolean_t *translated_eol;
871  svn_boolean_t repair;
872  apr_hash_t *keywords;
873  svn_boolean_t expand;
874
875  /* 'short boolean' array that encodes what character values
876     may trigger a translation action, hence are 'interesting' */
877  char interesting[256];
878
879  /* Length of the string EOL_STR points to. */
880  apr_size_t eol_str_len;
881
882  /* Buffer to cache any newline state between translation chunks */
883  char newline_buf[2];
884
885  /* Offset (within newline_buf) of the first *unused* character */
886  apr_size_t newline_off;
887
888  /* Buffer to cache keyword-parsing state between translation chunks */
889  char keyword_buf[SVN_KEYWORD_MAX_LEN];
890
891  /* Offset (within keyword-buf) to the first *unused* character */
892  apr_size_t keyword_off;
893
894  /* EOL style used in the chunk-source */
895  char src_format[2];
896
897  /* Length of the EOL style string found in the chunk-source,
898     or zero if none encountered yet */
899  apr_size_t src_format_len;
900
901  /* If this is svn_tristate_false, translate_newline() will be called
902     for every newline in the file */
903  svn_tristate_t nl_translation_skippable;
904};
905
906
907/* Allocate a baton for use with translate_chunk() in POOL and
908 * initialize it for the first iteration.
909 *
910 * The caller must assure that EOL_STR and KEYWORDS at least
911 * have the same life time as that of POOL.
912 */
913static struct translation_baton *
914create_translation_baton(const char *eol_str,
915                         svn_boolean_t *translated_eol,
916                         svn_boolean_t repair,
917                         apr_hash_t *keywords,
918                         svn_boolean_t expand,
919                         apr_pool_t *pool)
920{
921  struct translation_baton *b = apr_palloc(pool, sizeof(*b));
922
923  /* For efficiency, convert an empty set of keywords to NULL. */
924  if (keywords && (apr_hash_count(keywords) == 0))
925    keywords = NULL;
926
927  b->eol_str = eol_str;
928  b->eol_str_len = eol_str ? strlen(eol_str) : 0;
929  b->translated_eol = translated_eol;
930  b->repair = repair;
931  b->keywords = keywords;
932  b->expand = expand;
933  b->newline_off = 0;
934  b->keyword_off = 0;
935  b->src_format_len = 0;
936  b->nl_translation_skippable = svn_tristate_unknown;
937
938  /* Most characters don't start translation actions.
939   * Mark those that do depending on the parameters we got. */
940  memset(b->interesting, FALSE, sizeof(b->interesting));
941  if (keywords)
942    b->interesting['$'] = TRUE;
943  if (eol_str)
944    {
945      b->interesting['\r'] = TRUE;
946      b->interesting['\n'] = TRUE;
947    }
948
949  return b;
950}
951
952/* Return TRUE if the EOL starting at BUF matches the eol_str member of B.
953 * Be aware of special cases like "\n\r\n" and "\n\n\r". For sequences like
954 * "\n$" (an EOL followed by a keyword), the result will be FALSE since it is
955 * more efficient to handle that special case implicitly in the calling code
956 * by exiting the quick scan loop.
957 * The caller must ensure that buf[0] and buf[1] refer to valid memory
958 * locations.
959 */
960static APR_INLINE svn_boolean_t
961eol_unchanged(struct translation_baton *b,
962              const char *buf)
963{
964  /* If the first byte doesn't match, the whole EOL won't.
965   * This does also handle the (certainly invalid) case that
966   * eol_str would be an empty string.
967   */
968  if (buf[0] != b->eol_str[0])
969    return FALSE;
970
971  /* two-char EOLs must be a full match */
972  if (b->eol_str_len == 2)
973    return buf[1] == b->eol_str[1];
974
975  /* The first char matches the required 1-byte EOL.
976   * But maybe, buf[] contains a 2-byte EOL?
977   * In that case, the second byte will be interesting
978   * and not be another EOL of its own.
979   */
980  return !b->interesting[(unsigned char)buf[1]] || buf[0] == buf[1];
981}
982
983
984/* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN
985 * according to the settings and state stored in baton B.
986 *
987 * Write output to stream DST.
988 *
989 * To finish a series of chunk translations, flush all buffers by calling
990 * this routine with a NULL value for BUF.
991 *
992 * If B->translated_eol is not NULL, then set *B->translated_eol to TRUE if
993 * an end-of-line sequence was changed, otherwise leave it untouched.
994 *
995 * Use POOL for temporary allocations.
996 */
997static svn_error_t *
998translate_chunk(svn_stream_t *dst,
999                struct translation_baton *b,
1000                const char *buf,
1001                apr_size_t buflen,
1002                apr_pool_t *pool)
1003{
1004  const char *p;
1005  apr_size_t len;
1006
1007  if (buf)
1008    {
1009      /* precalculate some oft-used values */
1010      const char *end = buf + buflen;
1011      const char *interesting = b->interesting;
1012      apr_size_t next_sign_off = 0;
1013
1014      /* At the beginning of this loop, assume that we might be in an
1015       * interesting state, i.e. with data in the newline or keyword
1016       * buffer.  First try to get to the boring state so we can copy
1017       * a run of boring characters; then try to get back to the
1018       * interesting state by processing an interesting character,
1019       * and repeat. */
1020      for (p = buf; p < end;)
1021        {
1022          /* Try to get to the boring state, if necessary. */
1023          if (b->newline_off)
1024            {
1025              if (*p == '\n')
1026                b->newline_buf[b->newline_off++] = *p++;
1027
1028              SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1029                                        b->src_format,
1030                                        &b->src_format_len, b->newline_buf,
1031                                        b->newline_off, dst, b->translated_eol,
1032                                        b->repair));
1033
1034              b->newline_off = 0;
1035            }
1036          else if (b->keyword_off && *p == '$')
1037            {
1038              svn_boolean_t keyword_matches;
1039              char keyword_name[SVN_KEYWORD_MAX_LEN + 1];
1040
1041              /* If keyword is matched, but not correctly translated, try to
1042               * look for the next ending '$'. */
1043              b->keyword_buf[b->keyword_off++] = *p++;
1044              keyword_matches = match_keyword(b->keyword_buf, b->keyword_off,
1045                                              keyword_name, b->keywords);
1046              if (!keyword_matches)
1047                {
1048                  /* reuse the ending '$' */
1049                  p--;
1050                  b->keyword_off--;
1051                }
1052
1053              if (!keyword_matches ||
1054                  translate_keyword(b->keyword_buf, &b->keyword_off,
1055                                    keyword_name, b->expand, b->keywords) ||
1056                  b->keyword_off >= SVN_KEYWORD_MAX_LEN)
1057                {
1058                  /* write out non-matching text or translated keyword */
1059                  SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1060
1061                  next_sign_off = 0;
1062                  b->keyword_off = 0;
1063                }
1064              else
1065                {
1066                  if (next_sign_off == 0)
1067                    next_sign_off = b->keyword_off - 1;
1068
1069                  continue;
1070                }
1071            }
1072          else if (b->keyword_off == SVN_KEYWORD_MAX_LEN - 1
1073                   || (b->keyword_off && (*p == '\r' || *p == '\n')))
1074            {
1075              if (next_sign_off > 0)
1076              {
1077                /* rolling back, continue with next '$' in keyword_buf */
1078                p -= (b->keyword_off - next_sign_off);
1079                b->keyword_off = next_sign_off;
1080                next_sign_off = 0;
1081              }
1082              /* No closing '$' found; flush the keyword buffer. */
1083              SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1084
1085              b->keyword_off = 0;
1086            }
1087          else if (b->keyword_off)
1088            {
1089              b->keyword_buf[b->keyword_off++] = *p++;
1090              continue;
1091            }
1092
1093          /* translate_newline will modify the baton for src_format_len==0
1094             or may return an error if b->repair is FALSE.  In all other
1095             cases, we can skip the newline translation as long as source
1096             EOL format and actual EOL format match.  If there is a
1097             mismatch, translate_newline will be called regardless of
1098             nl_translation_skippable.
1099           */
1100          if (b->nl_translation_skippable == svn_tristate_unknown &&
1101              b->src_format_len > 0)
1102            {
1103              /* test whether translate_newline may return an error */
1104              if (b->eol_str_len == b->src_format_len &&
1105                  strncmp(b->eol_str, b->src_format, b->eol_str_len) == 0)
1106                b->nl_translation_skippable = svn_tristate_true;
1107              else if (b->repair)
1108                b->nl_translation_skippable = svn_tristate_true;
1109              else
1110                b->nl_translation_skippable = svn_tristate_false;
1111            }
1112
1113          /* We're in the boring state; look for interesting characters.
1114             Offset len such that it will become 0 in the first iteration.
1115           */
1116          len = 0 - b->eol_str_len;
1117
1118          /* Look for the next EOL (or $) that actually needs translation.
1119             Stop there or at EOF, whichever is encountered first.
1120           */
1121          do
1122            {
1123              /* skip current EOL */
1124              len += b->eol_str_len;
1125
1126              if (b->keywords)
1127                {
1128                  /* Check 4 bytes at once to allow for efficient pipelining
1129                    and to reduce loop condition overhead. */
1130                  while ((p + len + 4) <= end)
1131                    {
1132                      if (interesting[(unsigned char)p[len]]
1133                          || interesting[(unsigned char)p[len+1]]
1134                          || interesting[(unsigned char)p[len+2]]
1135                          || interesting[(unsigned char)p[len+3]])
1136                        break;
1137
1138                      len += 4;
1139                    }
1140
1141                  /* Found an interesting char or EOF in the next 4 bytes.
1142                     Find its exact position. */
1143                  while ((p + len) < end
1144                         && !interesting[(unsigned char)p[len]])
1145                    ++len;
1146                }
1147              else
1148                {
1149                  /* use our optimized sub-routine to find the next EOL */
1150                  const char *start = p + len;
1151                  const char *eol
1152                    = svn_eol__find_eol_start((char *)start, end - start);
1153
1154                  /* EOL will be NULL if we did not find a line ending */
1155                  len += (eol ? eol : end) - start;
1156                }
1157            }
1158          while (b->nl_translation_skippable ==
1159                   svn_tristate_true &&       /* can potentially skip EOLs */
1160                 p + len + 2 < end &&         /* not too close to EOF */
1161                 eol_unchanged(b, p + len));  /* EOL format already ok */
1162
1163          while ((p + len) < end && !interesting[(unsigned char)p[len]])
1164            len++;
1165
1166          if (len)
1167            {
1168              SVN_ERR(translate_write(dst, p, len));
1169              p += len;
1170            }
1171
1172          /* Set up state according to the interesting character, if any. */
1173          if (p < end)
1174            {
1175              switch (*p)
1176                {
1177                case '$':
1178                  b->keyword_buf[b->keyword_off++] = *p++;
1179                  break;
1180                case '\r':
1181                  b->newline_buf[b->newline_off++] = *p++;
1182                  break;
1183                case '\n':
1184                  b->newline_buf[b->newline_off++] = *p++;
1185
1186                  SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1187                                            b->src_format,
1188                                            &b->src_format_len,
1189                                            b->newline_buf,
1190                                            b->newline_off, dst,
1191                                            b->translated_eol, b->repair));
1192
1193                  b->newline_off = 0;
1194                  break;
1195
1196                }
1197            }
1198        }
1199    }
1200  else
1201    {
1202      if (b->newline_off)
1203        {
1204          SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1205                                    b->src_format, &b->src_format_len,
1206                                    b->newline_buf, b->newline_off,
1207                                    dst, b->translated_eol, b->repair));
1208          b->newline_off = 0;
1209        }
1210
1211      if (b->keyword_off)
1212        {
1213          SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1214          b->keyword_off = 0;
1215        }
1216    }
1217
1218  return SVN_NO_ERROR;
1219}
1220
1221/* Baton for use with translated stream callbacks. */
1222struct translated_stream_baton
1223{
1224  /* Stream to take input from (before translation) on read
1225     /write output to (after translation) on write. */
1226  svn_stream_t *stream;
1227
1228  /* Input/Output translation batons to make them separate chunk streams. */
1229  struct translation_baton *in_baton, *out_baton;
1230
1231  /* Remembers whether any write operations have taken place;
1232     if so, we need to flush the output chunk stream. */
1233  svn_boolean_t written;
1234
1235  /* Buffer to hold translated read data. */
1236  svn_stringbuf_t *readbuf;
1237
1238  /* Offset of the first non-read character in readbuf. */
1239  apr_size_t readbuf_off;
1240
1241  /* Buffer to hold read data
1242     between svn_stream_read() and translate_chunk(). */
1243  char *buf;
1244#define SVN__TRANSLATION_BUF_SIZE (SVN__STREAM_CHUNK_SIZE + 1)
1245
1246  /* Pool for callback iterations */
1247  apr_pool_t *iterpool;
1248};
1249
1250
1251/* Implements svn_read_fn_t. */
1252static svn_error_t *
1253translated_stream_read(void *baton,
1254                       char *buffer,
1255                       apr_size_t *len)
1256{
1257  struct translated_stream_baton *b = baton;
1258  apr_size_t readlen = SVN__STREAM_CHUNK_SIZE;
1259  apr_size_t unsatisfied = *len;
1260  apr_size_t off = 0;
1261
1262  /* Optimization for a frequent special case. The configuration parser (and
1263     a few others) reads the stream one byte at a time. All the memcpy, pool
1264     clearing etc. imposes a huge overhead in that case. In most cases, we
1265     can just take that single byte directly from the read buffer.
1266
1267     Since *len > 1 requires lots of code to be run anyways, we can afford
1268     the extra overhead of checking for *len == 1.
1269
1270     See <http://mail-archives.apache.org/mod_mbox/subversion-dev/201003.mbox/%3C4B94011E.1070207@alice-dsl.de%3E>.
1271  */
1272  if (unsatisfied == 1 && b->readbuf_off < b->readbuf->len)
1273    {
1274      /* Just take it from the read buffer */
1275      *buffer = b->readbuf->data[b->readbuf_off++];
1276
1277      return SVN_NO_ERROR;
1278    }
1279
1280  /* Standard code path. */
1281  while (readlen == SVN__STREAM_CHUNK_SIZE && unsatisfied > 0)
1282    {
1283      apr_size_t to_copy;
1284      apr_size_t buffer_remainder;
1285
1286      svn_pool_clear(b->iterpool);
1287      /* fill read buffer, if necessary */
1288      if (! (b->readbuf_off < b->readbuf->len))
1289        {
1290          svn_stream_t *buf_stream;
1291
1292          svn_stringbuf_setempty(b->readbuf);
1293          b->readbuf_off = 0;
1294          SVN_ERR(svn_stream_read_full(b->stream, b->buf, &readlen));
1295          buf_stream = svn_stream_from_stringbuf(b->readbuf, b->iterpool);
1296
1297          SVN_ERR(translate_chunk(buf_stream, b->in_baton, b->buf,
1298                                  readlen, b->iterpool));
1299
1300          if (readlen != SVN__STREAM_CHUNK_SIZE)
1301            SVN_ERR(translate_chunk(buf_stream, b->in_baton, NULL, 0,
1302                                    b->iterpool));
1303
1304          SVN_ERR(svn_stream_close(buf_stream));
1305        }
1306
1307      /* Satisfy from the read buffer */
1308      buffer_remainder = b->readbuf->len - b->readbuf_off;
1309      to_copy = (buffer_remainder > unsatisfied)
1310        ? unsatisfied : buffer_remainder;
1311      memcpy(buffer + off, b->readbuf->data + b->readbuf_off, to_copy);
1312      off += to_copy;
1313      b->readbuf_off += to_copy;
1314      unsatisfied -= to_copy;
1315    }
1316
1317  *len -= unsatisfied;
1318
1319  return SVN_NO_ERROR;
1320}
1321
1322/* Implements svn_write_fn_t. */
1323static svn_error_t *
1324translated_stream_write(void *baton,
1325                        const char *buffer,
1326                        apr_size_t *len)
1327{
1328  struct translated_stream_baton *b = baton;
1329  svn_pool_clear(b->iterpool);
1330
1331  b->written = TRUE;
1332  return translate_chunk(b->stream, b->out_baton, buffer, *len, b->iterpool);
1333}
1334
1335/* Implements svn_close_fn_t. */
1336static svn_error_t *
1337translated_stream_close(void *baton)
1338{
1339  struct translated_stream_baton *b = baton;
1340  svn_error_t *err = NULL;
1341
1342  if (b->written)
1343    err = translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool);
1344
1345  err = svn_error_compose_create(err, svn_stream_close(b->stream));
1346
1347  svn_pool_destroy(b->iterpool);
1348
1349  return svn_error_trace(err);
1350}
1351
1352
1353/* svn_stream_mark_t for translation streams. */
1354typedef struct mark_translated_t
1355{
1356  /* Saved translation state. */
1357  struct translated_stream_baton saved_baton;
1358
1359  /* Mark set on the underlying stream. */
1360  svn_stream_mark_t *mark;
1361} mark_translated_t;
1362
1363/* Implements svn_stream_mark_fn_t. */
1364static svn_error_t *
1365translated_stream_mark(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
1366{
1367  mark_translated_t *mt;
1368  struct translated_stream_baton *b = baton;
1369
1370  mt = apr_palloc(pool, sizeof(*mt));
1371  SVN_ERR(svn_stream_mark(b->stream, &mt->mark, pool));
1372
1373  /* Save translation state. */
1374  mt->saved_baton.in_baton = apr_pmemdup(pool, b->in_baton,
1375                                         sizeof(*mt->saved_baton.in_baton));
1376  mt->saved_baton.out_baton = apr_pmemdup(pool, b->out_baton,
1377                                          sizeof(*mt->saved_baton.out_baton));
1378  mt->saved_baton.written = b->written;
1379  mt->saved_baton.readbuf = svn_stringbuf_dup(b->readbuf, pool);
1380  mt->saved_baton.readbuf_off = b->readbuf_off;
1381  mt->saved_baton.buf = apr_pmemdup(pool, b->buf, SVN__TRANSLATION_BUF_SIZE);
1382
1383  *mark = (svn_stream_mark_t *)mt;
1384
1385  return SVN_NO_ERROR;
1386}
1387
1388/* Implements svn_stream_seek_fn_t. */
1389static svn_error_t *
1390translated_stream_seek(void *baton, const svn_stream_mark_t *mark)
1391{
1392  struct translated_stream_baton *b = baton;
1393
1394  if (mark != NULL)
1395    {
1396      const mark_translated_t *mt = (const mark_translated_t *)mark;
1397
1398      /* Flush output buffer if necessary. */
1399      if (b->written)
1400        SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0,
1401                                b->iterpool));
1402
1403      SVN_ERR(svn_stream_seek(b->stream, mt->mark));
1404
1405      /* Restore translation state, avoiding new allocations. */
1406      *b->in_baton = *mt->saved_baton.in_baton;
1407      *b->out_baton = *mt->saved_baton.out_baton;
1408      b->written = mt->saved_baton.written;
1409      svn_stringbuf_setempty(b->readbuf);
1410      svn_stringbuf_appendbytes(b->readbuf, mt->saved_baton.readbuf->data,
1411                                mt->saved_baton.readbuf->len);
1412      b->readbuf_off = mt->saved_baton.readbuf_off;
1413      memcpy(b->buf, mt->saved_baton.buf, SVN__TRANSLATION_BUF_SIZE);
1414    }
1415  else
1416    {
1417      SVN_ERR(svn_stream_reset(b->stream));
1418
1419      b->in_baton->newline_off = 0;
1420      b->in_baton->keyword_off = 0;
1421      b->in_baton->src_format_len = 0;
1422      b->out_baton->newline_off = 0;
1423      b->out_baton->keyword_off = 0;
1424      b->out_baton->src_format_len = 0;
1425
1426      b->written = FALSE;
1427      svn_stringbuf_setempty(b->readbuf);
1428      b->readbuf_off = 0;
1429    }
1430
1431  return SVN_NO_ERROR;
1432}
1433
1434/* Implements svn_stream__is_buffered_fn_t. */
1435static svn_boolean_t
1436translated_stream_is_buffered(void *baton)
1437{
1438  struct translated_stream_baton *b = baton;
1439  return svn_stream__is_buffered(b->stream);
1440}
1441
1442svn_error_t *
1443svn_subst_read_specialfile(svn_stream_t **stream,
1444                           const char *path,
1445                           apr_pool_t *result_pool,
1446                           apr_pool_t *scratch_pool)
1447{
1448  apr_finfo_t finfo;
1449  svn_string_t *buf;
1450
1451  /* First determine what type of special file we are
1452     detranslating. */
1453  SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK,
1454                      scratch_pool));
1455
1456  switch (finfo.filetype) {
1457  case APR_REG:
1458    /* Nothing special to do here, just create stream from the original
1459       file's contents. */
1460    SVN_ERR(svn_stream_open_readonly(stream, path, result_pool, scratch_pool));
1461    break;
1462
1463  case APR_LNK:
1464    /* Determine the destination of the link. */
1465    SVN_ERR(svn_io_read_link(&buf, path, scratch_pool));
1466    *stream = svn_stream_from_string(svn_string_createf(result_pool,
1467                                                        "link %s",
1468                                                        buf->data),
1469                                     result_pool);
1470    break;
1471
1472  default:
1473    SVN_ERR_MALFUNCTION();
1474  }
1475
1476  return SVN_NO_ERROR;
1477}
1478
1479/* Same as svn_subst_stream_translated(), except for the following.
1480 *
1481 * If TRANSLATED_EOL is not NULL, then reading and/or writing to the stream
1482 * will set *TRANSLATED_EOL to TRUE if an end-of-line sequence was changed,
1483 * otherwise leave it untouched.
1484 */
1485static svn_stream_t *
1486stream_translated(svn_stream_t *stream,
1487                  const char *eol_str,
1488                  svn_boolean_t *translated_eol,
1489                  svn_boolean_t repair,
1490                  apr_hash_t *keywords,
1491                  svn_boolean_t expand,
1492                  apr_pool_t *result_pool)
1493{
1494  struct translated_stream_baton *baton
1495    = apr_palloc(result_pool, sizeof(*baton));
1496  svn_stream_t *s = svn_stream_create(baton, result_pool);
1497
1498  /* Make sure EOL_STR and KEYWORDS are allocated in RESULT_POOL
1499     so they have the same lifetime as the stream. */
1500  if (eol_str)
1501    eol_str = apr_pstrdup(result_pool, eol_str);
1502  if (keywords)
1503    {
1504      if (apr_hash_count(keywords) == 0)
1505        keywords = NULL;
1506      else
1507        {
1508          /* deep copy the hash to make sure it's allocated in RESULT_POOL */
1509          apr_hash_t *copy = apr_hash_make(result_pool);
1510          apr_hash_index_t *hi;
1511          apr_pool_t *subpool;
1512
1513          subpool = svn_pool_create(result_pool);
1514          for (hi = apr_hash_first(subpool, keywords);
1515               hi; hi = apr_hash_next(hi))
1516            {
1517              const void *key;
1518              void *val;
1519
1520              apr_hash_this(hi, &key, NULL, &val);
1521              svn_hash_sets(copy, apr_pstrdup(result_pool, key),
1522                            svn_string_dup(val, result_pool));
1523            }
1524          svn_pool_destroy(subpool);
1525
1526          keywords = copy;
1527        }
1528    }
1529
1530  /* Setup the baton fields */
1531  baton->stream = stream;
1532  baton->in_baton
1533    = create_translation_baton(eol_str, translated_eol, repair, keywords,
1534                               expand, result_pool);
1535  baton->out_baton
1536    = create_translation_baton(eol_str, translated_eol, repair, keywords,
1537                               expand, result_pool);
1538  baton->written = FALSE;
1539  baton->readbuf = svn_stringbuf_create_empty(result_pool);
1540  baton->readbuf_off = 0;
1541  baton->iterpool = svn_pool_create(result_pool);
1542  baton->buf = apr_palloc(result_pool, SVN__TRANSLATION_BUF_SIZE);
1543
1544  /* Setup the stream methods */
1545  svn_stream_set_read2(s, NULL /* only full read support */,
1546                       translated_stream_read);
1547  svn_stream_set_write(s, translated_stream_write);
1548  svn_stream_set_close(s, translated_stream_close);
1549  svn_stream_set_mark(s, translated_stream_mark);
1550  svn_stream_set_seek(s, translated_stream_seek);
1551  svn_stream__set_is_buffered(s, translated_stream_is_buffered);
1552
1553  return s;
1554}
1555
1556svn_stream_t *
1557svn_subst_stream_translated(svn_stream_t *stream,
1558                            const char *eol_str,
1559                            svn_boolean_t repair,
1560                            apr_hash_t *keywords,
1561                            svn_boolean_t expand,
1562                            apr_pool_t *result_pool)
1563{
1564  return stream_translated(stream, eol_str, NULL, repair, keywords, expand,
1565                           result_pool);
1566}
1567
1568/* Same as svn_subst_translate_cstring2(), except for the following.
1569 *
1570 * If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if an
1571 * end-of-line sequence was changed, or to FALSE otherwise.
1572 */
1573static svn_error_t *
1574translate_cstring(const char **dst,
1575                  svn_boolean_t *translated_eol,
1576                  const char *src,
1577                  const char *eol_str,
1578                  svn_boolean_t repair,
1579                  apr_hash_t *keywords,
1580                  svn_boolean_t expand,
1581                  apr_pool_t *pool)
1582{
1583  svn_stringbuf_t *dst_stringbuf;
1584  svn_stream_t *dst_stream;
1585  apr_size_t len = strlen(src);
1586
1587  /* The easy way out:  no translation needed, just copy. */
1588  if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1589    {
1590      *dst = apr_pstrmemdup(pool, src, len);
1591      return SVN_NO_ERROR;
1592    }
1593
1594  /* Create a stringbuf and wrapper stream to hold the output. */
1595  dst_stringbuf = svn_stringbuf_create_empty(pool);
1596  dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool);
1597
1598  if (translated_eol)
1599    *translated_eol = FALSE;
1600
1601  /* Another wrapper to translate the content. */
1602  dst_stream = stream_translated(dst_stream, eol_str, translated_eol, repair,
1603                                 keywords, expand, pool);
1604
1605  /* Jam the text into the destination stream (to translate it). */
1606  SVN_ERR(svn_stream_write(dst_stream, src, &len));
1607
1608  /* Close the destination stream to flush unwritten data. */
1609  SVN_ERR(svn_stream_close(dst_stream));
1610
1611  *dst = dst_stringbuf->data;
1612  return SVN_NO_ERROR;
1613}
1614
1615svn_error_t *
1616svn_subst_translate_cstring2(const char *src,
1617                             const char **dst,
1618                             const char *eol_str,
1619                             svn_boolean_t repair,
1620                             apr_hash_t *keywords,
1621                             svn_boolean_t expand,
1622                             apr_pool_t *pool)
1623{
1624  return translate_cstring(dst, NULL, src, eol_str, repair, keywords, expand,
1625                            pool);
1626}
1627
1628/* Given a special file at SRC, generate a textual representation of
1629   it in a normal file at DST.  Perform all allocations in POOL. */
1630/* ### this should be folded into svn_subst_copy_and_translate3 */
1631static svn_error_t *
1632detranslate_special_file(const char *src, const char *dst,
1633                         svn_cancel_func_t cancel_func, void *cancel_baton,
1634                         apr_pool_t *scratch_pool)
1635{
1636  const char *dst_tmp;
1637  svn_stream_t *src_stream;
1638  svn_stream_t *dst_stream;
1639
1640  /* Open a temporary destination that we will eventually atomically
1641     rename into place. */
1642  SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1643                                 svn_dirent_dirname(dst, scratch_pool),
1644                                 svn_io_file_del_none,
1645                                 scratch_pool, scratch_pool));
1646  SVN_ERR(svn_subst_read_specialfile(&src_stream, src,
1647                                     scratch_pool, scratch_pool));
1648  SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
1649                           cancel_func, cancel_baton, scratch_pool));
1650
1651  /* Do the atomic rename from our temporary location. */
1652  return svn_error_trace(svn_io_file_rename(dst_tmp, dst, scratch_pool));
1653}
1654
1655/* Creates a special file DST from the "normal form" located in SOURCE.
1656 *
1657 * All temporary allocations will be done in POOL.
1658 */
1659static svn_error_t *
1660create_special_file_from_stream(svn_stream_t *source, const char *dst,
1661                                apr_pool_t *pool)
1662{
1663  svn_stringbuf_t *contents;
1664  svn_boolean_t eof;
1665  const char *identifier;
1666  const char *remainder;
1667  const char *dst_tmp;
1668  svn_boolean_t create_using_internal_representation = FALSE;
1669
1670  SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool));
1671
1672  /* Separate off the identifier.  The first space character delimits
1673     the identifier, after which any remaining characters are specific
1674     to the actual special file type being created. */
1675  identifier = contents->data;
1676  for (remainder = identifier; *remainder; remainder++)
1677    {
1678      if (*remainder == ' ')
1679        {
1680          remainder++;
1681          break;
1682        }
1683    }
1684
1685  if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ",
1686                sizeof(SVN_SUBST__SPECIAL_LINK_STR " ")-1))
1687    {
1688      /* For symlinks, the type specific data is just a filesystem
1689         path that the symlink should reference. */
1690      svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder,
1691                                                   ".tmp", pool);
1692
1693      /* If we had an error, check to see if it was because symlinks are
1694         not supported on the platform.  If so, fall back
1695         to using the internal representation. */
1696      if (err)
1697        {
1698          if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
1699            {
1700              svn_error_clear(err);
1701              create_using_internal_representation = TRUE;
1702            }
1703          else
1704            return err;
1705        }
1706    }
1707  else
1708    {
1709      /* Just create a normal file using the internal special file
1710         representation.  We don't want a commit of an unknown special
1711         file type to DoS all the clients. */
1712      create_using_internal_representation = TRUE;
1713    }
1714
1715  /* If nothing else worked, write out the internal representation to
1716     a file that can be edited by the user. */
1717  if (create_using_internal_representation)
1718    {
1719      svn_stream_t *new_stream;
1720      apr_size_t len;
1721
1722      SVN_ERR(svn_stream_open_unique(&new_stream, &dst_tmp,
1723                                     svn_dirent_dirname(dst, pool),
1724                                     svn_io_file_del_none,
1725                                     pool, pool));
1726
1727      if (!eof)
1728        svn_stringbuf_appendcstr(contents, "\n");
1729      len = contents->len;
1730      SVN_ERR(svn_stream_write(new_stream, contents->data, &len));
1731      SVN_ERR(svn_stream_copy3(svn_stream_disown(source, pool), new_stream,
1732                               NULL, NULL, pool));
1733    }
1734
1735  /* Do the atomic rename from our temporary location. */
1736  return svn_error_trace(svn_io_file_rename(dst_tmp, dst, pool));
1737}
1738
1739
1740svn_error_t *
1741svn_subst_copy_and_translate4(const char *src,
1742                              const char *dst,
1743                              const char *eol_str,
1744                              svn_boolean_t repair,
1745                              apr_hash_t *keywords,
1746                              svn_boolean_t expand,
1747                              svn_boolean_t special,
1748                              svn_cancel_func_t cancel_func,
1749                              void *cancel_baton,
1750                              apr_pool_t *pool)
1751{
1752  svn_stream_t *src_stream;
1753  svn_stream_t *dst_stream;
1754  const char *dst_tmp;
1755  svn_error_t *err;
1756  svn_node_kind_t kind;
1757  svn_boolean_t path_special;
1758
1759  SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool));
1760
1761  /* If this is a 'special' file, we may need to create it or
1762     detranslate it. */
1763  if (special || path_special)
1764    {
1765      if (expand)
1766        {
1767          if (path_special)
1768            {
1769              /* We are being asked to create a special file from a special
1770                 file.  Do a temporary detranslation and work from there. */
1771
1772              /* ### woah. this section just undoes all the work we already did
1773                 ### to read the contents of the special file. shoot... the
1774                 ### svn_subst_read_specialfile even checks the file type
1775                 ### for us! */
1776
1777              SVN_ERR(svn_subst_read_specialfile(&src_stream, src, pool, pool));
1778            }
1779          else
1780            {
1781              SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1782            }
1783
1784          SVN_ERR(create_special_file_from_stream(src_stream, dst, pool));
1785
1786          return svn_error_trace(svn_stream_close(src_stream));
1787        }
1788      /* else !expand */
1789
1790      return svn_error_trace(detranslate_special_file(src, dst,
1791                                                      cancel_func,
1792                                                      cancel_baton,
1793                                                      pool));
1794    }
1795
1796  /* The easy way out:  no translation needed, just copy. */
1797  if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1798    return svn_error_trace(svn_io_copy_file(src, dst, FALSE, pool));
1799
1800  /* Open source file. */
1801  SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1802
1803  /* For atomicity, we translate to a tmp file and then rename the tmp file
1804     over the real destination. */
1805  SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1806                                 svn_dirent_dirname(dst, pool),
1807                                 svn_io_file_del_none, pool, pool));
1808
1809  dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair,
1810                                           keywords, expand, pool);
1811
1812  /* ###: use cancel func/baton in place of NULL/NULL below. */
1813  err = svn_stream_copy3(src_stream, dst_stream, cancel_func, cancel_baton,
1814                         pool);
1815  if (err)
1816    {
1817      /* On errors, we have a pathname available. */
1818      if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1819        err = svn_error_createf(SVN_ERR_IO_INCONSISTENT_EOL, err,
1820                                _("File '%s' has inconsistent newlines"),
1821                                svn_dirent_local_style(src, pool));
1822      return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp,
1823                                                               FALSE, pool));
1824    }
1825
1826  /* Now that dst_tmp contains the translated data, do the atomic rename. */
1827  SVN_ERR(svn_io_file_rename(dst_tmp, dst, pool));
1828
1829  /* Preserve the source file's permission bits. */
1830  SVN_ERR(svn_io_copy_perms(src, dst, pool));
1831
1832  return SVN_NO_ERROR;
1833}
1834
1835
1836/*** 'Special file' stream support */
1837
1838struct special_stream_baton
1839{
1840  svn_stream_t *read_stream;
1841  svn_stringbuf_t *write_content;
1842  svn_stream_t *write_stream;
1843  const char *path;
1844  apr_pool_t *pool;
1845};
1846
1847
1848static svn_error_t *
1849read_handler_special(void *baton, char *buffer, apr_size_t *len)
1850{
1851  struct special_stream_baton *btn = baton;
1852
1853  if (btn->read_stream)
1854    /* We actually found a file to read from */
1855    return svn_stream_read_full(btn->read_stream, buffer, len);
1856  else
1857    return svn_error_createf(APR_ENOENT, NULL,
1858                             _("Can't read special file: File '%s' not found"),
1859                             svn_dirent_local_style(btn->path, btn->pool));
1860}
1861
1862static svn_error_t *
1863write_handler_special(void *baton, const char *buffer, apr_size_t *len)
1864{
1865  struct special_stream_baton *btn = baton;
1866
1867  return svn_stream_write(btn->write_stream, buffer, len);
1868}
1869
1870
1871static svn_error_t *
1872close_handler_special(void *baton)
1873{
1874  struct special_stream_baton *btn = baton;
1875
1876  if (btn->write_content->len)
1877    {
1878      /* yeay! we received data and need to create a special file! */
1879
1880      svn_stream_t *source = svn_stream_from_stringbuf(btn->write_content,
1881                                                       btn->pool);
1882      SVN_ERR(create_special_file_from_stream(source, btn->path, btn->pool));
1883    }
1884
1885  return SVN_NO_ERROR;
1886}
1887
1888
1889svn_error_t *
1890svn_subst_create_specialfile(svn_stream_t **stream,
1891                             const char *path,
1892                             apr_pool_t *result_pool,
1893                             apr_pool_t *scratch_pool)
1894{
1895  struct special_stream_baton *baton = apr_palloc(result_pool, sizeof(*baton));
1896
1897  baton->path = apr_pstrdup(result_pool, path);
1898
1899  /* SCRATCH_POOL may not exist after the function returns. */
1900  baton->pool = result_pool;
1901
1902  baton->write_content = svn_stringbuf_create_empty(result_pool);
1903  baton->write_stream = svn_stream_from_stringbuf(baton->write_content,
1904                                                  result_pool);
1905
1906  *stream = svn_stream_create(baton, result_pool);
1907  svn_stream_set_write(*stream, write_handler_special);
1908  svn_stream_set_close(*stream, close_handler_special);
1909
1910  return SVN_NO_ERROR;
1911}
1912
1913
1914/* NOTE: this function is deprecated, but we cannot move it over to
1915   deprecated.c because it uses stuff private to this file, and it is
1916   not easily rebuilt in terms of "new" functions. */
1917svn_error_t *
1918svn_subst_stream_from_specialfile(svn_stream_t **stream,
1919                                  const char *path,
1920                                  apr_pool_t *pool)
1921{
1922  struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton));
1923  svn_error_t *err;
1924
1925  baton->pool = pool;
1926  baton->path = apr_pstrdup(pool, path);
1927
1928  err = svn_subst_read_specialfile(&baton->read_stream, path, pool, pool);
1929
1930  /* File might not exist because we intend to create it upon close. */
1931  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1932    {
1933      svn_error_clear(err);
1934
1935      /* Note: the special file is missing. the caller won't find out
1936         until the first read. Oh well. This function is deprecated anyways,
1937         so they can just deal with the weird behavior. */
1938      baton->read_stream = NULL;
1939    }
1940
1941  baton->write_content = svn_stringbuf_create_empty(pool);
1942  baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool);
1943
1944  *stream = svn_stream_create(baton, pool);
1945  svn_stream_set_read2(*stream, NULL /* only full read support */,
1946                       read_handler_special);
1947  svn_stream_set_write(*stream, write_handler_special);
1948  svn_stream_set_close(*stream, close_handler_special);
1949
1950  return SVN_NO_ERROR;
1951}
1952
1953
1954
1955/*** String translation */
1956svn_error_t *
1957svn_subst_translate_string2(svn_string_t **new_value,
1958                            svn_boolean_t *translated_to_utf8,
1959                            svn_boolean_t *translated_line_endings,
1960                            const svn_string_t *value,
1961                            const char *encoding,
1962                            svn_boolean_t repair,
1963                            apr_pool_t *result_pool,
1964                            apr_pool_t *scratch_pool)
1965{
1966  const char *val_utf8;
1967  const char *val_utf8_lf;
1968
1969  if (value == NULL)
1970    {
1971      *new_value = NULL;
1972      return SVN_NO_ERROR;
1973    }
1974
1975  if (encoding && !strcmp(encoding, "UTF-8"))
1976    {
1977      val_utf8 = value->data;
1978    }
1979  else if (encoding)
1980    {
1981      SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data,
1982                                          encoding, scratch_pool));
1983    }
1984  else
1985    {
1986      SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, scratch_pool));
1987    }
1988
1989  if (translated_to_utf8)
1990    *translated_to_utf8 = (strcmp(value->data, val_utf8) != 0);
1991
1992  SVN_ERR(translate_cstring(&val_utf8_lf,
1993                            translated_line_endings,
1994                            val_utf8,
1995                            "\n",  /* translate to LF */
1996                            repair,
1997                            NULL,  /* no keywords */
1998                            FALSE, /* no expansion */
1999                            scratch_pool));
2000
2001  *new_value = svn_string_create(val_utf8_lf, result_pool);
2002  return SVN_NO_ERROR;
2003}
2004
2005
2006svn_error_t *
2007svn_subst_detranslate_string(svn_string_t **new_value,
2008                             const svn_string_t *value,
2009                             svn_boolean_t for_output,
2010                             apr_pool_t *pool)
2011{
2012  svn_error_t *err;
2013  const char *val_neol;
2014  const char *val_nlocale_neol;
2015
2016  if (value == NULL)
2017    {
2018      *new_value = NULL;
2019      return SVN_NO_ERROR;
2020    }
2021
2022  SVN_ERR(svn_subst_translate_cstring2(value->data,
2023                                       &val_neol,
2024                                       APR_EOL_STR,  /* 'native' eol */
2025                                       FALSE, /* no repair */
2026                                       NULL,  /* no keywords */
2027                                       FALSE, /* no expansion */
2028                                       pool));
2029
2030  if (for_output)
2031    {
2032      err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2033      if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2034        {
2035          val_nlocale_neol =
2036            svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool);
2037          svn_error_clear(err);
2038        }
2039      else if (err)
2040        return err;
2041    }
2042  else
2043    {
2044      err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2045      if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2046        {
2047          val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool);
2048          svn_error_clear(err);
2049        }
2050      else if (err)
2051        return err;
2052    }
2053
2054  *new_value = svn_string_create(val_nlocale_neol, pool);
2055
2056  return SVN_NO_ERROR;
2057}
2058