Jak sprawdzić poprawny ciąg zakodowany w Base64


127

Czy istnieje sposób w C #, aby sprawdzić, czy ciąg jest zakodowany w Base 64, inny niż tylko próba przekonwertowania go i sprawdzenie, czy wystąpił błąd? Mam taki kod:

// Convert base64-encoded hash value into a byte array.
byte[] HashBytes = Convert.FromBase64String(Value);

Chcę uniknąć wyjątku „Nieprawidłowy znak w ciągu Base-64”, który ma miejsce, jeśli wartość nie jest prawidłowym ciągiem bazowym 64. Chcę po prostu sprawdzić i zwrócić false zamiast obsługiwać wyjątek, ponieważ oczekuję, że czasami ta wartość nie będzie ciągiem bazowym 64. Czy istnieje sposób na sprawdzenie przed użyciem funkcji Convert.FromBase64String?

Dzięki!

Aktualizacja:
Dziękuję za wszystkie odpowiedzi. Oto metoda rozszerzenia, z której wszyscy do tej pory korzystali, wydaje się, że zapewnia ona, że ​​łańcuch przejdzie Convert.FromBase64String bez wyjątku. NET wydaje się ignorować wszystkie końcowe i końcowe spacje podczas konwersji do bazy 64, więc „1234” jest poprawne, podobnie jak „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);

}

Dla tych, którzy zastanawiają się nad wydajnością testowania w porównaniu z przechwytywaniem i wyjątkami, w większości przypadków dla tej podstawowej 64 rzeczy szybsze jest sprawdzenie niż złapanie wyjątku, dopóki nie osiągniesz określonej długości. Im mniejsza długość, tym szybciej

W moich bardzo nienaukowych testach: dla 10000 iteracji dla długości znaków 100 000 - 110000 testowanie w pierwszej kolejności było 2,7 razy szybsze.

Dla 1000 iteracji dla znaków o długości od 1 do 16 znaków w sumie dla 16 000 testów było 10,9 razy szybciej.

Jestem pewien, że jest moment, w którym lepiej jest testować przy użyciu metody opartej na wyjątkach. Po prostu nie wiem, w którym momencie to jest.


1
To zależy od tego, jak „dokładny” ma być czek. Możesz użyć niektórych funkcji wstępnej weryfikacji, używając wyrażenia regularnego, tak jak odpowiedzieli inni, ale to nie jedyny wskaźnik. kodowanie base64 wymaga w niektórych przypadkach dopełnienia za pomocą =znaku. Jeśli wypełnienie jest nieprawidłowe, spowoduje to błąd, nawet jeśli dane wejściowe są zgodne z wyrażeniem.
vcsjones,

1
Twój stan nie spełnia wyłącznie łańcuchów base64. Rozważ łańcuch \n\fLE16- Twoja metoda przyniosłaby fałszywie dodatni wynik. Dla każdego, kto czyta i szuka niezawodnej metody; Poleciłbym złapanie FormatException lub użycie RegEx dopasowanego do specyfikacji, zobacz stackoverflow.com/questions/475074/… .
nieważne

jeśli powyższa metoda zwróci wartość false, w jaki sposób mogę dopełnić ciąg do prawidłowej długości?
Paul Alexander,

3
Uważam, że RegEx powinno być@"^[a-zA-Z0-9\+/]*={0,2}$"
azatar

To rozwiązanie nie jest niezawodne. Nie powiedzie się, jeśli dodasz 4 takie same ciągi znaków.
Bettimms

Odpowiedzi:


49

Łatwo jest rozpoznać ciąg Base64, ponieważ będzie on składał się tylko ze znaków 'A'..'Z', 'a'..'z', '0'..'9', '+', '/'i często jest dopełniony na końcu maksymalnie trzema znakami „=”, aby długość była wielokrotnością 4. Ale zamiast ich porównywać, „ lepiej byłoby zignorować wyjątek, jeśli taki wystąpi.


1
Myślę, że jesteś na dobrej drodze. Zrobiłem kilka testów i wygląda na to, że jest to wielokrotność 4 zamiast 3.
Chris Mullins,

1
Jego długość musi być wielokrotnością 3 w momencie kodowania, aby kodowanie było udane! Przepraszam za to ... i tak, masz rację ... Zakodowany ciąg ma długość będącą wielokrotnością 4. To dlatego dopełniamy do 3 '='.
Anirudh Ramanathan

