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 115 116/* Context for sorting property names */ 117struct simprop_context_t 118{ 119 svn_string_t name; /* The name of the property we're comparing with */ 120 svn_membuf_t buffer; /* Buffer for similarity testing */ 121}; 122 123struct simprop_t 124{ 125 const char *propname; /* The original svn: property name */ 126 svn_string_t name; /* The property name without the svn: prefix */ 127 unsigned int score; /* The similarity score */ 128 apr_size_t diff; /* Number of chars different from context.name */ 129 struct simprop_context_t *context; /* Sorting context for qsort() */ 130}; 131 132/* Similarity test between two property names */ 133static APR_INLINE unsigned int 134simprop_key_diff(const svn_string_t *key, const svn_string_t *ctx, 135 svn_membuf_t *buffer, apr_size_t *diff) 136{ 137 apr_size_t lcs; 138 const unsigned int score = svn_string__similarity(key, ctx, buffer, &lcs); 139 if (key->len > ctx->len) 140 *diff = key->len - lcs; 141 else 142 *diff = ctx->len - lcs; 143 return score; 144} 145 146/* Key comparator for qsort for simprop_t */ 147static int 148simprop_compare(const void *pkeya, const void *pkeyb) 149{ 150 struct simprop_t *const keya = *(struct simprop_t *const *)pkeya; 151 struct simprop_t *const keyb = *(struct simprop_t *const *)pkeyb; 152 struct simprop_context_t *const context = keya->context; 153 154 if (keya->score == -1) 155 keya->score = simprop_key_diff(&keya->name, &context->name, 156 &context->buffer, &keya->diff); 157 if (keyb->score == -1) 158 keyb->score = simprop_key_diff(&keyb->name, &context->name, 159 &context->buffer, &keyb->diff); 160 161 return (keya->score < keyb->score ? 1 162 : (keya->score > keyb->score ? -1 163 : (keya->diff > keyb->diff ? 1 164 : (keya->diff < keyb->diff ? -1 : 0)))); 165} 166 167 168static const char* 169force_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name, 170 apr_pool_t *scratch_pool) 171{ 172 switch (prop_use) 173 { 174 case svn_cl__prop_use_set: 175 return apr_psprintf( 176 scratch_pool, 177 _("(To set the '%s' property, re-run with '--force'.)"), 178 prop_name); 179 case svn_cl__prop_use_edit: 180 return apr_psprintf( 181 scratch_pool, 182 _("(To edit the '%s' property, re-run with '--force'.)"), 183 prop_name); 184 case svn_cl__prop_use_use: 185 default: 186 return apr_psprintf( 187 scratch_pool, 188 _("(To use the '%s' property, re-run with '--force'.)"), 189 prop_name); 190 } 191} 192 193static const char* 194wrong_prop_error_message(svn_cl__prop_use_t prop_use, const char *prop_name, 195 apr_pool_t *scratch_pool) 196{ 197 switch (prop_use) 198 { 199 case svn_cl__prop_use_set: 200 return apr_psprintf( 201 scratch_pool, 202 _("'%s' is not a valid %s property name;" 203 " re-run with '--force' to set it"), 204 prop_name, SVN_PROP_PREFIX); 205 case svn_cl__prop_use_edit: 206 return apr_psprintf( 207 scratch_pool, 208 _("'%s' is not a valid %s property name;" 209 " re-run with '--force' to edit it"), 210 prop_name, SVN_PROP_PREFIX); 211 case svn_cl__prop_use_use: 212 default: 213 return apr_psprintf( 214 scratch_pool, 215 _("'%s' is not a valid %s property name;" 216 " re-run with '--force' to use it"), 217 prop_name, SVN_PROP_PREFIX); 218 } 219} 220 221svn_error_t * 222svn_cl__check_svn_prop_name(const char *propname, 223 svn_boolean_t revprop, 224 svn_cl__prop_use_t prop_use, 225 apr_pool_t *scratch_pool) 226{ 227 static const char *const nodeprops[] = 228 { 229 SVN_PROP_NODE_ALL_PROPS 230 }; 231 static const apr_size_t nodeprops_len = sizeof(nodeprops)/sizeof(*nodeprops); 232 233 static const char *const revprops[] = 234 { 235 SVN_PROP_REVISION_ALL_PROPS 236 }; 237 static const apr_size_t revprops_len = sizeof(revprops)/sizeof(*revprops); 238 239 const char *const *const proplist = (revprop ? revprops : nodeprops); 240 const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len); 241 242 struct simprop_t **propkeys; 243 struct simprop_t *propbuf; 244 apr_size_t i; 245 246 struct simprop_context_t context; 247 svn_string_t prefix; 248 249 context.name.data = propname; 250 context.name.len = strlen(propname); 251 prefix.data = SVN_PROP_PREFIX; 252 prefix.len = strlen(SVN_PROP_PREFIX); 253 254 svn_membuf__create(&context.buffer, 0, scratch_pool); 255 256 /* First, check if the name is even close to being in the svn: namespace. 257 It must contain a colon in the right place, and we only allow 258 one-char typos or a single transposition. */ 259 if (context.name.len < prefix.len 260 || context.name.data[prefix.len - 1] != prefix.data[prefix.len - 1]) 261 return SVN_NO_ERROR; /* Wrong prefix, ignore */ 262 else 263 { 264 apr_size_t lcs; 265 const apr_size_t name_len = context.name.len; 266 context.name.len = prefix.len; /* Only check up to the prefix length */ 267 svn_string__similarity(&context.name, &prefix, &context.buffer, &lcs); 268 context.name.len = name_len; /* Restore the original propname length */ 269 if (lcs < prefix.len - 1) 270 return SVN_NO_ERROR; /* Wrong prefix, ignore */ 271 272 /* If the prefix is slightly different, the rest must be 273 identical in order to trigger the error. */ 274 if (lcs == prefix.len - 1) 275 { 276 for (i = 0; i < numprops; ++i) 277 { 278 if (0 == strcmp(proplist[i] + prefix.len, propname + prefix.len)) 279 return svn_error_createf( 280 SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 281 _("'%s' is not a valid %s property name;" 282 " did you mean '%s'?\n%s"), 283 propname, SVN_PROP_PREFIX, proplist[i], 284 force_prop_option_message(prop_use, propname, scratch_pool)); 285 } 286 return SVN_NO_ERROR; 287 } 288 } 289 290 /* Now find the closest match from amongst the set of reserved 291 node or revision property names. Skip the prefix while matching, 292 we already know that it's the same and looking at it would only 293 skew the results. */ 294 propkeys = apr_palloc(scratch_pool, 295 numprops * sizeof(struct simprop_t*)); 296 propbuf = apr_palloc(scratch_pool, 297 numprops * sizeof(struct simprop_t)); 298 context.name.data += prefix.len; 299 context.name.len -= prefix.len; 300 for (i = 0; i < numprops; ++i) 301 { 302 propkeys[i] = &propbuf[i]; 303 propbuf[i].propname = proplist[i]; 304 propbuf[i].name.data = proplist[i] + prefix.len; 305 propbuf[i].name.len = strlen(propbuf[i].name.data); 306 propbuf[i].score = (unsigned int)-1; 307 propbuf[i].context = &context; 308 } 309 310 qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare); 311 312 if (0 == propkeys[0]->diff) 313 return SVN_NO_ERROR; /* We found an exact match. */ 314 315 /* See if we can suggest a sane alternative spelling */ 316 for (i = 0; i < numprops; ++i) 317 if (propkeys[i]->score < 666) /* 2/3 similarity required */ 318 break; 319 320 switch (i) 321 { 322 case 0: 323 /* The best alternative isn't good enough */ 324 return svn_error_create( 325 SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 326 wrong_prop_error_message(prop_use, propname, scratch_pool)); 327 328 case 1: 329 /* There is only one good candidate */ 330 return svn_error_createf( 331 SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 332 _("'%s' is not a valid %s property name; did you mean '%s'?\n%s"), 333 propname, SVN_PROP_PREFIX, propkeys[0]->propname, 334 force_prop_option_message(prop_use, propname, scratch_pool)); 335 336 case 2: 337 /* Suggest a list of the most likely candidates */ 338 return svn_error_createf( 339 SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 340 _("'%s' is not a valid %s property name\n" 341 "Did you mean '%s' or '%s'?\n%s"), 342 propname, SVN_PROP_PREFIX, 343 propkeys[0]->propname, propkeys[1]->propname, 344 force_prop_option_message(prop_use, propname, scratch_pool)); 345 346 default: 347 /* Never suggest more than three candidates */ 348 return svn_error_createf( 349 SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 350 _("'%s' is not a valid %s property name\n" 351 "Did you mean '%s', '%s' or '%s'?\n%s"), 352 propname, SVN_PROP_PREFIX, 353 propkeys[0]->propname, propkeys[1]->propname, propkeys[2]->propname, 354 force_prop_option_message(prop_use, propname, scratch_pool)); 355 } 356} 357