Merge branch 'kplibdesktop' into integrate_kplib_2_24_update
Conflicts: src/KeePassLib2Android/Cryptography/CryptoRandom.cs src/KeePassLib2Android/Keys/KcpKeyFile.cs src/KeePassLib2Android/Keys/KcpUserAccount.cs src/KeePassLib2Android/Native/NativeLib.cs src/KeePassLib2Android/PwCustomIcon.cs src/KeePassLib2Android/PwDatabase.cs src/KeePassLib2Android/PwEntry.cs src/KeePassLib2Android/PwGroup.cs src/KeePassLib2Android/PwUuid.cs src/KeePassLib2Android/Security/ProtectedBinary.cs src/KeePassLib2Android/Serialization/FileLock.cs src/KeePassLib2Android/Serialization/FileTransactionEx.cs src/KeePassLib2Android/Serialization/HashedBlockStream.cs src/KeePassLib2Android/Serialization/IOConnection.cs src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs src/KeePassLib2Android/Serialization/KdbxFile.Read.cs src/KeePassLib2Android/Serialization/KdbxFile.Write.cs src/KeePassLib2Android/Translation/KPControlCustomization.cs src/KeePassLib2Android/Translation/KPFormCustomization.cs src/KeePassLib2Android/Translation/KPStringTable.cs src/KeePassLib2Android/Translation/KPTranslation.cs src/KeePassLib2Android/Utility/GfxUtil.cs src/KeePassLib2Android/Utility/MessageService.cs src/KeePassLib2Android/Utility/StrUtil.cs
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2012 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2013 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll
|
||||
|
||||
@@ -22,14 +22,16 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Security.Cryptography;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
|
||||
using KeePassLib.Collections;
|
||||
using KeePassLib.Cryptography.PasswordGenerator;
|
||||
using KeePassLib.Native;
|
||||
using KeePassLib.Security;
|
||||
using KeePassLib.Resources;
|
||||
@@ -214,41 +216,47 @@ namespace KeePassLib.Utility
|
||||
{
|
||||
get
|
||||
{
|
||||
if(m_lEncs == null)
|
||||
{
|
||||
m_lEncs = new List<StrEncodingInfo>();
|
||||
if(m_lEncs != null) return m_lEncs;
|
||||
|
||||
m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Default,
|
||||
#if !KeePassLibSD
|
||||
Encoding.Default.EncodingName,
|
||||
List<StrEncodingInfo> l = new List<StrEncodingInfo>();
|
||||
|
||||
l.Add(new StrEncodingInfo(StrEncodingType.Default,
|
||||
#if KeePassRT
|
||||
StrUtil.Utf8.WebName, StrUtil.Utf8, 1, null));
|
||||
#else
|
||||
Encoding.Default.WebName,
|
||||
#endif
|
||||
Encoding.Default,
|
||||
(uint)Encoding.Default.GetBytes("a").Length, null));
|
||||
m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Ascii,
|
||||
"ASCII", Encoding.ASCII, 1, null));
|
||||
m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf7,
|
||||
"Unicode (UTF-7)", Encoding.UTF7, 1, null));
|
||||
m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf8,
|
||||
"Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF }));
|
||||
m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf16LE,
|
||||
"Unicode (UTF-16 LE)", new UnicodeEncoding(false, false),
|
||||
2, new byte[] { 0xFF, 0xFE }));
|
||||
m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf16BE,
|
||||
"Unicode (UTF-16 BE)", new UnicodeEncoding(true, false),
|
||||
2, new byte[] { 0xFE, 0xFF }));
|
||||
#if !KeePassLibSD
|
||||
m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf32LE,
|
||||
"Unicode (UTF-32 LE)", new UTF32Encoding(false, false),
|
||||
4, new byte[] { 0xFF, 0xFE, 0x0, 0x0 }));
|
||||
m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf32BE,
|
||||
"Unicode (UTF-32 BE)", new UTF32Encoding(true, false),
|
||||
4, new byte[] { 0x0, 0x0, 0xFE, 0xFF }));
|
||||
Encoding.Default.EncodingName,
|
||||
#else
|
||||
Encoding.Default.WebName,
|
||||
#endif
|
||||
Encoding.Default,
|
||||
(uint)Encoding.Default.GetBytes("a").Length, null));
|
||||
#endif
|
||||
#if !KeePassRT
|
||||
l.Add(new StrEncodingInfo(StrEncodingType.Ascii,
|
||||
"ASCII", Encoding.ASCII, 1, null));
|
||||
l.Add(new StrEncodingInfo(StrEncodingType.Utf7,
|
||||
"Unicode (UTF-7)", Encoding.UTF7, 1, null));
|
||||
#endif
|
||||
l.Add(new StrEncodingInfo(StrEncodingType.Utf8,
|
||||
"Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF }));
|
||||
l.Add(new StrEncodingInfo(StrEncodingType.Utf16LE,
|
||||
"Unicode (UTF-16 LE)", new UnicodeEncoding(false, false),
|
||||
2, new byte[] { 0xFF, 0xFE }));
|
||||
l.Add(new StrEncodingInfo(StrEncodingType.Utf16BE,
|
||||
"Unicode (UTF-16 BE)", new UnicodeEncoding(true, false),
|
||||
2, new byte[] { 0xFE, 0xFF }));
|
||||
#if (!KeePassLibSD && !KeePassRT)
|
||||
l.Add(new StrEncodingInfo(StrEncodingType.Utf32LE,
|
||||
"Unicode (UTF-32 LE)", new UTF32Encoding(false, false),
|
||||
4, new byte[] { 0xFF, 0xFE, 0x0, 0x0 }));
|
||||
l.Add(new StrEncodingInfo(StrEncodingType.Utf32BE,
|
||||
"Unicode (UTF-32 BE)", new UTF32Encoding(true, false),
|
||||
4, new byte[] { 0x0, 0x0, 0xFE, 0xFF }));
|
||||
#endif
|
||||
}
|
||||
|
||||
return m_lEncs;
|
||||
m_lEncs = l;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,16 +284,21 @@ namespace KeePassLib.Utility
|
||||
// {
|
||||
// char ch = str[i];
|
||||
// if((int)ch >= 256)
|
||||
// {
|
||||
// sbEncoded.Append("\\u");
|
||||
// sbEncoded.Append((int)ch);
|
||||
// sbEncoded.Append('?');
|
||||
// }
|
||||
// sbEncoded.Append(StrUtil.RtfEncodeChar(ch));
|
||||
// else sbEncoded.Append(ch);
|
||||
// }
|
||||
// return sbEncoded.ToString();
|
||||
// }
|
||||
|
||||
public static string RtfEncodeChar(char ch)
|
||||
{
|
||||
// Unicode character values must be encoded using
|
||||
// 16-bit numbers (decimal); Unicode values greater
|
||||
// than 32767 must be expressed as negative numbers
|
||||
short sh = (short)ch;
|
||||
return ("\\u" + sh.ToString(NumberFormatInfo.InvariantInfo) + "?");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a string into a valid HTML sequence representing that string.
|
||||
/// </summary>
|
||||
@@ -484,8 +497,10 @@ namespace KeePassLib.Utility
|
||||
if(excp.StackTrace != null)
|
||||
strText += excp.StackTrace + MessageService.NewLine;
|
||||
#if !KeePassLibSD
|
||||
#if !KeePassRT
|
||||
if(excp.TargetSite != null)
|
||||
strText += excp.TargetSite.ToString() + MessageService.NewLine;
|
||||
#endif
|
||||
|
||||
if(excp.Data != null)
|
||||
{
|
||||
@@ -508,8 +523,10 @@ namespace KeePassLib.Utility
|
||||
if(excp.InnerException.StackTrace != null)
|
||||
strText += excp.InnerException.StackTrace + MessageService.NewLine;
|
||||
#if !KeePassLibSD
|
||||
#if !KeePassRT
|
||||
if(excp.InnerException.TargetSite != null)
|
||||
strText += excp.InnerException.TargetSite.ToString();
|
||||
#endif
|
||||
|
||||
if(excp.InnerException.Data != null)
|
||||
{
|
||||
@@ -540,7 +557,25 @@ namespace KeePassLib.Utility
|
||||
return int.TryParse(str, out n);
|
||||
#else
|
||||
try { n = int.Parse(str); return true; }
|
||||
catch(Exception) { n = 0; return false; }
|
||||
catch(Exception) { n = 0; }
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool TryParseIntInvariant(string str, out int n)
|
||||
{
|
||||
#if !KeePassLibSD
|
||||
return int.TryParse(str, NumberStyles.Integer,
|
||||
NumberFormatInfo.InvariantInfo, out n);
|
||||
#else
|
||||
try
|
||||
{
|
||||
n = int.Parse(str, NumberStyles.Integer,
|
||||
NumberFormatInfo.InvariantInfo);
|
||||
return true;
|
||||
}
|
||||
catch(Exception) { n = 0; }
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -550,7 +585,25 @@ namespace KeePassLib.Utility
|
||||
return uint.TryParse(str, out u);
|
||||
#else
|
||||
try { u = uint.Parse(str); return true; }
|
||||
catch(Exception) { u = 0; return false; }
|
||||
catch(Exception) { u = 0; }
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool TryParseUIntInvariant(string str, out uint u)
|
||||
{
|
||||
#if !KeePassLibSD
|
||||
return uint.TryParse(str, NumberStyles.Integer,
|
||||
NumberFormatInfo.InvariantInfo, out u);
|
||||
#else
|
||||
try
|
||||
{
|
||||
u = uint.Parse(str, NumberStyles.Integer,
|
||||
NumberFormatInfo.InvariantInfo);
|
||||
return true;
|
||||
}
|
||||
catch(Exception) { u = 0; }
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -560,7 +613,25 @@ namespace KeePassLib.Utility
|
||||
return long.TryParse(str, out n);
|
||||
#else
|
||||
try { n = long.Parse(str); return true; }
|
||||
catch(Exception) { n = 0; return false; }
|
||||
catch(Exception) { n = 0; }
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool TryParseLongInvariant(string str, out long n)
|
||||
{
|
||||
#if !KeePassLibSD
|
||||
return long.TryParse(str, NumberStyles.Integer,
|
||||
NumberFormatInfo.InvariantInfo, out n);
|
||||
#else
|
||||
try
|
||||
{
|
||||
n = long.Parse(str, NumberStyles.Integer,
|
||||
NumberFormatInfo.InvariantInfo);
|
||||
return true;
|
||||
}
|
||||
catch(Exception) { n = 0; }
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -570,7 +641,25 @@ namespace KeePassLib.Utility
|
||||
return ulong.TryParse(str, out u);
|
||||
#else
|
||||
try { u = ulong.Parse(str); return true; }
|
||||
catch(Exception) { u = 0; return false; }
|
||||
catch(Exception) { u = 0; }
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool TryParseULongInvariant(string str, out ulong u)
|
||||
{
|
||||
#if !KeePassLibSD
|
||||
return ulong.TryParse(str, NumberStyles.Integer,
|
||||
NumberFormatInfo.InvariantInfo, out u);
|
||||
#else
|
||||
try
|
||||
{
|
||||
u = ulong.Parse(str, NumberStyles.Integer,
|
||||
NumberFormatInfo.InvariantInfo);
|
||||
return true;
|
||||
}
|
||||
catch(Exception) { u = 0; }
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -638,19 +727,34 @@ namespace KeePassLib.Utility
|
||||
Debug.Assert(strText != null); // No throw
|
||||
if(string.IsNullOrEmpty(strText)) return strText;
|
||||
|
||||
char[] vChars = strText.ToCharArray();
|
||||
StringBuilder sb = new StringBuilder(strText.Length, strText.Length);
|
||||
char ch;
|
||||
int nLength = strText.Length;
|
||||
StringBuilder sb = new StringBuilder(nLength);
|
||||
|
||||
for(int i = 0; i < vChars.Length; ++i)
|
||||
for(int i = 0; i < nLength; ++i)
|
||||
{
|
||||
ch = vChars[i];
|
||||
char ch = strText[i];
|
||||
|
||||
if(((ch >= 0x20) && (ch <= 0xD7FF)) ||
|
||||
(ch == 0x9) || (ch == 0xA) || (ch == 0xD) ||
|
||||
((ch >= 0xE000) && (ch <= 0xFFFD)))
|
||||
if(((ch >= '\u0020') && (ch <= '\uD7FF')) ||
|
||||
(ch == '\u0009') || (ch == '\u000A') || (ch == '\u000D') ||
|
||||
((ch >= '\uE000') && (ch <= '\uFFFD')))
|
||||
sb.Append(ch);
|
||||
// Range ((ch >= 0x10000) && (ch <= 0x10FFFF)) excluded
|
||||
else if((ch >= '\uD800') && (ch <= '\uDBFF')) // High surrogate
|
||||
{
|
||||
if((i + 1) < nLength)
|
||||
{
|
||||
char chLow = strText[i + 1];
|
||||
if((chLow >= '\uDC00') && (chLow <= '\uDFFF')) // Low sur.
|
||||
{
|
||||
sb.Append(ch);
|
||||
sb.Append(chLow);
|
||||
++i;
|
||||
}
|
||||
else { Debug.Assert(false); } // Low sur. invalid
|
||||
}
|
||||
else { Debug.Assert(false); } // Low sur. missing
|
||||
}
|
||||
|
||||
Debug.Assert((ch < '\uDC00') || (ch > '\uDFFF')); // Lonely low sur.
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
@@ -671,7 +775,12 @@ namespace KeePassLib.Utility
|
||||
strY = strY.ToLower();
|
||||
|
||||
if(m_rxNaturalSplit == null)
|
||||
m_rxNaturalSplit = new Regex(@"([0-9]+)", RegexOptions.Compiled);
|
||||
m_rxNaturalSplit = new Regex(@"([0-9]+)",
|
||||
#if KeePassRT
|
||||
RegexOptions.None);
|
||||
#else
|
||||
RegexOptions.Compiled);
|
||||
#endif
|
||||
|
||||
string[] vPartsX = m_rxNaturalSplit.Split(strX);
|
||||
string[] vPartsY = m_rxNaturalSplit.Split(strY);
|
||||
@@ -726,6 +835,20 @@ namespace KeePassLib.Utility
|
||||
return str;
|
||||
}
|
||||
|
||||
public static string EncodeMenuText(string strText)
|
||||
{
|
||||
if(strText == null) throw new ArgumentNullException("strText");
|
||||
|
||||
return strText.Replace(@"&", @"&&");
|
||||
}
|
||||
|
||||
public static string EncodeToolTipText(string strText)
|
||||
{
|
||||
if(strText == null) throw new ArgumentNullException("strText");
|
||||
|
||||
return strText.Replace(@"&", @"&&&");
|
||||
}
|
||||
|
||||
public static bool IsHexString(string str, bool bStrict)
|
||||
{
|
||||
if(str == null) throw new ArgumentNullException("str");
|
||||
@@ -972,7 +1095,7 @@ namespace KeePassLib.Utility
|
||||
bMultiComp = true;
|
||||
}
|
||||
|
||||
str = us.ToString() + str;
|
||||
str = us.ToString(NumberFormatInfo.InvariantInfo) + str;
|
||||
}
|
||||
|
||||
uVersion >>= 16;
|
||||
@@ -1022,7 +1145,7 @@ namespace KeePassLib.Utility
|
||||
for(int i = 0; i < vNumbers.Length; ++i)
|
||||
{
|
||||
if(i > 0) sb.Append(' ');
|
||||
sb.Append(vNumbers[i]);
|
||||
sb.Append(vNumbers[i].ToString(NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
@@ -1039,7 +1162,7 @@ namespace KeePassLib.Utility
|
||||
for(int i = 0; i < vParts.Length; ++i)
|
||||
{
|
||||
int n;
|
||||
if(!TryParseInt(vParts[i], out n)) { Debug.Assert(false); }
|
||||
if(!TryParseIntInvariant(vParts[i], out n)) { Debug.Assert(false); }
|
||||
v[i] = n;
|
||||
}
|
||||
|
||||
@@ -1099,7 +1222,7 @@ namespace KeePassLib.Utility
|
||||
Array.Reverse(pb);
|
||||
for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)(pb[i] ^ 0x65);
|
||||
|
||||
#if !KeePassLibSD
|
||||
#if (!KeePassLibSD && !KeePassRT)
|
||||
return Convert.ToBase64String(pb, Base64FormattingOptions.None);
|
||||
#else
|
||||
return Convert.ToBase64String(pb);
|
||||
@@ -1217,9 +1340,33 @@ namespace KeePassLib.Utility
|
||||
|
||||
public static bool IsDataUri(string strUri)
|
||||
{
|
||||
if(strUri == null) { Debug.Assert(false); return false; }
|
||||
return IsDataUri(strUri, null);
|
||||
}
|
||||
|
||||
return strUri.StartsWith("data:", StrUtil.CaseIgnoreCmp);
|
||||
public static bool IsDataUri(string strUri, string strReqMimeType)
|
||||
{
|
||||
if(strUri == null) { Debug.Assert(false); return false; }
|
||||
// strReqMimeType may be null
|
||||
|
||||
const string strPrefix = "data:";
|
||||
if(!strUri.StartsWith(strPrefix, StrUtil.CaseIgnoreCmp))
|
||||
return false;
|
||||
|
||||
int iC = strUri.IndexOf(',');
|
||||
if(iC < 0) return false;
|
||||
|
||||
if(!string.IsNullOrEmpty(strReqMimeType))
|
||||
{
|
||||
int iS = strUri.IndexOf(';', 0, iC);
|
||||
int iTerm = ((iS >= 0) ? iS : iC);
|
||||
|
||||
string strMime = strUri.Substring(strPrefix.Length,
|
||||
iTerm - strPrefix.Length);
|
||||
if(!strMime.Equals(strReqMimeType, StrUtil.CaseIgnoreCmp))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1235,7 +1382,7 @@ namespace KeePassLib.Utility
|
||||
|
||||
if(strMimeType == null) strMimeType = "application/octet-stream";
|
||||
|
||||
#if !KeePassLibSD
|
||||
#if (!KeePassLibSD && !KeePassRT)
|
||||
return ("data:" + strMimeType + ";base64," + Convert.ToBase64String(
|
||||
pbData, Base64FormattingOptions.None));
|
||||
#else
|
||||
@@ -1266,13 +1413,19 @@ namespace KeePassLib.Utility
|
||||
|
||||
MemoryStream ms = new MemoryStream();
|
||||
|
||||
#if KeePassRT
|
||||
Encoding enc = StrUtil.Utf8;
|
||||
#else
|
||||
Encoding enc = Encoding.ASCII;
|
||||
#endif
|
||||
|
||||
string[] v = strData.Split('%');
|
||||
byte[] pb = Encoding.ASCII.GetBytes(v[0]);
|
||||
byte[] pb = enc.GetBytes(v[0]);
|
||||
ms.Write(pb, 0, pb.Length);
|
||||
for(int i = 1; i < v.Length; ++i)
|
||||
{
|
||||
ms.WriteByte(Convert.ToByte(v[i].Substring(0, 2), 16));
|
||||
pb = Encoding.ASCII.GetBytes(v[i].Substring(2));
|
||||
pb = enc.GetBytes(v[i].Substring(2));
|
||||
ms.Write(pb, 0, pb.Length);
|
||||
}
|
||||
|
||||
@@ -1322,5 +1475,72 @@ namespace KeePassLib.Utility
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string[] m_vPrefSepChars = null;
|
||||
/// <summary>
|
||||
/// Find a character that does not occur within a given text.
|
||||
/// </summary>
|
||||
public static char GetUnusedChar(string strText)
|
||||
{
|
||||
if(strText == null) { Debug.Assert(false); return '@'; }
|
||||
|
||||
if(m_vPrefSepChars == null)
|
||||
m_vPrefSepChars = new string[5] {
|
||||
"@!$%#/\\:;,.*-_?",
|
||||
PwCharSet.UpperCase, PwCharSet.LowerCase,
|
||||
PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial
|
||||
};
|
||||
|
||||
for(int i = 0; i < m_vPrefSepChars.Length; ++i)
|
||||
{
|
||||
foreach(char ch in m_vPrefSepChars[i])
|
||||
{
|
||||
if(strText.IndexOf(ch) < 0) return ch;
|
||||
}
|
||||
}
|
||||
|
||||
for(char ch = '\u00C0'; ch < char.MaxValue; ++ch)
|
||||
{
|
||||
if(strText.IndexOf(ch) < 0) return ch;
|
||||
}
|
||||
|
||||
return char.MinValue;
|
||||
}
|
||||
|
||||
public static char ByteToSafeChar(byte bt)
|
||||
{
|
||||
const char chDefault = '.';
|
||||
|
||||
// 00-1F are C0 control chars
|
||||
if(bt < 0x20) return chDefault;
|
||||
|
||||
// 20-7F are basic Latin; 7F is DEL
|
||||
if(bt < 0x7F) return (char)bt;
|
||||
|
||||
// 80-9F are C1 control chars
|
||||
if(bt < 0xA0) return chDefault;
|
||||
|
||||
// A0-FF are Latin-1 supplement; AD is soft hyphen
|
||||
if(bt == 0xAD) return '-';
|
||||
return (char)bt;
|
||||
}
|
||||
|
||||
public static int Count(string str, string strNeedle)
|
||||
{
|
||||
if(str == null) { Debug.Assert(false); return 0; }
|
||||
if(string.IsNullOrEmpty(strNeedle)) { Debug.Assert(false); return 0; }
|
||||
|
||||
int iOffset = 0, iCount = 0;
|
||||
while(iOffset < str.Length)
|
||||
{
|
||||
int p = str.IndexOf(strNeedle, iOffset);
|
||||
if(p < 0) break;
|
||||
|
||||
++iCount;
|
||||
iOffset = p + 1;
|
||||
}
|
||||
|
||||
return iCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user