props.c revision 299742
1/*
2 * props.c: Utility functions for property handling
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
27
28/*** Includes. ***/
29
30#include <stdlib.h>
31
32#include <apr_hash.h>
33#include "svn_hash.h"
34#include "svn_cmdline.h"
35#include "svn_string.h"
36#include "svn_error.h"
37#include "svn_sorts.h"
38#include "svn_subst.h"
39#include "svn_props.h"
40#include "svn_string.h"
41#include "svn_opt.h"
42#include "svn_xml.h"
43#include "svn_base64.h"
44#include "cl.h"
45
46#include "private/svn_string_private.h"
47#include "private/svn_cmdline_private.h"
48
49#include "svn_private_config.h"
50
51
52svn_error_t *
53svn_cl__revprop_prepare(const svn_opt_revision_t *revision,
54                        const apr_array_header_t *targets,
55                        const char **URL,
56                        svn_client_ctx_t *ctx,
57                        apr_pool_t *pool)
58{
59  const char *target;
60
61  if (revision->kind != svn_opt_revision_number
62      && revision->kind != svn_opt_revision_date
63      && revision->kind != svn_opt_revision_head)
64    return svn_error_create
65      (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
66       _("Must specify the revision as a number, a date or 'HEAD' "
67         "when operating on a revision property"));
68
69  /* There must be exactly one target at this point.  If it was optional and
70     unspecified by the user, the caller has already added the implicit '.'. */
71  if (targets->nelts != 1)
72    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
73                            _("Wrong number of targets specified"));
74
75  /* (The docs say the target must be either a URL or implicit '.', but
76     explicit WC targets are also accepted.) */
77  target = APR_ARRAY_IDX(targets, 0, const char *);
78  SVN_ERR(svn_client_url_from_path2(URL, target, ctx, pool, pool));
79  if (*URL == NULL)
80    return svn_error_create
81      (SVN_ERR_UNVERSIONED_RESOURCE, NULL,
82       _("Either a URL or versioned item is required"));
83
84  return SVN_NO_ERROR;
85}
86
87void
88svn_cl__check_boolean_prop_val(const char *propname, const char *propval,
89                               apr_pool_t *pool)
90{
91  svn_stringbuf_t *propbuf;
92
93  if (!svn_prop_is_boolean(propname))
94    return;
95
96  propbuf = svn_stringbuf_create(propval, pool);
97  svn_stringbuf_strip_whitespace(propbuf);
98
99  if (propbuf->data[0] == '\0'
100      || svn_cstring_casecmp(propbuf->data, "0") == 0
101      || svn_cstring_casecmp(propbuf->data, "no") == 0
102      || svn_cstring_casecmp(propbuf->data, "off") == 0
103      || svn_cstring_casecmp(propbuf->data, "false") == 0)
104    {
105      svn_error_t *err = svn_error_createf
106        (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
107         _("To turn off the %s property, use 'svn propdel';\n"
108           "setting the property to '%s' will not turn it off."),
109           propname, propval);
110      svn_handle_warning2(stderr, err, "svn: ");
111      svn_error_clear(err);
112    }
113}
114
115static const char*
116force_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name,
117                          apr_pool_t *scratch_pool)
118{
119  switch (prop_use)
120    {
121    case svn_cl__prop_use_set:
122      return apr_psprintf(
123          scratch_pool,
124          _("Use '--force' to set the '%s' property."),
125          prop_name);
126    case svn_cl__prop_use_edit:
127      return apr_psprintf(
128          scratch_pool,
129          _("Use '--force' to edit the '%s' property."),
130          prop_name);
131    case svn_cl__prop_use_use:
132    default:
133      return apr_psprintf(
134          scratch_pool,
135          _("Use '--force' to use the '%s' property'."),
136          prop_name);
137    }
138}
139
140static const char*
141wrong_prop_error_message(svn_cl__prop_use_t prop_use, const char *prop_name,
142                         apr_pool_t *scratch_pool)
143{
144  switch (prop_use)
145    {
146    case svn_cl__prop_use_set:
147      return apr_psprintf(
148          scratch_pool,
149          _("'%s' is not a valid %s property name; use '--force' to set it"),
150          prop_name, SVN_PROP_PREFIX);
151    case svn_cl__prop_use_edit:
152      return apr_psprintf(
153          scratch_pool,
154          _("'%s' is not a valid %s property name; use '--force' to edit it"),
155          prop_name, SVN_PROP_PREFIX);
156    case svn_cl__prop_use_use:
157    default:
158      return apr_psprintf(
159          scratch_pool,
160          _("'%s' is not a valid %s property name; use '--force' to use it"),
161          prop_name, SVN_PROP_PREFIX);
162    }
163}
164
165svn_error_t *
166svn_cl__check_svn_prop_name(const char *propname,
167                            svn_boolean_t revprop,
168                            svn_cl__prop_use_t prop_use,
169                            apr_pool_t *scratch_pool)
170{
171  static const char *const nodeprops[] =
172    {
173      SVN_PROP_NODE_ALL_PROPS
174    };
175  static const apr_size_t nodeprops_len = sizeof(nodeprops)/sizeof(*nodeprops);
176
177  static const char *const revprops[] =
178    {
179      SVN_PROP_REVISION_ALL_PROPS
180    };
181  static const apr_size_t revprops_len = sizeof(revprops)/sizeof(*revprops);
182
183  const char *const *const proplist = (revprop ? revprops : nodeprops);
184  const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len);
185
186  svn_cl__simcheck_t **propkeys;
187  svn_cl__simcheck_t *propbuf;
188  apr_size_t i;
189
190  svn_string_t propstring;
191  svn_string_t prefix;
192  svn_membuf_t buffer;
193
194  propstring.data = propname;
195  propstring.len = strlen(propname);
196  prefix.data = SVN_PROP_PREFIX;
197  prefix.len = strlen(SVN_PROP_PREFIX);
198
199  svn_membuf__create(&buffer, 0, scratch_pool);
200
201  /* First, check if the name is even close to being in the svn: namespace.
202     It must contain a colon in the right place, and we only allow
203     one-char typos or a single transposition. */
204  if (propstring.len < prefix.len
205      || propstring.data[prefix.len - 1] != prefix.data[prefix.len - 1])
206    return SVN_NO_ERROR;        /* Wrong prefix, ignore */
207  else
208    {
209      apr_size_t lcs;
210      const apr_size_t name_len = propstring.len;
211      propstring.len = prefix.len; /* Only check up to the prefix length */
212      svn_string__similarity(&propstring, &prefix, &buffer, &lcs);
213      propstring.len = name_len; /* Restore the original propname length */
214      if (lcs < prefix.len - 1)
215        return SVN_NO_ERROR;    /* Wrong prefix, ignore */
216
217      /* If the prefix is slightly different, the rest must be
218         identical in order to trigger the error. */
219      if (lcs == prefix.len - 1)
220        {
221          for (i = 0; i < numprops; ++i)
222            {
223              if (0 == strcmp(proplist[i] + prefix.len, propname + prefix.len))
224                return svn_error_quick_wrap(svn_error_createf(
225                  SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
226                  _("'%s' is not a valid %s property name;"
227                    " did you mean '%s'?"),
228                  propname, SVN_PROP_PREFIX, proplist[i]),
229                  force_prop_option_message(prop_use, propname, scratch_pool));
230            }
231          return SVN_NO_ERROR;
232        }
233    }
234
235  /* Now find the closest match from amongst the set of reserved
236     node or revision property names. Skip the prefix while matching,
237     we already know that it's the same and looking at it would only
238     skew the results. */
239  propkeys = apr_palloc(scratch_pool,
240                        numprops * sizeof(svn_cl__simcheck_t*));
241  propbuf = apr_palloc(scratch_pool,
242                       numprops * sizeof(svn_cl__simcheck_t));
243  propstring.data += prefix.len;
244  propstring.len -= prefix.len;
245  for (i = 0; i < numprops; ++i)
246    {
247      propkeys[i] = &propbuf[i];
248      propbuf[i].token.data = proplist[i] + prefix.len;
249      propbuf[i].token.len = strlen(propbuf[i].token.data);
250      propbuf[i].data = proplist[i];
251    }
252
253  switch (svn_cl__similarity_check(
254              propstring.data, propkeys, numprops, scratch_pool))
255    {
256    case 0:
257      return SVN_NO_ERROR;      /* We found an exact match. */
258
259    case 1:
260      /* The best alternative isn't good enough */
261      return svn_error_create(
262        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
263        wrong_prop_error_message(prop_use, propname, scratch_pool));
264
265    case 2:
266      /* There is only one good candidate */
267      return svn_error_quick_wrap(svn_error_createf(
268        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
269        _("'%s' is not a valid %s property name; did you mean '%s'?"),
270        propname, SVN_PROP_PREFIX,
271        (const char *)propkeys[0]->data),
272        force_prop_option_message(prop_use, propname, scratch_pool));
273
274    case 3:
275      /* Suggest a list of the most likely candidates */
276      return svn_error_quick_wrap(svn_error_createf(
277        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
278        _("'%s' is not a valid %s property name; "
279          "did you mean '%s' or '%s'?"),
280        propname, SVN_PROP_PREFIX,
281        (const char *)propkeys[0]->data, (const char *)propkeys[1]->data),
282        force_prop_option_message(prop_use, propname, scratch_pool));
283
284    default:
285      /* Never suggest more than three candidates */
286      return svn_error_quick_wrap(svn_error_createf(
287        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
288        _("'%s' is not a valid %s property name; "
289          "did you mean '%s', '%s' or '%s'?"),
290        propname, SVN_PROP_PREFIX,
291        (const char *)propkeys[0]->data,
292        (const char *)propkeys[1]->data, (const char *)propkeys[2]->data),
293        force_prop_option_message(prop_use, propname, scratch_pool));
294    }
295}
296