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 
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 
62 void
svn_subst_eol_style_from_value(svn_subst_eol_style_t * style,const char ** eol,const char * value)63 svn_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 
107 svn_boolean_t
svn_subst_translation_required(svn_subst_eol_style_t style,const char * eol,apr_hash_t * keywords,svn_boolean_t special,svn_boolean_t force_eol_check)108 svn_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  */
153 static svn_string_t *
keyword_printf(const char * fmt,const char * rev,const char * url,const char * repos_root_url,apr_time_t date,const char * author,apr_pool_t * pool)154 keyword_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 
280 static svn_error_t *
build_keywords(apr_hash_t ** kw,svn_boolean_t expand_custom_keywords,const char * keywords_val,const char * rev,const char * url,const char * repos_root_url,apr_time_t date,const char * author,apr_pool_t * pool)281 build_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 
392 svn_error_t *
svn_subst_build_keywords2(apr_hash_t ** kw,const char * keywords_val,const char * rev,const char * url,apr_time_t date,const char * author,apr_pool_t * pool)393 svn_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 
406 svn_error_t *
svn_subst_build_keywords3(apr_hash_t ** kw,const char * keywords_val,const char * rev,const char * url,const char * repos_root_url,apr_time_t date,const char * author,apr_pool_t * pool)407 svn_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. */
427 static svn_error_t *
translate_write(svn_stream_t * stream,const void * buf,apr_size_t len)428 translate_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.  */
443 static svn_boolean_t
translate_keyword_subst(char * buf,apr_size_t * len,const char * keyword,apr_size_t keyword_len,const svn_string_t * value)444 translate_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. */
601 static svn_boolean_t
match_keyword(char * buf,apr_size_t len,char * keyword_name,apr_hash_t * keywords)602 match_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).  */
637 static svn_boolean_t
translate_keyword(char * buf,apr_size_t * len,const char * keyword_name,svn_boolean_t expand,apr_hash_t * keywords)638 translate_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 */
713 static svn_error_t *
translate_newline(const char * eol_str,apr_size_t eol_str_len,char * src_format,apr_size_t * src_format_len,const char * newline_buf,apr_size_t newline_len,svn_stream_t * dst,svn_boolean_t * translated_eol,svn_boolean_t repair)714 translate_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 
761 svn_boolean_t
svn_subst_keywords_differ(const svn_subst_keywords_t * a,const svn_subst_keywords_t * b,svn_boolean_t compare_values)762 svn_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 
825 svn_boolean_t
svn_subst_keywords_differ2(apr_hash_t * a,apr_hash_t * b,svn_boolean_t compare_values,apr_pool_t * pool)826 svn_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. */
867 struct 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  */
913 static struct translation_baton *
create_translation_baton(const char * eol_str,svn_boolean_t * translated_eol,svn_boolean_t repair,apr_hash_t * keywords,svn_boolean_t expand,apr_pool_t * pool)914 create_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  */
960 static APR_INLINE svn_boolean_t
eol_unchanged(struct translation_baton * b,const char * buf)961 eol_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  */
997 static svn_error_t *
translate_chunk(svn_stream_t * dst,struct translation_baton * b,const char * buf,apr_size_t buflen,apr_pool_t * pool)998 translate_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 ((end - p) >= (len + 4))
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                  (end - p) > (len + 2) &&     /* 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. */
1222 struct 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. */
1252 static svn_error_t *
translated_stream_read(void * baton,char * buffer,apr_size_t * len)1253 translated_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. */
1323 static svn_error_t *
translated_stream_write(void * baton,const char * buffer,apr_size_t * len)1324 translated_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. */
1336 static svn_error_t *
translated_stream_close(void * baton)1337 translated_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. */
1354 typedef 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. */
1364 static svn_error_t *
translated_stream_mark(void * baton,svn_stream_mark_t ** mark,apr_pool_t * pool)1365 translated_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. */
1389 static svn_error_t *
translated_stream_seek(void * baton,const svn_stream_mark_t * mark)1390 translated_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 svn_error_t *
svn_subst_read_specialfile(svn_stream_t ** stream,const char * path,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1435 svn_subst_read_specialfile(svn_stream_t **stream,
1436                            const char *path,
1437                            apr_pool_t *result_pool,
1438                            apr_pool_t *scratch_pool)
1439 {
1440   apr_finfo_t finfo;
1441   svn_string_t *buf;
1442 
1443   /* First determine what type of special file we are
1444      detranslating. */
1445   SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK,
1446                       scratch_pool));
1447 
1448   switch (finfo.filetype) {
1449   case APR_REG:
1450     /* Nothing special to do here, just create stream from the original
1451        file's contents. */
1452     SVN_ERR(svn_stream_open_readonly(stream, path, result_pool, scratch_pool));
1453     break;
1454 
1455   case APR_LNK:
1456     /* Determine the destination of the link. */
1457     SVN_ERR(svn_io_read_link(&buf, path, scratch_pool));
1458     *stream = svn_stream_from_string(svn_string_createf(result_pool,
1459                                                         "link %s",
1460                                                         buf->data),
1461                                      result_pool);
1462     break;
1463 
1464   default:
1465     SVN_ERR_MALFUNCTION();
1466   }
1467 
1468   return SVN_NO_ERROR;
1469 }
1470 
1471 /* Same as svn_subst_stream_translated(), except for the following.
1472  *
1473  * If TRANSLATED_EOL is not NULL, then reading and/or writing to the stream
1474  * will set *TRANSLATED_EOL to TRUE if an end-of-line sequence was changed,
1475  * otherwise leave it untouched.
1476  */
1477 static svn_stream_t *
stream_translated(svn_stream_t * stream,const char * eol_str,svn_boolean_t * translated_eol,svn_boolean_t repair,apr_hash_t * keywords,svn_boolean_t expand,apr_pool_t * result_pool)1478 stream_translated(svn_stream_t *stream,
1479                   const char *eol_str,
1480                   svn_boolean_t *translated_eol,
1481                   svn_boolean_t repair,
1482                   apr_hash_t *keywords,
1483                   svn_boolean_t expand,
1484                   apr_pool_t *result_pool)
1485 {
1486   struct translated_stream_baton *baton
1487     = apr_palloc(result_pool, sizeof(*baton));
1488   svn_stream_t *s = svn_stream_create(baton, result_pool);
1489 
1490   /* Make sure EOL_STR and KEYWORDS are allocated in RESULT_POOL
1491      so they have the same lifetime as the stream. */
1492   if (eol_str)
1493     eol_str = apr_pstrdup(result_pool, eol_str);
1494   if (keywords)
1495     {
1496       if (apr_hash_count(keywords) == 0)
1497         keywords = NULL;
1498       else
1499         {
1500           /* deep copy the hash to make sure it's allocated in RESULT_POOL */
1501           apr_hash_t *copy = apr_hash_make(result_pool);
1502           apr_hash_index_t *hi;
1503           apr_pool_t *subpool;
1504 
1505           subpool = svn_pool_create(result_pool);
1506           for (hi = apr_hash_first(subpool, keywords);
1507                hi; hi = apr_hash_next(hi))
1508             {
1509               const void *key;
1510               void *val;
1511 
1512               apr_hash_this(hi, &key, NULL, &val);
1513               svn_hash_sets(copy, apr_pstrdup(result_pool, key),
1514                             svn_string_dup(val, result_pool));
1515             }
1516           svn_pool_destroy(subpool);
1517 
1518           keywords = copy;
1519         }
1520     }
1521 
1522   /* Setup the baton fields */
1523   baton->stream = stream;
1524   baton->in_baton
1525     = create_translation_baton(eol_str, translated_eol, repair, keywords,
1526                                expand, result_pool);
1527   baton->out_baton
1528     = create_translation_baton(eol_str, translated_eol, repair, keywords,
1529                                expand, result_pool);
1530   baton->written = FALSE;
1531   baton->readbuf = svn_stringbuf_create_empty(result_pool);
1532   baton->readbuf_off = 0;
1533   baton->iterpool = svn_pool_create(result_pool);
1534   baton->buf = apr_palloc(result_pool, SVN__TRANSLATION_BUF_SIZE);
1535 
1536   /* Setup the stream methods */
1537   svn_stream_set_read2(s, NULL /* only full read support */,
1538                        translated_stream_read);
1539   svn_stream_set_write(s, translated_stream_write);
1540   svn_stream_set_close(s, translated_stream_close);
1541   if (svn_stream_supports_mark(stream))
1542     {
1543       svn_stream_set_mark(s, translated_stream_mark);
1544       svn_stream_set_seek(s, translated_stream_seek);
1545     }
1546 
1547   return s;
1548 }
1549 
1550 svn_stream_t *
svn_subst_stream_translated(svn_stream_t * stream,const char * eol_str,svn_boolean_t repair,apr_hash_t * keywords,svn_boolean_t expand,apr_pool_t * result_pool)1551 svn_subst_stream_translated(svn_stream_t *stream,
1552                             const char *eol_str,
1553                             svn_boolean_t repair,
1554                             apr_hash_t *keywords,
1555                             svn_boolean_t expand,
1556                             apr_pool_t *result_pool)
1557 {
1558   return stream_translated(stream, eol_str, NULL, repair, keywords, expand,
1559                            result_pool);
1560 }
1561 
1562 /* Same as svn_subst_translate_cstring2(), except for the following.
1563  *
1564  * If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if an
1565  * end-of-line sequence was changed, or to FALSE otherwise.
1566  */
1567 static svn_error_t *
translate_cstring(const char ** dst,svn_boolean_t * translated_eol,const char * src,const char * eol_str,svn_boolean_t repair,apr_hash_t * keywords,svn_boolean_t expand,apr_pool_t * pool)1568 translate_cstring(const char **dst,
1569                   svn_boolean_t *translated_eol,
1570                   const char *src,
1571                   const char *eol_str,
1572                   svn_boolean_t repair,
1573                   apr_hash_t *keywords,
1574                   svn_boolean_t expand,
1575                   apr_pool_t *pool)
1576 {
1577   svn_stringbuf_t *dst_stringbuf;
1578   svn_stream_t *dst_stream;
1579   apr_size_t len = strlen(src);
1580 
1581   /* The easy way out:  no translation needed, just copy. */
1582   if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1583     {
1584       *dst = apr_pstrmemdup(pool, src, len);
1585       return SVN_NO_ERROR;
1586     }
1587 
1588   /* Create a stringbuf and wrapper stream to hold the output. */
1589   dst_stringbuf = svn_stringbuf_create_empty(pool);
1590   dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool);
1591 
1592   if (translated_eol)
1593     *translated_eol = FALSE;
1594 
1595   /* Another wrapper to translate the content. */
1596   dst_stream = stream_translated(dst_stream, eol_str, translated_eol, repair,
1597                                  keywords, expand, pool);
1598 
1599   /* Jam the text into the destination stream (to translate it). */
1600   SVN_ERR(svn_stream_write(dst_stream, src, &len));
1601 
1602   /* Close the destination stream to flush unwritten data. */
1603   SVN_ERR(svn_stream_close(dst_stream));
1604 
1605   *dst = dst_stringbuf->data;
1606   return SVN_NO_ERROR;
1607 }
1608 
1609 svn_error_t *
svn_subst_translate_cstring2(const char * src,const char ** dst,const char * eol_str,svn_boolean_t repair,apr_hash_t * keywords,svn_boolean_t expand,apr_pool_t * pool)1610 svn_subst_translate_cstring2(const char *src,
1611                              const char **dst,
1612                              const char *eol_str,
1613                              svn_boolean_t repair,
1614                              apr_hash_t *keywords,
1615                              svn_boolean_t expand,
1616                              apr_pool_t *pool)
1617 {
1618   return translate_cstring(dst, NULL, src, eol_str, repair, keywords, expand,
1619                             pool);
1620 }
1621 
1622 /* Given a special file at SRC, generate a textual representation of
1623    it in a normal file at DST.  Perform all allocations in POOL. */
1624 /* ### this should be folded into svn_subst_copy_and_translate3 */
1625 static svn_error_t *
detranslate_special_file(const char * src,const char * dst,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)1626 detranslate_special_file(const char *src, const char *dst,
1627                          svn_cancel_func_t cancel_func, void *cancel_baton,
1628                          apr_pool_t *scratch_pool)
1629 {
1630   const char *dst_tmp;
1631   svn_stream_t *src_stream;
1632   svn_stream_t *dst_stream;
1633 
1634   /* Open a temporary destination that we will eventually atomically
1635      rename into place. */
1636   SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1637                                  svn_dirent_dirname(dst, scratch_pool),
1638                                  svn_io_file_del_none,
1639                                  scratch_pool, scratch_pool));
1640   SVN_ERR(svn_subst_read_specialfile(&src_stream, src,
1641                                      scratch_pool, scratch_pool));
1642   SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
1643                            cancel_func, cancel_baton, scratch_pool));
1644 
1645   /* Do the atomic rename from our temporary location. */
1646   return svn_error_trace(svn_io_file_rename2(dst_tmp, dst, FALSE, scratch_pool));
1647 }
1648 
1649 /* Creates a special file DST from the "normal form" located in SOURCE.
1650  *
1651  * All temporary allocations will be done in POOL.
1652  */
1653 static svn_error_t *
create_special_file_from_stream(svn_stream_t * source,const char * dst,apr_pool_t * pool)1654 create_special_file_from_stream(svn_stream_t *source, const char *dst,
1655                                 apr_pool_t *pool)
1656 {
1657   svn_stringbuf_t *contents;
1658   svn_boolean_t eof;
1659   const char *identifier;
1660   const char *remainder;
1661   const char *dst_tmp;
1662   svn_boolean_t create_using_internal_representation = FALSE;
1663 
1664   SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool));
1665 
1666   /* Separate off the identifier.  The first space character delimits
1667      the identifier, after which any remaining characters are specific
1668      to the actual special file type being created. */
1669   identifier = contents->data;
1670   for (remainder = identifier; *remainder; remainder++)
1671     {
1672       if (*remainder == ' ')
1673         {
1674           remainder++;
1675           break;
1676         }
1677     }
1678 
1679   if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ",
1680                 sizeof(SVN_SUBST__SPECIAL_LINK_STR " ")-1))
1681     {
1682       /* For symlinks, the type specific data is just a filesystem
1683          path that the symlink should reference. */
1684       svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder,
1685                                                    ".tmp", pool);
1686 
1687       /* If we had an error, check to see if it was because symlinks are
1688          not supported on the platform.  If so, fall back to using the
1689          internal representation. */
1690       if (err && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
1691         {
1692           svn_error_clear(err);
1693           create_using_internal_representation = TRUE;
1694         }
1695       else if (err)
1696         {
1697           return svn_error_trace(err);
1698         }
1699     }
1700   else
1701     {
1702       /* Just create a normal file using the internal special file
1703          representation.  We don't want a commit of an unknown special
1704          file type to DoS all the clients. */
1705       create_using_internal_representation = TRUE;
1706     }
1707 
1708   /* If nothing else worked, write out the internal representation to
1709      a file that can be edited by the user. */
1710   if (create_using_internal_representation)
1711     {
1712       svn_stream_t *new_stream;
1713       apr_size_t len;
1714 
1715       SVN_ERR(svn_stream_open_unique(&new_stream, &dst_tmp,
1716                                      svn_dirent_dirname(dst, pool),
1717                                      svn_io_file_del_none,
1718                                      pool, pool));
1719 
1720       if (!eof)
1721         svn_stringbuf_appendcstr(contents, "\n");
1722       len = contents->len;
1723       SVN_ERR(svn_stream_write(new_stream, contents->data, &len));
1724       SVN_ERR(svn_stream_copy3(svn_stream_disown(source, pool), new_stream,
1725                                NULL, NULL, pool));
1726     }
1727 
1728   /* Do the atomic rename from our temporary location. */
1729   return svn_error_trace(svn_io_file_rename2(dst_tmp, dst, FALSE, pool));
1730 }
1731 
1732 
1733 svn_error_t *
svn_subst_copy_and_translate4(const char * src,const char * dst,const char * eol_str,svn_boolean_t repair,apr_hash_t * keywords,svn_boolean_t expand,svn_boolean_t special,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)1734 svn_subst_copy_and_translate4(const char *src,
1735                               const char *dst,
1736                               const char *eol_str,
1737                               svn_boolean_t repair,
1738                               apr_hash_t *keywords,
1739                               svn_boolean_t expand,
1740                               svn_boolean_t special,
1741                               svn_cancel_func_t cancel_func,
1742                               void *cancel_baton,
1743                               apr_pool_t *pool)
1744 {
1745   svn_stream_t *src_stream;
1746   svn_stream_t *dst_stream;
1747   const char *dst_tmp;
1748   svn_error_t *err;
1749   svn_node_kind_t kind;
1750   svn_boolean_t path_special;
1751 
1752   SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool));
1753 
1754   /* If this is a 'special' file, we may need to create it or
1755      detranslate it. */
1756   if (special || path_special)
1757     {
1758       if (expand)
1759         {
1760           if (path_special)
1761             {
1762               /* We are being asked to create a special file from a special
1763                  file.  Do a temporary detranslation and work from there. */
1764 
1765               /* ### woah. this section just undoes all the work we already did
1766                  ### to read the contents of the special file. shoot... the
1767                  ### svn_subst_read_specialfile even checks the file type
1768                  ### for us! */
1769 
1770               SVN_ERR(svn_subst_read_specialfile(&src_stream, src, pool, pool));
1771             }
1772           else
1773             {
1774               SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1775             }
1776 
1777           SVN_ERR(create_special_file_from_stream(src_stream, dst, pool));
1778 
1779           return svn_error_trace(svn_stream_close(src_stream));
1780         }
1781       /* else !expand */
1782 
1783       return svn_error_trace(detranslate_special_file(src, dst,
1784                                                       cancel_func,
1785                                                       cancel_baton,
1786                                                       pool));
1787     }
1788 
1789   /* The easy way out:  no translation needed, just copy. */
1790   if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1791     return svn_error_trace(svn_io_copy_file(src, dst, FALSE, pool));
1792 
1793   /* Open source file. */
1794   SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1795 
1796   /* For atomicity, we translate to a tmp file and then rename the tmp file
1797      over the real destination. */
1798   SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1799                                  svn_dirent_dirname(dst, pool),
1800                                  svn_io_file_del_none, pool, pool));
1801 
1802   dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair,
1803                                            keywords, expand, pool);
1804 
1805   /* ###: use cancel func/baton in place of NULL/NULL below. */
1806   err = svn_stream_copy3(src_stream, dst_stream, cancel_func, cancel_baton,
1807                          pool);
1808   if (err)
1809     {
1810       /* On errors, we have a pathname available. */
1811       if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1812         err = svn_error_createf(SVN_ERR_IO_INCONSISTENT_EOL, err,
1813                                 _("File '%s' has inconsistent newlines"),
1814                                 svn_dirent_local_style(src, pool));
1815       return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp,
1816                                                                FALSE, pool));
1817     }
1818 
1819   /* Now that dst_tmp contains the translated data, do the atomic rename. */
1820   SVN_ERR(svn_io_file_rename2(dst_tmp, dst, FALSE, pool));
1821 
1822   /* Preserve the source file's permission bits. */
1823   SVN_ERR(svn_io_copy_perms(src, dst, pool));
1824 
1825   return SVN_NO_ERROR;
1826 }
1827 
1828 
1829 /*** 'Special file' stream support */
1830 
1831 struct special_stream_baton
1832 {
1833   svn_stream_t *read_stream;
1834   svn_stringbuf_t *write_content;
1835   svn_stream_t *write_stream;
1836   const char *path;
1837   apr_pool_t *pool;
1838 };
1839 
1840 
1841 static svn_error_t *
read_handler_special(void * baton,char * buffer,apr_size_t * len)1842 read_handler_special(void *baton, char *buffer, apr_size_t *len)
1843 {
1844   struct special_stream_baton *btn = baton;
1845 
1846   if (btn->read_stream)
1847     /* We actually found a file to read from */
1848     return svn_stream_read_full(btn->read_stream, buffer, len);
1849   else
1850     return svn_error_createf(APR_ENOENT, NULL,
1851                              _("Can't read special file: File '%s' not found"),
1852                              svn_dirent_local_style(btn->path, btn->pool));
1853 }
1854 
1855 static svn_error_t *
write_handler_special(void * baton,const char * buffer,apr_size_t * len)1856 write_handler_special(void *baton, const char *buffer, apr_size_t *len)
1857 {
1858   struct special_stream_baton *btn = baton;
1859 
1860   return svn_stream_write(btn->write_stream, buffer, len);
1861 }
1862 
1863 
1864 static svn_error_t *
close_handler_special(void * baton)1865 close_handler_special(void *baton)
1866 {
1867   struct special_stream_baton *btn = baton;
1868 
1869   if (btn->write_content->len)
1870     {
1871       /* yeay! we received data and need to create a special file! */
1872 
1873       svn_stream_t *source = svn_stream_from_stringbuf(btn->write_content,
1874                                                        btn->pool);
1875       SVN_ERR(create_special_file_from_stream(source, btn->path, btn->pool));
1876     }
1877 
1878   return SVN_NO_ERROR;
1879 }
1880 
1881 
1882 svn_error_t *
svn_subst_create_specialfile(svn_stream_t ** stream,const char * path,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1883 svn_subst_create_specialfile(svn_stream_t **stream,
1884                              const char *path,
1885                              apr_pool_t *result_pool,
1886                              apr_pool_t *scratch_pool)
1887 {
1888   struct special_stream_baton *baton = apr_palloc(result_pool, sizeof(*baton));
1889 
1890   baton->path = apr_pstrdup(result_pool, path);
1891 
1892   /* SCRATCH_POOL may not exist after the function returns. */
1893   baton->pool = result_pool;
1894 
1895   baton->write_content = svn_stringbuf_create_empty(result_pool);
1896   baton->write_stream = svn_stream_from_stringbuf(baton->write_content,
1897                                                   result_pool);
1898 
1899   *stream = svn_stream_create(baton, result_pool);
1900   svn_stream_set_write(*stream, write_handler_special);
1901   svn_stream_set_close(*stream, close_handler_special);
1902 
1903   return SVN_NO_ERROR;
1904 }
1905 
1906 
1907 /* NOTE: this function is deprecated, but we cannot move it over to
1908    deprecated.c because it uses stuff private to this file, and it is
1909    not easily rebuilt in terms of "new" functions. */
1910 svn_error_t *
svn_subst_stream_from_specialfile(svn_stream_t ** stream,const char * path,apr_pool_t * pool)1911 svn_subst_stream_from_specialfile(svn_stream_t **stream,
1912                                   const char *path,
1913                                   apr_pool_t *pool)
1914 {
1915   struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton));
1916   svn_error_t *err;
1917 
1918   baton->pool = pool;
1919   baton->path = apr_pstrdup(pool, path);
1920 
1921   err = svn_subst_read_specialfile(&baton->read_stream, path, pool, pool);
1922 
1923   /* File might not exist because we intend to create it upon close. */
1924   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1925     {
1926       svn_error_clear(err);
1927 
1928       /* Note: the special file is missing. the caller won't find out
1929          until the first read. Oh well. This function is deprecated anyways,
1930          so they can just deal with the weird behavior. */
1931       baton->read_stream = NULL;
1932     }
1933 
1934   baton->write_content = svn_stringbuf_create_empty(pool);
1935   baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool);
1936 
1937   *stream = svn_stream_create(baton, pool);
1938   svn_stream_set_read2(*stream, NULL /* only full read support */,
1939                        read_handler_special);
1940   svn_stream_set_write(*stream, write_handler_special);
1941   svn_stream_set_close(*stream, close_handler_special);
1942 
1943   return SVN_NO_ERROR;
1944 }
1945 
1946 
1947 
1948 /*** String translation */
1949 svn_error_t *
svn_subst_translate_string2(svn_string_t ** new_value,svn_boolean_t * translated_to_utf8,svn_boolean_t * translated_line_endings,const svn_string_t * value,const char * encoding,svn_boolean_t repair,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1950 svn_subst_translate_string2(svn_string_t **new_value,
1951                             svn_boolean_t *translated_to_utf8,
1952                             svn_boolean_t *translated_line_endings,
1953                             const svn_string_t *value,
1954                             const char *encoding,
1955                             svn_boolean_t repair,
1956                             apr_pool_t *result_pool,
1957                             apr_pool_t *scratch_pool)
1958 {
1959   const char *val_utf8;
1960   const char *val_utf8_lf;
1961 
1962   if (value == NULL)
1963     {
1964       *new_value = NULL;
1965       return SVN_NO_ERROR;
1966     }
1967 
1968   if (encoding && !strcmp(encoding, "UTF-8"))
1969     {
1970       val_utf8 = value->data;
1971     }
1972   else if (encoding)
1973     {
1974       SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data,
1975                                           encoding, scratch_pool));
1976     }
1977   else
1978     {
1979       SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, scratch_pool));
1980     }
1981 
1982   if (translated_to_utf8)
1983     *translated_to_utf8 = (strcmp(value->data, val_utf8) != 0);
1984 
1985   SVN_ERR(translate_cstring(&val_utf8_lf,
1986                             translated_line_endings,
1987                             val_utf8,
1988                             "\n",  /* translate to LF */
1989                             repair,
1990                             NULL,  /* no keywords */
1991                             FALSE, /* no expansion */
1992                             scratch_pool));
1993 
1994   *new_value = svn_string_create(val_utf8_lf, result_pool);
1995   return SVN_NO_ERROR;
1996 }
1997 
1998 
1999 svn_error_t *
svn_subst_detranslate_string(svn_string_t ** new_value,const svn_string_t * value,svn_boolean_t for_output,apr_pool_t * pool)2000 svn_subst_detranslate_string(svn_string_t **new_value,
2001                              const svn_string_t *value,
2002                              svn_boolean_t for_output,
2003                              apr_pool_t *pool)
2004 {
2005   svn_error_t *err;
2006   const char *val_neol;
2007   const char *val_nlocale_neol;
2008 
2009   if (value == NULL)
2010     {
2011       *new_value = NULL;
2012       return SVN_NO_ERROR;
2013     }
2014 
2015   SVN_ERR(svn_subst_translate_cstring2(value->data,
2016                                        &val_neol,
2017                                        APR_EOL_STR,  /* 'native' eol */
2018                                        FALSE, /* no repair */
2019                                        NULL,  /* no keywords */
2020                                        FALSE, /* no expansion */
2021                                        pool));
2022 
2023   if (for_output)
2024     {
2025       err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2026       if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2027         {
2028           val_nlocale_neol =
2029             svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool);
2030           svn_error_clear(err);
2031         }
2032       else if (err)
2033         return err;
2034     }
2035   else
2036     {
2037       err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2038       if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2039         {
2040           val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool);
2041           svn_error_clear(err);
2042         }
2043       else if (err)
2044         return err;
2045     }
2046 
2047   *new_value = svn_string_create(val_nlocale_neol, pool);
2048 
2049   return SVN_NO_ERROR;
2050 }
2051