subst.c revision 262253
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
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
61void
62svn_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
106svn_boolean_t
107svn_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
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 */
152static svn_string_t *
153keyword_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
279static svn_error_t *
280build_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
391svn_error_t *
392svn_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
405svn_error_t *
406svn_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
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. */
426static svn_error_t *
427translate_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.  */
442static svn_boolean_t
443translate_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. */
600static svn_boolean_t
601match_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).  */
636static svn_boolean_t
637translate_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*/
712static svn_error_t *
713translate_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
758/*** Public interfaces. ***/
759
760svn_boolean_t
761svn_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
824svn_boolean_t
825svn_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. */
866struct 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 */
912static struct translation_baton *
913create_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 */
959static APR_INLINE svn_boolean_t
960eol_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 */
996static svn_error_t *
997translate_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. */
1207struct 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. */
1237static svn_error_t *
1238translated_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. */
1308static svn_error_t *
1309translated_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. */
1321static svn_error_t *
1322translated_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. */
1339typedef 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. */
1349static svn_error_t *
1350translated_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. */
1374static svn_error_t *
1375translated_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. */
1420static svn_boolean_t
1421translated_stream_is_buffered(void *baton)
1422{
1423  struct translated_stream_baton *b = baton;
1424  return svn_stream__is_buffered(b->stream);
1425}
1426
1427svn_error_t *
1428svn_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 */
1470static svn_stream_t *
1471stream_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
1540svn_stream_t *
1541svn_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 */
1557static svn_error_t *
1558translate_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
1599svn_error_t *
1600svn_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 */
1615static svn_error_t *
1616detranslate_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 */
1643static svn_error_t *
1644create_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    {
1706      apr_file_t *new_file;
1707      SVN_ERR(svn_io_open_unique_file3(&new_file, &dst_tmp,
1708                                       svn_dirent_dirname(dst, pool),
1709                                       svn_io_file_del_none,
1710                                       pool, pool));
1711
1712      SVN_ERR(svn_io_file_write_full(new_file,
1713                                     contents->data, contents->len, NULL,
1714                                     pool));
1715
1716      SVN_ERR(svn_io_file_close(new_file, pool));
1717    }
1718
1719  /* Do the atomic rename from our temporary location. */
1720  return svn_io_file_rename(dst_tmp, dst, pool);
1721}
1722
1723
1724svn_error_t *
1725svn_subst_copy_and_translate4(const char *src,
1726                              const char *dst,
1727                              const char *eol_str,
1728                              svn_boolean_t repair,
1729                              apr_hash_t *keywords,
1730                              svn_boolean_t expand,
1731                              svn_boolean_t special,
1732                              svn_cancel_func_t cancel_func,
1733                              void *cancel_baton,
1734                              apr_pool_t *pool)
1735{
1736  svn_stream_t *src_stream;
1737  svn_stream_t *dst_stream;
1738  const char *dst_tmp;
1739  svn_error_t *err;
1740  svn_node_kind_t kind;
1741  svn_boolean_t path_special;
1742
1743  SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool));
1744
1745  /* If this is a 'special' file, we may need to create it or
1746     detranslate it. */
1747  if (special || path_special)
1748    {
1749      if (expand)
1750        {
1751          if (path_special)
1752            {
1753              /* We are being asked to create a special file from a special
1754                 file.  Do a temporary detranslation and work from there. */
1755
1756              /* ### woah. this section just undoes all the work we already did
1757                 ### to read the contents of the special file. shoot... the
1758                 ### svn_subst_read_specialfile even checks the file type
1759                 ### for us! */
1760
1761              SVN_ERR(svn_subst_read_specialfile(&src_stream, src, pool, pool));
1762            }
1763          else
1764            {
1765              SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1766            }
1767
1768          return svn_error_trace(create_special_file_from_stream(src_stream,
1769                                                                 dst, pool));
1770        }
1771      /* else !expand */
1772
1773      return svn_error_trace(detranslate_special_file(src, dst,
1774                                                      cancel_func,
1775                                                      cancel_baton,
1776                                                      pool));
1777    }
1778
1779  /* The easy way out:  no translation needed, just copy. */
1780  if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1781    return svn_error_trace(svn_io_copy_file(src, dst, FALSE, pool));
1782
1783  /* Open source file. */
1784  SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1785
1786  /* For atomicity, we translate to a tmp file and then rename the tmp file
1787     over the real destination. */
1788  SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1789                                 svn_dirent_dirname(dst, pool),
1790                                 svn_io_file_del_none, pool, pool));
1791
1792  dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair,
1793                                           keywords, expand, pool);
1794
1795  /* ###: use cancel func/baton in place of NULL/NULL below. */
1796  err = svn_stream_copy3(src_stream, dst_stream, cancel_func, cancel_baton,
1797                         pool);
1798  if (err)
1799    {
1800      /* On errors, we have a pathname available. */
1801      if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1802        err = svn_error_createf(SVN_ERR_IO_INCONSISTENT_EOL, err,
1803                                _("File '%s' has inconsistent newlines"),
1804                                svn_dirent_local_style(src, pool));
1805      return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp,
1806                                                               FALSE, pool));
1807    }
1808
1809  /* Now that dst_tmp contains the translated data, do the atomic rename. */
1810  SVN_ERR(svn_io_file_rename(dst_tmp, dst, pool));
1811
1812  /* Preserve the source file's permission bits. */
1813  SVN_ERR(svn_io_copy_perms(src, dst, pool));
1814
1815  return SVN_NO_ERROR;
1816}
1817
1818
1819/*** 'Special file' stream support */
1820
1821struct special_stream_baton
1822{
1823  svn_stream_t *read_stream;
1824  svn_stringbuf_t *write_content;
1825  svn_stream_t *write_stream;
1826  const char *path;
1827  apr_pool_t *pool;
1828};
1829
1830
1831static svn_error_t *
1832read_handler_special(void *baton, char *buffer, apr_size_t *len)
1833{
1834  struct special_stream_baton *btn = baton;
1835
1836  if (btn->read_stream)
1837    /* We actually found a file to read from */
1838    return svn_stream_read(btn->read_stream, buffer, len);
1839  else
1840    return svn_error_createf(APR_ENOENT, NULL,
1841                             "Can't read special file: File '%s' not found",
1842                             svn_dirent_local_style(btn->path, btn->pool));
1843}
1844
1845static svn_error_t *
1846write_handler_special(void *baton, const char *buffer, apr_size_t *len)
1847{
1848  struct special_stream_baton *btn = baton;
1849
1850  return svn_stream_write(btn->write_stream, buffer, len);
1851}
1852
1853
1854static svn_error_t *
1855close_handler_special(void *baton)
1856{
1857  struct special_stream_baton *btn = baton;
1858
1859  if (btn->write_content->len)
1860    {
1861      /* yeay! we received data and need to create a special file! */
1862
1863      svn_stream_t *source = svn_stream_from_stringbuf(btn->write_content,
1864                                                       btn->pool);
1865      SVN_ERR(create_special_file_from_stream(source, btn->path, btn->pool));
1866    }
1867
1868  return SVN_NO_ERROR;
1869}
1870
1871
1872svn_error_t *
1873svn_subst_create_specialfile(svn_stream_t **stream,
1874                             const char *path,
1875                             apr_pool_t *result_pool,
1876                             apr_pool_t *scratch_pool)
1877{
1878  struct special_stream_baton *baton = apr_palloc(result_pool, sizeof(*baton));
1879
1880  baton->path = apr_pstrdup(result_pool, path);
1881
1882  /* SCRATCH_POOL may not exist after the function returns. */
1883  baton->pool = result_pool;
1884
1885  baton->write_content = svn_stringbuf_create_empty(result_pool);
1886  baton->write_stream = svn_stream_from_stringbuf(baton->write_content,
1887                                                  result_pool);
1888
1889  *stream = svn_stream_create(baton, result_pool);
1890  svn_stream_set_write(*stream, write_handler_special);
1891  svn_stream_set_close(*stream, close_handler_special);
1892
1893  return SVN_NO_ERROR;
1894}
1895
1896
1897/* NOTE: this function is deprecated, but we cannot move it over to
1898   deprecated.c because it uses stuff private to this file, and it is
1899   not easily rebuilt in terms of "new" functions. */
1900svn_error_t *
1901svn_subst_stream_from_specialfile(svn_stream_t **stream,
1902                                  const char *path,
1903                                  apr_pool_t *pool)
1904{
1905  struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton));
1906  svn_error_t *err;
1907
1908  baton->pool = pool;
1909  baton->path = apr_pstrdup(pool, path);
1910
1911  err = svn_subst_read_specialfile(&baton->read_stream, path, pool, pool);
1912
1913  /* File might not exist because we intend to create it upon close. */
1914  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1915    {
1916      svn_error_clear(err);
1917
1918      /* Note: the special file is missing. the caller won't find out
1919         until the first read. Oh well. This function is deprecated anyways,
1920         so they can just deal with the weird behavior. */
1921      baton->read_stream = NULL;
1922    }
1923
1924  baton->write_content = svn_stringbuf_create_empty(pool);
1925  baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool);
1926
1927  *stream = svn_stream_create(baton, pool);
1928  svn_stream_set_read(*stream, read_handler_special);
1929  svn_stream_set_write(*stream, write_handler_special);
1930  svn_stream_set_close(*stream, close_handler_special);
1931
1932  return SVN_NO_ERROR;
1933}
1934
1935
1936
1937/*** String translation */
1938svn_error_t *
1939svn_subst_translate_string2(svn_string_t **new_value,
1940                            svn_boolean_t *translated_to_utf8,
1941                            svn_boolean_t *translated_line_endings,
1942                            const svn_string_t *value,
1943                            const char *encoding,
1944                            svn_boolean_t repair,
1945                            apr_pool_t *result_pool,
1946                            apr_pool_t *scratch_pool)
1947{
1948  const char *val_utf8;
1949  const char *val_utf8_lf;
1950
1951  if (value == NULL)
1952    {
1953      *new_value = NULL;
1954      return SVN_NO_ERROR;
1955    }
1956
1957  if (encoding && !strcmp(encoding, "UTF-8"))
1958    {
1959      val_utf8 = value->data;
1960    }
1961  else if (encoding)
1962    {
1963      SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data,
1964                                          encoding, scratch_pool));
1965    }
1966  else
1967    {
1968      SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, scratch_pool));
1969    }
1970
1971  if (translated_to_utf8)
1972    *translated_to_utf8 = (strcmp(value->data, val_utf8) != 0);
1973
1974  SVN_ERR(translate_cstring(&val_utf8_lf,
1975                            translated_line_endings,
1976                            val_utf8,
1977                            "\n",  /* translate to LF */
1978                            repair,
1979                            NULL,  /* no keywords */
1980                            FALSE, /* no expansion */
1981                            scratch_pool));
1982
1983  *new_value = svn_string_create(val_utf8_lf, result_pool);
1984  return SVN_NO_ERROR;
1985}
1986
1987
1988svn_error_t *
1989svn_subst_detranslate_string(svn_string_t **new_value,
1990                             const svn_string_t *value,
1991                             svn_boolean_t for_output,
1992                             apr_pool_t *pool)
1993{
1994  svn_error_t *err;
1995  const char *val_neol;
1996  const char *val_nlocale_neol;
1997
1998  if (value == NULL)
1999    {
2000      *new_value = NULL;
2001      return SVN_NO_ERROR;
2002    }
2003
2004  SVN_ERR(svn_subst_translate_cstring2(value->data,
2005                                       &val_neol,
2006                                       APR_EOL_STR,  /* 'native' eol */
2007                                       FALSE, /* no repair */
2008                                       NULL,  /* no keywords */
2009                                       FALSE, /* no expansion */
2010                                       pool));
2011
2012  if (for_output)
2013    {
2014      err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2015      if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2016        {
2017          val_nlocale_neol =
2018            svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool);
2019          svn_error_clear(err);
2020        }
2021      else if (err)
2022        return err;
2023    }
2024  else
2025    {
2026      err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2027      if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2028        {
2029          val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool);
2030          svn_error_clear(err);
2031        }
2032      else if (err)
2033        return err;
2034    }
2035
2036  *new_value = svn_string_create(val_nlocale_neol, pool);
2037
2038  return SVN_NO_ERROR;
2039}
2040