4
Oznaczono jako poprawne, ponieważ jako pierwszy wspomniał o wielu rzeczach. Zaktualizowałem moje pytanie o implementację rozwiązania, daj mi znać, jeśli zauważysz jakieś problemy z tym rozwiązaniem.
Chris Mullins

47

Użyj Convert.TryFromBase64String z 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);
}

1
Nie wiedziałem, że to jest rzecz. Myślę, że to powinna być nowa odpowiedź, jeśli używasz c # 7.2
Chris Mullins,

4
Działa tylko w .NET Core 2.1+ lub .NET Standard 2.1+
Cyrus

C # to kompilator, a TryFromBase64String to API platformy .NET Framework :)
user960567

Będzie to return false dla non-wyściełane strun, tu jest dylemat: Convert.TryFromBase64String(base64.PadRight(base64.Length / 4 * 4 + (base64.Length % 4 == 0 ? 0 : 4), '='), new Span<byte>(new byte[base64.Length]), out _). Dziękuję Ci.
rvnlord

44

Wiem, że powiedziałeś, że nie chcesz złapać wyjątku. Ale ponieważ wyłapywanie wyjątku jest bardziej niezawodne, podam i opublikuję tę odpowiedź.

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;
}

Aktualizacja: zaktualizowałem stan dzięki oybekowi, aby jeszcze bardziej poprawić niezawodność.


1
base64String.Containswielokrotne wywoływanie może spowodować słabą wydajność, nawet jeśli base64Stringjest to duży ciąg.
NucS,

@NucS Masz rację, możemy tutaj użyć skompilowanego wyrażenia regularnego.
harsimranb

1
można sprawdzić base64String== null || base64String.Length == 0zstring.IsNullOrEmpty(base64String)
Daniel tulp

Zauważ, że Base64 może bez problemu zawierać spacje (np. Podziały wierszy). Są ignorowane przez parser.
Tymoteusz

2
Ponieważ mamy teraz dostęp do kodu źródłowego .NET, możemy zobaczyć, że funkcja FromBase64String () wykonuje wszystkie te sprawdzenia. referenceource.microsoft.com/#mscorlib/system/… Jeśli jest to prawidłowy ciąg base64, sprawdzasz go dwukrotnie. Może warto spróbować / złapać wyjątek.
iheartcsharp

16

Uważam, że wyrażenie regularne powinno wyglądać następująco:

    Regex.IsMatch(s, @"^[a-zA-Z0-9\+/]*={0,2}$")

Dopasowanie tylko jednego lub dwóch końcowych znaków „=”, a nie trzech.

spowinien być ciągiem, który zostanie sprawdzony. Regexjest częścią System.Text.RegularExpressionsprzestrzeni nazw.


1
nie sprawdza, czy długość struny jest modą 4 = 0
calingasan

7

Dlaczego po prostu nie złapać wyjątku i nie zwrócić False?

Pozwala to uniknąć dodatkowego obciążenia w typowym przypadku.


1
Jest to nietypowy przypadek, w którym zamierzam użyć wartości, która prawdopodobnie nie będzie bazową 64, więc wolałbym uniknąć narzutu związanego z wyjątkiem. Sprawdzenie wcześniej jest znacznie szybsze. Próbuję przekonwertować stary system, który odziedziczyłem z haseł w postaci zwykłego tekstu, na zaszyfrowane wartości.
Chris Mullins,

2
Wyrażenia regularne nigdy nie są szybsze niż to, co sugeruje Tyler.
Vincent Koeman

Zobacz komentarz na dole mojego posta. Myślę, że w zależności od długości ciągów, z którymi pracujesz, może być szybsze przetestowanie najpierw, szczególnie w przypadku małych ciągów, takich jak zaszyfrowane hasła. Ciąg musi być wielokrotnością 4, aby dostać się do wyrażenia regularnego, a wtedy wyrażenie regularne na małym łańcuchu jest szybsze niż na bardzo dużym.
Chris Mullins

2
W idealnym świecie nie należy pisać kodu, którego logika biznesowa jest zaprojektowana lub jest znana z generowania wyjątków. Blok try / catch wyjątku jest zbyt drogi, aby można go było wykorzystać jako blok decyzyjny.
Ismail Hawayel

7

Ze względu na kompletność chciałbym zapewnić pewną implementację. Ogólnie rzecz biorąc, Regex jest kosztownym podejściem, zwłaszcza jeśli ciąg jest duży (co ma miejsce podczas przesyłania dużych plików). Poniższe podejście próbuje najpierw najszybszych sposobów wykrywania.

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;
    }
}

