Existe-t-il un moyen en C # de voir si une chaîne est codée en Base 64 autrement qu'en essayant simplement de la convertir et de voir s'il y a une erreur? J'ai un code comme ça:
// Convert base64-encoded hash value into a byte array.
byte[] HashBytes = Convert.FromBase64String(Value);
Je veux éviter l'exception "Caractère non valide dans une chaîne en base 64" qui se produit si la valeur n'est pas une chaîne en base 64 valide. Je veux juste vérifier et renvoyer false au lieu de gérer une exception car je m'attends à ce que parfois cette valeur ne soit pas une chaîne de base 64. Existe-t-il un moyen de vérifier avant d'utiliser la fonction Convert.FromBase64String?
Merci!
Mettre à jour:
Merci pour toutes vos réponses. Voici une méthode d’extension que vous pouvez tous utiliser jusqu’à présent. Il semble s’assurer que votre chaîne transmettra Convert.FromBase64String sans exception. .NET semble ignorer tous les espaces de fin et de fin lors de la conversion en base 64, donc "1234" est valide et donc "1234".
public static bool IsBase64String(this string s)
{
s = s.Trim();
return (s.Length % 4 == 0) && Regex.IsMatch(s, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None);
}
Pour ceux qui s’interrogent sur les performances des tests par rapport aux captures et aux exceptions, dans la plupart des cas, il est plus rapide de vérifier que d’attraper l’exception jusqu’à atteindre une certaine longueur. Plus la longueur est courte, plus vite
Lors de mes tests très peu scientifiques: Pour 10000 itérations pour des longueurs de caractères de 100 000 à 110000, il était 2,7 fois plus rapide à tester en premier.
Pour 1000 itérations pour une longueur de caractères de 1 à 16 caractères pour un total de 16 000 tests, il était 10,9 fois plus rapide.
Je suis sûr qu'il y a un point où il devient préférable de tester avec la méthode basée sur les exceptions. Je ne sais pas à quel moment c'est.
Il est assez facile de reconnaître une chaîne Base64, car elle ne sera composée que de caractères 'A'..'Z', 'a'..'z', '0'..'9', '+', '/'
et elle est souvent complétée à la fin par deux "=" au maximum, pour que la longueur soit un multiple de 4. Mais au lieu Il vaudrait mieux ignorer l'exception, si cela se produit.
Je sais que vous avez dit que vous ne vouliez pas attraper une exception. Mais, comme attraper une exception est plus fiable, je vais aller de l'avant et poster cette réponse.
public static bool IsBase64(this string base64String) {
// Credit: oybek https://stackoverflow.com/users/794764/oybek
if (string.IsNullOrEmpty(base64String) || base64String.Length % 4 != 0
|| base64String.Contains(" ") || base64String.Contains("\t") || base64String.Contains("\r") || base64String.Contains("\n"))
return false;
try{
Convert.FromBase64String(base64String);
return true;
}
catch(Exception exception){
// Handle the exception
}
return false;
}
Mise à jour: J'ai mis à jour la condition grâce à oybek pour améliorer encore la fiabilité.
Je crois que la regex devrait être:
Regex.IsMatch(s, @"^[a-zA-Z0-9\+/]*={0,2}$")
Correspondant seulement à un ou deux signes de fin "=", pas trois.
s
devrait être la chaîne qui sera vérifiée. Regex
fait partie de l'espace de noms System.Text.RegularExpressions
.
Pourquoi ne pas attraper l'exception et renvoyer False?
Cela évite des frais généraux supplémentaires dans le cas courant.
Par souci d’exhaustivité, je souhaite fournir une implémentation ..__ En général, Regex est une approche coûteuse, en particulier si la chaîne est volumineuse (ce qui se produit lors du transfert de fichiers volumineux). L’approche suivante essaie d’abord les méthodes de détection les plus rapides.
public static class HelperExtensions {
// Characters that are used in base64 strings.
private static Char[] Base64Chars = new[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
/// <summary>
/// Extension method to test whether the value is a base64 string
/// </summary>
/// <param name="value">Value to test</param>
/// <returns>Boolean value, true if the string is base64, otherwise false</returns>
public static Boolean IsBase64String(this String value) {
// The quickest test. If the value is null or is equal to 0 it is not base64
// Base64 string's length is always divisible by four, i.e. 8, 16, 20 etc.
// If it is not you can return false. Quite effective
// Further, if it meets the above criterias, then test for spaces.
// If it contains spaces, it is not base64
if (value == null || value.Length == 0 || value.Length % 4 != 0
|| value.Contains(' ') || value.Contains('\t') || value.Contains('\r') || value.Contains('\n'))
return false;
// 98% of all non base64 values are invalidated by this time.
var index = value.Length - 1;
// if there is padding step back
if (value[index] == '=')
index--;
// if there are two padding chars step back a second time
if (value[index] == '=')
index--;
// Now traverse over characters
// You should note that I'm not creating any copy of the existing strings,
// assuming that they may be quite large
for (var i = 0; i <= index; i++)
// If any of the character is not from the allowed list
if (!Base64Chars.Contains(value[i]))
// return false
return false;
// If we got here, then the value is a valid base64 string
return true;
}
}
MODIFIER
Comme suggéré par Sam , vous pouvez également modifier légèrement le code source. Il fournit une approche plus performante pour la dernière étape des tests. La routine
private static Boolean IsInvalid(char value) {
var intValue = (Int32)value;
// 1 - 9
if (intValue >= 48 && intValue <= 57)
return false;
// A - Z
if (intValue >= 65 && intValue <= 90)
return false;
// a - z
if (intValue >= 97 && intValue <= 122)
return false;
// + or /
return intValue != 43 && intValue != 47;
}
peut être utilisé pour remplacer la ligne if (!Base64Chars.Contains(value[i]))
par if (IsInvalid(value[i]))
Le code source complet avec les améliorations de Sam ressemblera à ceci (commentaires supprimés pour plus de clarté)
public static class HelperExtensions {
public static Boolean IsBase64String(this String value) {
if (value == null || value.Length == 0 || value.Length % 4 != 0
|| value.Contains(' ') || value.Contains('\t') || value.Contains('\r') || value.Contains('\n'))
return false;
var index = value.Length - 1;
if (value[index] == '=')
index--;
if (value[index] == '=')
index--;
for (var i = 0; i <= index; i++)
if (IsInvalid(value[i]))
return false;
return true;
}
// Make it private as there is the name makes no sense for an outside caller
private static Boolean IsInvalid(char value) {
var intValue = (Int32)value;
if (intValue >= 48 && intValue <= 57)
return false;
if (intValue >= 65 && intValue <= 90)
return false;
if (intValue >= 97 && intValue <= 122)
return false;
return intValue != 43 && intValue != 47;
}
}
La réponse doit dépendre de l'utilisation de la chaîne. Il existe de nombreuses chaînes qui peuvent être "base64 valide" selon la syntaxe suggérée par plusieurs afficheurs, mais qui peuvent "correctement" décoder, sans exception, de la malbouffe. Exemple: la chaîne 8char Portland
est valide Base64. Quel est le point de dire que ceci est valide Base64? Je suppose qu’à un moment donné, vous voudriez savoir que cette chaîne devrait ou non être décodée en Base64.
Dans mon cas, j'ai des chaînes de connexion Oracle qui peuvent être en texte brut comme:
Data source=mydb/DBNAME;User Id=Roland;Password=.....`
ou en base64 comme
VXNlciBJZD1sa.....................................==
Je dois juste vérifier la présence d'un point-virgule, car cela prouve que ce n'est pas base64, ce qui est bien sûr plus rapide que n'importe quelle méthode ci-dessus.
public static bool IsBase64String1(string value)
{
if (string.IsNullOrEmpty(value))
{
return false;
}
try
{
Convert.FromBase64String(value);
if (value.EndsWith("="))
{
value = value.Trim();
int mod4 = value.Length % 4;
if (mod4 != 0)
{
return false;
}
return true;
}
else
{
return false;
}
}
catch (FormatException)
{
return false;
}
}
Je vais utiliser comme ça pour ne pas avoir à appeler à nouveau la méthode convert
public static bool IsBase64(this string base64String,out byte[] bytes)
{
bytes = null;
// Credit: oybek http://stackoverflow.com/users/794764/oybek
if (string.IsNullOrEmpty(base64String) || base64String.Length % 4 != 0
|| base64String.Contains(" ") || base64String.Contains("\t") || base64String.Contains("\r") || base64String.Contains("\n"))
return false;
try
{
bytes=Convert.FromBase64String(base64String);
return true;
}
catch (Exception)
{
// Handle the exception
}
return false;
}
Utilisez Convert.TryFromBase64String à partir de C # 7.2
public static bool IsBase64String(string base64)
{
Span<byte> buffer = new Span<byte>(new byte[base64.Length]);
return Convert.TryFromBase64String(base64, buffer , out int bytesParsed);
}
Knibb High règles de football!
Cela devrait être relativement rapide et précis, mais j’avoue que je n’ai pas subi d’essais approfondis, juste quelques-uns.
Il évite les exceptions coûteuses et les expressions rationnelles, et évite également de parcourir en boucle un jeu de caractères au lieu d'utiliser des plages ascii pour la validation.
public static bool IsBase64String(string s)
{
s = s.Trim();
int mod4 = s.Length % 4;
if(mod4!=0){
return false;
}
int i=0;
bool checkPadding = false;
int paddingCount = 1;//only applies when the first is encountered.
for(i=0;i<s.Length;i++){
char c = s[i];
if (checkPadding)
{
if (c != '=')
{
return false;
}
paddingCount++;
if (paddingCount > 3)
{
return false;
}
continue;
}
if(c>='A' && c<='z' || c>='0' && c<='9'){
continue;
}
switch(c){
case '+':
case '/':
continue;
case '=':
checkPadding = true;
continue;
}
return false;
}
//if here
//, length was correct
//, there were no invalid characters
//, padding was correct
return true;
}
J'aime l'idée d'un chèque d'expressions régulières. Les expressions régulières peuvent être rapides et économiser parfois la surcharge de codage. l'enquête initiale, avait une mise à jour qui a fait cela. Je trouve cependant que je ne peux jamais supposer que les chaînes ne seraient pas nulles. Je voudrais développer la fonction Extension pour vérifier la chaîne source pour null, ou uniquement des caractères d'espacement.
public static bool IsBase64String(this string s)
{
if (string.IsNullOrWhiteSpace(s))
return false;
s = s.Trim();
return (s.Length % 4 == 0) && Regex.IsMatch(s, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None);
}
Je suggérerais de créer une expression rationnelle pour faire le travail .. Vous devrez vérifier quelque chose comme ceci: [a-zA-Z0-9 +/=]. la ficelle. Je ne suis pas sûr sur celui-ci, mais je suis à peu près sûr que si quelque chose est coupé (à part le rembourrage "="), il va exploser.
Ou mieux encore consultez cette question de stackoverflow
Sûr. Assurez-vous simplement que chaque caractère est compris entre a-z
, A-Z
, 0-9
, /
et +
et que la chaîne se termine par ==
. (Du moins, c'est l'implémentation Base64 la plus courante. Vous pouvez trouver certaines implémentations utilisant des caractères différents de /
ou +
pour les deux derniers caractères.)
Oui, puisque Base64 code les données binaires en ASCII - chaînes utilisant un jeu de caractères limité, vous pouvez simplement le vérifier avec cette expression régulière:
/ ^ [A-Za-z0-9\=\+ \/\ s\n] + $/s
ce qui assurera que la chaîne ne contient que AZ, a-z, 0-9, '+', '/', '=' et des espaces.
Ce n'est pas vraiment possible. Toutes les solutions publiées échouent pour des chaînes telles que "test", etc. S'ils peuvent être divisés par 4, s'ils ne sont pas nuls ou vides, et s'ils constituent un caractère base64 valide, ils réussiront tous les tests. Cela peut être beaucoup de chaînes ...
Il n’existe donc aucune solution réelle autre que sachant qu’il s’agit d’une chaîne codée en base 64}. Voici ce que j'ai trouvé:
if (base64DecodedString.StartsWith("<xml>")
{
// This was really a base64 encoded string I was expecting. Yippie!
}
else
{
// This is gibberish.
}
Je m'attends à ce que la chaîne décodée commence par une certaine structure, je vérifie donc.
Décodez, réencodez et comparez le résultat avec la chaîne d'origine
public static Boolean IsBase64(this String str)
{
if ((str.Length % 4) != 0)
{
return false;
}
//decode - encode and compare
try
{
string decoded = System.Text.Encoding.UTF8.GetString(System.Convert.FromBase64String(str));
string encoded = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(decoded));
if (str.Equals(encoded, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
catch { }
return false;
}
Je viens d'avoir une exigence très similaire: je laisse l'utilisateur manipuler des images dans un élément <canvas>
, puis envoyer l'image résultante récupérée avec .toDataURL()
au serveur. Je voulais faire une validation du serveur avant de sauvegarder l'image et j'ai implémenté une ValidationAttribute
en utilisant une partie du code provenant d'autres réponses:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class Bae64PngImageAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null || string.IsNullOrWhiteSpace(value as string))
return true; // not concerned with whether or not this field is required
var base64string = (value as string).Trim();
// we are expecting a URL type string
if (!base64string.StartsWith("data:image/png;base64,"))
return false;
base64string = base64string.Substring("data:image/png;base64,".Length);
// match length and regular expression
if (base64string.Length % 4 != 0 || !Regex.IsMatch(base64string, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None))
return false;
// finally, try to convert it to a byte array and catch exceptions
try
{
byte[] converted = Convert.FromBase64String(base64string);
return true;
}
catch(Exception)
{
return false;
}
}
}
Comme vous pouvez le constater, j'attends une chaîne de type image/png, qui est la valeur par défaut renvoyée par <canvas>
lorsque vous utilisez .toDataURL()
.