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