svn_error_t * svn_repos__validate_prop(const char *name, const svn_string_t *value, apr_pool_t *pool) { svn_prop_kind_t kind = svn_property_kind2(name); /* Allow deleting any property, even a property we don't allow to set. */ if (value == NULL) return SVN_NO_ERROR; /* Disallow setting non-regular properties. */ if (kind != svn_prop_regular_kind) return svn_error_createf (SVN_ERR_REPOS_BAD_ARGS, NULL, _("Storage of non-regular property '%s' is disallowed through the " "repository interface, and could indicate a bug in your client"), name); /* Validate "svn:" properties. */ if (svn_prop_is_svn_prop(name) && value != NULL) { /* Validate that translated props (e.g., svn:log) are UTF-8 with * LF line endings. */ if (svn_prop_needs_translation(name)) { if (!svn_utf__is_valid(value->data, value->len)) { return svn_error_createf (SVN_ERR_BAD_PROPERTY_VALUE, NULL, _("Cannot accept '%s' property because it is not encoded in " "UTF-8"), name); } /* Disallow inconsistent line ending style, by simply looking for * carriage return characters ('\r'). */ if (strchr(value->data, '\r') != NULL) { return svn_error_createf (SVN_ERR_BAD_PROPERTY_VALUE, NULL, _("Cannot accept non-LF line endings in '%s' property"), name); } } /* "svn:date" should be a valid date. */ if (strcmp(name, SVN_PROP_REVISION_DATE) == 0) { apr_time_t temp; svn_error_t *err; err = svn_time_from_cstring(&temp, value->data, pool); if (err) return svn_error_create(SVN_ERR_BAD_PROPERTY_VALUE, err, NULL); } } return SVN_NO_ERROR; }
svn_boolean_t svn_utf__cstring_is_valid(const char *data) { if (!data) return FALSE; return svn_utf__is_valid(data, strlen(data)); }
/* Make a best effort to convert a X.509 name to a UTF-8 encoded * string and return it. If we can't properly convert just do a * fuzzy conversion so we have something to display. */ static const char * x509name_to_utf8_string(const x509_name *name, apr_pool_t *result_pool) { const svn_string_t *src_string; const svn_string_t *utf8_string; svn_error_t *err; src_string = svn_string_ncreate((const char *)name->val.p, name->val.len, result_pool); switch (name->val.tag) { case ASN1_UTF8_STRING: if (svn_utf__is_valid(src_string->data, src_string->len)) return nul_escape(src_string, result_pool); else /* not a valid UTF-8 string, who knows what it is, * so run it through the fuzzy_escape code. */ return fuzzy_escape(src_string, result_pool); break; /* Both BMP and UNIVERSAL should always be in Big Endian (aka * network byte order). But rumor has it that there are certs * out there with other endianess and even Byte Order Marks. * If we actually run into these, we might need to do something * about it. */ case ASN1_BMP_STRING: if (0 != src_string->len % sizeof(apr_uint16_t)) return fuzzy_escape(src_string, result_pool); err = svn_utf__utf16_to_utf8(&utf8_string, (const void*)(src_string->data), src_string->len / sizeof(apr_uint16_t), TRUE, result_pool, result_pool); break; case ASN1_UNIVERSAL_STRING: if (0 != src_string->len % sizeof(apr_int32_t)) return fuzzy_escape(src_string, result_pool); err = svn_utf__utf32_to_utf8(&utf8_string, (const void*)(src_string->data), src_string->len / sizeof(apr_int32_t), TRUE, result_pool, result_pool); break; /* Despite what all the IETF, ISO, ITU bits say everything out * on the Internet that I can find treats this as ISO-8859-1. * Even the name is misleading, it's not actually T.61. All the * gory details can be found in the Character Sets section of: * https://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt */ case ASN1_T61_STRING: err = latin1_to_utf8(&utf8_string, src_string, result_pool); break; /* This leaves two types out there in the wild. PrintableString, * which is just a subset of ASCII and IA5 which is ASCII (though * 0x24 '$' and 0x23 '#' may be defined with differnet symbols * depending on the location, in practice it seems everyone just * treats it as ASCII). Since these are just ASCII run through * the fuzzy_escape code to deal with anything that isn't actually * ASCII. There shouldn't be any other types here but if we find * a cert with some other encoding, the best we can do is the * fuzzy_escape(). Note: Technically IA5 isn't valid in this * context, however in the real world it may pop up. */ default: return fuzzy_escape(src_string, result_pool); } if (err) { svn_error_clear(err); return fuzzy_escape(src_string, result_pool); } return nul_escape(utf8_string, result_pool); }
/* Explicit tests of various valid/invalid sequences */ static svn_error_t * utf_validate(apr_pool_t *pool) { struct data { svn_boolean_t valid; char string[20]; } tests[] = { {TRUE, {'a', 'b', '\0'}}, {FALSE, {'a', 'b', '\x80', '\0'}}, {FALSE, {'a', 'b', '\xC0', '\0'}}, {FALSE, {'a', 'b', '\xC0', '\x81', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xC5', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xC5', '\xC0', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xE0', '\0'}}, {FALSE, {'a', 'b', '\xE0', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xE0', '\xA0', '\0'}}, {FALSE, {'a', 'b', '\xE0', '\xA0', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xE0', '\xA0', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xE0', '\x9F', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xE0', '\xCF', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xE5', '\0'}}, {FALSE, {'a', 'b', '\xE5', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xE5', '\x81', '\0'}}, {FALSE, {'a', 'b', '\xE5', '\x81', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xE5', '\x81', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xE5', '\xE1', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xE5', '\x81', '\xE1', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xED', '\0'}}, {FALSE, {'a', 'b', '\xED', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xED', '\x81', '\0'}}, {FALSE, {'a', 'b', '\xED', '\x81', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xED', '\x81', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xED', '\xA0', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xED', '\x81', '\xC1', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xEE', '\0'}}, {FALSE, {'a', 'b', '\xEE', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xEE', '\x81', '\0'}}, {FALSE, {'a', 'b', '\xEE', '\x81', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xEE', '\x81', '\x81', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xEE', '\xA0', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xEE', '\xC0', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xEE', '\x81', '\xC1', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF0', '\0'}}, {FALSE, {'a', 'b', '\xF0', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF0', '\x91', '\0'}}, {FALSE, {'a', 'b', '\xF0', '\x91', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF0', '\x91', '\x81', '\0'}}, {FALSE, {'a', 'b', '\xF0', '\x91', '\x81', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xF0', '\x91', '\x81', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF0', '\x81', '\x81', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF0', '\xC1', '\x81', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF0', '\x91', '\xC1', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF0', '\x91', '\x81', '\xC1', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF2', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF2', '\x91', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF2', '\x91', '\x81', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xF2', '\x91', '\x81', '\x81', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xF2', '\x81', '\x81', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF2', '\xC1', '\x81', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF2', '\x91', '\xC1', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF2', '\x91', '\x81', '\xC1', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF4', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF4', '\x91', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF4', '\x91', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF4', '\x91', '\x81', '\x81', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xF4', '\x81', '\x81', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF4', '\xC1', '\x81', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF4', '\x91', '\xC1', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF4', '\x91', '\x81', '\xC1', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF5', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF5', '\x81', 'x', 'y', '\0'}}, {TRUE, {'a', 'b', '\xF4', '\x81', '\x81', '\x81', 'x', 'y', 'a', 'b', '\xF2', '\x91', '\x81', '\x81', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF4', '\x81', '\x81', '\x81', 'x', 'y', 'a', 'b', '\xF2', '\x91', '\x81', '\xC1', 'x', 'y', '\0'}}, {FALSE, {'a', 'b', '\xF4', '\x81', '\x81', '\x81', 'x', 'y', 'a', 'b', '\xF2', '\x91', '\x81', 'x', 'y', '\0'}}, {-1}, }; int i = 0; while (tests[i].valid != -1) { const char *last = svn_utf__last_valid(tests[i].string, strlen(tests[i].string)); apr_size_t len = strlen(tests[i].string); if ((svn_utf__cstring_is_valid(tests[i].string) != tests[i].valid) || (svn_utf__is_valid(tests[i].string, len) != tests[i].valid)) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "is_valid test %d failed", i); if (!svn_utf__is_valid(tests[i].string, last - tests[i].string) || (tests[i].valid && *last)) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "last_valid test %d failed", i); ++i; } return SVN_NO_ERROR; }