1/*
2 * opt.c :  option and argument parsing for Subversion command lines
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#define APR_WANT_STRFUNC
27#include <apr_want.h>
28
29#include <stdio.h>
30#include <string.h>
31#include <assert.h>
32#include <apr_pools.h>
33#include <apr_general.h>
34#include <apr_lib.h>
35#include <apr_file_info.h>
36
37#include "svn_hash.h"
38#include "svn_cmdline.h"
39#include "svn_version.h"
40#include "svn_types.h"
41#include "svn_opt.h"
42#include "svn_error.h"
43#include "svn_dirent_uri.h"
44#include "svn_path.h"
45#include "svn_utf.h"
46#include "svn_time.h"
47#include "svn_props.h"
48#include "svn_ctype.h"
49
50#include "private/svn_opt_private.h"
51
52#include "opt.h"
53#include "svn_private_config.h"
54
55
56/*** Code. ***/
57
58const svn_opt_subcommand_desc2_t *
59svn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table,
60                                  const char *cmd_name)
61{
62  int i = 0;
63
64  if (cmd_name == NULL)
65    return NULL;
66
67  while (table[i].name) {
68    int j;
69    if (strcmp(cmd_name, table[i].name) == 0)
70      return table + i;
71    for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
72      if (strcmp(cmd_name, table[i].aliases[j]) == 0)
73        return table + i;
74
75    i++;
76  }
77
78  /* If we get here, there was no matching subcommand name or alias. */
79  return NULL;
80}
81
82const apr_getopt_option_t *
83svn_opt_get_option_from_code2(int code,
84                              const apr_getopt_option_t *option_table,
85                              const svn_opt_subcommand_desc2_t *command,
86                              apr_pool_t *pool)
87{
88  apr_size_t i;
89
90  for (i = 0; option_table[i].optch; i++)
91    if (option_table[i].optch == code)
92      {
93        if (command)
94          {
95            int j;
96
97            for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) &&
98                         command->desc_overrides[j].optch); j++)
99              if (command->desc_overrides[j].optch == code)
100                {
101                  apr_getopt_option_t *tmpopt =
102                      apr_palloc(pool, sizeof(*tmpopt));
103                  *tmpopt = option_table[i];
104                  tmpopt->description = command->desc_overrides[j].desc;
105                  return tmpopt;
106                }
107          }
108        return &(option_table[i]);
109      }
110
111  return NULL;
112}
113
114
115const apr_getopt_option_t *
116svn_opt_get_option_from_code(int code,
117                             const apr_getopt_option_t *option_table)
118{
119  apr_size_t i;
120
121  for (i = 0; option_table[i].optch; i++)
122    if (option_table[i].optch == code)
123      return &(option_table[i]);
124
125  return NULL;
126}
127
128
129/* Like svn_opt_get_option_from_code2(), but also, if CODE appears a second
130 * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that
131 * second name, else set it to NULL. */
132static const apr_getopt_option_t *
133get_option_from_code(const char **long_alias,
134                     int code,
135                     const apr_getopt_option_t *option_table,
136                     const svn_opt_subcommand_desc2_t *command,
137                     apr_pool_t *pool)
138{
139  const apr_getopt_option_t *i;
140  const apr_getopt_option_t *opt
141    = svn_opt_get_option_from_code2(code, option_table, command, pool);
142
143  /* Find a long alias in the table, if there is one. */
144  *long_alias = NULL;
145  for (i = option_table; i->optch; i++)
146    {
147      if (i->optch == code && i->name != opt->name)
148        {
149          *long_alias = i->name;
150          break;
151        }
152    }
153
154  return opt;
155}
156
157
158/* Print an option OPT nicely into a STRING allocated in POOL.
159 * If OPT has a single-character short form, then print OPT->name (if not
160 * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias.
161 * If DOC is set, include the generic documentation string of OPT,
162 * localized to the current locale if a translation is available.
163 */
164static void
165format_option(const char **string,
166              const apr_getopt_option_t *opt,
167              const char *long_alias,
168              svn_boolean_t doc,
169              apr_pool_t *pool)
170{
171  char *opts;
172
173  if (opt == NULL)
174    {
175      *string = "?";
176      return;
177    }
178
179  /* We have a valid option which may or may not have a "short
180     name" (a single-character alias for the long option). */
181  if (opt->optch <= 255)
182    opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name);
183  else if (long_alias)
184    opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias);
185  else
186    opts = apr_psprintf(pool, "--%s", opt->name);
187
188  if (opt->has_arg)
189    opts = apr_pstrcat(pool, opts, _(" ARG"), SVN_VA_NULL);
190
191  if (doc)
192    opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description));
193
194  *string = opts;
195}
196
197void
198svn_opt_format_option(const char **string,
199                      const apr_getopt_option_t *opt,
200                      svn_boolean_t doc,
201                      apr_pool_t *pool)
202{
203  format_option(string, opt, NULL, doc, pool);
204}
205
206
207svn_boolean_t
208svn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command,
209                                 int option_code,
210                                 const int *global_options)
211{
212  apr_size_t i;
213
214  for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
215    if (command->valid_options[i] == option_code)
216      return TRUE;
217
218  if (global_options)
219    for (i = 0; global_options[i]; i++)
220      if (global_options[i] == option_code)
221        return TRUE;
222
223  return FALSE;
224}
225
226svn_boolean_t
227svn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command,
228                                 int option_code)
229{
230  return svn_opt_subcommand_takes_option3(command,
231                                          option_code,
232                                          NULL);
233}
234
235
236svn_boolean_t
237svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command,
238                                int option_code)
239{
240  apr_size_t i;
241
242  for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
243    if (command->valid_options[i] == option_code)
244      return TRUE;
245
246  return FALSE;
247}
248
249
250/* Print the canonical command name for CMD, and all its aliases, to
251   STREAM.  If HELP is set, print CMD's help string too, in which case
252   obtain option usage from OPTIONS_TABLE. */
253static svn_error_t *
254print_command_info2(const svn_opt_subcommand_desc2_t *cmd,
255                    const apr_getopt_option_t *options_table,
256                    const int *global_options,
257                    svn_boolean_t help,
258                    apr_pool_t *pool,
259                    FILE *stream)
260{
261  svn_boolean_t first_time;
262  apr_size_t i;
263
264  /* Print the canonical command name. */
265  SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
266
267  /* Print the list of aliases. */
268  first_time = TRUE;
269  for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
270    {
271      if (cmd->aliases[i] == NULL)
272        break;
273
274      if (first_time) {
275        SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
276        first_time = FALSE;
277      }
278      else
279        SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
280
281      SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
282    }
283
284  if (! first_time)
285    SVN_ERR(svn_cmdline_fputs(")", stream, pool));
286
287  if (help)
288    {
289      const apr_getopt_option_t *option;
290      const char *long_alias;
291      svn_boolean_t have_options = FALSE;
292
293      SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help)));
294
295      /* Loop over all valid option codes attached to the subcommand */
296      for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
297        {
298          if (cmd->valid_options[i])
299            {
300              if (!have_options)
301                {
302                  SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
303                                            stream, pool));
304                  have_options = TRUE;
305                }
306
307              /* convert each option code into an option */
308              option = get_option_from_code(&long_alias, cmd->valid_options[i],
309                                            options_table, cmd, pool);
310
311              /* print the option's docstring */
312              if (option && option->description)
313                {
314                  const char *optstr;
315                  format_option(&optstr, option, long_alias, TRUE, pool);
316                  SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
317                                              optstr));
318                }
319            }
320        }
321      /* And global options too */
322      if (global_options && *global_options)
323        {
324          SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"),
325                                    stream, pool));
326          have_options = TRUE;
327
328          for (i = 0; global_options[i]; i++)
329            {
330
331              /* convert each option code into an option */
332              option = get_option_from_code(&long_alias, global_options[i],
333                                            options_table, cmd, pool);
334
335              /* print the option's docstring */
336              if (option && option->description)
337                {
338                  const char *optstr;
339                  format_option(&optstr, option, long_alias, TRUE, pool);
340                  SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
341                                              optstr));
342                }
343            }
344        }
345
346      if (have_options)
347        SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
348    }
349
350  return SVN_NO_ERROR;
351}
352
353void
354svn_opt_print_generic_help2(const char *header,
355                            const svn_opt_subcommand_desc2_t *cmd_table,
356                            const apr_getopt_option_t *opt_table,
357                            const char *footer,
358                            apr_pool_t *pool, FILE *stream)
359{
360  int i = 0;
361  svn_error_t *err;
362
363  if (header)
364    if ((err = svn_cmdline_fputs(header, stream, pool)))
365      goto print_error;
366
367  while (cmd_table[i].name)
368    {
369      if ((err = svn_cmdline_fputs("   ", stream, pool))
370          || (err = print_command_info2(cmd_table + i, opt_table,
371                                        NULL, FALSE,
372                                        pool, stream))
373          || (err = svn_cmdline_fputs("\n", stream, pool)))
374        goto print_error;
375      i++;
376    }
377
378  if ((err = svn_cmdline_fputs("\n", stream, pool)))
379    goto print_error;
380
381  if (footer)
382    if ((err = svn_cmdline_fputs(footer, stream, pool)))
383      goto print_error;
384
385  return;
386
387 print_error:
388  /* Issue #3014:
389   * Don't print anything on broken pipes. The pipe was likely
390   * closed by the process at the other end. We expect that
391   * process to perform error reporting as necessary.
392   *
393   * ### This assumes that there is only one error in a chain for
394   * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
395  if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
396    svn_handle_error2(err, stderr, FALSE, "svn: ");
397  svn_error_clear(err);
398}
399
400
401void
402svn_opt_subcommand_help3(const char *subcommand,
403                         const svn_opt_subcommand_desc2_t *table,
404                         const apr_getopt_option_t *options_table,
405                         const int *global_options,
406                         apr_pool_t *pool)
407{
408  const svn_opt_subcommand_desc2_t *cmd =
409    svn_opt_get_canonical_subcommand2(table, subcommand);
410  svn_error_t *err;
411
412  if (cmd)
413    err = print_command_info2(cmd, options_table, global_options,
414                              TRUE, pool, stdout);
415  else
416    err = svn_cmdline_fprintf(stderr, pool,
417                              _("\"%s\": unknown command.\n\n"), subcommand);
418
419  if (err) {
420    /* Issue #3014: Don't print anything on broken pipes. */
421    if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
422      svn_handle_error2(err, stderr, FALSE, "svn: ");
423    svn_error_clear(err);
424  }
425}
426
427
428
429/*** Parsing revision and date options. ***/
430
431
432/** Parsing "X:Y"-style arguments. **/
433
434/* If WORD matches one of the special revision descriptors,
435 * case-insensitively, set *REVISION accordingly:
436 *
437 *   - For "head", set REVISION->kind to svn_opt_revision_head.
438 *
439 *   - For "prev", set REVISION->kind to svn_opt_revision_previous.
440 *
441 *   - For "base", set REVISION->kind to svn_opt_revision_base.
442 *
443 *   - For "committed", set REVISION->kind to svn_opt_revision_committed.
444 *
445 * If match, return 0, else return -1 and don't touch REVISION.
446 */
447static int
448revision_from_word(svn_opt_revision_t *revision, const char *word)
449{
450  if (svn_cstring_casecmp(word, "head") == 0)
451    {
452      revision->kind = svn_opt_revision_head;
453    }
454  else if (svn_cstring_casecmp(word, "prev") == 0)
455    {
456      revision->kind = svn_opt_revision_previous;
457    }
458  else if (svn_cstring_casecmp(word, "base") == 0)
459    {
460      revision->kind = svn_opt_revision_base;
461    }
462  else if (svn_cstring_casecmp(word, "committed") == 0)
463    {
464      revision->kind = svn_opt_revision_committed;
465    }
466  else
467    return -1;
468
469  return 0;
470}
471
472
473/* Parse one revision specification.  Return pointer to character
474   after revision, or NULL if the revision is invalid.  Modifies
475   str, so make sure to pass a copy of anything precious.  Uses
476   POOL for temporary allocation. */
477static char *parse_one_rev(svn_opt_revision_t *revision, char *str,
478                           apr_pool_t *pool)
479{
480  char *end, save;
481
482  /* Allow any number of 'r's to prefix a revision number, because
483     that way if a script pastes svn output into another svn command
484     (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work,
485     even when compounded.
486
487     As it happens, none of our special revision words begins with
488     "r".  If any ever do, then this code will have to get smarter.
489
490     Incidentally, this allows "r{DATE}".  We could avoid that with
491     some trivial code rearrangement, but it's not clear what would
492     be gained by doing so. */
493  while (*str == 'r')
494    str++;
495
496  if (*str == '{')
497    {
498      svn_boolean_t matched;
499      apr_time_t tm;
500      svn_error_t *err;
501
502      /* Brackets denote a date. */
503      str++;
504      end = strchr(str, '}');
505      if (!end)
506        return NULL;
507      *end = '\0';
508      err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
509      if (err)
510        {
511          svn_error_clear(err);
512          return NULL;
513        }
514      if (!matched)
515        return NULL;
516      revision->kind = svn_opt_revision_date;
517      revision->value.date = tm;
518      return end + 1;
519    }
520  else if (svn_ctype_isdigit(*str))
521    {
522      /* It's a number. */
523      end = str + 1;
524      while (svn_ctype_isdigit(*end))
525        end++;
526      save = *end;
527      *end = '\0';
528      revision->kind = svn_opt_revision_number;
529      revision->value.number = SVN_STR_TO_REV(str);
530      *end = save;
531      return end;
532    }
533  else if (svn_ctype_isalpha(*str))
534    {
535      end = str + 1;
536      while (svn_ctype_isalpha(*end))
537        end++;
538      save = *end;
539      *end = '\0';
540      if (revision_from_word(revision, str) != 0)
541        return NULL;
542      *end = save;
543      return end;
544    }
545  else
546    return NULL;
547}
548
549
550int
551svn_opt_parse_revision(svn_opt_revision_t *start_revision,
552                       svn_opt_revision_t *end_revision,
553                       const char *arg,
554                       apr_pool_t *pool)
555{
556  char *left_rev, *right_rev, *end;
557
558  /* Operate on a copy of the argument. */
559  left_rev = apr_pstrdup(pool, arg);
560
561  right_rev = parse_one_rev(start_revision, left_rev, pool);
562  if (right_rev && *right_rev == ':')
563    {
564      right_rev++;
565      end = parse_one_rev(end_revision, right_rev, pool);
566      if (!end || *end != '\0')
567        return -1;
568    }
569  else if (!right_rev || *right_rev != '\0')
570    return -1;
571
572  return 0;
573}
574
575
576int
577svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
578                                const char *arg,
579                                apr_pool_t *pool)
580{
581  svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
582
583  range->start.kind = svn_opt_revision_unspecified;
584  range->end.kind = svn_opt_revision_unspecified;
585
586  if (svn_opt_parse_revision(&(range->start), &(range->end),
587                             arg, pool) == -1)
588    return -1;
589
590  APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
591  return 0;
592}
593
594svn_error_t *
595svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
596                          svn_opt_revision_t *op_rev,
597                          svn_boolean_t is_url,
598                          svn_boolean_t notice_local_mods,
599                          apr_pool_t *pool)
600{
601  if (peg_rev->kind == svn_opt_revision_unspecified)
602    {
603      if (is_url)
604        {
605          peg_rev->kind = svn_opt_revision_head;
606        }
607      else
608        {
609          if (notice_local_mods)
610            peg_rev->kind = svn_opt_revision_working;
611          else
612            peg_rev->kind = svn_opt_revision_base;
613        }
614    }
615
616  if (op_rev->kind == svn_opt_revision_unspecified)
617    *op_rev = *peg_rev;
618
619  return SVN_NO_ERROR;
620}
621
622const char *
623svn_opt__revision_to_string(const svn_opt_revision_t *revision,
624                            apr_pool_t *result_pool)
625{
626  switch (revision->kind)
627    {
628      case svn_opt_revision_unspecified:
629        return "unspecified";
630      case svn_opt_revision_number:
631        return apr_psprintf(result_pool, "%ld", revision->value.number);
632      case svn_opt_revision_date:
633        /* ### svn_time_to_human_cstring()? */
634        return svn_time_to_cstring(revision->value.date, result_pool);
635      case svn_opt_revision_committed:
636        return "committed";
637      case svn_opt_revision_previous:
638        return "previous";
639      case svn_opt_revision_base:
640        return "base";
641      case svn_opt_revision_working:
642        return "working";
643      case svn_opt_revision_head:
644        return "head";
645      default:
646        return NULL;
647    }
648}
649
650svn_opt_revision_range_t *
651svn_opt__revision_range_create(const svn_opt_revision_t *start_revision,
652                               const svn_opt_revision_t *end_revision,
653                               apr_pool_t *result_pool)
654{
655  svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
656
657  range->start = *start_revision;
658  range->end = *end_revision;
659  return range;
660}
661
662svn_opt_revision_range_t *
663svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,
664                                     svn_revnum_t end_revnum,
665                                     apr_pool_t *result_pool)
666{
667  svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
668
669  range->start.kind = svn_opt_revision_number;
670  range->start.value.number = start_revnum;
671  range->end.kind = svn_opt_revision_number;
672  range->end.value.number = end_revnum;
673  return range;
674}
675
676
677
678/*** Parsing arguments. ***/
679#define DEFAULT_ARRAY_SIZE 5
680
681
682/* Copy STR into POOL and push the copy onto ARRAY. */
683static void
684array_push_str(apr_array_header_t *array,
685               const char *str,
686               apr_pool_t *pool)
687{
688  /* ### Not sure if this function is still necessary.  It used to
689     convert str to svn_stringbuf_t * and push it, but now it just
690     dups str in pool and pushes the copy.  So its only effect is
691     transfer str's lifetime to pool.  Is that something callers are
692     depending on? */
693
694  APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
695}
696
697
698void
699svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
700                                 apr_pool_t *pool)
701{
702  if (targets->nelts == 0)
703    APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */
704  assert(targets->nelts);
705}
706
707
708svn_error_t *
709svn_opt_parse_num_args(apr_array_header_t **args_p,
710                       apr_getopt_t *os,
711                       int num_args,
712                       apr_pool_t *pool)
713{
714  int i;
715  apr_array_header_t *args
716    = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
717
718  /* loop for num_args and add each arg to the args array */
719  for (i = 0; i < num_args; i++)
720    {
721      if (os->ind >= os->argc)
722        {
723          return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
724        }
725      array_push_str(args, os->argv[os->ind++], pool);
726    }
727
728  *args_p = args;
729  return SVN_NO_ERROR;
730}
731
732svn_error_t *
733svn_opt_parse_all_args(apr_array_header_t **args_p,
734                       apr_getopt_t *os,
735                       apr_pool_t *pool)
736{
737  apr_array_header_t *args
738    = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
739
740  if (os->ind > os->argc)
741    {
742      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
743    }
744  while (os->ind < os->argc)
745    {
746      array_push_str(args, os->argv[os->ind++], pool);
747    }
748
749  *args_p = args;
750  return SVN_NO_ERROR;
751}
752
753
754svn_error_t *
755svn_opt_parse_path(svn_opt_revision_t *rev,
756                   const char **truepath,
757                   const char *path /* UTF-8! */,
758                   apr_pool_t *pool)
759{
760  const char *peg_rev;
761
762  SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool));
763
764  /* Parse the peg revision, if one was found */
765  if (strlen(peg_rev))
766    {
767      int ret;
768      svn_opt_revision_t start_revision, end_revision;
769
770      end_revision.kind = svn_opt_revision_unspecified;
771
772      if (peg_rev[1] == '\0')  /* looking at empty peg revision */
773        {
774          ret = 0;
775          start_revision.kind = svn_opt_revision_unspecified;
776          start_revision.value.number = 0;
777        }
778      else  /* looking at non-empty peg revision */
779        {
780          const char *rev_str = &peg_rev[1];
781
782          /* URLs get treated differently from wc paths. */
783          if (svn_path_is_url(path))
784            {
785              /* URLs are URI-encoded, so we look for dates with
786                 URI-encoded delimiters.  */
787              size_t rev_len = strlen(rev_str);
788              if (rev_len > 6
789                  && rev_str[0] == '%'
790                  && rev_str[1] == '7'
791                  && (rev_str[2] == 'B'
792                      || rev_str[2] == 'b')
793                  && rev_str[rev_len-3] == '%'
794                  && rev_str[rev_len-2] == '7'
795                  && (rev_str[rev_len-1] == 'D'
796                      || rev_str[rev_len-1] == 'd'))
797                {
798                  rev_str = svn_path_uri_decode(rev_str, pool);
799                }
800            }
801          ret = svn_opt_parse_revision(&start_revision,
802                                       &end_revision,
803                                       rev_str, pool);
804        }
805
806      if (ret || end_revision.kind != svn_opt_revision_unspecified)
807        {
808          /* If an svn+ssh URL was used and it contains only one @,
809           * provide an error message that presents a possible solution
810           * to the parsing error (issue #2349). */
811          if (strncmp(path, "svn+ssh://", 10) == 0)
812            {
813              const char *at;
814
815              at = strchr(path, '@');
816              if (at && strrchr(path, '@') == at)
817                return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
818                                         _("Syntax error parsing peg revision "
819                                           "'%s'; did you mean '%s@'?"),
820                                       &peg_rev[1], path);
821            }
822
823          return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
824                                   _("Syntax error parsing peg revision '%s'"),
825                                   &peg_rev[1]);
826        }
827      rev->kind = start_revision.kind;
828      rev->value = start_revision.value;
829    }
830  else
831    {
832      /* Didn't find a peg revision. */
833      rev->kind = svn_opt_revision_unspecified;
834    }
835
836  return SVN_NO_ERROR;
837}
838
839
840/* Note: This is substantially copied into svn_client_args_to_target_array() in
841 * order to move to libsvn_client while maintaining backward compatibility. */
842svn_error_t *
843svn_opt__args_to_target_array(apr_array_header_t **targets_p,
844                              apr_getopt_t *os,
845                              const apr_array_header_t *known_targets,
846                              apr_pool_t *pool)
847{
848  int i;
849  svn_error_t *err = SVN_NO_ERROR;
850  apr_array_header_t *input_targets =
851    apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
852  apr_array_header_t *output_targets =
853    apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
854
855  /* Step 1:  create a master array of targets that are in UTF-8
856     encoding, and come from concatenating the targets left by apr_getopt,
857     plus any extra targets (e.g., from the --targets switch.) */
858
859  for (; os->ind < os->argc; os->ind++)
860    {
861      /* The apr_getopt targets are still in native encoding. */
862      const char *raw_target = os->argv[os->ind];
863      SVN_ERR(svn_utf_cstring_to_utf8
864              ((const char **) apr_array_push(input_targets),
865               raw_target, pool));
866    }
867
868  if (known_targets)
869    {
870      for (i = 0; i < known_targets->nelts; i++)
871        {
872          /* The --targets array have already been converted to UTF-8,
873             because we needed to split up the list with svn_cstring_split. */
874          const char *utf8_target = APR_ARRAY_IDX(known_targets,
875                                                  i, const char *);
876          APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
877        }
878    }
879
880  /* Step 2:  process each target.  */
881
882  for (i = 0; i < input_targets->nelts; i++)
883    {
884      const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
885      const char *true_target;
886      const char *target;      /* after all processing is finished */
887      const char *peg_rev;
888
889      /*
890       * This is needed so that the target can be properly canonicalized,
891       * otherwise the canonicalization does not treat a ".@BASE" as a "."
892       * with a BASE peg revision, and it is not canonicalized to "@BASE".
893       * If any peg revision exists, it is appended to the final
894       * canonicalized path or URL.  Do not use svn_opt_parse_path()
895       * because the resulting peg revision is a structure that would have
896       * to be converted back into a string.  Converting from a string date
897       * to the apr_time_t field in the svn_opt_revision_value_t and back to
898       * a string would not necessarily preserve the exact bytes of the
899       * input date, so its easier just to keep it in string form.
900       */
901      SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
902                                                 utf8_target, pool));
903
904      /* URLs and wc-paths get treated differently. */
905      if (svn_path_is_url(true_target))
906        {
907          SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target,
908                                                 pool));
909        }
910      else  /* not a url, so treat as a path */
911        {
912          const char *base_name;
913
914          SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target,
915                                                 pool));
916
917          /* If the target has the same name as a Subversion
918             working copy administrative dir, skip it. */
919          base_name = svn_dirent_basename(true_target, pool);
920
921          /* FIXME:
922             The canonical list of administrative directory names is
923             maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
924             That list can't be used here, because that use would
925             create a circular dependency between libsvn_wc and
926             libsvn_subr.  Make sure changes to the lists are always
927             synchronized! */
928          if (0 == strcmp(base_name, ".svn")
929              || 0 == strcmp(base_name, "_svn"))
930            {
931              err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
932                                      err, _("'%s' ends in a reserved name"),
933                                      utf8_target);
934              continue;
935            }
936        }
937
938      target = apr_pstrcat(pool, true_target, peg_rev, SVN_VA_NULL);
939
940      APR_ARRAY_PUSH(output_targets, const char *) = target;
941    }
942
943
944  /* kff todo: need to remove redundancies from targets before
945     passing it to the cmd_func. */
946
947  *targets_p = output_targets;
948
949  return err;
950}
951
952svn_error_t *
953svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
954                      apr_pool_t *pool)
955{
956  const char *sep, *propname;
957  svn_string_t *propval;
958
959  if (! *revprop_spec)
960    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
961                            _("Revision property pair is empty"));
962
963  if (! *revprop_table_p)
964    *revprop_table_p = apr_hash_make(pool);
965
966  sep = strchr(revprop_spec, '=');
967  if (sep)
968    {
969      propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec);
970      SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool));
971      propval = svn_string_create(sep + 1, pool);
972    }
973  else
974    {
975      SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
976      propval = svn_string_create_empty(pool);
977    }
978
979  if (!svn_prop_name_is_valid(propname))
980    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
981                             _("'%s' is not a valid Subversion property name"),
982                             propname);
983
984  svn_hash_sets(*revprop_table_p, propname, propval);
985
986  return SVN_NO_ERROR;
987}
988
989svn_error_t *
990svn_opt__split_arg_at_peg_revision(const char **true_target,
991                                   const char **peg_revision,
992                                   const char *utf8_target,
993                                   apr_pool_t *pool)
994{
995  const char *peg_start = NULL; /* pointer to the peg revision, if any */
996  const char *ptr;
997
998  for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target;
999        --ptr)
1000    {
1001      /* If we hit a path separator, stop looking.  This is OK
1002          only because our revision specifiers can't contain '/'. */
1003      if (*ptr == '/')
1004        break;
1005
1006      if (*ptr == '@')
1007        {
1008          peg_start = ptr;
1009          break;
1010        }
1011    }
1012
1013  if (peg_start)
1014    {
1015      *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target);
1016      if (peg_revision)
1017        *peg_revision = apr_pstrdup(pool, peg_start);
1018    }
1019  else
1020    {
1021      *true_target = utf8_target;
1022      if (peg_revision)
1023        *peg_revision = "";
1024    }
1025
1026  return SVN_NO_ERROR;
1027}
1028
1029svn_error_t *
1030svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in,
1031                              apr_pool_t *pool)
1032{
1033  const char *target;
1034
1035  /* Convert to URI. */
1036  target = svn_path_uri_from_iri(url_in, pool);
1037  /* Auto-escape some ASCII characters. */
1038  target = svn_path_uri_autoescape(target, pool);
1039
1040#if '/' != SVN_PATH_LOCAL_SEPARATOR
1041  /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */
1042  if (strchr(target, SVN_PATH_LOCAL_SEPARATOR))
1043    {
1044      char *p = apr_pstrdup(pool, target);
1045      target = p;
1046
1047      /* Convert all local-style separators to the canonical ones. */
1048      for (; *p != '\0'; ++p)
1049        if (*p == SVN_PATH_LOCAL_SEPARATOR)
1050          *p = '/';
1051    }
1052#endif
1053
1054  /* Verify that no backpaths are present in the URL. */
1055  if (svn_path_is_backpath_present(target))
1056    return svn_error_createf(SVN_ERR_BAD_URL, 0,
1057                             _("URL '%s' contains a '..' element"),
1058                             target);
1059
1060  /* Strip any trailing '/' and collapse other redundant elements. */
1061  target = svn_uri_canonicalize(target, pool);
1062
1063  *url_out = target;
1064  return SVN_NO_ERROR;
1065}
1066
1067svn_error_t *
1068svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in,
1069                               apr_pool_t *pool)
1070{
1071  const char *apr_target;
1072  char *truenamed_target; /* APR-encoded */
1073  apr_status_t apr_err;
1074
1075  /* canonicalize case, and change all separators to '/'. */
1076  SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool));
1077  apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
1078                               APR_FILEPATH_TRUENAME, pool);
1079
1080  if (!apr_err)
1081    /* We have a canonicalized APR-encoded target now. */
1082    apr_target = truenamed_target;
1083  else if (APR_STATUS_IS_ENOENT(apr_err))
1084    /* It's okay for the file to not exist, that just means we
1085       have to accept the case given to the client. We'll use
1086       the original APR-encoded target. */
1087    ;
1088  else
1089    return svn_error_createf(apr_err, NULL,
1090                             _("Error resolving case of '%s'"),
1091                             svn_dirent_local_style(path_in, pool));
1092
1093  /* convert back to UTF-8. */
1094  SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool));
1095  *path_out = svn_dirent_canonicalize(*path_out, pool);
1096
1097  return SVN_NO_ERROR;
1098}
1099
1100
1101svn_error_t *
1102svn_opt__print_version_info(const char *pgm_name,
1103                            const char *footer,
1104                            const svn_version_extended_t *info,
1105                            svn_boolean_t quiet,
1106                            svn_boolean_t verbose,
1107                            apr_pool_t *pool)
1108{
1109  if (quiet)
1110    return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER);
1111
1112  SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
1113                                     "   compiled on %s\n\n"),
1114                             pgm_name, SVN_VERSION,
1115                             svn_version_ext_build_host(info)));
1116  SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
1117
1118  if (footer)
1119    {
1120      SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
1121    }
1122
1123  if (verbose)
1124    {
1125      const apr_array_header_t *libs;
1126
1127      SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
1128      SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
1129                                 svn_version_ext_runtime_host(info)));
1130      if (svn_version_ext_runtime_osname(info))
1131        {
1132          SVN_ERR(svn_cmdline_printf(pool, _("  - %s\n"),
1133                                     svn_version_ext_runtime_osname(info)));
1134        }
1135
1136      libs = svn_version_ext_linked_libs(info);
1137      if (libs && libs->nelts)
1138        {
1139          const svn_version_ext_linked_lib_t *lib;
1140          int i;
1141
1142          SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
1143                                    stdout, pool));
1144          for (i = 0; i < libs->nelts; ++i)
1145            {
1146              lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
1147              if (lib->runtime_version)
1148                SVN_ERR(svn_cmdline_printf(pool,
1149                                           "  - %s %s (compiled with %s)\n",
1150                                           lib->name,
1151                                           lib->runtime_version,
1152                                           lib->compiled_version));
1153              else
1154                SVN_ERR(svn_cmdline_printf(pool,
1155                                           "  - %s %s (static)\n",
1156                                           lib->name,
1157                                           lib->compiled_version));
1158            }
1159        }
1160
1161      libs = svn_version_ext_loaded_libs(info);
1162      if (libs && libs->nelts)
1163        {
1164          const svn_version_ext_loaded_lib_t *lib;
1165          int i;
1166
1167          SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
1168                                    stdout, pool));
1169          for (i = 0; i < libs->nelts; ++i)
1170            {
1171              lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
1172              if (lib->version)
1173                SVN_ERR(svn_cmdline_printf(pool,
1174                                           "  - %s   (%s)\n",
1175                                           lib->name, lib->version));
1176              else
1177                SVN_ERR(svn_cmdline_printf(pool, "  - %s\n", lib->name));
1178            }
1179        }
1180    }
1181
1182  return SVN_NO_ERROR;
1183}
1184
1185svn_error_t *
1186svn_opt_print_help4(apr_getopt_t *os,
1187                    const char *pgm_name,
1188                    svn_boolean_t print_version,
1189                    svn_boolean_t quiet,
1190                    svn_boolean_t verbose,
1191                    const char *version_footer,
1192                    const char *header,
1193                    const svn_opt_subcommand_desc2_t *cmd_table,
1194                    const apr_getopt_option_t *option_table,
1195                    const int *global_options,
1196                    const char *footer,
1197                    apr_pool_t *pool)
1198{
1199  apr_array_header_t *targets = NULL;
1200
1201  if (os)
1202    SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1203
1204  if (os && targets->nelts)  /* help on subcommand(s) requested */
1205    {
1206      int i;
1207
1208      for (i = 0; i < targets->nelts; i++)
1209        {
1210          svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *),
1211                                   cmd_table, option_table,
1212                                   global_options, pool);
1213        }
1214    }
1215  else if (print_version)   /* just --version */
1216    {
1217      SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
1218                                          svn_version_extended(verbose, pool),
1219                                          quiet, verbose, pool));
1220    }
1221  else if (os && !targets->nelts)            /* `-h', `--help', or `help' */
1222    svn_opt_print_generic_help2(header,
1223                                cmd_table,
1224                                option_table,
1225                                footer,
1226                                pool,
1227                                stdout);
1228  else                                       /* unknown option or cmd */
1229    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1230                                _("Type '%s help' for usage.\n"), pgm_name));
1231
1232  return SVN_NO_ERROR;
1233}
1234