EDYTOWAĆ

Zgodnie z sugestią Sama możesz także nieznacznie zmienić kod źródłowy. Zapewnia lepsze podejście do ostatniego etapu testów. Rutyna

    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;
    } 

można użyć do zastąpienia if (!Base64Chars.Contains(value[i]))liniiif (IsInvalid(value[i]))

Kompletny kod źródłowy z ulepszeniami Sama będzie wyglądał tak (usunięto komentarze dla przejrzystości)

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;
    }
}

4

Odpowiedź musi zależeć od użycia ciągu. Istnieje wiele łańcuchów, które mogą być „prawidłowym base64” zgodnie ze składnią sugerowaną przez kilka plakatów, ale mogą one „poprawnie” dekodować, bez wyjątku, do śmieci. Przykład: ciąg 8 znakówPortland jest prawidłowym Base64. Jaki jest sens stwierdzenia, że ​​jest to poprawny Base64? Myślę, że w pewnym momencie chciałbyś wiedzieć, że ten ciąg powinien lub nie powinien być dekodowany w Base64.

W moim przypadku mam ciągi połączeń Oracle, które mogą być zwykłym tekstem, na przykład:

Data source=mydb/DBNAME;User Id=Roland;Password=.....`

lub w base64 jak

VXNlciBJZD1sa.....................................==

Muszę tylko sprawdzić obecność średnika, ponieważ to dowodzi, że NIE jest to base64, co jest oczywiście szybsze niż jakakolwiek powyższa metoda.


Zgadzam się, specyfika przypadku wymaga również pewnych dodatkowych szybkich kontroli. Podobnie jak łańcuch połączenia w postaci zwykłego tekstu w porównaniu z kodowaniem base64.
Oybek

2

Knibb High Zasady piłki nożnej!

Powinno to być stosunkowo szybkie i dokładne, ale przyznaję, że nie przeszedłem dokładnego testu, tylko kilka.

Pozwala uniknąć kosztownych wyjątków, regex, a także unika pętli przez zestaw znaków, zamiast tego używa zakresów ascii do walidacji.

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;
    }

2
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;
            }
        }

dlaczego najpierw próbujesz konwertować, a potem kontrolować inne rzeczy
Snr

@Snr masz rację. Myślę, że to właśnie powinien zmienić: if (value.EndsWith ("=")) {wartość = wartość.Trim (); int mod4 = wartość.Length% 4; if (mod4! = 0) {return false; } Convert.FromBase64String (wartość); powrót prawda; } else {return false; }
Wajid khan

2

Będę używać w ten sposób, aby nie musieć ponownie wywoływać metody konwersji

   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;
    }

2

Dekoduj, ponownie zakoduj i porównaj wynik z oryginalnym ciągiem

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;
}

1

Imho, to naprawdę nie jest możliwe. Wszystkie opublikowane rozwiązania kończą się niepowodzeniem w przypadku ciągów typu „test” i tak dalej. Jeśli można je podzielić przez 4, nie są puste ani puste i jeśli są prawidłowymi znakami base64, przejdą wszystkie testy. To może być wiele strun ...

Nie ma więc prawdziwego rozwiązania poza wiedzą, że jest to ciąg zakodowany w oparciu o 64 . Oto co wymyśliłem:

if (base64DecodedString.StartsWith("<xml>")
{
    // This was really a base64 encoded string I was expecting. Yippie!
}
else
{
    // This is gibberish.
}

Spodziewam się, że dekodowany ciąg zaczyna się od określonej struktury, więc sprawdzam to.


0

Pewnie. Wystarczy upewnić się, że każda postać jest w zasięgu a-z, A-Z, 0-9, /, lub +, a końce ciąg z ==. (Przynajmniej jest to najczęstsza implementacja Base64. Możesz znaleźć implementacje, które używają znaków innych niż ostatnie dwa znaki /lub +dla nich).


Jeśli rozumiem, końcowe znaki zależą od końcowej długości zakodowanego tekstu. Więc jeśli zakodowany tekst nie ma długości% 4, to dołączane są znaki „=”.
Rafael Diego Nicoletti

0

Tak, ponieważ Base64 koduje dane binarne w ciągi ASCII przy użyciu ograniczonego zestawu znaków, możesz to po prostu sprawdzić za pomocą tego wyrażenia regularnego:

/ ^ [A-Za-z0-9 \ = \ + \ / \ s \ n] + $ / s

co zapewni, że łańcuch zawiera tylko AZ, az, 0-9, „+”, „/”, „=” i białe znaki.


Nie zawsze jest to pewny sposób na stwierdzenie. Base64 wykonuje pewne dopełnienie za pomocą =znaku na końcu. Jeśli to dopełnienie jest nieprawidłowe, nie jest to poprawne kodowanie base64, mimo że pasuje do Twojego wyrażenia regularnego. Możesz to zademonstrować, znajdując ciąg bazowy 64 z 1 lub 2 =na końcu, usuwając je i próbując go zdekodować.
vcsjones

Uważam, że OP poprosił o pułapkę na nielegalne znaki, a nie jeśli str był legalnym Base64. Jeśli to drugie, masz rację, chociaż błędy wypełniania w Base64 są łatwiejsze do zalewkowania przy użyciu wyjątków.
Rob Raisch

Nieprawda, przynajmniej wersja .Net parsera base64 całkowicie ignoruje wypełnianie.
Jay

0

Sugerowałbym utworzenie wyrażenia regularnego do wykonania tej pracy. Będziesz musiał sprawdzić coś takiego: [a-zA-Z0-9 + / =] Będziesz musiał również sprawdzić długość łańcucha. Nie jestem pewien co do tego, ale jestem prawie pewien, że jeśli coś zostanie przycięte (poza wyściółką „=”), to wybuchnie.

Albo jeszcze lepiej, sprawdź to pytanie o przepełnienie stosu


0

Właśnie miałem bardzo podobny wymóg, w którym pozwalam użytkownikowi na wykonanie pewnych manipulacji obrazem w <canvas>elemencie, a następnie wysyłam uzyskany obraz .toDataURL()do zaplecza. Chciałem przeprowadzić weryfikację serwera przed zapisaniem obrazu i zaimplementowałem ValidationAttributeużycie części kodu z innych odpowiedzi:

[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;
        }
    }
}

Jak widać, oczekuję ciągu typu image / png, który jest wartością domyślną zwracaną <canvas>podczas używania .toDataURL().


0

Sprawdź Base64 lub normalny ciąg

public bool IsBase64Encoded (String str)

{

try

{
    // If no exception is caught, then it is possibly a base64 encoded string
    byte[] data = Convert.FromBase64String(str);
    // The part that checks if the string was properly padded to the
    // correct length was borrowed from d@anish's solution
    return (str.Replace(" ","").Length % 4 == 0);
}
catch
{
    // If exception is caught, then it is not a base64 encoded string
   return false;
}

}


0

Wszystkie odpowiedzi zostały podzielone na 1 funkcję, która zapewnia w 100%, że jej wyniki będą dokładne.


1) użyj funkcji jak poniżej:

    string encoded = "WW91ckJhc2U2NHN0cmluZw==";
    msgbox("Is string base64=" + IsBase64(encoded));

2) Poniżej znajduje się funkcja:

  public bool IsBase64(string base64String)
    {
        try
        {
            if (!base64String.Length < 1)
            {
                if (!base64String.Equals(Convert.ToBase64String(Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(Convert.FromBase64String(base64String)))), StringComparison.InvariantCultureIgnoreCase) & !System.Text.RegularExpressions.Regex.IsMatch(base64String, @"^[a-zA-Z0-9\+/]*={0,2}$"))
                {
                    return false;
                    return;
                }
                if ((base64String.Length % 4) != 0 || string.IsNullOrEmpty(base64String) || base64String.Length % 4 != 0 || base64String.Contains(" ") || base64String.Contains(Constants.vbTab) || base64String.Contains(Constants.vbCr) || base64String.Contains(Constants.vbLf))
                {
                    return false;
                    return;
                }
            }
            else
            {
                return false;
                return;
            }

            return true;
            return;
        }
        catch (FormatException ex)
        {
            return false;
            return;
        }
    }

-1

Podoba mi się pomysł sprawdzenia wyrażenia regularnego. Wyrażenia regularne mogą być szybkie i czasami oszczędzać narzuty związane z kodowaniem. pierwotne zapytanie miało aktualizację, która właśnie to zrobiła. Uważam jednak, że nigdy nie mogę założyć, że łańcuchy nie będą zerowe. Rozwinąłbym funkcję rozszerzenia, aby sprawdzić ciąg źródłowy pod kątem null lub białych znaków.

    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);

    }

To się nie udaje. Spróbuj przekazać ciąg zawierający 4 znaki, tak samo jak „aaaa”.
Bettimms
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.