1251881Speter/*
2251881Speter * subst.c :  generic eol/keyword substitution routines
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#define APR_WANT_STRFUNC
27251881Speter#include <apr_want.h>
28251881Speter
29251881Speter#include <stdlib.h>
30251881Speter#include <assert.h>
31251881Speter#include <apr_pools.h>
32251881Speter#include <apr_tables.h>
33251881Speter#include <apr_file_io.h>
34251881Speter#include <apr_strings.h>
35251881Speter
36251881Speter#include "svn_hash.h"
37251881Speter#include "svn_cmdline.h"
38251881Speter#include "svn_types.h"
39251881Speter#include "svn_string.h"
40251881Speter#include "svn_time.h"
41251881Speter#include "svn_dirent_uri.h"
42251881Speter#include "svn_path.h"
43251881Speter#include "svn_error.h"
44251881Speter#include "svn_utf.h"
45251881Speter#include "svn_io.h"
46251881Speter#include "svn_subst.h"
47251881Speter#include "svn_pools.h"
48251881Speter#include "private/svn_io_private.h"
49251881Speter
50251881Speter#include "svn_private_config.h"
51251881Speter
52251881Speter#include "private/svn_string_private.h"
53289180Speter#include "private/svn_eol_private.h"
54251881Speter
55251881Speter/**
56251881Speter * The textual elements of a detranslated special file.  One of these
57251881Speter * strings must appear as the first element of any special file as it
58251881Speter * exists in the repository or the text base.
59251881Speter */
60251881Speter#define SVN_SUBST__SPECIAL_LINK_STR "link"
61251881Speter
62251881Spetervoid
63251881Spetersvn_subst_eol_style_from_value(svn_subst_eol_style_t *style,
64251881Speter                               const char **eol,
65251881Speter                               const char *value)
66251881Speter{
67251881Speter  if (value == NULL)
68251881Speter    {
69251881Speter      /* property doesn't exist. */
70251881Speter      *eol = NULL;
71251881Speter      if (style)
72251881Speter        *style = svn_subst_eol_style_none;
73251881Speter    }
74251881Speter  else if (! strcmp("native", value))
75251881Speter    {
76251881Speter      *eol = APR_EOL_STR;       /* whee, a portability library! */
77251881Speter      if (style)
78251881Speter        *style = svn_subst_eol_style_native;
79251881Speter    }
80251881Speter  else if (! strcmp("LF", value))
81251881Speter    {
82251881Speter      *eol = "\n";
83251881Speter      if (style)
84251881Speter        *style = svn_subst_eol_style_fixed;
85251881Speter    }
86251881Speter  else if (! strcmp("CR", value))
87251881Speter    {
88251881Speter      *eol = "\r";
89251881Speter      if (style)
90251881Speter        *style = svn_subst_eol_style_fixed;
91251881Speter    }
92251881Speter  else if (! strcmp("CRLF", value))
93251881Speter    {
94251881Speter      *eol = "\r\n";
95251881Speter      if (style)
96251881Speter        *style = svn_subst_eol_style_fixed;
97251881Speter    }
98251881Speter  else
99251881Speter    {
100251881Speter      *eol = NULL;
101251881Speter      if (style)
102251881Speter        *style = svn_subst_eol_style_unknown;
103251881Speter    }
104251881Speter}
105251881Speter
106251881Speter
107251881Spetersvn_boolean_t
108251881Spetersvn_subst_translation_required(svn_subst_eol_style_t style,
109251881Speter                               const char *eol,
110251881Speter                               apr_hash_t *keywords,
111251881Speter                               svn_boolean_t special,
112251881Speter                               svn_boolean_t force_eol_check)
113251881Speter{
114251881Speter  return (special || keywords
115251881Speter          || (style != svn_subst_eol_style_none && force_eol_check)
116251881Speter          || (style == svn_subst_eol_style_native &&
117251881Speter              strcmp(APR_EOL_STR, SVN_SUBST_NATIVE_EOL_STR) != 0)
118251881Speter          || (style == svn_subst_eol_style_fixed &&
119251881Speter              strcmp(APR_EOL_STR, eol) != 0));
120251881Speter}
121251881Speter
122251881Speter
123251881Speter
124251881Speter/* Helper function for svn_subst_build_keywords */
125251881Speter
126251881Speter/* Given a printf-like format string, return a string with proper
127251881Speter * information filled in.
128251881Speter *
129251881Speter * Important API note: This function is the core of the implementation of
130251881Speter * svn_subst_build_keywords (all versions), and as such must implement the
131289180Speter * tolerance of NULL and zero inputs that that function's documentation
132251881Speter * stipulates.
133251881Speter *
134251881Speter * The format codes:
135251881Speter *
136251881Speter * %a author of this revision
137251881Speter * %b basename of the URL of this file
138251881Speter * %d short format of date of this revision
139251881Speter * %D long format of date of this revision
140251881Speter * %P path relative to root of repos
141251881Speter * %r number of this revision
142251881Speter * %R root url of repository
143251881Speter * %u URL of this file
144251881Speter * %_ a space
145251881Speter * %% a literal %
146251881Speter *
147251881Speter * The following special format codes are also recognized:
148251881Speter *   %H is equivalent to %P%_%r%_%d%_%a
149251881Speter *   %I is equivalent to %b%_%r%_%d%_%a
150251881Speter *
151251881Speter * All memory is allocated out of @a pool.
152251881Speter */
153251881Speterstatic svn_string_t *
154251881Speterkeyword_printf(const char *fmt,
155251881Speter               const char *rev,
156251881Speter               const char *url,
157251881Speter               const char *repos_root_url,
158251881Speter               apr_time_t date,
159251881Speter               const char *author,
160251881Speter               apr_pool_t *pool)
161251881Speter{
162289180Speter  svn_stringbuf_t *value = svn_stringbuf_create_empty(pool);
163251881Speter  const char *cur;
164251881Speter  size_t n;
165251881Speter
166251881Speter  for (;;)
167251881Speter    {
168251881Speter      cur = fmt;
169251881Speter
170251881Speter      while (*cur != '\0' && *cur != '%')
171251881Speter        cur++;
172251881Speter
173251881Speter      if ((n = cur - fmt) > 0) /* Do we have an as-is string? */
174251881Speter        svn_stringbuf_appendbytes(value, fmt, n);
175251881Speter
176251881Speter      if (*cur == '\0')
177251881Speter        break;
178251881Speter
179251881Speter      switch (cur[1])
180251881Speter        {
181251881Speter        case 'a': /* author of this revision */
182251881Speter          if (author)
183251881Speter            svn_stringbuf_appendcstr(value, author);
184251881Speter          break;
185251881Speter        case 'b': /* basename of this file */
186251881Speter          if (url && *url)
187251881Speter            {
188251881Speter              const char *base_name = svn_uri_basename(url, pool);
189251881Speter              svn_stringbuf_appendcstr(value, base_name);
190251881Speter            }
191251881Speter          break;
192251881Speter        case 'd': /* short format of date of this revision */
193251881Speter          if (date)
194251881Speter            {
195251881Speter              apr_time_exp_t exploded_time;
196251881Speter              const char *human;
197251881Speter
198251881Speter              apr_time_exp_gmt(&exploded_time, date);
199251881Speter
200251881Speter              human = apr_psprintf(pool, "%04d-%02d-%02d %02d:%02d:%02dZ",
201251881Speter                                   exploded_time.tm_year + 1900,
202251881Speter                                   exploded_time.tm_mon + 1,
203251881Speter                                   exploded_time.tm_mday,
204251881Speter                                   exploded_time.tm_hour,
205251881Speter                                   exploded_time.tm_min,
206251881Speter                                   exploded_time.tm_sec);
207251881Speter
208251881Speter              svn_stringbuf_appendcstr(value, human);
209251881Speter            }
210251881Speter          break;
211251881Speter        case 'D': /* long format of date of this revision */
212251881Speter          if (date)
213251881Speter            svn_stringbuf_appendcstr(value,
214251881Speter                                     svn_time_to_human_cstring(date, pool));
215251881Speter          break;
216251881Speter        case 'P': /* relative path of this file */
217251881Speter          if (repos_root_url && *repos_root_url != '\0' && url && *url != '\0')
218251881Speter            {
219251881Speter              const char *repos_relpath;
220251881Speter
221251881Speter              repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, pool);
222251881Speter              if (repos_relpath)
223251881Speter                svn_stringbuf_appendcstr(value, repos_relpath);
224251881Speter            }
225251881Speter          break;
226251881Speter        case 'R': /* root of repos */
227251881Speter          if (repos_root_url && *repos_root_url != '\0')
228251881Speter            svn_stringbuf_appendcstr(value, repos_root_url);
229251881Speter          break;
230251881Speter        case 'r': /* number of this revision */
231251881Speter          if (rev)
232251881Speter            svn_stringbuf_appendcstr(value, rev);
233251881Speter          break;
234251881Speter        case 'u': /* URL of this file */
235251881Speter          if (url)
236251881Speter            svn_stringbuf_appendcstr(value, url);
237251881Speter          break;
238251881Speter        case '_': /* '%_' => a space */
239251881Speter          svn_stringbuf_appendbyte(value, ' ');
240251881Speter          break;
241251881Speter        case '%': /* '%%' => a literal % */
242251881Speter          svn_stringbuf_appendbyte(value, *cur);
243251881Speter          break;
244251881Speter        case '\0': /* '%' as the last character of the string. */
245251881Speter          svn_stringbuf_appendbyte(value, *cur);
246251881Speter          /* Now go back one character, since this was just a one character
247251881Speter           * sequence, whereas all others are two characters, and we do not
248251881Speter           * want to skip the null terminator entirely and carry on
249251881Speter           * formatting random memory contents. */
250251881Speter          cur--;
251251881Speter          break;
252251881Speter        case 'H':
253251881Speter          {
254251881Speter            svn_string_t *s = keyword_printf("%P%_%r%_%d%_%a", rev, url,
255251881Speter                                             repos_root_url, date, author,
256251881Speter                                             pool);
257251881Speter            svn_stringbuf_appendcstr(value, s->data);
258251881Speter          }
259251881Speter          break;
260251881Speter        case 'I':
261251881Speter          {
262251881Speter            svn_string_t *s = keyword_printf("%b%_%r%_%d%_%a", rev, url,
263251881Speter                                             repos_root_url, date, author,
264251881Speter                                             pool);
265251881Speter            svn_stringbuf_appendcstr(value, s->data);
266251881Speter          }
267251881Speter          break;
268251881Speter        default: /* Unrecognized code, just print it literally. */
269251881Speter          svn_stringbuf_appendbytes(value, cur, 2);
270251881Speter          break;
271251881Speter        }
272251881Speter
273251881Speter      /* Format code is processed - skip it, and get ready for next chunk. */
274251881Speter      fmt = cur + 2;
275251881Speter    }
276251881Speter
277251881Speter  return svn_stringbuf__morph_into_string(value);
278251881Speter}
279251881Speter
280251881Speterstatic svn_error_t *
281251881Speterbuild_keywords(apr_hash_t **kw,
282251881Speter               svn_boolean_t expand_custom_keywords,
283251881Speter               const char *keywords_val,
284251881Speter               const char *rev,
285251881Speter               const char *url,
286251881Speter               const char *repos_root_url,
287251881Speter               apr_time_t date,
288251881Speter               const char *author,
289251881Speter               apr_pool_t *pool)
290251881Speter{
291251881Speter  apr_array_header_t *keyword_tokens;
292251881Speter  int i;
293251881Speter  *kw = apr_hash_make(pool);
294251881Speter
295251881Speter  keyword_tokens = svn_cstring_split(keywords_val, " \t\v\n\b\r\f",
296251881Speter                                     TRUE /* chop */, pool);
297251881Speter
298251881Speter  for (i = 0; i < keyword_tokens->nelts; ++i)
299251881Speter    {
300251881Speter      const char *keyword = APR_ARRAY_IDX(keyword_tokens, i, const char *);
301251881Speter      const char *custom_fmt = NULL;
302251881Speter
303251881Speter      if (expand_custom_keywords)
304251881Speter        {
305251881Speter          char *sep;
306251881Speter
307251881Speter          /* Check if there is a custom keyword definition, started by '='. */
308251881Speter          sep = strchr(keyword, '=');
309251881Speter          if (sep)
310251881Speter            {
311251881Speter              *sep = '\0'; /* Split keyword's name from custom format. */
312251881Speter              custom_fmt = sep + 1;
313251881Speter            }
314251881Speter        }
315251881Speter
316251881Speter      if (custom_fmt)
317251881Speter        {
318251881Speter          svn_string_t *custom_val;
319251881Speter
320251881Speter          /* Custom keywords must be allowed to match the name of an
321251881Speter           * existing fixed keyword. This is for compatibility purposes,
322251881Speter           * in case new fixed keywords are added to Subversion which
323251881Speter           * happen to match a custom keyword defined somewhere.
324251881Speter           * There is only one global namespace for keyword names. */
325251881Speter          custom_val = keyword_printf(custom_fmt, rev, url, repos_root_url,
326251881Speter                                      date, author, pool);
327251881Speter          svn_hash_sets(*kw, keyword, custom_val);
328251881Speter        }
329251881Speter      else if ((! strcmp(keyword, SVN_KEYWORD_REVISION_LONG))
330251881Speter               || (! strcmp(keyword, SVN_KEYWORD_REVISION_MEDIUM))
331251881Speter               || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_REVISION_SHORT)))
332251881Speter        {
333251881Speter          svn_string_t *revision_val;
334251881Speter
335251881Speter          revision_val = keyword_printf("%r", rev, url, repos_root_url,
336251881Speter                                        date, author, pool);
337251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_REVISION_LONG, revision_val);
338251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_REVISION_MEDIUM, revision_val);
339251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_REVISION_SHORT, revision_val);
340251881Speter        }
341251881Speter      else if ((! strcmp(keyword, SVN_KEYWORD_DATE_LONG))
342251881Speter               || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_DATE_SHORT)))
343251881Speter        {
344251881Speter          svn_string_t *date_val;
345251881Speter
346251881Speter          date_val = keyword_printf("%D", rev, url, repos_root_url, date,
347251881Speter                                    author, pool);
348251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_DATE_LONG, date_val);
349251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_DATE_SHORT, date_val);
350251881Speter        }
351251881Speter      else if ((! strcmp(keyword, SVN_KEYWORD_AUTHOR_LONG))
352251881Speter               || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_AUTHOR_SHORT)))
353251881Speter        {
354251881Speter          svn_string_t *author_val;
355251881Speter
356251881Speter          author_val = keyword_printf("%a", rev, url, repos_root_url, date,
357251881Speter                                      author, pool);
358251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_LONG, author_val);
359251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_SHORT, author_val);
360251881Speter        }
361251881Speter      else if ((! strcmp(keyword, SVN_KEYWORD_URL_LONG))
362251881Speter               || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_URL_SHORT)))
363251881Speter        {
364251881Speter          svn_string_t *url_val;
365251881Speter
366251881Speter          url_val = keyword_printf("%u", rev, url, repos_root_url, date,
367251881Speter                                   author, pool);
368251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_URL_LONG, url_val);
369251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_URL_SHORT, url_val);
370251881Speter        }
371251881Speter      else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_ID)))
372251881Speter        {
373251881Speter          svn_string_t *id_val;
374251881Speter
375251881Speter          id_val = keyword_printf("%b %r %d %a", rev, url, repos_root_url,
376251881Speter                                  date, author, pool);
377251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_ID, id_val);
378251881Speter        }
379251881Speter      else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_HEADER)))
380251881Speter        {
381251881Speter          svn_string_t *header_val;
382251881Speter
383251881Speter          header_val = keyword_printf("%u %r %d %a", rev, url, repos_root_url,
384251881Speter                                      date, author, pool);
385251881Speter          svn_hash_sets(*kw, SVN_KEYWORD_HEADER, header_val);
386251881Speter        }
387251881Speter    }
388251881Speter
389251881Speter  return SVN_NO_ERROR;
390251881Speter}
391251881Speter
392251881Spetersvn_error_t *
393251881Spetersvn_subst_build_keywords2(apr_hash_t **kw,
394251881Speter                          const char *keywords_val,
395251881Speter                          const char *rev,
396251881Speter                          const char *url,
397251881Speter                          apr_time_t date,
398251881Speter                          const char *author,
399251881Speter                          apr_pool_t *pool)
400251881Speter{
401251881Speter  return svn_error_trace(build_keywords(kw, FALSE, keywords_val, rev, url,
402251881Speter                                        NULL, date, author, pool));
403251881Speter}
404251881Speter
405251881Speter
406251881Spetersvn_error_t *
407251881Spetersvn_subst_build_keywords3(apr_hash_t **kw,
408251881Speter                          const char *keywords_val,
409251881Speter                          const char *rev,
410251881Speter                          const char *url,
411251881Speter                          const char *repos_root_url,
412251881Speter                          apr_time_t date,
413251881Speter                          const char *author,
414251881Speter                          apr_pool_t *pool)
415251881Speter{
416251881Speter  return svn_error_trace(build_keywords(kw, TRUE, keywords_val,
417251881Speter                                        rev, url, repos_root_url,
418251881Speter                                        date, author, pool));
419251881Speter}
420251881Speter
421251881Speter
422251881Speter/*** Helpers for svn_subst_translate_stream2 ***/
423251881Speter
424251881Speter
425251881Speter/* Write out LEN bytes of BUF into STREAM. */
426251881Speter/* ### TODO: 'stream_write()' would be a better name for this. */
427251881Speterstatic svn_error_t *
428251881Spetertranslate_write(svn_stream_t *stream,
429251881Speter                const void *buf,
430251881Speter                apr_size_t len)
431251881Speter{
432251881Speter  SVN_ERR(svn_stream_write(stream, buf, &len));
433251881Speter  /* (No need to check LEN, as a short write always produces an error.) */
434251881Speter  return SVN_NO_ERROR;
435251881Speter}
436251881Speter
437251881Speter
438251881Speter/* Perform the substitution of VALUE into keyword string BUF (with len
439251881Speter   *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating
440251881Speter   *LEN to the new size of the substituted result.  Return TRUE if all
441251881Speter   goes well, FALSE otherwise.  If VALUE is NULL, keyword will be
442251881Speter   contracted, else it will be expanded.  */
443251881Speterstatic svn_boolean_t
444251881Spetertranslate_keyword_subst(char *buf,
445251881Speter                        apr_size_t *len,
446251881Speter                        const char *keyword,
447251881Speter                        apr_size_t keyword_len,
448251881Speter                        const svn_string_t *value)
449251881Speter{
450251881Speter  char *buf_ptr;
451251881Speter
452251881Speter  /* Make sure we gotz good stuffs. */
453251881Speter  assert(*len <= SVN_KEYWORD_MAX_LEN);
454251881Speter  assert((buf[0] == '$') && (buf[*len - 1] == '$'));
455251881Speter
456251881Speter  /* Need at least a keyword and two $'s. */
457251881Speter  if (*len < keyword_len + 2)
458251881Speter    return FALSE;
459251881Speter
460251881Speter  /* Need at least space for two $'s, two spaces and a colon, and that
461251881Speter     leaves zero space for the value itself. */
462251881Speter  if (keyword_len > SVN_KEYWORD_MAX_LEN - 5)
463251881Speter    return FALSE;
464251881Speter
465251881Speter  /* The keyword needs to match what we're looking for. */
466251881Speter  if (strncmp(buf + 1, keyword, keyword_len))
467251881Speter    return FALSE;
468251881Speter
469251881Speter  buf_ptr = buf + 1 + keyword_len;
470251881Speter
471251881Speter  /* Check for fixed-length expansion.
472251881Speter   * The format of fixed length keyword and its data is
473251881Speter   * Unexpanded keyword:         "$keyword::       $"
474251881Speter   * Expanded keyword:           "$keyword:: value $"
475251881Speter   * Expanded kw with filling:   "$keyword:: value   $"
476251881Speter   * Truncated keyword:          "$keyword:: longval#$"
477251881Speter   */
478251881Speter  if ((buf_ptr[0] == ':') /* first char after keyword is ':' */
479251881Speter      && (buf_ptr[1] == ':') /* second char after keyword is ':' */
480251881Speter      && (buf_ptr[2] == ' ') /* third char after keyword is ' ' */
481251881Speter      && ((buf[*len - 2] == ' ')  /* has ' ' for next to last character */
482251881Speter          || (buf[*len - 2] == '#')) /* .. or has '#' for next to last
483251881Speter                                        character */
484251881Speter      && ((6 + keyword_len) < *len))  /* holds "$kw:: x $" at least */
485251881Speter    {
486251881Speter      /* This is fixed length keyword, so *len remains unchanged */
487251881Speter      apr_size_t max_value_len = *len - (6 + keyword_len);
488251881Speter
489251881Speter      if (! value)
490251881Speter        {
491251881Speter          /* no value, so unexpand */
492251881Speter          buf_ptr += 2;
493251881Speter          while (*buf_ptr != '$')
494251881Speter            *(buf_ptr++) = ' ';
495251881Speter        }
496251881Speter      else
497251881Speter        {
498251881Speter          if (value->len <= max_value_len)
499251881Speter            { /* replacement not as long as template, pad with spaces */
500251881Speter              strncpy(buf_ptr + 3, value->data, value->len);
501251881Speter              buf_ptr += 3 + value->len;
502251881Speter              while (*buf_ptr != '$')
503251881Speter                *(buf_ptr++) = ' ';
504251881Speter            }
505251881Speter          else
506251881Speter            {
507251881Speter              /* replacement needs truncating */
508251881Speter              strncpy(buf_ptr + 3, value->data, max_value_len);
509251881Speter              buf[*len - 2] = '#';
510251881Speter              buf[*len - 1] = '$';
511251881Speter            }
512251881Speter        }
513251881Speter      return TRUE;
514251881Speter    }
515251881Speter
516251881Speter  /* Check for unexpanded keyword. */
517251881Speter  else if (buf_ptr[0] == '$')          /* "$keyword$" */
518251881Speter    {
519251881Speter      /* unexpanded... */
520251881Speter      if (value)
521251881Speter        {
522251881Speter          /* ...so expand. */
523251881Speter          buf_ptr[0] = ':';
524251881Speter          buf_ptr[1] = ' ';
525251881Speter          if (value->len)
526251881Speter            {
527251881Speter              apr_size_t vallen = value->len;
528251881Speter
529251881Speter              /* "$keyword: value $" */
530251881Speter              if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
531251881Speter                vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
532251881Speter              strncpy(buf_ptr + 2, value->data, vallen);
533251881Speter              buf_ptr[2 + vallen] = ' ';
534251881Speter              buf_ptr[2 + vallen + 1] = '$';
535251881Speter              *len = 5 + keyword_len + vallen;
536251881Speter            }
537251881Speter          else
538251881Speter            {
539251881Speter              /* "$keyword: $"  */
540251881Speter              buf_ptr[2] = '$';
541251881Speter              *len = 4 + keyword_len;
542251881Speter            }
543251881Speter        }
544251881Speter      else
545251881Speter        {
546251881Speter          /* ...but do nothing. */
547251881Speter        }
548251881Speter      return TRUE;
549251881Speter    }
550251881Speter
551251881Speter  /* Check for expanded keyword. */
552251881Speter  else if (((*len >= 4 + keyword_len ) /* holds at least "$keyword: $" */
553251881Speter           && (buf_ptr[0] == ':')      /* first char after keyword is ':' */
554251881Speter           && (buf_ptr[1] == ' ')      /* second char after keyword is ' ' */
555251881Speter           && (buf[*len - 2] == ' '))
556251881Speter        || ((*len >= 3 + keyword_len ) /* holds at least "$keyword:$" */
557251881Speter           && (buf_ptr[0] == ':')      /* first char after keyword is ':' */
558251881Speter           && (buf_ptr[1] == '$')))    /* second char after keyword is '$' */
559251881Speter    {
560251881Speter      /* expanded... */
561251881Speter      if (! value)
562251881Speter        {
563251881Speter          /* ...so unexpand. */
564251881Speter          buf_ptr[0] = '$';
565251881Speter          *len = 2 + keyword_len;
566251881Speter        }
567251881Speter      else
568251881Speter        {
569251881Speter          /* ...so re-expand. */
570251881Speter          buf_ptr[0] = ':';
571251881Speter          buf_ptr[1] = ' ';
572251881Speter          if (value->len)
573251881Speter            {
574251881Speter              apr_size_t vallen = value->len;
575251881Speter
576251881Speter              /* "$keyword: value $" */
577251881Speter              if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
578251881Speter                vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
579251881Speter              strncpy(buf_ptr + 2, value->data, vallen);
580251881Speter              buf_ptr[2 + vallen] = ' ';
581251881Speter              buf_ptr[2 + vallen + 1] = '$';
582251881Speter              *len = 5 + keyword_len + vallen;
583251881Speter            }
584251881Speter          else
585251881Speter            {
586251881Speter              /* "$keyword: $"  */
587251881Speter              buf_ptr[2] = '$';
588251881Speter              *len = 4 + keyword_len;
589251881Speter            }
590251881Speter        }
591251881Speter      return TRUE;
592251881Speter    }
593251881Speter
594251881Speter  return FALSE;
595251881Speter}
596251881Speter
597251881Speter/* Parse BUF (whose length is LEN, and which starts and ends with '$'),
598251881Speter   trying to match one of the keyword names in KEYWORDS.  If such a
599251881Speter   keyword is found, update *KEYWORD_NAME with the keyword name and
600251881Speter   return TRUE. */
601251881Speterstatic svn_boolean_t
602251881Spetermatch_keyword(char *buf,
603251881Speter              apr_size_t len,
604251881Speter              char *keyword_name,
605251881Speter              apr_hash_t *keywords)
606251881Speter{
607251881Speter  apr_size_t i;
608251881Speter
609251881Speter  /* Early return for ignored keywords */
610251881Speter  if (! keywords)
611251881Speter    return FALSE;
612251881Speter
613251881Speter  /* Extract the name of the keyword */
614251881Speter  for (i = 0; i < len - 2 && buf[i + 1] != ':'; i++)
615251881Speter    keyword_name[i] = buf[i + 1];
616251881Speter  keyword_name[i] = '\0';
617251881Speter
618251881Speter  return svn_hash_gets(keywords, keyword_name) != NULL;
619251881Speter}
620251881Speter
621251881Speter/* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN):
622251881Speter   optionally perform the substitution in place, update *LEN with
623251881Speter   the new length of the translated keyword string, and return TRUE.
624251881Speter   If this buffer doesn't contain a known keyword pattern, leave BUF
625251881Speter   and *LEN untouched and return FALSE.
626251881Speter
627251881Speter   See the docstring for svn_subst_copy_and_translate for how the
628251881Speter   EXPAND and KEYWORDS parameters work.
629251881Speter
630251881Speter   NOTE: It is assumed that BUF has been allocated to be at least
631251881Speter   SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less
632251881Speter   than or equal SVN_KEYWORD_MAX_LEN in length.  Also, any expansions
633251881Speter   which would result in a keyword string which is greater than
634251881Speter   SVN_KEYWORD_MAX_LEN will have their values truncated in such a way
635251881Speter   that the resultant keyword string is still valid (begins with
636251881Speter   "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long).  */
637251881Speterstatic svn_boolean_t
638251881Spetertranslate_keyword(char *buf,
639251881Speter                  apr_size_t *len,
640251881Speter                  const char *keyword_name,
641251881Speter                  svn_boolean_t expand,
642251881Speter                  apr_hash_t *keywords)
643251881Speter{
644251881Speter  const svn_string_t *value;
645251881Speter
646251881Speter  /* Make sure we gotz good stuffs. */
647251881Speter  assert(*len <= SVN_KEYWORD_MAX_LEN);
648251881Speter  assert((buf[0] == '$') && (buf[*len - 1] == '$'));
649251881Speter
650251881Speter  /* Early return for ignored keywords */
651251881Speter  if (! keywords)
652251881Speter    return FALSE;
653251881Speter
654251881Speter  value = svn_hash_gets(keywords, keyword_name);
655251881Speter
656251881Speter  if (value)
657251881Speter    {
658251881Speter      return translate_keyword_subst(buf, len,
659251881Speter                                     keyword_name, strlen(keyword_name),
660251881Speter                                     expand ? value : NULL);
661251881Speter    }
662251881Speter
663251881Speter  return FALSE;
664251881Speter}
665251881Speter
666251881Speter/* A boolean expression that evaluates to true if the first STR_LEN characters
667251881Speter   of the string STR are one of the end-of-line strings LF, CR, or CRLF;
668251881Speter   to false otherwise.  */
669251881Speter#define STRING_IS_EOL(str, str_len) \
670251881Speter  (((str_len) == 2 &&  (str)[0] == '\r' && (str)[1] == '\n') || \
671251881Speter   ((str_len) == 1 && ((str)[0] == '\n' || (str)[0] == '\r')))
672251881Speter
673251881Speter/* A boolean expression that evaluates to true if the end-of-line string EOL1,
674251881Speter   having length EOL1_LEN, and the end-of-line string EOL2, having length
675251881Speter   EOL2_LEN, are different, assuming that EOL1 and EOL2 are both from the
676251881Speter   set {"\n", "\r", "\r\n"};  to false otherwise.
677251881Speter
678251881Speter   Given that EOL1 and EOL2 are either "\n", "\r", or "\r\n", then if
679251881Speter   EOL1_LEN is not the same as EOL2_LEN, then EOL1 and EOL2 are of course
680251881Speter   different. If EOL1_LEN and EOL2_LEN are both 2 then EOL1 and EOL2 are both
681251881Speter   "\r\n" and *EOL1 == *EOL2. Otherwise, EOL1_LEN and EOL2_LEN are both 1.
682251881Speter   We need only check the one character for equality to determine whether
683251881Speter   EOL1 and EOL2 are different in that case. */
684251881Speter#define DIFFERENT_EOL_STRINGS(eol1, eol1_len, eol2, eol2_len) \
685251881Speter  (((eol1_len) != (eol2_len)) || (*(eol1) != *(eol2)))
686251881Speter
687251881Speter
688251881Speter/* Translate the newline string NEWLINE_BUF (of length NEWLINE_LEN) to
689251881Speter   the newline string EOL_STR (of length EOL_STR_LEN), writing the
690251881Speter   result (which is always EOL_STR) to the stream DST.
691251881Speter
692251881Speter   This function assumes that NEWLINE_BUF is either "\n", "\r", or "\r\n".
693251881Speter
694251881Speter   Also check for consistency of the source newline strings across
695251881Speter   multiple calls, using SRC_FORMAT (length *SRC_FORMAT_LEN) as a cache
696251881Speter   of the first newline found.  If the current newline is not the same
697251881Speter   as SRC_FORMAT, look to the REPAIR parameter.  If REPAIR is TRUE,
698251881Speter   ignore the inconsistency, else return an SVN_ERR_IO_INCONSISTENT_EOL
699251881Speter   error.  If *SRC_FORMAT_LEN is 0, assume we are examining the first
700251881Speter   newline in the file, and copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to
701251881Speter   use for later consistency checks.
702251881Speter
703251881Speter   If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if the
704251881Speter   newline string that was written (EOL_STR) is not the same as the newline
705251881Speter   string that was translated (NEWLINE_BUF), otherwise leave *TRANSLATED_EOL
706251881Speter   untouched.
707251881Speter
708251881Speter   Note: all parameters are required even if REPAIR is TRUE.
709251881Speter   ### We could require that REPAIR must not change across a sequence of
710251881Speter       calls, and could then optimize by not using SRC_FORMAT at all if
711251881Speter       REPAIR is TRUE.
712251881Speter*/
713251881Speterstatic svn_error_t *
714251881Spetertranslate_newline(const char *eol_str,
715251881Speter                  apr_size_t eol_str_len,
716251881Speter                  char *src_format,
717251881Speter                  apr_size_t *src_format_len,
718251881Speter                  const char *newline_buf,
719251881Speter                  apr_size_t newline_len,
720251881Speter                  svn_stream_t *dst,
721251881Speter                  svn_boolean_t *translated_eol,
722251881Speter                  svn_boolean_t repair)
723251881Speter{
724251881Speter  SVN_ERR_ASSERT(STRING_IS_EOL(newline_buf, newline_len));
725251881Speter
726251881Speter  /* If we've seen a newline before, compare it with our cache to
727251881Speter     check for consistency, else cache it for future comparisons. */
728251881Speter  if (*src_format_len)
729251881Speter    {
730251881Speter      /* Comparing with cache.  If we are inconsistent and
731251881Speter         we are NOT repairing the file, generate an error! */
732251881Speter      if ((! repair) && DIFFERENT_EOL_STRINGS(src_format, *src_format_len,
733251881Speter                                              newline_buf, newline_len))
734251881Speter        return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL, NULL, NULL);
735251881Speter    }
736251881Speter  else
737251881Speter    {
738251881Speter      /* This is our first line ending, so cache it before
739251881Speter         handling it. */
740251881Speter      strncpy(src_format, newline_buf, newline_len);
741251881Speter      *src_format_len = newline_len;
742251881Speter    }
743251881Speter
744251881Speter  /* Write the desired newline */
745251881Speter  SVN_ERR(translate_write(dst, eol_str, eol_str_len));
746251881Speter
747251881Speter  /* Report whether we translated it.  Note: Not using DIFFERENT_EOL_STRINGS()
748251881Speter   * because EOL_STR may not be a valid EOL sequence. */
749251881Speter  if (translated_eol != NULL &&
750251881Speter      (eol_str_len != newline_len ||
751251881Speter       memcmp(eol_str, newline_buf, eol_str_len) != 0))
752251881Speter    *translated_eol = TRUE;
753251881Speter
754251881Speter  return SVN_NO_ERROR;
755251881Speter}
756251881Speter
757251881Speter
758251881Speter
759251881Speter/*** Public interfaces. ***/
760251881Speter
761251881Spetersvn_boolean_t
762251881Spetersvn_subst_keywords_differ(const svn_subst_keywords_t *a,
763251881Speter                          const svn_subst_keywords_t *b,
764251881Speter                          svn_boolean_t compare_values)
765251881Speter{
766251881Speter  if (((a == NULL) && (b == NULL)) /* no A or B */
767251881Speter      /* no A, and B has no contents */
768251881Speter      || ((a == NULL)
769251881Speter          && (b->revision == NULL)
770251881Speter          && (b->date == NULL)
771251881Speter          && (b->author == NULL)
772251881Speter          && (b->url == NULL))
773251881Speter      /* no B, and A has no contents */
774251881Speter      || ((b == NULL)           && (a->revision == NULL)
775251881Speter          && (a->date == NULL)
776251881Speter          && (a->author == NULL)
777251881Speter          && (a->url == NULL))
778251881Speter      /* neither A nor B has any contents */
779251881Speter      || ((a != NULL) && (b != NULL)
780251881Speter          && (b->revision == NULL)
781251881Speter          && (b->date == NULL)
782251881Speter          && (b->author == NULL)
783251881Speter          && (b->url == NULL)
784251881Speter          && (a->revision == NULL)
785251881Speter          && (a->date == NULL)
786251881Speter          && (a->author == NULL)
787251881Speter          && (a->url == NULL)))
788251881Speter    {
789251881Speter      return FALSE;
790251881Speter    }
791251881Speter  else if ((a == NULL) || (b == NULL))
792251881Speter    return TRUE;
793251881Speter
794251881Speter  /* Else both A and B have some keywords. */
795251881Speter
796251881Speter  if ((! a->revision) != (! b->revision))
797251881Speter    return TRUE;
798251881Speter  else if ((compare_values && (a->revision != NULL))
799251881Speter           && (strcmp(a->revision->data, b->revision->data) != 0))
800251881Speter    return TRUE;
801251881Speter
802251881Speter  if ((! a->date) != (! b->date))
803251881Speter    return TRUE;
804251881Speter  else if ((compare_values && (a->date != NULL))
805251881Speter           && (strcmp(a->date->data, b->date->data) != 0))
806251881Speter    return TRUE;
807251881Speter
808251881Speter  if ((! a->author) != (! b->author))
809251881Speter    return TRUE;
810251881Speter  else if ((compare_values && (a->author != NULL))
811251881Speter           && (strcmp(a->author->data, b->author->data) != 0))
812251881Speter    return TRUE;
813251881Speter
814251881Speter  if ((! a->url) != (! b->url))
815251881Speter    return TRUE;
816251881Speter  else if ((compare_values && (a->url != NULL))
817251881Speter           && (strcmp(a->url->data, b->url->data) != 0))
818251881Speter    return TRUE;
819251881Speter
820251881Speter  /* Else we never found a difference, so they must be the same. */
821251881Speter
822251881Speter  return FALSE;
823251881Speter}
824251881Speter
825251881Spetersvn_boolean_t
826251881Spetersvn_subst_keywords_differ2(apr_hash_t *a,
827251881Speter                           apr_hash_t *b,
828251881Speter                           svn_boolean_t compare_values,
829251881Speter                           apr_pool_t *pool)
830251881Speter{
831251881Speter  apr_hash_index_t *hi;
832251881Speter  unsigned int a_count, b_count;
833251881Speter
834251881Speter  /* An empty hash is logically equal to a NULL,
835251881Speter   * as far as this API is concerned. */
836251881Speter  a_count = (a == NULL) ? 0 : apr_hash_count(a);
837251881Speter  b_count = (b == NULL) ? 0 : apr_hash_count(b);
838251881Speter
839251881Speter  if (a_count != b_count)
840251881Speter    return TRUE;
841251881Speter
842251881Speter  if (a_count == 0)
843251881Speter    return FALSE;
844251881Speter
845251881Speter  /* The hashes are both non-NULL, and have the same number of items.
846251881Speter   * We must check that every item of A is present in B. */
847251881Speter  for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi))
848251881Speter    {
849251881Speter      const void *key;
850251881Speter      apr_ssize_t klen;
851251881Speter      void *void_a_val;
852251881Speter      svn_string_t *a_val, *b_val;
853251881Speter
854251881Speter      apr_hash_this(hi, &key, &klen, &void_a_val);
855251881Speter      a_val = void_a_val;
856251881Speter      b_val = apr_hash_get(b, key, klen);
857251881Speter
858251881Speter      if (!b_val || (compare_values && !svn_string_compare(a_val, b_val)))
859251881Speter        return TRUE;
860251881Speter    }
861251881Speter
862251881Speter  return FALSE;
863251881Speter}
864251881Speter
865251881Speter
866251881Speter/* Baton for translate_chunk() to store its state in. */
867251881Speterstruct translation_baton
868251881Speter{
869251881Speter  const char *eol_str;
870251881Speter  svn_boolean_t *translated_eol;
871251881Speter  svn_boolean_t repair;
872251881Speter  apr_hash_t *keywords;
873251881Speter  svn_boolean_t expand;
874251881Speter
875251881Speter  /* 'short boolean' array that encodes what character values
876251881Speter     may trigger a translation action, hence are 'interesting' */
877251881Speter  char interesting[256];
878251881Speter
879251881Speter  /* Length of the string EOL_STR points to. */
880251881Speter  apr_size_t eol_str_len;
881251881Speter
882251881Speter  /* Buffer to cache any newline state between translation chunks */
883251881Speter  char newline_buf[2];
884251881Speter
885251881Speter  /* Offset (within newline_buf) of the first *unused* character */
886251881Speter  apr_size_t newline_off;
887251881Speter
888251881Speter  /* Buffer to cache keyword-parsing state between translation chunks */
889251881Speter  char keyword_buf[SVN_KEYWORD_MAX_LEN];
890251881Speter
891251881Speter  /* Offset (within keyword-buf) to the first *unused* character */
892251881Speter  apr_size_t keyword_off;
893251881Speter
894251881Speter  /* EOL style used in the chunk-source */
895251881Speter  char src_format[2];
896251881Speter
897251881Speter  /* Length of the EOL style string found in the chunk-source,
898251881Speter     or zero if none encountered yet */
899251881Speter  apr_size_t src_format_len;
900251881Speter
901251881Speter  /* If this is svn_tristate_false, translate_newline() will be called
902251881Speter     for every newline in the file */
903251881Speter  svn_tristate_t nl_translation_skippable;
904251881Speter};
905251881Speter
906251881Speter
907251881Speter/* Allocate a baton for use with translate_chunk() in POOL and
908251881Speter * initialize it for the first iteration.
909251881Speter *
910251881Speter * The caller must assure that EOL_STR and KEYWORDS at least
911251881Speter * have the same life time as that of POOL.
912251881Speter */
913251881Speterstatic struct translation_baton *
914251881Spetercreate_translation_baton(const char *eol_str,
915251881Speter                         svn_boolean_t *translated_eol,
916251881Speter                         svn_boolean_t repair,
917251881Speter                         apr_hash_t *keywords,
918251881Speter                         svn_boolean_t expand,
919251881Speter                         apr_pool_t *pool)
920251881Speter{
921251881Speter  struct translation_baton *b = apr_palloc(pool, sizeof(*b));
922251881Speter
923251881Speter  /* For efficiency, convert an empty set of keywords to NULL. */
924251881Speter  if (keywords && (apr_hash_count(keywords) == 0))
925251881Speter    keywords = NULL;
926251881Speter
927251881Speter  b->eol_str = eol_str;
928251881Speter  b->eol_str_len = eol_str ? strlen(eol_str) : 0;
929251881Speter  b->translated_eol = translated_eol;
930251881Speter  b->repair = repair;
931251881Speter  b->keywords = keywords;
932251881Speter  b->expand = expand;
933251881Speter  b->newline_off = 0;
934251881Speter  b->keyword_off = 0;
935251881Speter  b->src_format_len = 0;
936251881Speter  b->nl_translation_skippable = svn_tristate_unknown;
937251881Speter
938251881Speter  /* Most characters don't start translation actions.
939251881Speter   * Mark those that do depending on the parameters we got. */
940251881Speter  memset(b->interesting, FALSE, sizeof(b->interesting));
941251881Speter  if (keywords)
942251881Speter    b->interesting['$'] = TRUE;
943251881Speter  if (eol_str)
944251881Speter    {
945251881Speter      b->interesting['\r'] = TRUE;
946251881Speter      b->interesting['\n'] = TRUE;
947251881Speter    }
948251881Speter
949251881Speter  return b;
950251881Speter}
951251881Speter
952251881Speter/* Return TRUE if the EOL starting at BUF matches the eol_str member of B.
953251881Speter * Be aware of special cases like "\n\r\n" and "\n\n\r". For sequences like
954251881Speter * "\n$" (an EOL followed by a keyword), the result will be FALSE since it is
955251881Speter * more efficient to handle that special case implicitly in the calling code
956251881Speter * by exiting the quick scan loop.
957251881Speter * The caller must ensure that buf[0] and buf[1] refer to valid memory
958251881Speter * locations.
959251881Speter */
960251881Speterstatic APR_INLINE svn_boolean_t
961251881Spetereol_unchanged(struct translation_baton *b,
962251881Speter              const char *buf)
963251881Speter{
964251881Speter  /* If the first byte doesn't match, the whole EOL won't.
965251881Speter   * This does also handle the (certainly invalid) case that
966251881Speter   * eol_str would be an empty string.
967251881Speter   */
968251881Speter  if (buf[0] != b->eol_str[0])
969251881Speter    return FALSE;
970251881Speter
971251881Speter  /* two-char EOLs must be a full match */
972251881Speter  if (b->eol_str_len == 2)
973251881Speter    return buf[1] == b->eol_str[1];
974251881Speter
975251881Speter  /* The first char matches the required 1-byte EOL.
976251881Speter   * But maybe, buf[] contains a 2-byte EOL?
977251881Speter   * In that case, the second byte will be interesting
978251881Speter   * and not be another EOL of its own.
979251881Speter   */
980251881Speter  return !b->interesting[(unsigned char)buf[1]] || buf[0] == buf[1];
981251881Speter}
982251881Speter
983251881Speter
984251881Speter/* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN
985251881Speter * according to the settings and state stored in baton B.
986251881Speter *
987251881Speter * Write output to stream DST.
988251881Speter *
989251881Speter * To finish a series of chunk translations, flush all buffers by calling
990251881Speter * this routine with a NULL value for BUF.
991251881Speter *
992251881Speter * If B->translated_eol is not NULL, then set *B->translated_eol to TRUE if
993251881Speter * an end-of-line sequence was changed, otherwise leave it untouched.
994251881Speter *
995251881Speter * Use POOL for temporary allocations.
996251881Speter */
997251881Speterstatic svn_error_t *
998251881Spetertranslate_chunk(svn_stream_t *dst,
999251881Speter                struct translation_baton *b,
1000251881Speter                const char *buf,
1001251881Speter                apr_size_t buflen,
1002251881Speter                apr_pool_t *pool)
1003251881Speter{
1004251881Speter  const char *p;
1005251881Speter  apr_size_t len;
1006251881Speter
1007251881Speter  if (buf)
1008251881Speter    {
1009251881Speter      /* precalculate some oft-used values */
1010251881Speter      const char *end = buf + buflen;
1011251881Speter      const char *interesting = b->interesting;
1012251881Speter      apr_size_t next_sign_off = 0;
1013251881Speter
1014251881Speter      /* At the beginning of this loop, assume that we might be in an
1015251881Speter       * interesting state, i.e. with data in the newline or keyword
1016251881Speter       * buffer.  First try to get to the boring state so we can copy
1017251881Speter       * a run of boring characters; then try to get back to the
1018251881Speter       * interesting state by processing an interesting character,
1019251881Speter       * and repeat. */
1020251881Speter      for (p = buf; p < end;)
1021251881Speter        {
1022251881Speter          /* Try to get to the boring state, if necessary. */
1023251881Speter          if (b->newline_off)
1024251881Speter            {
1025251881Speter              if (*p == '\n')
1026251881Speter                b->newline_buf[b->newline_off++] = *p++;
1027251881Speter
1028251881Speter              SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1029251881Speter                                        b->src_format,
1030251881Speter                                        &b->src_format_len, b->newline_buf,
1031251881Speter                                        b->newline_off, dst, b->translated_eol,
1032251881Speter                                        b->repair));
1033251881Speter
1034251881Speter              b->newline_off = 0;
1035251881Speter            }
1036251881Speter          else if (b->keyword_off && *p == '$')
1037251881Speter            {
1038251881Speter              svn_boolean_t keyword_matches;
1039251881Speter              char keyword_name[SVN_KEYWORD_MAX_LEN + 1];
1040251881Speter
1041251881Speter              /* If keyword is matched, but not correctly translated, try to
1042251881Speter               * look for the next ending '$'. */
1043251881Speter              b->keyword_buf[b->keyword_off++] = *p++;
1044251881Speter              keyword_matches = match_keyword(b->keyword_buf, b->keyword_off,
1045251881Speter                                              keyword_name, b->keywords);
1046251881Speter              if (!keyword_matches)
1047251881Speter                {
1048251881Speter                  /* reuse the ending '$' */
1049251881Speter                  p--;
1050251881Speter                  b->keyword_off--;
1051251881Speter                }
1052251881Speter
1053251881Speter              if (!keyword_matches ||
1054251881Speter                  translate_keyword(b->keyword_buf, &b->keyword_off,
1055251881Speter                                    keyword_name, b->expand, b->keywords) ||
1056251881Speter                  b->keyword_off >= SVN_KEYWORD_MAX_LEN)
1057251881Speter                {
1058251881Speter                  /* write out non-matching text or translated keyword */
1059251881Speter                  SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1060251881Speter
1061251881Speter                  next_sign_off = 0;
1062251881Speter                  b->keyword_off = 0;
1063251881Speter                }
1064251881Speter              else
1065251881Speter                {
1066251881Speter                  if (next_sign_off == 0)
1067251881Speter                    next_sign_off = b->keyword_off - 1;
1068251881Speter
1069251881Speter                  continue;
1070251881Speter                }
1071251881Speter            }
1072251881Speter          else if (b->keyword_off == SVN_KEYWORD_MAX_LEN - 1
1073251881Speter                   || (b->keyword_off && (*p == '\r' || *p == '\n')))
1074251881Speter            {
1075251881Speter              if (next_sign_off > 0)
1076251881Speter              {
1077251881Speter                /* rolling back, continue with next '$' in keyword_buf */
1078251881Speter                p -= (b->keyword_off - next_sign_off);
1079251881Speter                b->keyword_off = next_sign_off;
1080251881Speter                next_sign_off = 0;
1081251881Speter              }
1082251881Speter              /* No closing '$' found; flush the keyword buffer. */
1083251881Speter              SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1084251881Speter
1085251881Speter              b->keyword_off = 0;
1086251881Speter            }
1087251881Speter          else if (b->keyword_off)
1088251881Speter            {
1089251881Speter              b->keyword_buf[b->keyword_off++] = *p++;
1090251881Speter              continue;
1091251881Speter            }
1092251881Speter
1093251881Speter          /* translate_newline will modify the baton for src_format_len==0
1094251881Speter             or may return an error if b->repair is FALSE.  In all other
1095251881Speter             cases, we can skip the newline translation as long as source
1096251881Speter             EOL format and actual EOL format match.  If there is a
1097251881Speter             mismatch, translate_newline will be called regardless of
1098251881Speter             nl_translation_skippable.
1099251881Speter           */
1100251881Speter          if (b->nl_translation_skippable == svn_tristate_unknown &&
1101251881Speter              b->src_format_len > 0)
1102251881Speter            {
1103251881Speter              /* test whether translate_newline may return an error */
1104251881Speter              if (b->eol_str_len == b->src_format_len &&
1105251881Speter                  strncmp(b->eol_str, b->src_format, b->eol_str_len) == 0)
1106251881Speter                b->nl_translation_skippable = svn_tristate_true;
1107251881Speter              else if (b->repair)
1108251881Speter                b->nl_translation_skippable = svn_tristate_true;
1109251881Speter              else
1110251881Speter                b->nl_translation_skippable = svn_tristate_false;
1111251881Speter            }
1112251881Speter
1113251881Speter          /* We're in the boring state; look for interesting characters.
1114251881Speter             Offset len such that it will become 0 in the first iteration.
1115251881Speter           */
1116251881Speter          len = 0 - b->eol_str_len;
1117251881Speter
1118251881Speter          /* Look for the next EOL (or $) that actually needs translation.
1119251881Speter             Stop there or at EOF, whichever is encountered first.
1120251881Speter           */
1121251881Speter          do
1122251881Speter            {
1123251881Speter              /* skip current EOL */
1124251881Speter              len += b->eol_str_len;
1125251881Speter
1126289180Speter              if (b->keywords)
1127251881Speter                {
1128289180Speter                  /* Check 4 bytes at once to allow for efficient pipelining
1129289180Speter                    and to reduce loop condition overhead. */
1130362181Sdim                  while ((end - p) >= (len + 4))
1131289180Speter                    {
1132289180Speter                      if (interesting[(unsigned char)p[len]]
1133289180Speter                          || interesting[(unsigned char)p[len+1]]
1134289180Speter                          || interesting[(unsigned char)p[len+2]]
1135289180Speter                          || interesting[(unsigned char)p[len+3]])
1136289180Speter                        break;
1137251881Speter
1138289180Speter                      len += 4;
1139289180Speter                    }
1140289180Speter
1141289180Speter                  /* Found an interesting char or EOF in the next 4 bytes.
1142289180Speter                     Find its exact position. */
1143289180Speter                  while ((p + len) < end
1144289180Speter                         && !interesting[(unsigned char)p[len]])
1145289180Speter                    ++len;
1146251881Speter                }
1147289180Speter              else
1148289180Speter                {
1149289180Speter                  /* use our optimized sub-routine to find the next EOL */
1150289180Speter                  const char *start = p + len;
1151289180Speter                  const char *eol
1152289180Speter                    = svn_eol__find_eol_start((char *)start, end - start);
1153251881Speter
1154289180Speter                  /* EOL will be NULL if we did not find a line ending */
1155289180Speter                  len += (eol ? eol : end) - start;
1156289180Speter                }
1157251881Speter            }
1158251881Speter          while (b->nl_translation_skippable ==
1159251881Speter                   svn_tristate_true &&       /* can potentially skip EOLs */
1160362181Sdim                 (end - p) > (len + 2) &&     /* not too close to EOF */
1161289180Speter                 eol_unchanged(b, p + len));  /* EOL format already ok */
1162251881Speter
1163251881Speter          while ((p + len) < end && !interesting[(unsigned char)p[len]])
1164251881Speter            len++;
1165251881Speter
1166251881Speter          if (len)
1167251881Speter            {
1168251881Speter              SVN_ERR(translate_write(dst, p, len));
1169251881Speter              p += len;
1170251881Speter            }
1171251881Speter
1172251881Speter          /* Set up state according to the interesting character, if any. */
1173251881Speter          if (p < end)
1174251881Speter            {
1175251881Speter              switch (*p)
1176251881Speter                {
1177251881Speter                case '$':
1178251881Speter                  b->keyword_buf[b->keyword_off++] = *p++;
1179251881Speter                  break;
1180251881Speter                case '\r':
1181251881Speter                  b->newline_buf[b->newline_off++] = *p++;
1182251881Speter                  break;
1183251881Speter                case '\n':
1184251881Speter                  b->newline_buf[b->newline_off++] = *p++;
1185251881Speter
1186251881Speter                  SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1187251881Speter                                            b->src_format,
1188251881Speter                                            &b->src_format_len,
1189251881Speter                                            b->newline_buf,
1190251881Speter                                            b->newline_off, dst,
1191251881Speter                                            b->translated_eol, b->repair));
1192251881Speter
1193251881Speter                  b->newline_off = 0;
1194251881Speter                  break;
1195251881Speter
1196251881Speter                }
1197251881Speter            }
1198251881Speter        }
1199251881Speter    }
1200251881Speter  else
1201251881Speter    {
1202251881Speter      if (b->newline_off)
1203251881Speter        {
1204251881Speter          SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1205251881Speter                                    b->src_format, &b->src_format_len,
1206251881Speter                                    b->newline_buf, b->newline_off,
1207251881Speter                                    dst, b->translated_eol, b->repair));
1208251881Speter          b->newline_off = 0;
1209251881Speter        }
1210251881Speter
1211251881Speter      if (b->keyword_off)
1212251881Speter        {
1213251881Speter          SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1214251881Speter          b->keyword_off = 0;
1215251881Speter        }
1216251881Speter    }
1217251881Speter
1218251881Speter  return SVN_NO_ERROR;
1219251881Speter}
1220251881Speter
1221251881Speter/* Baton for use with translated stream callbacks. */
1222251881Speterstruct translated_stream_baton
1223251881Speter{
1224251881Speter  /* Stream to take input from (before translation) on read
1225251881Speter     /write output to (after translation) on write. */
1226251881Speter  svn_stream_t *stream;
1227251881Speter
1228251881Speter  /* Input/Output translation batons to make them separate chunk streams. */
1229251881Speter  struct translation_baton *in_baton, *out_baton;
1230251881Speter
1231251881Speter  /* Remembers whether any write operations have taken place;
1232251881Speter     if so, we need to flush the output chunk stream. */
1233251881Speter  svn_boolean_t written;
1234251881Speter
1235251881Speter  /* Buffer to hold translated read data. */
1236251881Speter  svn_stringbuf_t *readbuf;
1237251881Speter
1238251881Speter  /* Offset of the first non-read character in readbuf. */
1239251881Speter  apr_size_t readbuf_off;
1240251881Speter
1241251881Speter  /* Buffer to hold read data
1242251881Speter     between svn_stream_read() and translate_chunk(). */
1243251881Speter  char *buf;
1244251881Speter#define SVN__TRANSLATION_BUF_SIZE (SVN__STREAM_CHUNK_SIZE + 1)
1245251881Speter
1246251881Speter  /* Pool for callback iterations */
1247251881Speter  apr_pool_t *iterpool;
1248251881Speter};
1249251881Speter
1250251881Speter
1251251881Speter/* Implements svn_read_fn_t. */
1252251881Speterstatic svn_error_t *
1253251881Spetertranslated_stream_read(void *baton,
1254251881Speter                       char *buffer,
1255251881Speter                       apr_size_t *len)
1256251881Speter{
1257251881Speter  struct translated_stream_baton *b = baton;
1258251881Speter  apr_size_t readlen = SVN__STREAM_CHUNK_SIZE;
1259251881Speter  apr_size_t unsatisfied = *len;
1260251881Speter  apr_size_t off = 0;
1261251881Speter
1262251881Speter  /* Optimization for a frequent special case. The configuration parser (and
1263251881Speter     a few others) reads the stream one byte at a time. All the memcpy, pool
1264251881Speter     clearing etc. imposes a huge overhead in that case. In most cases, we
1265251881Speter     can just take that single byte directly from the read buffer.
1266251881Speter
1267251881Speter     Since *len > 1 requires lots of code to be run anyways, we can afford
1268251881Speter     the extra overhead of checking for *len == 1.
1269251881Speter
1270251881Speter     See <http://mail-archives.apache.org/mod_mbox/subversion-dev/201003.mbox/%3C4B94011E.1070207@alice-dsl.de%3E>.
1271251881Speter  */
1272251881Speter  if (unsatisfied == 1 && b->readbuf_off < b->readbuf->len)
1273251881Speter    {
1274251881Speter      /* Just take it from the read buffer */
1275251881Speter      *buffer = b->readbuf->data[b->readbuf_off++];
1276251881Speter
1277251881Speter      return SVN_NO_ERROR;
1278251881Speter    }
1279251881Speter
1280251881Speter  /* Standard code path. */
1281251881Speter  while (readlen == SVN__STREAM_CHUNK_SIZE && unsatisfied > 0)
1282251881Speter    {
1283251881Speter      apr_size_t to_copy;
1284251881Speter      apr_size_t buffer_remainder;
1285251881Speter
1286251881Speter      svn_pool_clear(b->iterpool);
1287251881Speter      /* fill read buffer, if necessary */
1288251881Speter      if (! (b->readbuf_off < b->readbuf->len))
1289251881Speter        {
1290251881Speter          svn_stream_t *buf_stream;
1291251881Speter
1292251881Speter          svn_stringbuf_setempty(b->readbuf);
1293251881Speter          b->readbuf_off = 0;
1294289180Speter          SVN_ERR(svn_stream_read_full(b->stream, b->buf, &readlen));
1295251881Speter          buf_stream = svn_stream_from_stringbuf(b->readbuf, b->iterpool);
1296251881Speter
1297251881Speter          SVN_ERR(translate_chunk(buf_stream, b->in_baton, b->buf,
1298251881Speter                                  readlen, b->iterpool));
1299251881Speter
1300251881Speter          if (readlen != SVN__STREAM_CHUNK_SIZE)
1301251881Speter            SVN_ERR(translate_chunk(buf_stream, b->in_baton, NULL, 0,
1302251881Speter                                    b->iterpool));
1303251881Speter
1304251881Speter          SVN_ERR(svn_stream_close(buf_stream));
1305251881Speter        }
1306251881Speter
1307251881Speter      /* Satisfy from the read buffer */
1308251881Speter      buffer_remainder = b->readbuf->len - b->readbuf_off;
1309251881Speter      to_copy = (buffer_remainder > unsatisfied)
1310251881Speter        ? unsatisfied : buffer_remainder;
1311251881Speter      memcpy(buffer + off, b->readbuf->data + b->readbuf_off, to_copy);
1312251881Speter      off += to_copy;
1313251881Speter      b->readbuf_off += to_copy;
1314251881Speter      unsatisfied -= to_copy;
1315251881Speter    }
1316251881Speter
1317251881Speter  *len -= unsatisfied;
1318251881Speter
1319251881Speter  return SVN_NO_ERROR;
1320251881Speter}
1321251881Speter
1322251881Speter/* Implements svn_write_fn_t. */
1323251881Speterstatic svn_error_t *
1324251881Spetertranslated_stream_write(void *baton,
1325251881Speter                        const char *buffer,
1326251881Speter                        apr_size_t *len)
1327251881Speter{
1328251881Speter  struct translated_stream_baton *b = baton;
1329251881Speter  svn_pool_clear(b->iterpool);
1330251881Speter
1331251881Speter  b->written = TRUE;
1332251881Speter  return translate_chunk(b->stream, b->out_baton, buffer, *len, b->iterpool);
1333251881Speter}
1334251881Speter
1335251881Speter/* Implements svn_close_fn_t. */
1336251881Speterstatic svn_error_t *
1337251881Spetertranslated_stream_close(void *baton)
1338251881Speter{
1339251881Speter  struct translated_stream_baton *b = baton;
1340251881Speter  svn_error_t *err = NULL;
1341251881Speter
1342251881Speter  if (b->written)
1343251881Speter    err = translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool);
1344251881Speter
1345251881Speter  err = svn_error_compose_create(err, svn_stream_close(b->stream));
1346251881Speter
1347251881Speter  svn_pool_destroy(b->iterpool);
1348251881Speter
1349251881Speter  return svn_error_trace(err);
1350251881Speter}
1351251881Speter
1352251881Speter
1353251881Speter/* svn_stream_mark_t for translation streams. */
1354251881Spetertypedef struct mark_translated_t
1355251881Speter{
1356251881Speter  /* Saved translation state. */
1357251881Speter  struct translated_stream_baton saved_baton;
1358251881Speter
1359251881Speter  /* Mark set on the underlying stream. */
1360251881Speter  svn_stream_mark_t *mark;
1361251881Speter} mark_translated_t;
1362251881Speter
1363251881Speter/* Implements svn_stream_mark_fn_t. */
1364251881Speterstatic svn_error_t *
1365251881Spetertranslated_stream_mark(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
1366251881Speter{
1367251881Speter  mark_translated_t *mt;
1368251881Speter  struct translated_stream_baton *b = baton;
1369251881Speter
1370251881Speter  mt = apr_palloc(pool, sizeof(*mt));
1371251881Speter  SVN_ERR(svn_stream_mark(b->stream, &mt->mark, pool));
1372251881Speter
1373251881Speter  /* Save translation state. */
1374251881Speter  mt->saved_baton.in_baton = apr_pmemdup(pool, b->in_baton,
1375251881Speter                                         sizeof(*mt->saved_baton.in_baton));
1376251881Speter  mt->saved_baton.out_baton = apr_pmemdup(pool, b->out_baton,
1377251881Speter                                          sizeof(*mt->saved_baton.out_baton));
1378251881Speter  mt->saved_baton.written = b->written;
1379251881Speter  mt->saved_baton.readbuf = svn_stringbuf_dup(b->readbuf, pool);
1380251881Speter  mt->saved_baton.readbuf_off = b->readbuf_off;
1381251881Speter  mt->saved_baton.buf = apr_pmemdup(pool, b->buf, SVN__TRANSLATION_BUF_SIZE);
1382251881Speter
1383251881Speter  *mark = (svn_stream_mark_t *)mt;
1384251881Speter
1385251881Speter  return SVN_NO_ERROR;
1386251881Speter}
1387251881Speter
1388251881Speter/* Implements svn_stream_seek_fn_t. */
1389251881Speterstatic svn_error_t *
1390251881Spetertranslated_stream_seek(void *baton, const svn_stream_mark_t *mark)
1391251881Speter{
1392251881Speter  struct translated_stream_baton *b = baton;
1393251881Speter
1394251881Speter  if (mark != NULL)
1395251881Speter    {
1396251881Speter      const mark_translated_t *mt = (const mark_translated_t *)mark;
1397251881Speter
1398251881Speter      /* Flush output buffer if necessary. */
1399251881Speter      if (b->written)
1400251881Speter        SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0,
1401251881Speter                                b->iterpool));
1402251881Speter
1403251881Speter      SVN_ERR(svn_stream_seek(b->stream, mt->mark));
1404251881Speter
1405251881Speter      /* Restore translation state, avoiding new allocations. */
1406251881Speter      *b->in_baton = *mt->saved_baton.in_baton;
1407251881Speter      *b->out_baton = *mt->saved_baton.out_baton;
1408251881Speter      b->written = mt->saved_baton.written;
1409251881Speter      svn_stringbuf_setempty(b->readbuf);
1410251881Speter      svn_stringbuf_appendbytes(b->readbuf, mt->saved_baton.readbuf->data,
1411251881Speter                                mt->saved_baton.readbuf->len);
1412251881Speter      b->readbuf_off = mt->saved_baton.readbuf_off;
1413251881Speter      memcpy(b->buf, mt->saved_baton.buf, SVN__TRANSLATION_BUF_SIZE);
1414251881Speter    }
1415251881Speter  else
1416251881Speter    {
1417251881Speter      SVN_ERR(svn_stream_reset(b->stream));
1418251881Speter
1419251881Speter      b->in_baton->newline_off = 0;
1420251881Speter      b->in_baton->keyword_off = 0;
1421251881Speter      b->in_baton->src_format_len = 0;
1422251881Speter      b->out_baton->newline_off = 0;
1423251881Speter      b->out_baton->keyword_off = 0;
1424251881Speter      b->out_baton->src_format_len = 0;
1425251881Speter
1426251881Speter      b->written = FALSE;
1427251881Speter      svn_stringbuf_setempty(b->readbuf);
1428251881Speter      b->readbuf_off = 0;
1429251881Speter    }
1430251881Speter
1431251881Speter  return SVN_NO_ERROR;
1432251881Speter}
1433251881Speter
1434251881Spetersvn_error_t *
1435251881Spetersvn_subst_read_specialfile(svn_stream_t **stream,
1436251881Speter                           const char *path,
1437251881Speter                           apr_pool_t *result_pool,
1438251881Speter                           apr_pool_t *scratch_pool)
1439251881Speter{
1440251881Speter  apr_finfo_t finfo;
1441251881Speter  svn_string_t *buf;
1442251881Speter
1443251881Speter  /* First determine what type of special file we are
1444251881Speter     detranslating. */
1445251881Speter  SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK,
1446251881Speter                      scratch_pool));
1447251881Speter
1448251881Speter  switch (finfo.filetype) {
1449251881Speter  case APR_REG:
1450251881Speter    /* Nothing special to do here, just create stream from the original
1451251881Speter       file's contents. */
1452251881Speter    SVN_ERR(svn_stream_open_readonly(stream, path, result_pool, scratch_pool));
1453251881Speter    break;
1454251881Speter
1455251881Speter  case APR_LNK:
1456251881Speter    /* Determine the destination of the link. */
1457251881Speter    SVN_ERR(svn_io_read_link(&buf, path, scratch_pool));
1458251881Speter    *stream = svn_stream_from_string(svn_string_createf(result_pool,
1459251881Speter                                                        "link %s",
1460251881Speter                                                        buf->data),
1461251881Speter                                     result_pool);
1462251881Speter    break;
1463251881Speter
1464251881Speter  default:
1465251881Speter    SVN_ERR_MALFUNCTION();
1466251881Speter  }
1467251881Speter
1468251881Speter  return SVN_NO_ERROR;
1469251881Speter}
1470251881Speter
1471251881Speter/* Same as svn_subst_stream_translated(), except for the following.
1472251881Speter *
1473251881Speter * If TRANSLATED_EOL is not NULL, then reading and/or writing to the stream
1474251881Speter * will set *TRANSLATED_EOL to TRUE if an end-of-line sequence was changed,
1475251881Speter * otherwise leave it untouched.
1476251881Speter */
1477251881Speterstatic svn_stream_t *
1478251881Speterstream_translated(svn_stream_t *stream,
1479251881Speter                  const char *eol_str,
1480251881Speter                  svn_boolean_t *translated_eol,
1481251881Speter                  svn_boolean_t repair,
1482251881Speter                  apr_hash_t *keywords,
1483251881Speter                  svn_boolean_t expand,
1484251881Speter                  apr_pool_t *result_pool)
1485251881Speter{
1486251881Speter  struct translated_stream_baton *baton
1487251881Speter    = apr_palloc(result_pool, sizeof(*baton));
1488251881Speter  svn_stream_t *s = svn_stream_create(baton, result_pool);
1489251881Speter
1490251881Speter  /* Make sure EOL_STR and KEYWORDS are allocated in RESULT_POOL
1491251881Speter     so they have the same lifetime as the stream. */
1492251881Speter  if (eol_str)
1493251881Speter    eol_str = apr_pstrdup(result_pool, eol_str);
1494251881Speter  if (keywords)
1495251881Speter    {
1496251881Speter      if (apr_hash_count(keywords) == 0)
1497251881Speter        keywords = NULL;
1498251881Speter      else
1499251881Speter        {
1500251881Speter          /* deep copy the hash to make sure it's allocated in RESULT_POOL */
1501251881Speter          apr_hash_t *copy = apr_hash_make(result_pool);
1502251881Speter          apr_hash_index_t *hi;
1503251881Speter          apr_pool_t *subpool;
1504251881Speter
1505251881Speter          subpool = svn_pool_create(result_pool);
1506251881Speter          for (hi = apr_hash_first(subpool, keywords);
1507251881Speter               hi; hi = apr_hash_next(hi))
1508251881Speter            {
1509251881Speter              const void *key;
1510251881Speter              void *val;
1511251881Speter
1512251881Speter              apr_hash_this(hi, &key, NULL, &val);
1513251881Speter              svn_hash_sets(copy, apr_pstrdup(result_pool, key),
1514251881Speter                            svn_string_dup(val, result_pool));
1515251881Speter            }
1516251881Speter          svn_pool_destroy(subpool);
1517251881Speter
1518251881Speter          keywords = copy;
1519251881Speter        }
1520251881Speter    }
1521251881Speter
1522251881Speter  /* Setup the baton fields */
1523251881Speter  baton->stream = stream;
1524251881Speter  baton->in_baton
1525251881Speter    = create_translation_baton(eol_str, translated_eol, repair, keywords,
1526251881Speter                               expand, result_pool);
1527251881Speter  baton->out_baton
1528251881Speter    = create_translation_baton(eol_str, translated_eol, repair, keywords,
1529251881Speter                               expand, result_pool);
1530251881Speter  baton->written = FALSE;
1531251881Speter  baton->readbuf = svn_stringbuf_create_empty(result_pool);
1532251881Speter  baton->readbuf_off = 0;
1533251881Speter  baton->iterpool = svn_pool_create(result_pool);
1534251881Speter  baton->buf = apr_palloc(result_pool, SVN__TRANSLATION_BUF_SIZE);
1535251881Speter
1536251881Speter  /* Setup the stream methods */
1537289180Speter  svn_stream_set_read2(s, NULL /* only full read support */,
1538289180Speter                       translated_stream_read);
1539251881Speter  svn_stream_set_write(s, translated_stream_write);
1540251881Speter  svn_stream_set_close(s, translated_stream_close);
1541362181Sdim  if (svn_stream_supports_mark(stream))
1542362181Sdim    {
1543362181Sdim      svn_stream_set_mark(s, translated_stream_mark);
1544362181Sdim      svn_stream_set_seek(s, translated_stream_seek);
1545362181Sdim    }
1546251881Speter
1547251881Speter  return s;
1548251881Speter}
1549251881Speter
1550251881Spetersvn_stream_t *
1551251881Spetersvn_subst_stream_translated(svn_stream_t *stream,
1552251881Speter                            const char *eol_str,
1553251881Speter                            svn_boolean_t repair,
1554251881Speter                            apr_hash_t *keywords,
1555251881Speter                            svn_boolean_t expand,
1556251881Speter                            apr_pool_t *result_pool)
1557251881Speter{
1558251881Speter  return stream_translated(stream, eol_str, NULL, repair, keywords, expand,
1559251881Speter                           result_pool);
1560251881Speter}
1561251881Speter
1562251881Speter/* Same as svn_subst_translate_cstring2(), except for the following.
1563251881Speter *
1564251881Speter * If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if an
1565251881Speter * end-of-line sequence was changed, or to FALSE otherwise.
1566251881Speter */
1567251881Speterstatic svn_error_t *
1568251881Spetertranslate_cstring(const char **dst,
1569251881Speter                  svn_boolean_t *translated_eol,
1570251881Speter                  const char *src,
1571251881Speter                  const char *eol_str,
1572251881Speter                  svn_boolean_t repair,
1573251881Speter                  apr_hash_t *keywords,
1574251881Speter                  svn_boolean_t expand,
1575251881Speter                  apr_pool_t *pool)
1576251881Speter{
1577251881Speter  svn_stringbuf_t *dst_stringbuf;
1578251881Speter  svn_stream_t *dst_stream;
1579251881Speter  apr_size_t len = strlen(src);
1580251881Speter
1581251881Speter  /* The easy way out:  no translation needed, just copy. */
1582251881Speter  if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1583251881Speter    {
1584251881Speter      *dst = apr_pstrmemdup(pool, src, len);
1585251881Speter      return SVN_NO_ERROR;
1586251881Speter    }
1587251881Speter
1588251881Speter  /* Create a stringbuf and wrapper stream to hold the output. */
1589251881Speter  dst_stringbuf = svn_stringbuf_create_empty(pool);
1590251881Speter  dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool);
1591251881Speter
1592251881Speter  if (translated_eol)
1593251881Speter    *translated_eol = FALSE;
1594251881Speter
1595251881Speter  /* Another wrapper to translate the content. */
1596251881Speter  dst_stream = stream_translated(dst_stream, eol_str, translated_eol, repair,
1597251881Speter                                 keywords, expand, pool);
1598251881Speter
1599251881Speter  /* Jam the text into the destination stream (to translate it). */
1600251881Speter  SVN_ERR(svn_stream_write(dst_stream, src, &len));
1601251881Speter
1602251881Speter  /* Close the destination stream to flush unwritten data. */
1603251881Speter  SVN_ERR(svn_stream_close(dst_stream));
1604251881Speter
1605251881Speter  *dst = dst_stringbuf->data;
1606251881Speter  return SVN_NO_ERROR;
1607251881Speter}
1608251881Speter
1609251881Spetersvn_error_t *
1610251881Spetersvn_subst_translate_cstring2(const char *src,
1611251881Speter                             const char **dst,
1612251881Speter                             const char *eol_str,
1613251881Speter                             svn_boolean_t repair,
1614251881Speter                             apr_hash_t *keywords,
1615251881Speter                             svn_boolean_t expand,
1616251881Speter                             apr_pool_t *pool)
1617251881Speter{
1618251881Speter  return translate_cstring(dst, NULL, src, eol_str, repair, keywords, expand,
1619251881Speter                            pool);
1620251881Speter}
1621251881Speter
1622251881Speter/* Given a special file at SRC, generate a textual representation of
1623251881Speter   it in a normal file at DST.  Perform all allocations in POOL. */
1624251881Speter/* ### this should be folded into svn_subst_copy_and_translate3 */
1625251881Speterstatic svn_error_t *
1626251881Speterdetranslate_special_file(const char *src, const char *dst,
1627251881Speter                         svn_cancel_func_t cancel_func, void *cancel_baton,
1628251881Speter                         apr_pool_t *scratch_pool)
1629251881Speter{
1630251881Speter  const char *dst_tmp;
1631251881Speter  svn_stream_t *src_stream;
1632251881Speter  svn_stream_t *dst_stream;
1633251881Speter
1634251881Speter  /* Open a temporary destination that we will eventually atomically
1635251881Speter     rename into place. */
1636251881Speter  SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1637251881Speter                                 svn_dirent_dirname(dst, scratch_pool),
1638251881Speter                                 svn_io_file_del_none,
1639251881Speter                                 scratch_pool, scratch_pool));
1640251881Speter  SVN_ERR(svn_subst_read_specialfile(&src_stream, src,
1641251881Speter                                     scratch_pool, scratch_pool));
1642251881Speter  SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
1643251881Speter                           cancel_func, cancel_baton, scratch_pool));
1644251881Speter
1645251881Speter  /* Do the atomic rename from our temporary location. */
1646362181Sdim  return svn_error_trace(svn_io_file_rename2(dst_tmp, dst, FALSE, scratch_pool));
1647251881Speter}
1648251881Speter
1649251881Speter/* Creates a special file DST from the "normal form" located in SOURCE.
1650251881Speter *
1651251881Speter * All temporary allocations will be done in POOL.
1652251881Speter */
1653251881Speterstatic svn_error_t *
1654251881Spetercreate_special_file_from_stream(svn_stream_t *source, const char *dst,
1655251881Speter                                apr_pool_t *pool)
1656251881Speter{
1657251881Speter  svn_stringbuf_t *contents;
1658251881Speter  svn_boolean_t eof;
1659251881Speter  const char *identifier;
1660251881Speter  const char *remainder;
1661251881Speter  const char *dst_tmp;
1662251881Speter  svn_boolean_t create_using_internal_representation = FALSE;
1663251881Speter
1664251881Speter  SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool));
1665251881Speter
1666251881Speter  /* Separate off the identifier.  The first space character delimits
1667251881Speter     the identifier, after which any remaining characters are specific
1668251881Speter     to the actual special file type being created. */
1669251881Speter  identifier = contents->data;
1670251881Speter  for (remainder = identifier; *remainder; remainder++)
1671251881Speter    {
1672251881Speter      if (*remainder == ' ')
1673251881Speter        {
1674251881Speter          remainder++;
1675251881Speter          break;
1676251881Speter        }
1677251881Speter    }
1678251881Speter
1679251881Speter  if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ",
1680251881Speter                sizeof(SVN_SUBST__SPECIAL_LINK_STR " ")-1))
1681251881Speter    {
1682251881Speter      /* For symlinks, the type specific data is just a filesystem
1683251881Speter         path that the symlink should reference. */
1684251881Speter      svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder,
1685251881Speter                                                   ".tmp", pool);
1686251881Speter
1687251881Speter      /* If we had an error, check to see if it was because symlinks are
1688362181Sdim         not supported on the platform.  If so, fall back to using the
1689362181Sdim         internal representation. */
1690362181Sdim      if (err && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
1691251881Speter        {
1692362181Sdim          svn_error_clear(err);
1693362181Sdim          create_using_internal_representation = TRUE;
1694251881Speter        }
1695362181Sdim      else if (err)
1696362181Sdim        {
1697362181Sdim          return svn_error_trace(err);
1698362181Sdim        }
1699251881Speter    }
1700251881Speter  else
1701251881Speter    {
1702251881Speter      /* Just create a normal file using the internal special file
1703251881Speter         representation.  We don't want a commit of an unknown special
1704251881Speter         file type to DoS all the clients. */
1705251881Speter      create_using_internal_representation = TRUE;
1706251881Speter    }
1707251881Speter
1708251881Speter  /* If nothing else worked, write out the internal representation to
1709289180Speter     a file that can be edited by the user. */
1710251881Speter  if (create_using_internal_representation)
1711262250Speter    {
1712289180Speter      svn_stream_t *new_stream;
1713289180Speter      apr_size_t len;
1714251881Speter
1715289180Speter      SVN_ERR(svn_stream_open_unique(&new_stream, &dst_tmp,
1716289180Speter                                     svn_dirent_dirname(dst, pool),
1717289180Speter                                     svn_io_file_del_none,
1718289180Speter                                     pool, pool));
1719262250Speter
1720289180Speter      if (!eof)
1721289180Speter        svn_stringbuf_appendcstr(contents, "\n");
1722289180Speter      len = contents->len;
1723289180Speter      SVN_ERR(svn_stream_write(new_stream, contents->data, &len));
1724289180Speter      SVN_ERR(svn_stream_copy3(svn_stream_disown(source, pool), new_stream,
1725289180Speter                               NULL, NULL, pool));
1726262250Speter    }
1727262250Speter
1728251881Speter  /* Do the atomic rename from our temporary location. */
1729362181Sdim  return svn_error_trace(svn_io_file_rename2(dst_tmp, dst, FALSE, pool));
1730251881Speter}
1731251881Speter
1732251881Speter
1733251881Spetersvn_error_t *
1734251881Spetersvn_subst_copy_and_translate4(const char *src,
1735251881Speter                              const char *dst,
1736251881Speter                              const char *eol_str,
1737251881Speter                              svn_boolean_t repair,
1738251881Speter                              apr_hash_t *keywords,
1739251881Speter                              svn_boolean_t expand,
1740251881Speter                              svn_boolean_t special,
1741251881Speter                              svn_cancel_func_t cancel_func,
1742251881Speter                              void *cancel_baton,
1743251881Speter                              apr_pool_t *pool)
1744251881Speter{
1745251881Speter  svn_stream_t *src_stream;
1746251881Speter  svn_stream_t *dst_stream;
1747251881Speter  const char *dst_tmp;
1748251881Speter  svn_error_t *err;
1749251881Speter  svn_node_kind_t kind;
1750251881Speter  svn_boolean_t path_special;
1751251881Speter
1752251881Speter  SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool));
1753251881Speter
1754251881Speter  /* If this is a 'special' file, we may need to create it or
1755251881Speter     detranslate it. */
1756251881Speter  if (special || path_special)
1757251881Speter    {
1758251881Speter      if (expand)
1759251881Speter        {
1760251881Speter          if (path_special)
1761251881Speter            {
1762251881Speter              /* We are being asked to create a special file from a special
1763251881Speter                 file.  Do a temporary detranslation and work from there. */
1764251881Speter
1765251881Speter              /* ### woah. this section just undoes all the work we already did
1766251881Speter                 ### to read the contents of the special file. shoot... the
1767251881Speter                 ### svn_subst_read_specialfile even checks the file type
1768251881Speter                 ### for us! */
1769251881Speter
1770251881Speter              SVN_ERR(svn_subst_read_specialfile(&src_stream, src, pool, pool));
1771251881Speter            }
1772251881Speter          else
1773251881Speter            {
1774251881Speter              SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1775251881Speter            }
1776251881Speter
1777289180Speter          SVN_ERR(create_special_file_from_stream(src_stream, dst, pool));
1778289180Speter
1779289180Speter          return svn_error_trace(svn_stream_close(src_stream));
1780251881Speter        }
1781251881Speter      /* else !expand */
1782251881Speter
1783251881Speter      return svn_error_trace(detranslate_special_file(src, dst,
1784251881Speter                                                      cancel_func,
1785251881Speter                                                      cancel_baton,
1786251881Speter                                                      pool));
1787251881Speter    }
1788251881Speter
1789251881Speter  /* The easy way out:  no translation needed, just copy. */
1790251881Speter  if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1791251881Speter    return svn_error_trace(svn_io_copy_file(src, dst, FALSE, pool));
1792251881Speter
1793251881Speter  /* Open source file. */
1794251881Speter  SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1795251881Speter
1796251881Speter  /* For atomicity, we translate to a tmp file and then rename the tmp file
1797251881Speter     over the real destination. */
1798251881Speter  SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1799251881Speter                                 svn_dirent_dirname(dst, pool),
1800251881Speter                                 svn_io_file_del_none, pool, pool));
1801251881Speter
1802251881Speter  dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair,
1803251881Speter                                           keywords, expand, pool);
1804251881Speter
1805251881Speter  /* ###: use cancel func/baton in place of NULL/NULL below. */
1806251881Speter  err = svn_stream_copy3(src_stream, dst_stream, cancel_func, cancel_baton,
1807251881Speter                         pool);
1808251881Speter  if (err)
1809251881Speter    {
1810251881Speter      /* On errors, we have a pathname available. */
1811251881Speter      if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1812251881Speter        err = svn_error_createf(SVN_ERR_IO_INCONSISTENT_EOL, err,
1813251881Speter                                _("File '%s' has inconsistent newlines"),
1814251881Speter                                svn_dirent_local_style(src, pool));
1815251881Speter      return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp,
1816251881Speter                                                               FALSE, pool));
1817251881Speter    }
1818251881Speter
1819251881Speter  /* Now that dst_tmp contains the translated data, do the atomic rename. */
1820362181Sdim  SVN_ERR(svn_io_file_rename2(dst_tmp, dst, FALSE, pool));
1821251881Speter
1822251881Speter  /* Preserve the source file's permission bits. */
1823251881Speter  SVN_ERR(svn_io_copy_perms(src, dst, pool));
1824251881Speter
1825251881Speter  return SVN_NO_ERROR;
1826251881Speter}
1827251881Speter
1828251881Speter
1829251881Speter/*** 'Special file' stream support */
1830251881Speter
1831251881Speterstruct special_stream_baton
1832251881Speter{
1833251881Speter  svn_stream_t *read_stream;
1834251881Speter  svn_stringbuf_t *write_content;
1835251881Speter  svn_stream_t *write_stream;
1836251881Speter  const char *path;
1837251881Speter  apr_pool_t *pool;
1838251881Speter};
1839251881Speter
1840251881Speter
1841251881Speterstatic svn_error_t *
1842251881Speterread_handler_special(void *baton, char *buffer, apr_size_t *len)
1843251881Speter{
1844251881Speter  struct special_stream_baton *btn = baton;
1845251881Speter
1846251881Speter  if (btn->read_stream)
1847251881Speter    /* We actually found a file to read from */
1848289180Speter    return svn_stream_read_full(btn->read_stream, buffer, len);
1849251881Speter  else
1850251881Speter    return svn_error_createf(APR_ENOENT, NULL,
1851289180Speter                             _("Can't read special file: File '%s' not found"),
1852251881Speter                             svn_dirent_local_style(btn->path, btn->pool));
1853251881Speter}
1854251881Speter
1855251881Speterstatic svn_error_t *
1856251881Speterwrite_handler_special(void *baton, const char *buffer, apr_size_t *len)
1857251881Speter{
1858251881Speter  struct special_stream_baton *btn = baton;
1859251881Speter
1860251881Speter  return svn_stream_write(btn->write_stream, buffer, len);
1861251881Speter}
1862251881Speter
1863251881Speter
1864251881Speterstatic svn_error_t *
1865251881Speterclose_handler_special(void *baton)
1866251881Speter{
1867251881Speter  struct special_stream_baton *btn = baton;
1868251881Speter
1869251881Speter  if (btn->write_content->len)
1870251881Speter    {
1871251881Speter      /* yeay! we received data and need to create a special file! */
1872251881Speter
1873251881Speter      svn_stream_t *source = svn_stream_from_stringbuf(btn->write_content,
1874251881Speter                                                       btn->pool);
1875251881Speter      SVN_ERR(create_special_file_from_stream(source, btn->path, btn->pool));
1876251881Speter    }
1877251881Speter
1878251881Speter  return SVN_NO_ERROR;
1879251881Speter}
1880251881Speter
1881251881Speter
1882251881Spetersvn_error_t *
1883251881Spetersvn_subst_create_specialfile(svn_stream_t **stream,
1884251881Speter                             const char *path,
1885251881Speter                             apr_pool_t *result_pool,
1886251881Speter                             apr_pool_t *scratch_pool)
1887251881Speter{
1888251881Speter  struct special_stream_baton *baton = apr_palloc(result_pool, sizeof(*baton));
1889251881Speter
1890251881Speter  baton->path = apr_pstrdup(result_pool, path);
1891251881Speter
1892251881Speter  /* SCRATCH_POOL may not exist after the function returns. */
1893251881Speter  baton->pool = result_pool;
1894251881Speter
1895251881Speter  baton->write_content = svn_stringbuf_create_empty(result_pool);
1896251881Speter  baton->write_stream = svn_stream_from_stringbuf(baton->write_content,
1897251881Speter                                                  result_pool);
1898251881Speter
1899251881Speter  *stream = svn_stream_create(baton, result_pool);
1900251881Speter  svn_stream_set_write(*stream, write_handler_special);
1901251881Speter  svn_stream_set_close(*stream, close_handler_special);
1902251881Speter
1903251881Speter  return SVN_NO_ERROR;
1904251881Speter}
1905251881Speter
1906251881Speter
1907251881Speter/* NOTE: this function is deprecated, but we cannot move it over to
1908251881Speter   deprecated.c because it uses stuff private to this file, and it is
1909251881Speter   not easily rebuilt in terms of "new" functions. */
1910251881Spetersvn_error_t *
1911251881Spetersvn_subst_stream_from_specialfile(svn_stream_t **stream,
1912251881Speter                                  const char *path,
1913251881Speter                                  apr_pool_t *pool)
1914251881Speter{
1915251881Speter  struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton));
1916251881Speter  svn_error_t *err;
1917251881Speter
1918251881Speter  baton->pool = pool;
1919251881Speter  baton->path = apr_pstrdup(pool, path);
1920251881Speter
1921251881Speter  err = svn_subst_read_specialfile(&baton->read_stream, path, pool, pool);
1922251881Speter
1923251881Speter  /* File might not exist because we intend to create it upon close. */
1924251881Speter  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1925251881Speter    {
1926251881Speter      svn_error_clear(err);
1927251881Speter
1928251881Speter      /* Note: the special file is missing. the caller won't find out
1929251881Speter         until the first read. Oh well. This function is deprecated anyways,
1930251881Speter         so they can just deal with the weird behavior. */
1931251881Speter      baton->read_stream = NULL;
1932251881Speter    }
1933251881Speter
1934251881Speter  baton->write_content = svn_stringbuf_create_empty(pool);
1935251881Speter  baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool);
1936251881Speter
1937251881Speter  *stream = svn_stream_create(baton, pool);
1938289180Speter  svn_stream_set_read2(*stream, NULL /* only full read support */,
1939289180Speter                       read_handler_special);
1940251881Speter  svn_stream_set_write(*stream, write_handler_special);
1941251881Speter  svn_stream_set_close(*stream, close_handler_special);
1942251881Speter
1943251881Speter  return SVN_NO_ERROR;
1944251881Speter}
1945251881Speter
1946251881Speter
1947251881Speter
1948251881Speter/*** String translation */
1949251881Spetersvn_error_t *
1950251881Spetersvn_subst_translate_string2(svn_string_t **new_value,
1951251881Speter                            svn_boolean_t *translated_to_utf8,
1952251881Speter                            svn_boolean_t *translated_line_endings,
1953251881Speter                            const svn_string_t *value,
1954251881Speter                            const char *encoding,
1955251881Speter                            svn_boolean_t repair,
1956251881Speter                            apr_pool_t *result_pool,
1957251881Speter                            apr_pool_t *scratch_pool)
1958251881Speter{
1959251881Speter  const char *val_utf8;
1960251881Speter  const char *val_utf8_lf;
1961251881Speter
1962251881Speter  if (value == NULL)
1963251881Speter    {
1964251881Speter      *new_value = NULL;
1965251881Speter      return SVN_NO_ERROR;
1966251881Speter    }
1967251881Speter
1968289180Speter  if (encoding && !strcmp(encoding, "UTF-8"))
1969251881Speter    {
1970253734Speter      val_utf8 = value->data;
1971253734Speter    }
1972253734Speter  else if (encoding)
1973253734Speter    {
1974251881Speter      SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data,
1975251881Speter                                          encoding, scratch_pool));
1976251881Speter    }
1977251881Speter  else
1978251881Speter    {
1979251881Speter      SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, scratch_pool));
1980251881Speter    }
1981251881Speter
1982251881Speter  if (translated_to_utf8)
1983251881Speter    *translated_to_utf8 = (strcmp(value->data, val_utf8) != 0);
1984251881Speter
1985251881Speter  SVN_ERR(translate_cstring(&val_utf8_lf,
1986251881Speter                            translated_line_endings,
1987251881Speter                            val_utf8,
1988251881Speter                            "\n",  /* translate to LF */
1989251881Speter                            repair,
1990251881Speter                            NULL,  /* no keywords */
1991251881Speter                            FALSE, /* no expansion */
1992251881Speter                            scratch_pool));
1993251881Speter
1994251881Speter  *new_value = svn_string_create(val_utf8_lf, result_pool);
1995251881Speter  return SVN_NO_ERROR;
1996251881Speter}
1997251881Speter
1998251881Speter
1999251881Spetersvn_error_t *
2000251881Spetersvn_subst_detranslate_string(svn_string_t **new_value,
2001251881Speter                             const svn_string_t *value,
2002251881Speter                             svn_boolean_t for_output,
2003251881Speter                             apr_pool_t *pool)
2004251881Speter{
2005251881Speter  svn_error_t *err;
2006251881Speter  const char *val_neol;
2007251881Speter  const char *val_nlocale_neol;
2008251881Speter
2009251881Speter  if (value == NULL)
2010251881Speter    {
2011251881Speter      *new_value = NULL;
2012251881Speter      return SVN_NO_ERROR;
2013251881Speter    }
2014251881Speter
2015251881Speter  SVN_ERR(svn_subst_translate_cstring2(value->data,
2016251881Speter                                       &val_neol,
2017251881Speter                                       APR_EOL_STR,  /* 'native' eol */
2018251881Speter                                       FALSE, /* no repair */
2019251881Speter                                       NULL,  /* no keywords */
2020251881Speter                                       FALSE, /* no expansion */
2021251881Speter                                       pool));
2022251881Speter
2023251881Speter  if (for_output)
2024251881Speter    {
2025251881Speter      err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2026251881Speter      if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2027251881Speter        {
2028251881Speter          val_nlocale_neol =
2029251881Speter            svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool);
2030251881Speter          svn_error_clear(err);
2031251881Speter        }
2032251881Speter      else if (err)
2033251881Speter        return err;
2034251881Speter    }
2035251881Speter  else
2036251881Speter    {
2037251881Speter      err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2038251881Speter      if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2039251881Speter        {
2040251881Speter          val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool);
2041251881Speter          svn_error_clear(err);
2042251881Speter        }
2043251881Speter      else if (err)
2044251881Speter        return err;
2045251881Speter    }
2046251881Speter
2047251881Speter  *new_value = svn_string_create(val_nlocale_neol, pool);
2048251881Speter
2049251881Speter  return SVN_NO_ERROR;
2050251881Speter}
2051