Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_subr / subst.c
blob9073da62456b2d1fe19aadd516a87d830e0967c8
1 /*
2 * subst.c : generic eol/keyword substitution routines
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h>
24 #include <stdlib.h>
25 #include <assert.h>
26 #include <apr_pools.h>
27 #include <apr_tables.h>
28 #include <apr_file_io.h>
29 #include <apr_strings.h>
31 #include "svn_cmdline.h"
32 #include "svn_types.h"
33 #include "svn_string.h"
34 #include "svn_time.h"
35 #include "svn_path.h"
36 #include "svn_error.h"
37 #include "svn_utf.h"
38 #include "svn_io.h"
39 #include "svn_subst.h"
40 #include "svn_pools.h"
42 #include "svn_private_config.h"
44 /* The Repository Default EOL used for files which
45 * use the 'native' eol style.
47 #define SVN_SUBST__DEFAULT_EOL_STR "\n"
49 /**
50 * The textual elements of a detranslated special file. One of these
51 * strings must appear as the first element of any special file as it
52 * exists in the repository or the text base.
54 #define SVN_SUBST__SPECIAL_LINK_STR "link"
56 void
57 svn_subst_eol_style_from_value(svn_subst_eol_style_t *style,
58 const char **eol,
59 const char *value)
61 if (value == NULL)
63 /* property doesn't exist. */
64 *eol = NULL;
65 if (style)
66 *style = svn_subst_eol_style_none;
68 else if (! strcmp("native", value))
70 *eol = APR_EOL_STR; /* whee, a portability library! */
71 if (style)
72 *style = svn_subst_eol_style_native;
74 else if (! strcmp("LF", value))
76 *eol = "\n";
77 if (style)
78 *style = svn_subst_eol_style_fixed;
80 else if (! strcmp("CR", value))
82 *eol = "\r";
83 if (style)
84 *style = svn_subst_eol_style_fixed;
86 else if (! strcmp("CRLF", value))
88 *eol = "\r\n";
89 if (style)
90 *style = svn_subst_eol_style_fixed;
92 else
94 *eol = NULL;
95 if (style)
96 *style = svn_subst_eol_style_unknown;
101 svn_boolean_t
102 svn_subst_translation_required(svn_subst_eol_style_t style,
103 const char *eol,
104 apr_hash_t *keywords,
105 svn_boolean_t special,
106 svn_boolean_t force_eol_check)
108 return (special || keywords
109 || (style != svn_subst_eol_style_none && force_eol_check)
110 || (style == svn_subst_eol_style_native &&
111 strcmp(APR_EOL_STR, SVN_SUBST__DEFAULT_EOL_STR) != 0)
112 || (style == svn_subst_eol_style_fixed &&
113 strcmp(APR_EOL_STR, eol) != 0));
117 svn_error_t *
118 svn_subst_translate_to_normal_form(const char *src,
119 const char *dst,
120 svn_subst_eol_style_t eol_style,
121 const char *eol_str,
122 svn_boolean_t always_repair_eols,
123 apr_hash_t *keywords,
124 svn_boolean_t special,
125 apr_pool_t *pool)
128 if (eol_style == svn_subst_eol_style_native)
129 eol_str = SVN_SUBST__DEFAULT_EOL_STR;
130 else if (! (eol_style == svn_subst_eol_style_fixed
131 || eol_style == svn_subst_eol_style_none))
132 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
134 return svn_subst_copy_and_translate3(src, dst, eol_str,
135 eol_style == svn_subst_eol_style_fixed
136 || always_repair_eols,
137 keywords,
138 FALSE /* contract keywords */,
139 special,
140 pool);
143 svn_error_t *
144 svn_subst_stream_translated_to_normal_form(svn_stream_t **stream,
145 svn_stream_t *source,
146 svn_subst_eol_style_t eol_style,
147 const char *eol_str,
148 svn_boolean_t always_repair_eols,
149 apr_hash_t *keywords,
150 apr_pool_t *pool)
152 if (eol_style == svn_subst_eol_style_native)
153 eol_str = SVN_SUBST__DEFAULT_EOL_STR;
154 else if (! (eol_style == svn_subst_eol_style_fixed
155 || eol_style == svn_subst_eol_style_none))
156 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
158 *stream = svn_subst_stream_translated(source, eol_str,
159 eol_style == svn_subst_eol_style_fixed
160 || always_repair_eols,
161 keywords, FALSE, pool);
163 return SVN_NO_ERROR;
167 /* Helper function for svn_subst_build_keywords */
169 /* Given a printf-like format string, return a string with proper
170 * information filled in.
172 * Important API note: This function is the core of the implementation of
173 * svn_subst_build_keywords (all versions), and as such must implement the
174 * tolerance of NULL and zero inputs that that function's documention
175 * stipulates.
177 * The format codes:
179 * %a author of this revision
180 * %b basename of the URL of this file
181 * %d short format of date of this revision
182 * %D long format of date of this revision
183 * %r number of this revision
184 * %u URL of this file
185 * %% a literal %
187 * All memory is allocated out of @a pool.
189 static svn_string_t *
190 keyword_printf(const char *fmt,
191 const char *rev,
192 const char *url,
193 apr_time_t date,
194 const char *author,
195 apr_pool_t *pool)
197 svn_stringbuf_t *value = svn_stringbuf_ncreate("", 0, pool);
198 const char *cur;
199 int n;
201 for (;;)
203 cur = fmt;
205 while (*cur != '\0' && *cur != '%')
206 cur++;
208 if ((n = cur - fmt) > 0) /* Do we have an as-is string? */
209 svn_stringbuf_appendbytes(value, fmt, n);
211 if (*cur == '\0')
212 break;
214 switch (cur[1])
216 case 'a': /* author of this revision */
217 if (author)
218 svn_stringbuf_appendcstr(value, author);
219 break;
220 case 'b': /* basename of this file */
221 if (url)
223 const char *base_name
224 = svn_path_uri_decode(svn_path_basename(url, pool), pool);
225 svn_stringbuf_appendcstr(value, base_name);
227 break;
228 case 'd': /* short format of date of this revision */
229 if (date)
231 apr_time_exp_t exploded_time;
232 const char *human;
234 apr_time_exp_gmt(&exploded_time, date);
236 human = apr_psprintf(pool, "%04d-%02d-%02d %02d:%02d:%02dZ",
237 exploded_time.tm_year + 1900,
238 exploded_time.tm_mon + 1,
239 exploded_time.tm_mday,
240 exploded_time.tm_hour,
241 exploded_time.tm_min,
242 exploded_time.tm_sec);
244 svn_stringbuf_appendcstr(value, human);
246 break;
247 case 'D': /* long format of date of this revision */
248 if (date)
249 svn_stringbuf_appendcstr(value,
250 svn_time_to_human_cstring(date, pool));
251 break;
252 case 'r': /* number of this revision */
253 if (rev)
254 svn_stringbuf_appendcstr(value, rev);
255 break;
256 case 'u': /* URL of this file */
257 if (url)
258 svn_stringbuf_appendcstr(value, url);
259 break;
260 case '%': /* '%%' => a literal % */
261 svn_stringbuf_appendbytes(value, cur, 1);
262 break;
263 case '\0': /* '%' as the last character of the string. */
264 svn_stringbuf_appendbytes(value, cur, 1);
265 /* Now go back one character, since this was just a one character
266 * sequence, whereas all others are two characters, and we do not
267 * want to skip the null terminator entirely and carry on
268 * formatting random memory contents. */
269 cur--;
270 break;
271 default: /* Unrecognized code, just print it literally. */
272 svn_stringbuf_appendbytes(value, cur, 2);
273 break;
276 /* Format code is processed - skip it, and get ready for next chunk. */
277 fmt = cur + 2;
280 return svn_string_create_from_buf(value, pool);
283 /* Convert an old-style svn_subst_keywords_t struct * into a new-style
284 * keywords hash. Keyword values are shallow copies, so the produced
285 * hash must not be assumed to have lifetime longer than the struct it
286 * is based on. A NULL input causes a NULL output. */
287 static apr_hash_t *
288 kwstruct_to_kwhash(const svn_subst_keywords_t *kwstruct,
289 apr_pool_t *pool)
291 apr_hash_t *kwhash;
293 if (kwstruct == NULL)
294 return NULL;
296 kwhash = apr_hash_make(pool);
298 if (kwstruct->revision)
300 apr_hash_set(kwhash, SVN_KEYWORD_REVISION_LONG,
301 APR_HASH_KEY_STRING, kwstruct->revision);
302 apr_hash_set(kwhash, SVN_KEYWORD_REVISION_MEDIUM,
303 APR_HASH_KEY_STRING, kwstruct->revision);
304 apr_hash_set(kwhash, SVN_KEYWORD_REVISION_SHORT,
305 APR_HASH_KEY_STRING, kwstruct->revision);
307 if (kwstruct->date)
309 apr_hash_set(kwhash, SVN_KEYWORD_DATE_LONG,
310 APR_HASH_KEY_STRING, kwstruct->date);
311 apr_hash_set(kwhash, SVN_KEYWORD_DATE_SHORT,
312 APR_HASH_KEY_STRING, kwstruct->date);
314 if (kwstruct->author)
316 apr_hash_set(kwhash, SVN_KEYWORD_AUTHOR_LONG,
317 APR_HASH_KEY_STRING, kwstruct->author);
318 apr_hash_set(kwhash, SVN_KEYWORD_AUTHOR_SHORT,
319 APR_HASH_KEY_STRING, kwstruct->author);
321 if (kwstruct->url)
323 apr_hash_set(kwhash, SVN_KEYWORD_URL_LONG,
324 APR_HASH_KEY_STRING, kwstruct->url);
325 apr_hash_set(kwhash, SVN_KEYWORD_URL_SHORT,
326 APR_HASH_KEY_STRING, kwstruct->url);
328 if (kwstruct->id)
330 apr_hash_set(kwhash, SVN_KEYWORD_ID,
331 APR_HASH_KEY_STRING, kwstruct->id);
334 return kwhash;
337 svn_error_t *
338 svn_subst_build_keywords(svn_subst_keywords_t *kw,
339 const char *keywords_val,
340 const char *rev,
341 const char *url,
342 apr_time_t date,
343 const char *author,
344 apr_pool_t *pool)
346 apr_hash_t *kwhash;
347 const svn_string_t *val;
349 SVN_ERR(svn_subst_build_keywords2(&kwhash, keywords_val, rev,
350 url, date, author, pool));
352 /* The behaviour of pre-1.3 svn_subst_build_keywords, which we are
353 * replicating here, is to write to a slot in the svn_subst_keywords_t
354 * only if the relevant keyword was present in keywords_val, otherwise
355 * leaving that slot untouched. */
357 val = apr_hash_get(kwhash, SVN_KEYWORD_REVISION_LONG, APR_HASH_KEY_STRING);
358 if (val)
359 kw->revision = val;
361 val = apr_hash_get(kwhash, SVN_KEYWORD_DATE_LONG, APR_HASH_KEY_STRING);
362 if (val)
363 kw->date = val;
365 val = apr_hash_get(kwhash, SVN_KEYWORD_AUTHOR_LONG, APR_HASH_KEY_STRING);
366 if (val)
367 kw->author = val;
369 val = apr_hash_get(kwhash, SVN_KEYWORD_URL_LONG, APR_HASH_KEY_STRING);
370 if (val)
371 kw->url = val;
373 val = apr_hash_get(kwhash, SVN_KEYWORD_ID, APR_HASH_KEY_STRING);
374 if (val)
375 kw->id = val;
377 return SVN_NO_ERROR;
380 svn_error_t *
381 svn_subst_build_keywords2(apr_hash_t **kw,
382 const char *keywords_val,
383 const char *rev,
384 const char *url,
385 apr_time_t date,
386 const char *author,
387 apr_pool_t *pool)
389 apr_array_header_t *keyword_tokens;
390 int i;
391 *kw = apr_hash_make(pool);
393 keyword_tokens = svn_cstring_split(keywords_val, " \t\v\n\b\r\f",
394 TRUE /* chop */, pool);
396 for (i = 0; i < keyword_tokens->nelts; ++i)
398 const char *keyword = APR_ARRAY_IDX(keyword_tokens, i, const char *);
400 if ((! strcmp(keyword, SVN_KEYWORD_REVISION_LONG))
401 || (! strcmp(keyword, SVN_KEYWORD_REVISION_MEDIUM))
402 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_REVISION_SHORT)))
404 svn_string_t *revision_val;
406 revision_val = keyword_printf("%r", rev, url, date, author, pool);
407 apr_hash_set(*kw, SVN_KEYWORD_REVISION_LONG,
408 APR_HASH_KEY_STRING, revision_val);
409 apr_hash_set(*kw, SVN_KEYWORD_REVISION_MEDIUM,
410 APR_HASH_KEY_STRING, revision_val);
411 apr_hash_set(*kw, SVN_KEYWORD_REVISION_SHORT,
412 APR_HASH_KEY_STRING, revision_val);
414 else if ((! strcmp(keyword, SVN_KEYWORD_DATE_LONG))
415 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_DATE_SHORT)))
417 svn_string_t *date_val;
419 date_val = keyword_printf("%D", rev, url, date, author, pool);
420 apr_hash_set(*kw, SVN_KEYWORD_DATE_LONG,
421 APR_HASH_KEY_STRING, date_val);
422 apr_hash_set(*kw, SVN_KEYWORD_DATE_SHORT,
423 APR_HASH_KEY_STRING, date_val);
425 else if ((! strcmp(keyword, SVN_KEYWORD_AUTHOR_LONG))
426 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_AUTHOR_SHORT)))
428 svn_string_t *author_val;
430 author_val = keyword_printf("%a", rev, url, date, author, pool);
431 apr_hash_set(*kw, SVN_KEYWORD_AUTHOR_LONG,
432 APR_HASH_KEY_STRING, author_val);
433 apr_hash_set(*kw, SVN_KEYWORD_AUTHOR_SHORT,
434 APR_HASH_KEY_STRING, author_val);
436 else if ((! strcmp(keyword, SVN_KEYWORD_URL_LONG))
437 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_URL_SHORT)))
439 svn_string_t *url_val;
441 url_val = keyword_printf("%u", rev, url, date, author, pool);
442 apr_hash_set(*kw, SVN_KEYWORD_URL_LONG,
443 APR_HASH_KEY_STRING, url_val);
444 apr_hash_set(*kw, SVN_KEYWORD_URL_SHORT,
445 APR_HASH_KEY_STRING, url_val);
447 else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_ID)))
449 svn_string_t *id_val;
451 id_val = keyword_printf("%b %r %d %a", rev, url, date, author,
452 pool);
453 apr_hash_set(*kw, SVN_KEYWORD_ID,
454 APR_HASH_KEY_STRING, id_val);
458 return SVN_NO_ERROR;
462 /*** Helpers for svn_subst_translate_stream2 ***/
465 /* Write out LEN bytes of BUF into STREAM. */
466 static svn_error_t *
467 translate_write(svn_stream_t *stream,
468 const void *buf,
469 apr_size_t len)
471 apr_size_t wrote = len;
472 svn_error_t *write_err = svn_stream_write(stream, buf, &wrote);
473 if ((write_err) || (len != wrote))
474 return write_err;
476 return SVN_NO_ERROR;
480 /* Perform the substition of VALUE into keyword string BUF (with len
481 *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating
482 *LEN to the new size of the substituted result. Return TRUE if all
483 goes well, FALSE otherwise. If VALUE is NULL, keyword will be
484 contracted, else it will be expanded. */
485 static svn_boolean_t
486 translate_keyword_subst(char *buf,
487 apr_size_t *len,
488 const char *keyword,
489 apr_size_t keyword_len,
490 const svn_string_t *value)
492 char *buf_ptr;
494 /* Make sure we gotz good stuffs. */
495 assert(*len <= SVN_KEYWORD_MAX_LEN);
496 assert((buf[0] == '$') && (buf[*len - 1] == '$'));
498 /* Need at least a keyword and two $'s. */
499 if (*len < keyword_len + 2)
500 return FALSE;
502 /* The keyword needs to match what we're looking for. */
503 if (strncmp(buf + 1, keyword, keyword_len))
504 return FALSE;
506 buf_ptr = buf + 1 + keyword_len;
508 /* Check for fixed-length expansion.
509 * The format of fixed length keyword and its data is
510 * Unexpanded keyword: "$keyword:: $"
511 * Expanded keyword: "$keyword:: value $"
512 * Expanded kw with filling: "$keyword:: value $"
513 * Truncated keyword: "$keyword:: longval#$"
515 if ((buf_ptr[0] == ':') /* first char after keyword is ':' */
516 && (buf_ptr[1] == ':') /* second char after keyword is ':' */
517 && (buf_ptr[2] == ' ') /* third char after keyword is ' ' */
518 && ((buf[*len - 2] == ' ') /* has ' ' for next to last character */
519 || (buf[*len - 2] == '#')) /* .. or has '#' for next to last
520 character */
521 && ((6 + keyword_len) < *len)) /* holds "$kw:: x $" at least */
523 /* This is fixed length keyword, so *len remains unchanged */
524 apr_size_t max_value_len = *len - (6 + keyword_len);
526 if (! value)
528 /* no value, so unexpand */
529 buf_ptr += 2;
530 while (*buf_ptr != '$')
531 *(buf_ptr++) = ' ';
533 else
535 if (value->len <= max_value_len)
536 { /* replacement not as long as template, pad with spaces */
537 strncpy(buf_ptr + 3, value->data, value->len);
538 buf_ptr += 3 + value->len;
539 while (*buf_ptr != '$')
540 *(buf_ptr++) = ' ';
542 else
544 /* replacement needs truncating */
545 strncpy(buf_ptr + 3, value->data, max_value_len);
546 buf[*len - 2] = '#';
547 buf[*len - 1] = '$';
550 return TRUE;
553 /* Check for unexpanded keyword. */
554 else if (buf_ptr[0] == '$') /* "$keyword$" */
556 /* unexpanded... */
557 if (value)
559 /* ...so expand. */
560 buf_ptr[0] = ':';
561 buf_ptr[1] = ' ';
562 if (value->len)
564 apr_size_t vallen = value->len;
566 /* "$keyword: value $" */
567 if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
568 vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
569 strncpy(buf_ptr + 2, value->data, vallen);
570 buf_ptr[2 + vallen] = ' ';
571 buf_ptr[2 + vallen + 1] = '$';
572 *len = 5 + keyword_len + vallen;
574 else
576 /* "$keyword: $" */
577 buf_ptr[2] = '$';
578 *len = 4 + keyword_len;
581 else
583 /* ...but do nothing. */
585 return TRUE;
588 /* Check for expanded keyword. */
589 else if (((*len >= 4 + keyword_len ) /* holds at least "$keyword: $" */
590 && (buf_ptr[0] == ':') /* first char after keyword is ':' */
591 && (buf_ptr[1] == ' ') /* second char after keyword is ' ' */
592 && (buf[*len - 2] == ' '))
593 || ((*len >= 3 + keyword_len ) /* holds at least "$keyword:$" */
594 && (buf_ptr[0] == ':') /* first char after keyword is ':' */
595 && (buf_ptr[1] == '$'))) /* second char after keyword is '$' */
597 /* expanded... */
598 if (! value)
600 /* ...so unexpand. */
601 buf_ptr[0] = '$';
602 *len = 2 + keyword_len;
604 else
606 /* ...so re-expand. */
607 buf_ptr[0] = ':';
608 buf_ptr[1] = ' ';
609 if (value->len)
611 apr_size_t vallen = value->len;
613 /* "$keyword: value $" */
614 if (vallen > (SVN_KEYWORD_MAX_LEN - 5))
615 vallen = SVN_KEYWORD_MAX_LEN - 5;
616 strncpy(buf_ptr + 2, value->data, vallen);
617 buf_ptr[2 + vallen] = ' ';
618 buf_ptr[2 + vallen + 1] = '$';
619 *len = 5 + keyword_len + vallen;
621 else
623 /* "$keyword: $" */
624 buf_ptr[2] = '$';
625 *len = 4 + keyword_len;
628 return TRUE;
631 return FALSE;
634 /* Parse BUF (whose length is LEN, and which starts and ends with '$'),
635 trying to match one of the keyword names in KEYWORDS. If such a
636 keyword is found, update *KEYWORD_NAME with the keyword name and
637 return TRUE. */
638 static svn_boolean_t
639 match_keyword(char *buf,
640 apr_size_t len,
641 char *keyword_name,
642 apr_hash_t *keywords)
644 apr_size_t i;
646 /* Early return for ignored keywords */
647 if (! keywords)
648 return FALSE;
650 /* Extract the name of the keyword */
651 for (i = 0; i < len - 2 && buf[i + 1] != ':'; i++)
652 keyword_name[i] = buf[i + 1];
653 keyword_name[i] = '\0';
655 return apr_hash_get(keywords, keyword_name, APR_HASH_KEY_STRING) != NULL;
658 /* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN):
659 optionally perform the substitution in place, update *LEN with
660 the new length of the translated keyword string, and return TRUE.
661 If this buffer doesn't contain a known keyword pattern, leave BUF
662 and *LEN untouched and return FALSE.
664 See the docstring for svn_subst_copy_and_translate for how the
665 EXPAND and KEYWORDS parameters work.
667 NOTE: It is assumed that BUF has been allocated to be at least
668 SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less
669 than or equal SVN_KEYWORD_MAX_LEN in length. Also, any expansions
670 which would result in a keyword string which is greater than
671 SVN_KEYWORD_MAX_LEN will have their values truncated in such a way
672 that the resultant keyword string is still valid (begins with
673 "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long). */
674 static svn_boolean_t
675 translate_keyword(char *buf,
676 apr_size_t *len,
677 const char *keyword_name,
678 svn_boolean_t expand,
679 apr_hash_t *keywords)
681 const svn_string_t *value;
683 /* Make sure we gotz good stuffs. */
684 assert(*len <= SVN_KEYWORD_MAX_LEN);
685 assert((buf[0] == '$') && (buf[*len - 1] == '$'));
687 /* Early return for ignored keywords */
688 if (! keywords)
689 return FALSE;
691 value = apr_hash_get(keywords, keyword_name, APR_HASH_KEY_STRING);
693 if (value)
695 return translate_keyword_subst(buf, len,
696 keyword_name, strlen(keyword_name),
697 expand ? value : NULL);
700 return FALSE;
704 /* Translate NEWLINE_BUF (length of NEWLINE_LEN) to the newline format
705 specified in EOL_STR (length of EOL_STR_LEN), and write the
706 translated thing to FILE (whose path is DST_PATH).
708 SRC_FORMAT (length *SRC_FORMAT_LEN) is a cache of the first newline
709 found while processing SRC_PATH. If the current newline is not the
710 same style as that of SRC_FORMAT, look to the REPAIR parameter. If
711 REPAIR is TRUE, ignore the inconsistency, else return an
712 SVN_ERR_IO_INCONSISTENT_EOL error. If we are examining the first
713 newline in the file, copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to
714 use for later consistency checks. */
715 static svn_error_t *
716 translate_newline(const char *eol_str,
717 apr_size_t eol_str_len,
718 char *src_format,
719 apr_size_t *src_format_len,
720 char *newline_buf,
721 apr_size_t newline_len,
722 svn_stream_t *dst,
723 svn_boolean_t repair)
725 /* If this is the first newline we've seen, cache it
726 future comparisons, else compare it with our cache to
727 check for consistency. */
728 if (*src_format_len)
730 /* Comparing with cache. If we are inconsistent and
731 we are NOT repairing the file, generate an error! */
732 if ((! repair) &&
733 ((*src_format_len != newline_len) ||
734 (strncmp(src_format, newline_buf, newline_len))))
735 return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL, NULL, NULL);
737 else
739 /* This is our first line ending, so cache it before
740 handling it. */
741 strncpy(src_format, newline_buf, newline_len);
742 *src_format_len = newline_len;
744 /* Translate the newline */
745 return translate_write(dst, eol_str, eol_str_len);
750 /*** Public interfaces. ***/
752 svn_boolean_t
753 svn_subst_keywords_differ(const svn_subst_keywords_t *a,
754 const svn_subst_keywords_t *b,
755 svn_boolean_t compare_values)
757 if (((a == NULL) && (b == NULL)) /* no A or B */
758 /* no A, and B has no contents */
759 || ((a == NULL)
760 && (b->revision == NULL)
761 && (b->date == NULL)
762 && (b->author == NULL)
763 && (b->url == NULL))
764 /* no B, and A has no contents */
765 || ((b == NULL) && (a->revision == NULL)
766 && (a->date == NULL)
767 && (a->author == NULL)
768 && (a->url == NULL))
769 /* neither A nor B has any contents */
770 || ((a != NULL) && (b != NULL)
771 && (b->revision == NULL)
772 && (b->date == NULL)
773 && (b->author == NULL)
774 && (b->url == NULL)
775 && (a->revision == NULL)
776 && (a->date == NULL)
777 && (a->author == NULL)
778 && (a->url == NULL)))
780 return FALSE;
782 else if ((a == NULL) || (b == NULL))
783 return TRUE;
785 /* Else both A and B have some keywords. */
787 if ((! a->revision) != (! b->revision))
788 return TRUE;
789 else if ((compare_values && (a->revision != NULL))
790 && (strcmp(a->revision->data, b->revision->data) != 0))
791 return TRUE;
793 if ((! a->date) != (! b->date))
794 return TRUE;
795 else if ((compare_values && (a->date != NULL))
796 && (strcmp(a->date->data, b->date->data) != 0))
797 return TRUE;
799 if ((! a->author) != (! b->author))
800 return TRUE;
801 else if ((compare_values && (a->author != NULL))
802 && (strcmp(a->author->data, b->author->data) != 0))
803 return TRUE;
805 if ((! a->url) != (! b->url))
806 return TRUE;
807 else if ((compare_values && (a->url != NULL))
808 && (strcmp(a->url->data, b->url->data) != 0))
809 return TRUE;
811 /* Else we never found a difference, so they must be the same. */
813 return FALSE;
816 svn_boolean_t
817 svn_subst_keywords_differ2(apr_hash_t *a,
818 apr_hash_t *b,
819 svn_boolean_t compare_values,
820 apr_pool_t *pool)
822 apr_hash_index_t *hi;
823 unsigned int a_count, b_count;
825 /* An empty hash is logically equal to a NULL,
826 * as far as this API is concerned. */
827 a_count = (a == NULL) ? 0 : apr_hash_count(a);
828 b_count = (b == NULL) ? 0 : apr_hash_count(b);
830 if (a_count != b_count)
831 return TRUE;
833 if (a_count == 0)
834 return FALSE;
836 /* The hashes are both non-NULL, and have the same number of items.
837 * We must check that every item of A is present in B. */
838 for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi))
840 const void *key;
841 apr_ssize_t klen;
842 void *void_a_val;
843 svn_string_t *a_val, *b_val;
845 apr_hash_this(hi, &key, &klen, &void_a_val);
846 a_val = void_a_val;
847 b_val = apr_hash_get(b, key, klen);
849 if (!b_val || (compare_values && !svn_string_compare(a_val, b_val)))
850 return TRUE;
853 return FALSE;
856 svn_error_t *
857 svn_subst_translate_stream2(svn_stream_t *s, /* src stream */
858 svn_stream_t *d, /* dst stream */
859 const char *eol_str,
860 svn_boolean_t repair,
861 const svn_subst_keywords_t *keywords,
862 svn_boolean_t expand,
863 apr_pool_t *pool)
865 apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool);
867 return svn_subst_translate_stream3(s, d, eol_str, repair, kh, expand, pool);
870 /* Baton for translate_chunk() to store its state in. */
871 struct translation_baton
873 const char *eol_str;
874 svn_boolean_t repair;
875 apr_hash_t *keywords;
876 svn_boolean_t expand;
878 /* Characters (excluding the terminating NUL character) which
879 may trigger a translation action, hence are 'interesting' */
880 const char *interesting;
882 /* Length of the string EOL_STR points to. */
883 apr_size_t eol_str_len;
885 /* Buffer to cache any newline state between translation chunks */
886 char newline_buf[2];
888 /* Offset (within newline_buf) of the first *unused* character */
889 apr_size_t newline_off;
891 /* Buffer to cache keyword-parsing state between translation chunks */
892 char keyword_buf[SVN_KEYWORD_MAX_LEN];
894 /* Offset (within keyword-buf) to the first *unused* character */
895 apr_size_t keyword_off;
897 /* EOL style used in the chunk-source */
898 char src_format[2];
900 /* Length of the EOL style string found in the chunk-source,
901 or zero if none encountered yet */
902 apr_size_t src_format_len;
906 /* Allocate a baton for use with translate_chunk() in POOL and
907 * initialize it for the first iteration.
909 * The caller must assure that EOL_STR and KEYWORDS at least
910 * have the same life time as that of POOL.
914 static struct translation_baton *
915 create_translation_baton(const char *eol_str,
916 svn_boolean_t repair,
917 apr_hash_t *keywords,
918 svn_boolean_t expand,
919 apr_pool_t *pool)
921 struct translation_baton *b = apr_palloc(pool, sizeof(*b));
923 /* For efficiency, convert an empty set of keywords to NULL. */
924 if (keywords && (apr_hash_count(keywords) == 0))
925 keywords = NULL;
927 b->eol_str = eol_str;
928 b->eol_str_len = eol_str ? strlen(eol_str) : 0;
929 b->repair = repair;
930 b->keywords = keywords;
931 b->expand = expand;
932 b->interesting = (eol_str && keywords) ? "$\r\n" : eol_str ? "\r\n" : "$";
933 b->newline_off = 0;
934 b->keyword_off = 0;
935 b->src_format_len = 0;
937 return b;
940 /* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN
941 * according to the settings and state stored in baton B.
943 * Write output to stream DST.
945 * To finish a series of chunk translations, flush all buffers by calling
946 * this routine with a NULL value for BUF.
948 * Use POOL for temporary allocations.
950 static svn_error_t *
951 translate_chunk(svn_stream_t *dst,
952 struct translation_baton *b,
953 const char *buf,
954 apr_size_t buflen,
955 apr_pool_t *pool)
957 const char *p;
958 apr_size_t len;
960 if (buf)
962 /* precalculate some oft-used values */
963 const char *end = buf + buflen;
964 const char *interesting = b->interesting;
965 apr_size_t next_sign_off = 0;
967 /* At the beginning of this loop, assume that we might be in an
968 * interesting state, i.e. with data in the newline or keyword
969 * buffer. First try to get to the boring state so we can copy
970 * a run of boring characters; then try to get back to the
971 * interesting state by processing an interesting character,
972 * and repeat. */
973 for (p = buf; p < end;)
975 /* Try to get to the boring state, if necessary. */
976 if (b->newline_off)
978 if (*p == '\n')
979 b->newline_buf[b->newline_off++] = *p++;
981 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
982 b->src_format,
983 &b->src_format_len, b->newline_buf,
984 b->newline_off, dst, b->repair));
986 b->newline_off = 0;
988 else if (b->keyword_off && *p == '$')
990 svn_boolean_t keyword_matches;
991 char keyword_name[SVN_KEYWORD_MAX_LEN + 1];
993 /* If keyword is matched, but not correctly translated, try to
994 * look for the next ending '$'. */
995 b->keyword_buf[b->keyword_off++] = *p++;
996 keyword_matches = match_keyword(b->keyword_buf, b->keyword_off,
997 keyword_name, b->keywords);
998 if (keyword_matches == FALSE)
1000 /* reuse the ending '$' */
1001 p--;
1002 b->keyword_off--;
1005 if (keyword_matches == FALSE ||
1006 translate_keyword(b->keyword_buf, &b->keyword_off,
1007 keyword_name, b->expand, b->keywords) ||
1008 b->keyword_off >= SVN_KEYWORD_MAX_LEN)
1010 /* write out non-matching text or translated keyword */
1011 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1013 next_sign_off = 0;
1014 b->keyword_off = 0;
1016 else
1018 if (next_sign_off == 0)
1019 next_sign_off = b->keyword_off - 1;
1021 continue;
1024 else if (b->keyword_off == SVN_KEYWORD_MAX_LEN - 1
1025 || (b->keyword_off && (*p == '\r' || *p == '\n')))
1027 if (next_sign_off > 0)
1029 /* rolling back, continue with next '$' in keyword_buf */
1030 p -= (b->keyword_off - next_sign_off);
1031 b->keyword_off = next_sign_off;
1032 next_sign_off = 0;
1034 /* No closing '$' found; flush the keyword buffer. */
1035 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1037 b->keyword_off = 0;
1039 else if (b->keyword_off)
1041 b->keyword_buf[b->keyword_off++] = *p++;
1042 continue;
1045 /* We're in the boring state; look for interest characters. */
1046 len = 0;
1048 /* We wanted memcspn(), but lacking that, the loop below has
1049 the same effect.
1051 Also, skip NUL characters explicitly, since strchr()
1052 considers them part of the string argument,
1053 but we don't consider them interesting
1055 while ((p + len) < end
1056 && (! p[len] || ! strchr(interesting, p[len])))
1057 len++;
1059 if (len)
1060 SVN_ERR(translate_write(dst, p, len));
1062 p += len;
1064 /* Set up state according to the interesting character, if any. */
1065 if (p < end)
1067 switch (*p)
1069 case '$':
1070 b->keyword_buf[b->keyword_off++] = *p++;
1071 break;
1072 case '\r':
1073 b->newline_buf[b->newline_off++] = *p++;
1074 break;
1075 case '\n':
1076 b->newline_buf[b->newline_off++] = *p++;
1078 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1079 b->src_format,
1080 &b->src_format_len,
1081 b->newline_buf,
1082 b->newline_off, dst, b->repair));
1084 b->newline_off = 0;
1085 break;
1091 else
1093 if (b->newline_off)
1095 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1096 b->src_format, &b->src_format_len,
1097 b->newline_buf, b->newline_off,
1098 dst, b->repair));
1099 b->newline_off = 0;
1102 if (b->keyword_off)
1104 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1105 b->keyword_off = 0;
1109 return SVN_NO_ERROR;
1112 /* Baton for use with translated stream callbacks. */
1113 struct translated_stream_baton
1115 /* Stream to take input from (before translation) on read
1116 /write output to (after translation) on write. */
1117 svn_stream_t *stream;
1119 /* Input/Output translation batons to make them separate chunk streams. */
1120 struct translation_baton *in_baton, *out_baton;
1122 /* Remembers whether any write operations have taken place;
1123 if so, we need to flush the output chunk stream. */
1124 svn_boolean_t written;
1126 /* Buffer to hold translated read data. */
1127 svn_stringbuf_t *readbuf;
1129 /* Offset of the first non-read character in readbuf. */
1130 apr_size_t readbuf_off;
1132 /* Buffer to hold read data
1133 between svn_stream_read() and translate_chunk(). */
1134 char *buf;
1136 /* Pool in which (only!) this baton is allocated. */
1137 apr_pool_t *pool;
1139 /* Pool for callback iterations */
1140 apr_pool_t *iterpool;
1144 static svn_error_t *
1145 translated_stream_read(void *baton,
1146 char *buffer,
1147 apr_size_t *len)
1149 struct translated_stream_baton *b = baton;
1150 apr_size_t readlen = SVN__STREAM_CHUNK_SIZE;
1151 apr_size_t unsatisfied = *len;
1152 apr_size_t off = 0;
1153 apr_pool_t *iterpool;
1155 iterpool = b->iterpool;
1156 while (readlen == SVN__STREAM_CHUNK_SIZE && unsatisfied > 0)
1158 apr_size_t to_copy;
1159 apr_size_t buffer_remainder;
1161 svn_pool_clear(iterpool);
1162 /* fill read buffer, if necessary */
1163 if (! (b->readbuf_off < b->readbuf->len))
1165 svn_stream_t *buf_stream;
1167 svn_stringbuf_setempty(b->readbuf);
1168 b->readbuf_off = 0;
1169 SVN_ERR(svn_stream_read(b->stream, b->buf, &readlen));
1170 buf_stream = svn_stream_from_stringbuf(b->readbuf, iterpool);
1172 SVN_ERR(translate_chunk(buf_stream, b->in_baton, b->buf,
1173 readlen, iterpool));
1175 if (readlen != SVN__STREAM_CHUNK_SIZE)
1176 SVN_ERR(translate_chunk(buf_stream, b->in_baton, NULL, 0,
1177 iterpool));
1179 SVN_ERR(svn_stream_close(buf_stream));
1182 /* Satisfy from the read buffer */
1183 buffer_remainder = b->readbuf->len - b->readbuf_off;
1184 to_copy = (buffer_remainder > unsatisfied)
1185 ? unsatisfied : buffer_remainder;
1186 memcpy(buffer + off, b->readbuf->data + b->readbuf_off, to_copy);
1187 off += to_copy;
1188 b->readbuf_off += to_copy;
1189 unsatisfied -= to_copy;
1192 *len -= unsatisfied;
1194 return SVN_NO_ERROR;
1197 static svn_error_t *
1198 translated_stream_write(void *baton,
1199 const char *buffer,
1200 apr_size_t *len)
1202 struct translated_stream_baton *b = baton;
1203 svn_pool_clear(b->iterpool);
1205 b->written = TRUE;
1206 SVN_ERR(translate_chunk(b->stream, b->out_baton, buffer, *len,
1207 b->iterpool));
1209 return SVN_NO_ERROR;
1212 static svn_error_t *
1213 translated_stream_close(void *baton)
1215 struct translated_stream_baton *b = baton;
1217 if (b->written)
1218 SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool));
1220 SVN_ERR(svn_stream_close(b->stream));
1222 svn_pool_destroy(b->pool); /* Also destroys the baton itself */
1223 return SVN_NO_ERROR;
1226 /* Given a special file at SRC, set TRANSLATED_STREAM_P to a stream
1227 with the textual representation of it. Perform all allocations in POOL. */
1228 static svn_error_t *
1229 detranslated_stream_special(svn_stream_t **translated_stream_p,
1230 const char *src,
1231 apr_pool_t *pool)
1233 apr_finfo_t finfo;
1234 apr_file_t *s;
1235 svn_string_t *buf;
1236 svn_stringbuf_t *strbuf;
1238 /* First determine what type of special file we are
1239 detranslating. */
1240 SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_MIN | APR_FINFO_LINK, pool));
1242 switch (finfo.filetype) {
1243 case APR_REG:
1244 /* Nothing special to do here, just create stream from the original
1245 file's contents. */
1246 SVN_ERR(svn_io_file_open(&s, src, APR_READ | APR_BUFFERED,
1247 APR_OS_DEFAULT, pool));
1248 *translated_stream_p = svn_stream_from_aprfile2(s, FALSE, pool);
1250 break;
1251 case APR_LNK:
1252 /* Determine the destination of the link. */
1253 SVN_ERR(svn_io_read_link(&buf, src, pool));
1254 strbuf = svn_stringbuf_createf(pool, "link %s", buf->data);
1255 *translated_stream_p = svn_stream_from_stringbuf(strbuf, pool);
1257 break;
1258 default:
1259 abort();
1262 return SVN_NO_ERROR;
1265 svn_error_t *
1266 svn_subst_stream_detranslated(svn_stream_t **stream_p,
1267 const char *src,
1268 svn_subst_eol_style_t eol_style,
1269 const char *eol_str,
1270 svn_boolean_t always_repair_eols,
1271 apr_hash_t *keywords,
1272 svn_boolean_t special,
1273 apr_pool_t *pool)
1275 apr_file_t *file_h;
1276 svn_stream_t *src_stream;
1278 if (special)
1279 return detranslated_stream_special(stream_p, src, pool);
1281 if (eol_style == svn_subst_eol_style_native)
1282 eol_str = SVN_SUBST__DEFAULT_EOL_STR;
1283 else if (! (eol_style == svn_subst_eol_style_fixed
1284 || eol_style == svn_subst_eol_style_none))
1285 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
1287 SVN_ERR(svn_io_file_open(&file_h, src, APR_READ,
1288 APR_OS_DEFAULT, pool));
1290 src_stream = svn_stream_from_aprfile2(file_h, FALSE, pool);
1292 *stream_p = svn_subst_stream_translated(
1293 src_stream, eol_str,
1294 eol_style == svn_subst_eol_style_fixed || always_repair_eols,
1295 keywords, FALSE, pool);
1297 return SVN_NO_ERROR;
1300 svn_stream_t *
1301 svn_subst_stream_translated(svn_stream_t *stream,
1302 const char *eol_str,
1303 svn_boolean_t repair,
1304 apr_hash_t *keywords,
1305 svn_boolean_t expand,
1306 apr_pool_t *pool)
1308 apr_pool_t *baton_pool = svn_pool_create(pool);
1309 struct translated_stream_baton *baton
1310 = apr_palloc(baton_pool, sizeof(*baton));
1311 svn_stream_t *s = svn_stream_create(baton, baton_pool);
1313 /* Make sure EOL_STR and KEYWORDS are allocated in POOL, as
1314 required by create_translation_baton() */
1315 if (eol_str)
1316 eol_str = apr_pstrdup(baton_pool, eol_str);
1317 if (keywords)
1319 if (apr_hash_count(keywords) == 0)
1320 keywords = NULL;
1321 else
1323 /* deep copy the hash to make sure it's allocated in POOL */
1324 apr_hash_t *copy = apr_hash_make(baton_pool);
1325 apr_hash_index_t *hi;
1327 for (hi = apr_hash_first(pool, keywords);
1328 hi; hi = apr_hash_next(hi))
1330 const void *key;
1331 void *val;
1332 apr_hash_this(hi, &key, NULL, &val);
1333 apr_hash_set(copy, apr_pstrdup(baton_pool, key),
1334 APR_HASH_KEY_STRING,
1335 svn_string_dup(val, baton_pool));
1338 keywords = copy;
1342 /* Setup the baton fields */
1343 baton->stream = stream;
1344 baton->in_baton
1345 = create_translation_baton(eol_str, repair, keywords, expand, baton_pool);
1346 baton->out_baton
1347 = create_translation_baton(eol_str, repair, keywords, expand, baton_pool);
1348 baton->written = FALSE;
1349 baton->readbuf = svn_stringbuf_create("", baton_pool);
1350 baton->readbuf_off = 0;
1351 baton->iterpool = svn_pool_create(baton_pool);
1352 baton->pool = baton_pool;
1353 baton->buf = apr_palloc(baton->pool, SVN__STREAM_CHUNK_SIZE + 1);
1355 /* Setup the stream methods */
1356 svn_stream_set_read(s, translated_stream_read);
1357 svn_stream_set_write(s, translated_stream_write);
1358 svn_stream_set_close(s, translated_stream_close);
1360 return s;
1364 svn_error_t *
1365 svn_subst_translate_stream3(svn_stream_t *s, /* src stream */
1366 svn_stream_t *d, /* dst stream */
1367 const char *eol_str,
1368 svn_boolean_t repair,
1369 apr_hash_t *keywords,
1370 svn_boolean_t expand,
1371 apr_pool_t *pool)
1373 apr_pool_t *subpool = svn_pool_create(pool);
1374 apr_pool_t *iterpool = svn_pool_create(subpool);
1375 struct translation_baton *baton;
1376 apr_size_t readlen = SVN__STREAM_CHUNK_SIZE;
1377 char *buf = apr_palloc(subpool, SVN__STREAM_CHUNK_SIZE);
1379 /* The docstring requires that *some* translation be requested. */
1380 assert(eol_str || keywords);
1382 baton = create_translation_baton(eol_str, repair, keywords, expand, pool);
1383 while (readlen == SVN__STREAM_CHUNK_SIZE)
1385 svn_pool_clear(iterpool);
1386 SVN_ERR(svn_stream_read(s, buf, &readlen));
1387 SVN_ERR(translate_chunk(d, baton, buf, readlen, iterpool));
1390 SVN_ERR(translate_chunk(d, baton, NULL, 0, iterpool));
1392 svn_pool_destroy(subpool); /* also destroys iterpool */
1393 return SVN_NO_ERROR;
1397 svn_error_t *
1398 svn_subst_translate_stream(svn_stream_t *s, /* src stream */
1399 svn_stream_t *d, /* dst stream */
1400 const char *eol_str,
1401 svn_boolean_t repair,
1402 const svn_subst_keywords_t *keywords,
1403 svn_boolean_t expand)
1405 apr_pool_t *pool = svn_pool_create(NULL);
1406 svn_error_t *err = svn_subst_translate_stream2(s, d, eol_str, repair,
1407 keywords, expand, pool);
1408 svn_pool_destroy(pool);
1409 return err;
1413 svn_error_t *
1414 svn_subst_translate_cstring(const char *src,
1415 const char **dst,
1416 const char *eol_str,
1417 svn_boolean_t repair,
1418 const svn_subst_keywords_t *keywords,
1419 svn_boolean_t expand,
1420 apr_pool_t *pool)
1422 apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool);
1424 return svn_subst_translate_cstring2(src, dst, eol_str, repair,
1425 kh, expand, pool);
1428 svn_error_t *
1429 svn_subst_translate_cstring2(const char *src,
1430 const char **dst,
1431 const char *eol_str,
1432 svn_boolean_t repair,
1433 apr_hash_t *keywords,
1434 svn_boolean_t expand,
1435 apr_pool_t *pool)
1437 svn_stringbuf_t *src_stringbuf, *dst_stringbuf;
1438 svn_stream_t *src_stream, *dst_stream;
1439 svn_error_t *err;
1441 src_stringbuf = svn_stringbuf_create(src, pool);
1443 /* The easy way out: no translation needed, just copy. */
1444 if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1446 dst_stringbuf = svn_stringbuf_dup(src_stringbuf, pool);
1447 goto all_good;
1450 /* Convert our stringbufs into streams. */
1451 src_stream = svn_stream_from_stringbuf(src_stringbuf, pool);
1452 dst_stringbuf = svn_stringbuf_create("", pool);
1453 dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool);
1455 /* Translate src stream into dst stream. */
1456 err = svn_subst_translate_stream3(src_stream, dst_stream,
1457 eol_str, repair, keywords, expand, pool);
1458 if (err)
1460 svn_error_clear(svn_stream_close(src_stream));
1461 svn_error_clear(svn_stream_close(dst_stream));
1462 return err;
1465 /* clean up nicely. */
1466 SVN_ERR(svn_stream_close(src_stream));
1467 SVN_ERR(svn_stream_close(dst_stream));
1469 all_good:
1470 *dst = dst_stringbuf->data;
1471 return SVN_NO_ERROR;
1475 svn_error_t *
1476 svn_subst_copy_and_translate(const char *src,
1477 const char *dst,
1478 const char *eol_str,
1479 svn_boolean_t repair,
1480 const svn_subst_keywords_t *keywords,
1481 svn_boolean_t expand,
1482 apr_pool_t *pool)
1484 return svn_subst_copy_and_translate2(src, dst, eol_str, repair, keywords,
1485 expand, FALSE, pool);
1489 /* Set SRC_STREAM to a stream from which the internal representation
1490 * for the special file at SRC can be read.
1492 * The stream returned will be allocated in POOL.
1494 static svn_error_t *
1495 detranslate_special_file_to_stream(svn_stream_t **src_stream,
1496 const char *src,
1497 apr_pool_t *pool)
1499 apr_finfo_t finfo;
1500 apr_file_t *s;
1501 svn_string_t *buf;
1503 /* First determine what type of special file we are
1504 detranslating. */
1505 SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_MIN | APR_FINFO_LINK, pool));
1507 switch (finfo.filetype) {
1508 case APR_REG:
1509 /* Nothing special to do here, just copy the original file's
1510 contents. */
1511 SVN_ERR(svn_io_file_open(&s, src, APR_READ | APR_BUFFERED,
1512 APR_OS_DEFAULT, pool));
1513 *src_stream = svn_stream_from_aprfile(s, pool);
1515 break;
1516 case APR_LNK:
1517 /* Determine the destination of the link. */
1519 *src_stream = svn_stream_from_stringbuf(svn_stringbuf_create ("", pool),
1520 pool);
1521 SVN_ERR(svn_io_read_link(&buf, src, pool));
1523 SVN_ERR(svn_stream_printf(*src_stream, pool, "link %s",
1524 buf->data));
1525 break;
1526 default:
1527 abort ();
1530 return SVN_NO_ERROR;
1533 /* Given a special file at SRC, generate a textual representation of
1534 it in a normal file at DST. Perform all allocations in POOL. */
1535 static svn_error_t *
1536 detranslate_special_file(const char *src, const char *dst, apr_pool_t *pool)
1538 const char *dst_tmp;
1539 apr_file_t *d;
1540 svn_stream_t *src_stream, *dst_stream;
1543 /* Open a temporary destination that we will eventually atomically
1544 rename into place. */
1545 SVN_ERR(svn_io_open_unique_file2(&d, &dst_tmp, dst,
1546 ".tmp", svn_io_file_del_none, pool));
1548 dst_stream = svn_stream_from_aprfile2(d, FALSE, pool);
1550 SVN_ERR(detranslate_special_file_to_stream(&src_stream, src, pool));
1551 SVN_ERR(svn_stream_copy(src_stream, dst_stream, pool));
1553 SVN_ERR(svn_stream_close(dst_stream));
1555 /* Do the atomic rename from our temporary location. */
1556 SVN_ERR(svn_io_file_rename(dst_tmp, dst, pool));
1558 return SVN_NO_ERROR;
1561 /* Creates a special file DST from the internal representation given
1562 * in SRC.
1564 * All temporary allocations will be done in POOL.
1566 static svn_error_t *
1567 create_special_file_from_stringbuf(svn_stringbuf_t *src, const char *dst,
1568 apr_pool_t *pool)
1570 char *identifier, *remainder;
1571 const char *dst_tmp;
1572 svn_boolean_t create_using_internal_representation = FALSE;
1574 /* Separate off the identifier. The first space character delimits
1575 the identifier, after which any remaining characters are specific
1576 to the actual special file type being created. */
1577 identifier = src->data;
1578 for (remainder = identifier; *remainder; remainder++)
1580 if (*remainder == ' ')
1582 remainder++;
1583 break;
1587 if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ",
1588 strlen(SVN_SUBST__SPECIAL_LINK_STR " ")))
1590 /* For symlinks, the type specific data is just a filesystem
1591 path that the symlink should reference. */
1592 svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder,
1593 ".tmp", pool);
1595 /* If we had an error, check to see if it was because symlinks are
1596 not supported on the platform. If so, fall back
1597 to using the internal representation. */
1598 if (err)
1600 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
1602 svn_error_clear(err);
1603 create_using_internal_representation = TRUE;
1605 else
1606 return err;
1609 else
1611 /* Just create a normal file using the internal special file
1612 representation. We don't want a commit of an unknown special
1613 file type to DoS all the clients. */
1614 create_using_internal_representation = TRUE;
1617 /* If nothing else worked, write out the internal representation to
1618 a file that can be edited by the user. */
1619 if (create_using_internal_representation)
1621 apr_file_t *dst_tmp_file;
1622 apr_size_t written;
1625 SVN_ERR(svn_io_open_unique_file2(&dst_tmp_file, &dst_tmp,
1626 dst, ".tmp", svn_io_file_del_none,
1627 pool));
1628 SVN_ERR(svn_io_file_write_full(dst_tmp_file, src->data, src->len,
1629 &written, pool));
1630 SVN_ERR(svn_io_file_close(dst_tmp_file, pool));
1633 /* Do the atomic rename from our temporary location. */
1634 return svn_io_file_rename(dst_tmp, dst, pool);
1637 /* Given a file containing a repository representation of a special
1638 file in SRC, create the appropriate special file at location DST.
1639 Perform all allocations in POOL. */
1640 static svn_error_t *
1641 create_special_file(const char *src, const char *dst, apr_pool_t *pool)
1643 svn_stringbuf_t *contents;
1644 svn_node_kind_t kind;
1645 svn_boolean_t is_special;
1646 svn_stream_t *source;
1648 /* Check to see if we are being asked to create a special file from
1649 a special file. If so, do a temporary detranslation and work
1650 from there. */
1651 SVN_ERR(svn_io_check_special_path(src, &kind, &is_special, pool));
1653 if (is_special)
1655 svn_boolean_t eof;
1657 SVN_ERR(detranslate_special_file_to_stream(&source, src, pool));
1658 /* The special file normal form doesn't have line endings,
1659 * so, read all of the file into the stringbuf */
1660 SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool));
1662 else
1663 /* Read in the detranslated file. */
1664 SVN_ERR(svn_stringbuf_from_file(&contents, src, pool));
1666 return create_special_file_from_stringbuf(contents, dst, pool);
1670 svn_error_t *
1671 svn_subst_copy_and_translate2(const char *src,
1672 const char *dst,
1673 const char *eol_str,
1674 svn_boolean_t repair,
1675 const svn_subst_keywords_t *keywords,
1676 svn_boolean_t expand,
1677 svn_boolean_t special,
1678 apr_pool_t *pool)
1680 apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool);
1682 return svn_subst_copy_and_translate3(src, dst, eol_str,
1683 repair, kh, expand, special,
1684 pool);
1687 svn_error_t *
1688 svn_subst_copy_and_translate3(const char *src,
1689 const char *dst,
1690 const char *eol_str,
1691 svn_boolean_t repair,
1692 apr_hash_t *keywords,
1693 svn_boolean_t expand,
1694 svn_boolean_t special,
1695 apr_pool_t *pool)
1697 const char *dst_tmp = NULL;
1698 svn_stream_t *src_stream, *dst_stream;
1699 apr_file_t *s = NULL, *d = NULL; /* init to null important for APR */
1700 svn_error_t *err;
1701 svn_node_kind_t kind;
1702 svn_boolean_t path_special;
1704 SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool));
1706 /* If this is a 'special' file, we may need to create it or
1707 detranslate it. */
1708 if (special || path_special)
1710 if (expand)
1711 SVN_ERR(create_special_file(src, dst, pool));
1712 else
1713 SVN_ERR(detranslate_special_file(src, dst, pool));
1715 return SVN_NO_ERROR;
1718 /* The easy way out: no translation needed, just copy. */
1719 if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1720 return svn_io_copy_file(src, dst, FALSE, pool);
1722 /* Open source file. */
1723 SVN_ERR(svn_io_file_open(&s, src, APR_READ | APR_BUFFERED,
1724 APR_OS_DEFAULT, pool));
1726 /* For atomicity, we translate to a tmp file and
1727 then rename the tmp file over the real destination. */
1728 SVN_ERR(svn_io_open_unique_file2(&d, &dst_tmp, dst,
1729 ".tmp", svn_io_file_del_on_pool_cleanup,
1730 pool));
1732 /* Now convert our two open files into streams. */
1733 src_stream = svn_stream_from_aprfile(s, pool);
1734 dst_stream = svn_stream_from_aprfile(d, pool);
1736 /* Translate src stream into dst stream. */
1737 err = svn_subst_translate_stream3(src_stream, dst_stream, eol_str,
1738 repair, keywords, expand, pool);
1739 if (err)
1741 if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1742 return svn_error_createf
1743 (SVN_ERR_IO_INCONSISTENT_EOL, err,
1744 _("File '%s' has inconsistent newlines"),
1745 svn_path_local_style(src, pool));
1746 else
1747 return err;
1750 /* clean up nicely. */
1751 SVN_ERR(svn_stream_close(src_stream));
1752 SVN_ERR(svn_stream_close(dst_stream));
1753 SVN_ERR(svn_io_file_close(s, pool));
1754 SVN_ERR(svn_io_file_close(d, pool));
1756 /* Now that dst_tmp contains the translated data, do the atomic rename. */
1757 SVN_ERR(svn_io_file_rename(dst_tmp, dst, pool));
1759 return SVN_NO_ERROR;
1763 /*** 'Special file' stream support */
1765 struct special_stream_baton
1767 svn_stream_t *read_stream;
1768 svn_stringbuf_t *write_content;
1769 svn_stream_t *write_stream;
1770 const char *path;
1771 apr_pool_t *pool;
1775 static svn_error_t *
1776 read_handler_special(void *baton, char *buffer, apr_size_t *len)
1778 struct special_stream_baton *btn = baton;
1780 if (btn->read_stream)
1781 /* We actually found a file to read from */
1782 return svn_stream_read(btn->read_stream, buffer, len);
1783 else
1784 return svn_error_createf(APR_ENOENT, NULL,
1785 "Can't read special file: File '%s' not found",
1786 svn_path_local_style (btn->path, btn->pool));
1789 static svn_error_t *
1790 write_handler_special(void *baton, const char *buffer, apr_size_t *len)
1792 struct special_stream_baton *btn = baton;
1794 return svn_stream_write(btn->write_stream, buffer, len);
1798 static svn_error_t *
1799 close_handler_special (void *baton)
1801 struct special_stream_baton *btn = baton;
1803 if (btn->write_content->len)
1805 /* yeay! we received data and need to create a special file! */
1807 SVN_ERR(create_special_file_from_stringbuf(btn->write_content,
1808 btn->path,
1809 btn->pool));
1812 return SVN_NO_ERROR;
1816 svn_error_t *
1817 svn_subst_stream_from_specialfile(svn_stream_t **stream,
1818 const char *path,
1819 apr_pool_t *pool)
1821 struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton));
1822 svn_error_t *err;
1824 baton->pool = pool;
1825 baton->path = apr_pstrdup(pool, path);
1827 err = detranslate_special_file_to_stream(&baton->read_stream, path, pool);
1829 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1831 svn_error_clear(err);
1832 baton->read_stream = NULL;
1835 baton->write_content = svn_stringbuf_create("", pool);
1836 baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool);
1838 *stream = svn_stream_create(baton, pool);
1839 svn_stream_set_read(*stream, read_handler_special);
1840 svn_stream_set_write(*stream, write_handler_special);
1841 svn_stream_set_close(*stream, close_handler_special);
1843 return SVN_NO_ERROR;
1848 /*** String translation */
1849 svn_error_t *
1850 svn_subst_translate_string(svn_string_t **new_value,
1851 const svn_string_t *value,
1852 const char *encoding,
1853 apr_pool_t *pool)
1855 const char *val_utf8;
1856 const char *val_utf8_lf;
1858 if (value == NULL)
1860 *new_value = NULL;
1861 return SVN_NO_ERROR;
1864 if (encoding)
1866 SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data,
1867 encoding, pool));
1869 else
1871 SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, pool));
1874 SVN_ERR(svn_subst_translate_cstring2(val_utf8,
1875 &val_utf8_lf,
1876 "\n", /* translate to LF */
1877 FALSE, /* no repair */
1878 NULL, /* no keywords */
1879 FALSE, /* no expansion */
1880 pool));
1882 *new_value = svn_string_create(val_utf8_lf, pool);
1884 return SVN_NO_ERROR;
1888 svn_error_t *
1889 svn_subst_detranslate_string(svn_string_t **new_value,
1890 const svn_string_t *value,
1891 svn_boolean_t for_output,
1892 apr_pool_t *pool)
1894 svn_error_t *err;
1895 const char *val_neol;
1896 const char *val_nlocale_neol;
1898 if (value == NULL)
1900 *new_value = NULL;
1901 return SVN_NO_ERROR;
1904 SVN_ERR(svn_subst_translate_cstring2(value->data,
1905 &val_neol,
1906 APR_EOL_STR, /* 'native' eol */
1907 FALSE, /* no repair */
1908 NULL, /* no keywords */
1909 FALSE, /* no expansion */
1910 pool));
1912 if (for_output)
1914 err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
1915 if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
1917 val_nlocale_neol =
1918 svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool);
1919 svn_error_clear(err);
1921 else if (err)
1922 return err;
1924 else
1926 err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
1927 if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
1929 val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool);
1930 svn_error_clear(err);
1932 else if (err)
1933 return err;
1936 *new_value = svn_string_create(val_nlocale_neol, pool);
1938 return SVN_NO_ERROR;