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