1251881Speter/* 2251881Speter * props.c: Utility functions for property handling 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include <stdlib.h> 31251881Speter 32251881Speter#include <apr_hash.h> 33251881Speter#include "svn_hash.h" 34251881Speter#include "svn_cmdline.h" 35251881Speter#include "svn_string.h" 36251881Speter#include "svn_error.h" 37251881Speter#include "svn_sorts.h" 38251881Speter#include "svn_subst.h" 39251881Speter#include "svn_props.h" 40251881Speter#include "svn_string.h" 41251881Speter#include "svn_opt.h" 42251881Speter#include "svn_xml.h" 43251881Speter#include "svn_base64.h" 44251881Speter#include "cl.h" 45251881Speter 46251881Speter#include "private/svn_string_private.h" 47251881Speter#include "private/svn_cmdline_private.h" 48251881Speter 49251881Speter#include "svn_private_config.h" 50251881Speter 51251881Speter 52251881Spetersvn_error_t * 53251881Spetersvn_cl__revprop_prepare(const svn_opt_revision_t *revision, 54251881Speter const apr_array_header_t *targets, 55251881Speter const char **URL, 56251881Speter svn_client_ctx_t *ctx, 57251881Speter apr_pool_t *pool) 58251881Speter{ 59251881Speter const char *target; 60251881Speter 61251881Speter if (revision->kind != svn_opt_revision_number 62251881Speter && revision->kind != svn_opt_revision_date 63251881Speter && revision->kind != svn_opt_revision_head) 64251881Speter return svn_error_create 65251881Speter (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 66251881Speter _("Must specify the revision as a number, a date or 'HEAD' " 67251881Speter "when operating on a revision property")); 68251881Speter 69251881Speter /* There must be exactly one target at this point. If it was optional and 70251881Speter unspecified by the user, the caller has already added the implicit '.'. */ 71251881Speter if (targets->nelts != 1) 72251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 73251881Speter _("Wrong number of targets specified")); 74251881Speter 75251881Speter /* (The docs say the target must be either a URL or implicit '.', but 76251881Speter explicit WC targets are also accepted.) */ 77251881Speter target = APR_ARRAY_IDX(targets, 0, const char *); 78251881Speter SVN_ERR(svn_client_url_from_path2(URL, target, ctx, pool, pool)); 79251881Speter if (*URL == NULL) 80251881Speter return svn_error_create 81251881Speter (SVN_ERR_UNVERSIONED_RESOURCE, NULL, 82251881Speter _("Either a URL or versioned item is required")); 83251881Speter 84251881Speter return SVN_NO_ERROR; 85251881Speter} 86251881Speter 87251881Spetervoid 88251881Spetersvn_cl__check_boolean_prop_val(const char *propname, const char *propval, 89251881Speter apr_pool_t *pool) 90251881Speter{ 91251881Speter svn_stringbuf_t *propbuf; 92251881Speter 93251881Speter if (!svn_prop_is_boolean(propname)) 94251881Speter return; 95251881Speter 96251881Speter propbuf = svn_stringbuf_create(propval, pool); 97251881Speter svn_stringbuf_strip_whitespace(propbuf); 98251881Speter 99251881Speter if (propbuf->data[0] == '\0' 100251881Speter || svn_cstring_casecmp(propbuf->data, "0") == 0 101251881Speter || svn_cstring_casecmp(propbuf->data, "no") == 0 102251881Speter || svn_cstring_casecmp(propbuf->data, "off") == 0 103251881Speter || svn_cstring_casecmp(propbuf->data, "false") == 0) 104251881Speter { 105251881Speter svn_error_t *err = svn_error_createf 106251881Speter (SVN_ERR_BAD_PROPERTY_VALUE, NULL, 107251881Speter _("To turn off the %s property, use 'svn propdel';\n" 108251881Speter "setting the property to '%s' will not turn it off."), 109251881Speter propname, propval); 110251881Speter svn_handle_warning2(stderr, err, "svn: "); 111251881Speter svn_error_clear(err); 112251881Speter } 113251881Speter} 114251881Speter 115251881Speter 116251881Speter/* Context for sorting property names */ 117251881Speterstruct simprop_context_t 118251881Speter{ 119251881Speter svn_string_t name; /* The name of the property we're comparing with */ 120251881Speter svn_membuf_t buffer; /* Buffer for similarity testing */ 121251881Speter}; 122251881Speter 123251881Speterstruct simprop_t 124251881Speter{ 125251881Speter const char *propname; /* The original svn: property name */ 126251881Speter svn_string_t name; /* The property name without the svn: prefix */ 127251881Speter unsigned int score; /* The similarity score */ 128251881Speter apr_size_t diff; /* Number of chars different from context.name */ 129251881Speter struct simprop_context_t *context; /* Sorting context for qsort() */ 130251881Speter}; 131251881Speter 132251881Speter/* Similarity test between two property names */ 133251881Speterstatic APR_INLINE unsigned int 134251881Spetersimprop_key_diff(const svn_string_t *key, const svn_string_t *ctx, 135251881Speter svn_membuf_t *buffer, apr_size_t *diff) 136251881Speter{ 137251881Speter apr_size_t lcs; 138251881Speter const unsigned int score = svn_string__similarity(key, ctx, buffer, &lcs); 139251881Speter if (key->len > ctx->len) 140251881Speter *diff = key->len - lcs; 141251881Speter else 142251881Speter *diff = ctx->len - lcs; 143251881Speter return score; 144251881Speter} 145251881Speter 146251881Speter/* Key comparator for qsort for simprop_t */ 147251881Speterstatic int 148251881Spetersimprop_compare(const void *pkeya, const void *pkeyb) 149251881Speter{ 150251881Speter struct simprop_t *const keya = *(struct simprop_t *const *)pkeya; 151251881Speter struct simprop_t *const keyb = *(struct simprop_t *const *)pkeyb; 152251881Speter struct simprop_context_t *const context = keya->context; 153251881Speter 154251881Speter if (keya->score == -1) 155251881Speter keya->score = simprop_key_diff(&keya->name, &context->name, 156251881Speter &context->buffer, &keya->diff); 157251881Speter if (keyb->score == -1) 158251881Speter keyb->score = simprop_key_diff(&keyb->name, &context->name, 159251881Speter &context->buffer, &keyb->diff); 160251881Speter 161251881Speter return (keya->score < keyb->score ? 1 162251881Speter : (keya->score > keyb->score ? -1 163251881Speter : (keya->diff > keyb->diff ? 1 164251881Speter : (keya->diff < keyb->diff ? -1 : 0)))); 165251881Speter} 166251881Speter 167251881Speter 168251881Speterstatic const char* 169251881Speterforce_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name, 170251881Speter apr_pool_t *scratch_pool) 171251881Speter{ 172251881Speter switch (prop_use) 173251881Speter { 174251881Speter case svn_cl__prop_use_set: 175251881Speter return apr_psprintf( 176251881Speter scratch_pool, 177251881Speter _("(To set the '%s' property, re-run with '--force'.)"), 178251881Speter prop_name); 179251881Speter case svn_cl__prop_use_edit: 180251881Speter return apr_psprintf( 181251881Speter scratch_pool, 182251881Speter _("(To edit the '%s' property, re-run with '--force'.)"), 183251881Speter prop_name); 184251881Speter case svn_cl__prop_use_use: 185251881Speter default: 186251881Speter return apr_psprintf( 187251881Speter scratch_pool, 188251881Speter _("(To use the '%s' property, re-run with '--force'.)"), 189251881Speter prop_name); 190251881Speter } 191251881Speter} 192251881Speter 193251881Speterstatic const char* 194251881Speterwrong_prop_error_message(svn_cl__prop_use_t prop_use, const char *prop_name, 195251881Speter apr_pool_t *scratch_pool) 196251881Speter{ 197251881Speter switch (prop_use) 198251881Speter { 199251881Speter case svn_cl__prop_use_set: 200251881Speter return apr_psprintf( 201251881Speter scratch_pool, 202251881Speter _("'%s' is not a valid %s property name;" 203251881Speter " re-run with '--force' to set it"), 204251881Speter prop_name, SVN_PROP_PREFIX); 205251881Speter case svn_cl__prop_use_edit: 206251881Speter return apr_psprintf( 207251881Speter scratch_pool, 208251881Speter _("'%s' is not a valid %s property name;" 209251881Speter " re-run with '--force' to edit it"), 210251881Speter prop_name, SVN_PROP_PREFIX); 211251881Speter case svn_cl__prop_use_use: 212251881Speter default: 213251881Speter return apr_psprintf( 214251881Speter scratch_pool, 215251881Speter _("'%s' is not a valid %s property name;" 216251881Speter " re-run with '--force' to use it"), 217251881Speter prop_name, SVN_PROP_PREFIX); 218251881Speter } 219251881Speter} 220251881Speter 221251881Spetersvn_error_t * 222251881Spetersvn_cl__check_svn_prop_name(const char *propname, 223251881Speter svn_boolean_t revprop, 224251881Speter svn_cl__prop_use_t prop_use, 225251881Speter apr_pool_t *scratch_pool) 226251881Speter{ 227251881Speter static const char *const nodeprops[] = 228251881Speter { 229251881Speter SVN_PROP_NODE_ALL_PROPS 230251881Speter }; 231251881Speter static const apr_size_t nodeprops_len = sizeof(nodeprops)/sizeof(*nodeprops); 232251881Speter 233251881Speter static const char *const revprops[] = 234251881Speter { 235251881Speter SVN_PROP_REVISION_ALL_PROPS 236251881Speter }; 237251881Speter static const apr_size_t revprops_len = sizeof(revprops)/sizeof(*revprops); 238251881Speter 239251881Speter const char *const *const proplist = (revprop ? revprops : nodeprops); 240251881Speter const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len); 241251881Speter 242251881Speter struct simprop_t **propkeys; 243251881Speter struct simprop_t *propbuf; 244251881Speter apr_size_t i; 245251881Speter 246251881Speter struct simprop_context_t context; 247251881Speter svn_string_t prefix; 248251881Speter 249251881Speter context.name.data = propname; 250251881Speter context.name.len = strlen(propname); 251251881Speter prefix.data = SVN_PROP_PREFIX; 252251881Speter prefix.len = strlen(SVN_PROP_PREFIX); 253251881Speter 254251881Speter svn_membuf__create(&context.buffer, 0, scratch_pool); 255251881Speter 256251881Speter /* First, check if the name is even close to being in the svn: namespace. 257251881Speter It must contain a colon in the right place, and we only allow 258251881Speter one-char typos or a single transposition. */ 259251881Speter if (context.name.len < prefix.len 260251881Speter || context.name.data[prefix.len - 1] != prefix.data[prefix.len - 1]) 261251881Speter return SVN_NO_ERROR; /* Wrong prefix, ignore */ 262251881Speter else 263251881Speter { 264251881Speter apr_size_t lcs; 265251881Speter const apr_size_t name_len = context.name.len; 266251881Speter context.name.len = prefix.len; /* Only check up to the prefix length */ 267251881Speter svn_string__similarity(&context.name, &prefix, &context.buffer, &lcs); 268251881Speter context.name.len = name_len; /* Restore the original propname length */ 269251881Speter if (lcs < prefix.len - 1) 270251881Speter return SVN_NO_ERROR; /* Wrong prefix, ignore */ 271251881Speter 272251881Speter /* If the prefix is slightly different, the rest must be 273251881Speter identical in order to trigger the error. */ 274251881Speter if (lcs == prefix.len - 1) 275251881Speter { 276251881Speter for (i = 0; i < numprops; ++i) 277251881Speter { 278251881Speter if (0 == strcmp(proplist[i] + prefix.len, propname + prefix.len)) 279251881Speter return svn_error_createf( 280251881Speter SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 281251881Speter _("'%s' is not a valid %s property name;" 282251881Speter " did you mean '%s'?\n%s"), 283251881Speter propname, SVN_PROP_PREFIX, proplist[i], 284251881Speter force_prop_option_message(prop_use, propname, scratch_pool)); 285251881Speter } 286251881Speter return SVN_NO_ERROR; 287251881Speter } 288251881Speter } 289251881Speter 290251881Speter /* Now find the closest match from amongst the set of reserved 291251881Speter node or revision property names. Skip the prefix while matching, 292251881Speter we already know that it's the same and looking at it would only 293251881Speter skew the results. */ 294251881Speter propkeys = apr_palloc(scratch_pool, 295251881Speter numprops * sizeof(struct simprop_t*)); 296251881Speter propbuf = apr_palloc(scratch_pool, 297251881Speter numprops * sizeof(struct simprop_t)); 298251881Speter context.name.data += prefix.len; 299251881Speter context.name.len -= prefix.len; 300251881Speter for (i = 0; i < numprops; ++i) 301251881Speter { 302251881Speter propkeys[i] = &propbuf[i]; 303251881Speter propbuf[i].propname = proplist[i]; 304251881Speter propbuf[i].name.data = proplist[i] + prefix.len; 305251881Speter propbuf[i].name.len = strlen(propbuf[i].name.data); 306251881Speter propbuf[i].score = (unsigned int)-1; 307251881Speter propbuf[i].context = &context; 308251881Speter } 309251881Speter 310251881Speter qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare); 311251881Speter 312251881Speter if (0 == propkeys[0]->diff) 313251881Speter return SVN_NO_ERROR; /* We found an exact match. */ 314251881Speter 315251881Speter /* See if we can suggest a sane alternative spelling */ 316251881Speter for (i = 0; i < numprops; ++i) 317251881Speter if (propkeys[i]->score < 666) /* 2/3 similarity required */ 318251881Speter break; 319251881Speter 320251881Speter switch (i) 321251881Speter { 322251881Speter case 0: 323251881Speter /* The best alternative isn't good enough */ 324251881Speter return svn_error_create( 325251881Speter SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 326251881Speter wrong_prop_error_message(prop_use, propname, scratch_pool)); 327251881Speter 328251881Speter case 1: 329251881Speter /* There is only one good candidate */ 330251881Speter return svn_error_createf( 331251881Speter SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 332251881Speter _("'%s' is not a valid %s property name; did you mean '%s'?\n%s"), 333251881Speter propname, SVN_PROP_PREFIX, propkeys[0]->propname, 334251881Speter force_prop_option_message(prop_use, propname, scratch_pool)); 335251881Speter 336251881Speter case 2: 337251881Speter /* Suggest a list of the most likely candidates */ 338251881Speter return svn_error_createf( 339251881Speter SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 340251881Speter _("'%s' is not a valid %s property name\n" 341251881Speter "Did you mean '%s' or '%s'?\n%s"), 342251881Speter propname, SVN_PROP_PREFIX, 343251881Speter propkeys[0]->propname, propkeys[1]->propname, 344251881Speter force_prop_option_message(prop_use, propname, scratch_pool)); 345251881Speter 346251881Speter default: 347251881Speter /* Never suggest more than three candidates */ 348251881Speter return svn_error_createf( 349251881Speter SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 350251881Speter _("'%s' is not a valid %s property name\n" 351251881Speter "Did you mean '%s', '%s' or '%s'?\n%s"), 352251881Speter propname, SVN_PROP_PREFIX, 353251881Speter propkeys[0]->propname, propkeys[1]->propname, propkeys[2]->propname, 354251881Speter force_prop_option_message(prop_use, propname, scratch_pool)); 355251881Speter } 356251881Speter} 357