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"), (char *)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    svn_handle_error2(err, stderr, FALSE, "svn: ");
421    svn_error_clear(err);
422  }
423}
424
425
426
427/*** Parsing revision and date options. ***/
428
429
430/** Parsing "X:Y"-style arguments. **/
431
432/* If WORD matches one of the special revision descriptors,
433 * case-insensitively, set *REVISION accordingly:
434 *
435 *   - For "head", set REVISION->kind to svn_opt_revision_head.
436 *
437 *   - For "prev", set REVISION->kind to svn_opt_revision_previous.
438 *
439 *   - For "base", set REVISION->kind to svn_opt_revision_base.
440 *
441 *   - For "committed", set REVISION->kind to svn_opt_revision_committed.
442 *
443 * If match, return 0, else return -1 and don't touch REVISION.
444 */
445static int
446revision_from_word(svn_opt_revision_t *revision, const char *word)
447{
448  if (svn_cstring_casecmp(word, "head") == 0)
449    {
450      revision->kind = svn_opt_revision_head;
451    }
452  else if (svn_cstring_casecmp(word, "prev") == 0)
453    {
454      revision->kind = svn_opt_revision_previous;
455    }
456  else if (svn_cstring_casecmp(word, "base") == 0)
457    {
458      revision->kind = svn_opt_revision_base;
459    }
460  else if (svn_cstring_casecmp(word, "committed") == 0)
461    {
462      revision->kind = svn_opt_revision_committed;
463    }
464  else
465    return -1;
466
467  return 0;
468}
469
470
471/* Parse one revision specification.  Return pointer to character
472   after revision, or NULL if the revision is invalid.  Modifies
473   str, so make sure to pass a copy of anything precious.  Uses
474   POOL for temporary allocation. */
475static char *parse_one_rev(svn_opt_revision_t *revision, char *str,
476                           apr_pool_t *pool)
477{
478  char *end, save;
479
480  /* Allow any number of 'r's to prefix a revision number, because
481     that way if a script pastes svn output into another svn command
482     (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work,
483     even when compounded.
484
485     As it happens, none of our special revision words begins with
486     "r".  If any ever do, then this code will have to get smarter.
487
488     Incidentally, this allows "r{DATE}".  We could avoid that with
489     some trivial code rearrangement, but it's not clear what would
490     be gained by doing so. */
491  while (*str == 'r')
492    str++;
493
494  if (*str == '{')
495    {
496      svn_boolean_t matched;
497      apr_time_t tm;
498      svn_error_t *err;
499
500      /* Brackets denote a date. */
501      str++;
502      end = strchr(str, '}');
503      if (!end)
504        return NULL;
505      *end = '\0';
506      err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
507      if (err)
508        {
509          svn_error_clear(err);
510          return NULL;
511        }
512      if (!matched)
513        return NULL;
514      revision->kind = svn_opt_revision_date;
515      revision->value.date = tm;
516      return end + 1;
517    }
518  else if (svn_ctype_isdigit(*str))
519    {
520      /* It's a number. */
521      end = str + 1;
522      while (svn_ctype_isdigit(*end))
523        end++;
524      save = *end;
525      *end = '\0';
526      revision->kind = svn_opt_revision_number;
527      revision->value.number = SVN_STR_TO_REV(str);
528      *end = save;
529      return end;
530    }
531  else if (svn_ctype_isalpha(*str))
532    {
533      end = str + 1;
534      while (svn_ctype_isalpha(*end))
535        end++;
536      save = *end;
537      *end = '\0';
538      if (revision_from_word(revision, str) != 0)
539        return NULL;
540      *end = save;
541      return end;
542    }
543  else
544    return NULL;
545}
546
547
548int
549svn_opt_parse_revision(svn_opt_revision_t *start_revision,
550                       svn_opt_revision_t *end_revision,
551                       const char *arg,
552                       apr_pool_t *pool)
553{
554  char *left_rev, *right_rev, *end;
555
556  /* Operate on a copy of the argument. */
557  left_rev = apr_pstrdup(pool, arg);
558
559  right_rev = parse_one_rev(start_revision, left_rev, pool);
560  if (right_rev && *right_rev == ':')
561    {
562      right_rev++;
563      end = parse_one_rev(end_revision, right_rev, pool);
564      if (!end || *end != '\0')
565        return -1;
566    }
567  else if (!right_rev || *right_rev != '\0')
568    return -1;
569
570  return 0;
571}
572
573
574int
575svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
576                                const char *arg,
577                                apr_pool_t *pool)
578{
579  svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
580
581  range->start.kind = svn_opt_revision_unspecified;
582  range->end.kind = svn_opt_revision_unspecified;
583
584  if (svn_opt_parse_revision(&(range->start), &(range->end),
585                             arg, pool) == -1)
586    return -1;
587
588  APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
589  return 0;
590}
591
592svn_error_t *
593svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
594                          svn_opt_revision_t *op_rev,
595                          svn_boolean_t is_url,
596                          svn_boolean_t notice_local_mods,
597                          apr_pool_t *pool)
598{
599  if (peg_rev->kind == svn_opt_revision_unspecified)
600    {
601      if (is_url)
602        {
603          peg_rev->kind = svn_opt_revision_head;
604        }
605      else
606        {
607          if (notice_local_mods)
608            peg_rev->kind = svn_opt_revision_working;
609          else
610            peg_rev->kind = svn_opt_revision_base;
611        }
612    }
613
614  if (op_rev->kind == svn_opt_revision_unspecified)
615    *op_rev = *peg_rev;
616
617  return SVN_NO_ERROR;
618}
619
620const char *
621svn_opt__revision_to_string(const svn_opt_revision_t *revision,
622                            apr_pool_t *result_pool)
623{
624  switch (revision->kind)
625    {
626      case svn_opt_revision_unspecified:
627        return "unspecified";
628      case svn_opt_revision_number:
629        return apr_psprintf(result_pool, "%ld", revision->value.number);
630      case svn_opt_revision_date:
631        /* ### svn_time_to_human_cstring()? */
632        return svn_time_to_cstring(revision->value.date, result_pool);
633      case svn_opt_revision_committed:
634        return "committed";
635      case svn_opt_revision_previous:
636        return "previous";
637      case svn_opt_revision_base:
638        return "base";
639      case svn_opt_revision_working:
640        return "working";
641      case svn_opt_revision_head:
642        return "head";
643      default:
644        return NULL;
645    }
646}
647
648svn_opt_revision_range_t *
649svn_opt__revision_range_create(const svn_opt_revision_t *start_revision,
650                               const svn_opt_revision_t *end_revision,
651                               apr_pool_t *result_pool)
652{
653  svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
654
655  range->start = *start_revision;
656  range->end = *end_revision;
657  return range;
658}
659
660svn_opt_revision_range_t *
661svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,
662                                     svn_revnum_t end_revnum,
663                                     apr_pool_t *result_pool)
664{
665  svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
666
667  range->start.kind = svn_opt_revision_number;
668  range->start.value.number = start_revnum;
669  range->end.kind = svn_opt_revision_number;
670  range->end.value.number = end_revnum;
671  return range;
672}
673
674
675
676/*** Parsing arguments. ***/
677#define DEFAULT_ARRAY_SIZE 5
678
679
680/* Copy STR into POOL and push the copy onto ARRAY. */
681static void
682array_push_str(apr_array_header_t *array,
683               const char *str,
684               apr_pool_t *pool)
685{
686  /* ### Not sure if this function is still necessary.  It used to
687     convert str to svn_stringbuf_t * and push it, but now it just
688     dups str in pool and pushes the copy.  So its only effect is
689     transfer str's lifetime to pool.  Is that something callers are
690     depending on? */
691
692  APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
693}
694
695
696void
697svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
698                                 apr_pool_t *pool)
699{
700  if (targets->nelts == 0)
701    APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */
702  assert(targets->nelts);
703}
704
705
706svn_error_t *
707svn_opt_parse_num_args(apr_array_header_t **args_p,
708                       apr_getopt_t *os,
709                       int num_args,
710                       apr_pool_t *pool)
711{
712  int i;
713  apr_array_header_t *args
714    = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
715
716  /* loop for num_args and add each arg to the args array */
717  for (i = 0; i < num_args; i++)
718    {
719      if (os->ind >= os->argc)
720        {
721          return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
722        }
723      array_push_str(args, os->argv[os->ind++], pool);
724    }
725
726  *args_p = args;
727  return SVN_NO_ERROR;
728}
729
730svn_error_t *
731svn_opt_parse_all_args(apr_array_header_t **args_p,
732                       apr_getopt_t *os,
733                       apr_pool_t *pool)
734{
735  apr_array_header_t *args
736    = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
737
738  if (os->ind > os->argc)
739    {
740      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
741    }
742  while (os->ind < os->argc)
743    {
744      array_push_str(args, os->argv[os->ind++], pool);
745    }
746
747  *args_p = args;
748  return SVN_NO_ERROR;
749}
750
751
752svn_error_t *
753svn_opt_parse_path(svn_opt_revision_t *rev,
754                   const char **truepath,
755                   const char *path /* UTF-8! */,
756                   apr_pool_t *pool)
757{
758  const char *peg_rev;
759
760  SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool));
761
762  /* Parse the peg revision, if one was found */
763  if (strlen(peg_rev))
764    {
765      int ret;
766      svn_opt_revision_t start_revision, end_revision;
767
768      end_revision.kind = svn_opt_revision_unspecified;
769
770      if (peg_rev[1] == '\0')  /* looking at empty peg revision */
771        {
772          ret = 0;
773          start_revision.kind = svn_opt_revision_unspecified;
774          start_revision.value.number = 0;
775        }
776      else  /* looking at non-empty peg revision */
777        {
778          const char *rev_str = &peg_rev[1];
779
780          /* URLs get treated differently from wc paths. */
781          if (svn_path_is_url(path))
782            {
783              /* URLs are URI-encoded, so we look for dates with
784                 URI-encoded delimeters.  */
785              size_t rev_len = strlen(rev_str);
786              if (rev_len > 6
787                  && rev_str[0] == '%'
788                  && rev_str[1] == '7'
789                  && (rev_str[2] == 'B'
790                      || rev_str[2] == 'b')
791                  && rev_str[rev_len-3] == '%'
792                  && rev_str[rev_len-2] == '7'
793                  && (rev_str[rev_len-1] == 'D'
794                      || rev_str[rev_len-1] == 'd'))
795                {
796                  rev_str = svn_path_uri_decode(rev_str, pool);
797                }
798            }
799          ret = svn_opt_parse_revision(&start_revision,
800                                       &end_revision,
801                                       rev_str, pool);
802        }
803
804      if (ret || end_revision.kind != svn_opt_revision_unspecified)
805        {
806          /* If an svn+ssh URL was used and it contains only one @,
807           * provide an error message that presents a possible solution
808           * to the parsing error (issue #2349). */
809          if (strncmp(path, "svn+ssh://", 10) == 0)
810            {
811              const char *at;
812
813              at = strchr(path, '@');
814              if (at && strrchr(path, '@') == at)
815                return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
816                                         _("Syntax error parsing peg revision "
817                                           "'%s'; did you mean '%s@'?"),
818                                       &peg_rev[1], path);
819            }
820
821          return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
822                                   _("Syntax error parsing peg revision '%s'"),
823                                   &peg_rev[1]);
824        }
825      rev->kind = start_revision.kind;
826      rev->value = start_revision.value;
827    }
828  else
829    {
830      /* Didn't find a peg revision. */
831      rev->kind = svn_opt_revision_unspecified;
832    }
833
834  return SVN_NO_ERROR;
835}
836
837
838/* Note: This is substantially copied into svn_client_args_to_target_array() in
839 * order to move to libsvn_client while maintaining backward compatibility. */
840svn_error_t *
841svn_opt__args_to_target_array(apr_array_header_t **targets_p,
842                              apr_getopt_t *os,
843                              const apr_array_header_t *known_targets,
844                              apr_pool_t *pool)
845{
846  int i;
847  svn_error_t *err = SVN_NO_ERROR;
848  apr_array_header_t *input_targets =
849    apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
850  apr_array_header_t *output_targets =
851    apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
852
853  /* Step 1:  create a master array of targets that are in UTF-8
854     encoding, and come from concatenating the targets left by apr_getopt,
855     plus any extra targets (e.g., from the --targets switch.) */
856
857  for (; os->ind < os->argc; os->ind++)
858    {
859      /* The apr_getopt targets are still in native encoding. */
860      const char *raw_target = os->argv[os->ind];
861      SVN_ERR(svn_utf_cstring_to_utf8
862              ((const char **) apr_array_push(input_targets),
863               raw_target, pool));
864    }
865
866  if (known_targets)
867    {
868      for (i = 0; i < known_targets->nelts; i++)
869        {
870          /* The --targets array have already been converted to UTF-8,
871             because we needed to split up the list with svn_cstring_split. */
872          const char *utf8_target = APR_ARRAY_IDX(known_targets,
873                                                  i, const char *);
874          APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
875        }
876    }
877
878  /* Step 2:  process each target.  */
879
880  for (i = 0; i < input_targets->nelts; i++)
881    {
882      const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
883      const char *true_target;
884      const char *target;      /* after all processing is finished */
885      const char *peg_rev;
886
887      /*
888       * This is needed so that the target can be properly canonicalized,
889       * otherwise the canonicalization does not treat a ".@BASE" as a "."
890       * with a BASE peg revision, and it is not canonicalized to "@BASE".
891       * If any peg revision exists, it is appended to the final
892       * canonicalized path or URL.  Do not use svn_opt_parse_path()
893       * because the resulting peg revision is a structure that would have
894       * to be converted back into a string.  Converting from a string date
895       * to the apr_time_t field in the svn_opt_revision_value_t and back to
896       * a string would not necessarily preserve the exact bytes of the
897       * input date, so its easier just to keep it in string form.
898       */
899      SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
900                                                 utf8_target, pool));
901
902      /* URLs and wc-paths get treated differently. */
903      if (svn_path_is_url(true_target))
904        {
905          SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target,
906                                                 pool));
907        }
908      else  /* not a url, so treat as a path */
909        {
910          const char *base_name;
911
912          SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target,
913                                                 pool));
914
915          /* If the target has the same name as a Subversion
916             working copy administrative dir, skip it. */
917          base_name = svn_dirent_basename(true_target, pool);
918
919          /* FIXME:
920             The canonical list of administrative directory names is
921             maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
922             That list can't be used here, because that use would
923             create a circular dependency between libsvn_wc and
924             libsvn_subr.  Make sure changes to the lists are always
925             synchronized! */
926          if (0 == strcmp(base_name, ".svn")
927              || 0 == strcmp(base_name, "_svn"))
928            {
929              err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
930                                      err, _("'%s' ends in a reserved name"),
931                                      utf8_target);
932              continue;
933            }
934        }
935
936      target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL);
937
938      APR_ARRAY_PUSH(output_targets, const char *) = target;
939    }
940
941
942  /* kff todo: need to remove redundancies from targets before
943     passing it to the cmd_func. */
944
945  *targets_p = output_targets;
946
947  return err;
948}
949
950svn_error_t *
951svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
952                      apr_pool_t *pool)
953{
954  const char *sep, *propname;
955  svn_string_t *propval;
956
957  if (! *revprop_spec)
958    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
959                            _("Revision property pair is empty"));
960
961  if (! *revprop_table_p)
962    *revprop_table_p = apr_hash_make(pool);
963
964  sep = strchr(revprop_spec, '=');
965  if (sep)
966    {
967      propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec);
968      SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool));
969      propval = svn_string_create(sep + 1, pool);
970    }
971  else
972    {
973      SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
974      propval = svn_string_create_empty(pool);
975    }
976
977  if (!svn_prop_name_is_valid(propname))
978    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
979                             _("'%s' is not a valid Subversion property name"),
980                             propname);
981
982  svn_hash_sets(*revprop_table_p, propname, propval);
983
984  return SVN_NO_ERROR;
985}
986
987svn_error_t *
988svn_opt__split_arg_at_peg_revision(const char **true_target,
989                                   const char **peg_revision,
990                                   const char *utf8_target,
991                                   apr_pool_t *pool)
992{
993  const char *peg_start = NULL; /* pointer to the peg revision, if any */
994  const char *ptr;
995
996  for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target;
997        --ptr)
998    {
999      /* If we hit a path separator, stop looking.  This is OK
1000          only because our revision specifiers can't contain '/'. */
1001      if (*ptr == '/')
1002        break;
1003
1004      if (*ptr == '@')
1005        {
1006          peg_start = ptr;
1007          break;
1008        }
1009    }
1010
1011  if (peg_start)
1012    {
1013      /* Error out if target is the empty string. */
1014      if (ptr == utf8_target)
1015        return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
1016                                 _("'%s' is just a peg revision. "
1017                                   "Maybe try '%s@' instead?"),
1018                                 utf8_target, utf8_target);
1019
1020      *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target);
1021      if (peg_revision)
1022        *peg_revision = apr_pstrdup(pool, peg_start);
1023    }
1024  else
1025    {
1026      *true_target = utf8_target;
1027      if (peg_revision)
1028        *peg_revision = "";
1029    }
1030
1031  return SVN_NO_ERROR;
1032}
1033
1034svn_error_t *
1035svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in,
1036                              apr_pool_t *pool)
1037{
1038  const char *target;
1039
1040  /* Convert to URI. */
1041  target = svn_path_uri_from_iri(url_in, pool);
1042  /* Auto-escape some ASCII characters. */
1043  target = svn_path_uri_autoescape(target, pool);
1044
1045#if '/' != SVN_PATH_LOCAL_SEPARATOR
1046  /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */
1047  if (strchr(target, SVN_PATH_LOCAL_SEPARATOR))
1048    {
1049      char *p = apr_pstrdup(pool, target);
1050      target = p;
1051
1052      /* Convert all local-style separators to the canonical ones. */
1053      for (; *p != '\0'; ++p)
1054        if (*p == SVN_PATH_LOCAL_SEPARATOR)
1055          *p = '/';
1056    }
1057#endif
1058
1059  /* Verify that no backpaths are present in the URL. */
1060  if (svn_path_is_backpath_present(target))
1061    return svn_error_createf(SVN_ERR_BAD_URL, 0,
1062                             _("URL '%s' contains a '..' element"),
1063                             target);
1064
1065  /* Strip any trailing '/' and collapse other redundant elements. */
1066  target = svn_uri_canonicalize(target, pool);
1067
1068  *url_out = target;
1069  return SVN_NO_ERROR;
1070}
1071
1072svn_error_t *
1073svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in,
1074                               apr_pool_t *pool)
1075{
1076  const char *apr_target;
1077  char *truenamed_target; /* APR-encoded */
1078  apr_status_t apr_err;
1079
1080  /* canonicalize case, and change all separators to '/'. */
1081  SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool));
1082  apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
1083                               APR_FILEPATH_TRUENAME, pool);
1084
1085  if (!apr_err)
1086    /* We have a canonicalized APR-encoded target now. */
1087    apr_target = truenamed_target;
1088  else if (APR_STATUS_IS_ENOENT(apr_err))
1089    /* It's okay for the file to not exist, that just means we
1090       have to accept the case given to the client. We'll use
1091       the original APR-encoded target. */
1092    ;
1093  else
1094    return svn_error_createf(apr_err, NULL,
1095                             _("Error resolving case of '%s'"),
1096                             svn_dirent_local_style(path_in, pool));
1097
1098  /* convert back to UTF-8. */
1099  SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool));
1100  *path_out = svn_dirent_canonicalize(*path_out, pool);
1101
1102  return SVN_NO_ERROR;
1103}
1104
1105
1106svn_error_t *
1107svn_opt__print_version_info(const char *pgm_name,
1108                            const char *footer,
1109                            const svn_version_extended_t *info,
1110                            svn_boolean_t quiet,
1111                            svn_boolean_t verbose,
1112                            apr_pool_t *pool)
1113{
1114  if (quiet)
1115    return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER);
1116
1117  SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
1118                                     "   compiled on %s\n\n"),
1119                             pgm_name, SVN_VERSION,
1120                             svn_version_ext_build_host(info)));
1121  SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
1122
1123  if (footer)
1124    {
1125      SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
1126    }
1127
1128  if (verbose)
1129    {
1130      const apr_array_header_t *libs;
1131
1132      SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
1133      SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
1134                                 svn_version_ext_runtime_host(info)));
1135      if (svn_version_ext_runtime_osname(info))
1136        {
1137          SVN_ERR(svn_cmdline_printf(pool, _("  - %s\n"),
1138                                     svn_version_ext_runtime_osname(info)));
1139        }
1140
1141      libs = svn_version_ext_linked_libs(info);
1142      if (libs && libs->nelts)
1143        {
1144          const svn_version_ext_linked_lib_t *lib;
1145          int i;
1146
1147          SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
1148                                    stdout, pool));
1149          for (i = 0; i < libs->nelts; ++i)
1150            {
1151              lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
1152              if (lib->runtime_version)
1153                SVN_ERR(svn_cmdline_printf(pool,
1154                                           "  - %s %s (compiled with %s)\n",
1155                                           lib->name,
1156                                           lib->runtime_version,
1157                                           lib->compiled_version));
1158              else
1159                SVN_ERR(svn_cmdline_printf(pool,
1160                                           "  - %s %s (static)\n",
1161                                           lib->name,
1162                                           lib->compiled_version));
1163            }
1164        }
1165
1166      libs = svn_version_ext_loaded_libs(info);
1167      if (libs && libs->nelts)
1168        {
1169          const svn_version_ext_loaded_lib_t *lib;
1170          int i;
1171
1172          SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
1173                                    stdout, pool));
1174          for (i = 0; i < libs->nelts; ++i)
1175            {
1176              lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
1177              if (lib->version)
1178                SVN_ERR(svn_cmdline_printf(pool,
1179                                           "  - %s   (%s)\n",
1180                                           lib->name, lib->version));
1181              else
1182                SVN_ERR(svn_cmdline_printf(pool, "  - %s\n", lib->name));
1183            }
1184        }
1185    }
1186
1187  return SVN_NO_ERROR;
1188}
1189
1190svn_error_t *
1191svn_opt_print_help4(apr_getopt_t *os,
1192                    const char *pgm_name,
1193                    svn_boolean_t print_version,
1194                    svn_boolean_t quiet,
1195                    svn_boolean_t verbose,
1196                    const char *version_footer,
1197                    const char *header,
1198                    const svn_opt_subcommand_desc2_t *cmd_table,
1199                    const apr_getopt_option_t *option_table,
1200                    const int *global_options,
1201                    const char *footer,
1202                    apr_pool_t *pool)
1203{
1204  apr_array_header_t *targets = NULL;
1205
1206  if (os)
1207    SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1208
1209  if (os && targets->nelts)  /* help on subcommand(s) requested */
1210    {
1211      int i;
1212
1213      for (i = 0; i < targets->nelts; i++)
1214        {
1215          svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *),
1216                                   cmd_table, option_table,
1217                                   global_options, pool);
1218        }
1219    }
1220  else if (print_version)   /* just --version */
1221    {
1222      SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
1223                                          svn_version_extended(verbose, pool),
1224                                          quiet, verbose, pool));
1225    }
1226  else if (os && !targets->nelts)            /* `-h', `--help', or `help' */
1227    svn_opt_print_generic_help2(header,
1228                                cmd_table,
1229                                option_table,
1230                                footer,
1231                                pool,
1232                                stdout);
1233  else                                       /* unknown option or cmd */
1234    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1235                                _("Type '%s help' for usage.\n"), pgm_name));
1236
1237  return SVN_NO_ERROR;
1238}
1239