=== subversion/include/svn_wc.h ================================================================== --- subversion/include/svn_wc.h (revision 21716) +++ subversion/include/svn_wc.h (patch - level 1) @@ -2907,8 +2907,53 @@ /** Return true iff @a name is a 'entry' property name. */ svn_boolean_t svn_wc_is_entry_prop(const char *name); +/** Callback type used by @c svn_wc_canonicalize_svn_prop. + * + * It should set @a mime_type to the value of @a SVN_PROP_MIME_TYPE + * for the path passed to @c svn_prop_mime_type (allocated from @a + * pool), and then write the contents of the file to @a stream. + * + * (Currently, this is used if you are attempting to set the @a + * SVN_PROP_EOL_STYLE property, to make sure that the value matches + * the mime type and contents.) + */ +typedef svn_error_t *(*svn_wc_canonicalize_svn_prop_get_file_t) + (const svn_string_t **mime_type, + svn_stream_t *stream, + void *baton, + apr_pool_t *pool); +/** Canonicalize the value of an svn:* property @a propname with + * value @propval. + * + * If the property is not appropriate for a node of kind @a kind, or + * is otherwise invalid, throw an error. Otherwise, set @a *propval_p + * to a canonicalized version of the property value. If @a + * skip_some_checks is true, only some validity checks are taken. + * + * Some validity checks require access to the contents and MIME type + * of the target if it is a file; they will call @a getter with @a + * getter_baton, which then needs to set the MIME type and print the + * contents of the file to the given stream. + * + * @a path should be the path of the file in question; it is only used + * for error messages. + * + * ### This is not actually related to the WC, but it does need to call + * ### svn_wc_parse_externals_description2. + */ +svn_error_t *svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p, + const char *propname, + const svn_string_t *propval, + const char *path, + svn_node_kind_t kind, + svn_boolean_t skip_some_checks, + svn_wc_canonicalize_svn_prop_get_file_t getter, + void *getter_baton, + apr_pool_t *pool); + + /* Diffs */ === subversion/libsvn_wc/props.c ================================================================== --- subversion/libsvn_wc/props.c (revision 21716) +++ subversion/libsvn_wc/props.c (patch - level 1) @@ -1400,6 +1400,7 @@ SVN_PROP_NEEDS_LOCK, NULL }; const char **node_kind_prohibit; + const char *path_display = svn_path_is_url(path) ? path : svn_path_local_style(path, pool); switch (node_kind) { @@ -1409,7 +1410,7 @@ if (strcmp(name, *node_kind_prohibit++) == 0) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("Cannot set '%s' on a directory ('%s')"), - name, svn_path_local_style(path, pool)); + name, path_display); break; case svn_node_file: node_kind_prohibit = file_prohibit; @@ -1418,62 +1419,92 @@ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("Cannot set '%s' on a file ('%s')"), name, - svn_path_local_style(path, pool)); + path_display); break; default: return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, _("'%s' is not a file or directory"), - svn_path_local_style(path, pool)); + path_display); } return SVN_NO_ERROR; } +struct getter_baton { + const char *path; + svn_wc_adm_access_t *adm_access; +}; + + static svn_error_t * -validate_eol_prop_against_file(const char *path, - svn_wc_adm_access_t *adm_access, - apr_pool_t *pool) +get_file_for_validation(const svn_string_t **mime_type, + svn_stream_t *stream, + void *baton, + apr_pool_t *pool) { + struct getter_baton *gb = baton; apr_file_t *fp; - svn_stream_t *read_stream, *write_stream; - svn_error_t *err; - const svn_string_t *mime_type; + svn_stream_t *read_stream; - /* See if this file has been determined to be binary. */ - SVN_ERR(svn_wc_prop_get(&mime_type, SVN_PROP_MIME_TYPE, path, adm_access, + SVN_ERR(svn_wc_prop_get(mime_type, SVN_PROP_MIME_TYPE, gb->path, gb->adm_access, pool)); - if (mime_type && svn_mime_type_is_binary(mime_type->data)) - return svn_error_createf - (SVN_ERR_ILLEGAL_TARGET, NULL, - _("File '%s' has binary mime type property"), - svn_path_local_style(path, pool)); /* Open PATH. */ - SVN_ERR(svn_io_file_open(&fp, path, + SVN_ERR(svn_io_file_open(&fp, gb->path, (APR_READ | APR_BINARY | APR_BUFFERED), 0, pool)); /* Get a READ_STREAM from the file we just opened. */ read_stream = svn_stream_from_aprfile(fp, pool); - /* Now, make an empty WRITE_STREAM. */ - write_stream = svn_stream_empty(pool); + /* Copy from the file into the translating stream. */ + SVN_ERR(svn_stream_copy(read_stream, stream, pool)); - /* Do a newline translation. Of course, all we really care about - here is whether or not the function fails on inconsistent line - endings. The function is "translating" to an empty stream. This - is sneeeeeeeeeeeaky. */ - err = svn_subst_translate_stream3(read_stream, write_stream, - "", FALSE, NULL, FALSE, pool); + SVN_ERR(svn_stream_close(read_stream)); + SVN_ERR(svn_io_file_close(fp, pool)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +validate_eol_prop_against_file(const char *path, + svn_wc_canonicalize_svn_prop_get_file_t getter, + void *getter_baton, + apr_pool_t *pool) +{ + svn_stream_t *translating_stream; + svn_error_t *err; + const svn_string_t *mime_type; + const char *path_display = svn_path_is_url(path) ? path : svn_path_local_style(path, pool); + + /* The "getter" will do a newline translation. All we really care + about here is whether or not the function fails on inconsistent + line endings. The function is "translating" to an empty stream. + This is sneeeeeeeeeeeaky. */ + translating_stream = svn_subst_stream_translated(svn_stream_empty(pool), + "", FALSE, NULL, FALSE, pool); + + err = getter(&mime_type, translating_stream, getter_baton, pool); + + if (!err) + err = svn_stream_close(translating_stream); + if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err, _("File '%s' has inconsistent newlines"), - svn_path_local_style(path, pool)); + path_display); else if (err) return err; - SVN_ERR(svn_io_file_close(fp, pool)); + /* See if this file has been determined to be binary. */ + if (mime_type && svn_mime_type_is_binary(mime_type->data)) + return svn_error_createf + (SVN_ERR_ILLEGAL_TARGET, NULL, + _("File '%s' has binary mime type property"), + path_display); + return SVN_NO_ERROR; } @@ -1488,7 +1519,6 @@ { svn_error_t *err; apr_hash_t *prophash, *base_prophash; - svn_stringbuf_t *new_value = NULL; enum svn_prop_kind prop_kind = svn_property_kind(NULL, name); const char *base_name; svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool); @@ -1524,52 +1554,17 @@ inappropriate property is allowed, however, since older clients allowed (and other clients possibly still allow) setting it in the first place. */ - if (value) + if (value && svn_prop_is_svn_prop(name)) { - SVN_ERR(validate_prop_against_node_kind(name, path, entry->kind, - pool)); - if (!skip_checks && (strcmp(name, SVN_PROP_EOL_STYLE) == 0)) - { - new_value = svn_stringbuf_create_from_string(value, pool); - svn_stringbuf_strip_whitespace(new_value); - SVN_ERR(validate_eol_prop_against_file(path, adm_access, pool)); - } - else if (!skip_checks && (strcmp(name, SVN_PROP_MIME_TYPE) == 0)) - { - new_value = svn_stringbuf_create_from_string(value, pool); - svn_stringbuf_strip_whitespace(new_value); - SVN_ERR(svn_mime_type_validate(new_value->data, pool)); - } - else if (strcmp(name, SVN_PROP_IGNORE) == 0 - || strcmp(name, SVN_PROP_EXTERNALS) == 0) - { - /* Make sure that the last line ends in a newline */ - if (value->data[value->len - 1] != '\n') - { - new_value = svn_stringbuf_create_from_string(value, pool); - svn_stringbuf_appendbytes(new_value, "\n", 1); - } + const svn_string_t *new_value; + struct getter_baton *gb = apr_pcalloc(pool, sizeof(*gb)); + + gb->path = path; + gb->adm_access = adm_access; - /* Make sure this is a valid externals property. Do not - allow 'skip_checks' to override, as there is no circumstance in - which this is proper (because there is no circumstance in - which Subversion can handle it). */ - if (strcmp(name, SVN_PROP_EXTERNALS) == 0) - { - /* We don't allow "." nor ".." as target directories in - an svn:externals line. As it happens, our parse code - checks for this, so all we have to is invoke it -- - we're not interested in the parsed result, only in - whether or the parsing errored. */ - SVN_ERR(svn_wc_parse_externals_description2 - (NULL, path, value->data, pool)); - } - } - else if (strcmp(name, SVN_PROP_KEYWORDS) == 0) - { - new_value = svn_stringbuf_create_from_string(value, pool); - svn_stringbuf_strip_whitespace(new_value); - } + SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value, path, entry->kind, skip_checks, + get_file_for_validation, gb, pool)); + value = new_value; } if (entry->kind == svn_node_file && strcmp(name, SVN_PROP_EXECUTABLE) == 0) @@ -1619,9 +1614,6 @@ } } - if (new_value) - value = svn_string_create_from_buf(new_value, pool); - err = svn_wc__load_props(&base_prophash, &prophash, adm_access, base_name, pool); if (err) @@ -1688,7 +1680,73 @@ } +svn_error_t * +svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p, + const char *propname, + const svn_string_t *propval, + const char *path, + svn_node_kind_t kind, + svn_boolean_t skip_some_checks, + svn_wc_canonicalize_svn_prop_get_file_t getter, + void *getter_baton, + apr_pool_t *pool) +{ + svn_stringbuf_t *new_value = NULL; + SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool)); + + if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0)) + { + new_value = svn_stringbuf_create_from_string(propval, pool); + svn_stringbuf_strip_whitespace(new_value); + SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton, pool)); + } + else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)) + { + new_value = svn_stringbuf_create_from_string(propval, pool); + svn_stringbuf_strip_whitespace(new_value); + SVN_ERR(svn_mime_type_validate(new_value->data, pool)); + } + else if (strcmp(propname, SVN_PROP_IGNORE) == 0 + || strcmp(propname, SVN_PROP_EXTERNALS) == 0) + { + /* Make sure that the last line ends in a newline */ + if (propval->data[propval->len - 1] != '\n') + { + new_value = svn_stringbuf_create_from_string(propval, pool); + svn_stringbuf_appendbytes(new_value, "\n", 1); + } + + /* Make sure this is a valid externals property. Do not + allow 'skip_some_checks' to override, as there is no circumstance in + which this is proper (because there is no circumstance in + which Subversion can handle it). */ + if (strcmp(propname, SVN_PROP_EXTERNALS) == 0) + { + /* We don't allow "." nor ".." as target directories in + an svn:externals line. As it happens, our parse code + checks for this, so all we have to is invoke it -- + we're not interested in the parsed result, only in + whether or the parsing errored. */ + SVN_ERR(svn_wc_parse_externals_description2 + (NULL, path, propval->data, pool)); + } + } + else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0) + { + new_value = svn_stringbuf_create_from_string(propval, pool); + svn_stringbuf_strip_whitespace(new_value); + } + + if (new_value) + *propval_p = svn_string_create_from_buf(new_value, pool); + else + *propval_p = propval; + + return SVN_NO_ERROR; +} + + svn_boolean_t svn_wc_is_normal_prop(const char *name) { @@ -2020,6 +2078,8 @@ { apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool); int i; + const char *parent_directory_display = svn_path_is_url(parent_directory) ? + parent_directory : svn_path_local_style(parent_directory, pool); if (externals_p) *externals_p = apr_array_make(pool, 1, sizeof(svn_wc_external_item_t *)); @@ -2104,7 +2164,7 @@ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, _("Error parsing %s property on '%s': '%s'"), SVN_PROP_EXTERNALS, - svn_path_local_style(parent_directory, pool), + parent_directory_display, line); } @@ -2118,7 +2178,7 @@ _("Invalid %s property on '%s': " "target involves '.' or '..' or is an absolute path"), SVN_PROP_EXTERNALS, - svn_path_local_style(parent_directory, pool)); + parent_directory_display); } item->url = svn_path_canonicalize(item->url, pool);