mirror of
https://github.com/kasmtech/KasmVNC.git
synced 2025-02-02 11:39:12 +01:00
Simplify stream availability handling
Just have a simply number of bytes argument to avoid a lot of complexity.
This commit is contained in:
parent
92c7695981
commit
57a3c3bba8
@ -44,12 +44,12 @@ size_t BufferedInStream::pos()
|
|||||||
return offset + ptr - start;
|
return offset + ptr - start;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t BufferedInStream::overrun(size_t itemSize, size_t nItems, bool wait)
|
bool BufferedInStream::overrun(size_t needed, bool wait)
|
||||||
{
|
{
|
||||||
if (itemSize > bufSize)
|
if (needed > bufSize)
|
||||||
throw Exception("BufferedInStream overrun: "
|
throw Exception("BufferedInStream overrun: "
|
||||||
"requested size of %lu bytes exceeds maximum of %lu bytes",
|
"requested size of %lu bytes exceeds maximum of %lu bytes",
|
||||||
(long unsigned)itemSize, (long unsigned)bufSize);
|
(long unsigned)needed, (long unsigned)bufSize);
|
||||||
|
|
||||||
if (end - ptr != 0)
|
if (end - ptr != 0)
|
||||||
memmove(start, ptr, end - ptr);
|
memmove(start, ptr, end - ptr);
|
||||||
@ -58,15 +58,10 @@ size_t BufferedInStream::overrun(size_t itemSize, size_t nItems, bool wait)
|
|||||||
end -= ptr - start;
|
end -= ptr - start;
|
||||||
ptr = start;
|
ptr = start;
|
||||||
|
|
||||||
while (avail() < itemSize) {
|
while (avail() < needed) {
|
||||||
if (!fillBuffer(start + bufSize - end, wait))
|
if (!fillBuffer(start + bufSize - end, wait))
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t nAvail;
|
return true;
|
||||||
nAvail = avail() / itemSize;
|
|
||||||
if (nAvail < nItems)
|
|
||||||
return nAvail;
|
|
||||||
|
|
||||||
return nItems;
|
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ namespace rdr {
|
|||||||
private:
|
private:
|
||||||
virtual bool fillBuffer(size_t maxSize, bool wait) = 0;
|
virtual bool fillBuffer(size_t maxSize, bool wait) = 0;
|
||||||
|
|
||||||
virtual size_t overrun(size_t itemSize, size_t nItems, bool wait);
|
virtual bool overrun(size_t needed, bool wait);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t bufSize;
|
size_t bufSize;
|
||||||
|
@ -71,22 +71,22 @@ void BufferedOutStream::flush()
|
|||||||
ptr = sentUpTo = start;
|
ptr = sentUpTo = start;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t BufferedOutStream::overrun(size_t itemSize, size_t nItems)
|
void BufferedOutStream::overrun(size_t needed)
|
||||||
{
|
{
|
||||||
if (itemSize > bufSize)
|
if (needed > bufSize)
|
||||||
throw Exception("BufferedOutStream overrun: "
|
throw Exception("BufferedOutStream overrun: "
|
||||||
"requested size of %lu bytes exceeds maximum of %lu bytes",
|
"requested size of %lu bytes exceeds maximum of %lu bytes",
|
||||||
(long unsigned)itemSize, (long unsigned)bufSize);
|
(long unsigned)needed, (long unsigned)bufSize);
|
||||||
|
|
||||||
// First try to get rid of the data we have
|
// First try to get rid of the data we have
|
||||||
flush();
|
flush();
|
||||||
|
|
||||||
// Still not enough space?
|
// Still not enough space?
|
||||||
while (itemSize > avail()) {
|
while (needed > avail()) {
|
||||||
// Can we shuffle things around?
|
// Can we shuffle things around?
|
||||||
// (don't do this if it gains us less than 25%)
|
// (don't do this if it gains us less than 25%)
|
||||||
if (((size_t)(sentUpTo - start) > bufSize / 4) &&
|
if (((size_t)(sentUpTo - start) > bufSize / 4) &&
|
||||||
(itemSize < bufSize - (ptr - sentUpTo))) {
|
(needed < bufSize - (ptr - sentUpTo))) {
|
||||||
memmove(start, sentUpTo, ptr - sentUpTo);
|
memmove(start, sentUpTo, ptr - sentUpTo);
|
||||||
ptr = start + (ptr - sentUpTo);
|
ptr = start + (ptr - sentUpTo);
|
||||||
sentUpTo = start;
|
sentUpTo = start;
|
||||||
@ -105,11 +105,4 @@ size_t BufferedOutStream::overrun(size_t itemSize, size_t nItems)
|
|||||||
ptr = sentUpTo = start;
|
ptr = sentUpTo = start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t nAvail;
|
|
||||||
nAvail = avail() / itemSize;
|
|
||||||
if (nAvail < nItems)
|
|
||||||
return nAvail;
|
|
||||||
|
|
||||||
return nItems;
|
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ namespace rdr {
|
|||||||
|
|
||||||
virtual bool flushBuffer(bool wait) = 0;
|
virtual bool flushBuffer(bool wait) = 0;
|
||||||
|
|
||||||
virtual size_t overrun(size_t itemSize, size_t nItems);
|
virtual void overrun(size_t needed);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t bufSize;
|
size_t bufSize;
|
||||||
|
@ -73,7 +73,7 @@ decodeError:
|
|||||||
|
|
||||||
|
|
||||||
bool HexInStream::fillBuffer(size_t maxSize, bool wait) {
|
bool HexInStream::fillBuffer(size_t maxSize, bool wait) {
|
||||||
if (!in_stream.check(2, 1, wait))
|
if (!in_stream.check(2, wait))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const U8* iptr = in_stream.getptr();
|
const U8* iptr = in_stream.getptr();
|
||||||
|
@ -95,18 +95,10 @@ HexOutStream::flush() {
|
|||||||
out_stream.flush();
|
out_stream.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
void HexOutStream::overrun(size_t needed) {
|
||||||
HexOutStream::overrun(size_t itemSize, size_t nItems) {
|
if (needed > bufSize)
|
||||||
if (itemSize > bufSize)
|
throw Exception("HexOutStream overrun: buffer size exceeded");
|
||||||
throw Exception("HexOutStream overrun: max itemSize exceeded");
|
|
||||||
|
|
||||||
writeBuffer();
|
writeBuffer();
|
||||||
|
|
||||||
size_t nAvail;
|
|
||||||
nAvail = avail() / itemSize;
|
|
||||||
if (nAvail < nItems)
|
|
||||||
return nAvail;
|
|
||||||
|
|
||||||
return nItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ namespace rdr {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void writeBuffer();
|
void writeBuffer();
|
||||||
size_t overrun(size_t itemSize, size_t nItems);
|
virtual void overrun(size_t needed);
|
||||||
|
|
||||||
OutStream& out_stream;
|
OutStream& out_stream;
|
||||||
|
|
||||||
|
@ -43,28 +43,17 @@ namespace rdr {
|
|||||||
return end - ptr;
|
return end - ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check() ensures there is buffer data for at least one item of size
|
// check() ensures there is buffer data for at least needed bytes. Returns
|
||||||
// itemSize bytes. Returns the number of items in the buffer (up to a
|
// true once the data is available. If wait is false, then instead of
|
||||||
// maximum of nItems). If wait is false, then instead of blocking to wait
|
// blocking to wait for the bytes, false is returned if the bytes are not
|
||||||
// for the bytes, zero is returned if the bytes are not immediately
|
// immediately available.
|
||||||
// available. If itemSize or nItems is zero, check() will return zero.
|
|
||||||
|
|
||||||
inline size_t check(size_t itemSize, size_t nItems=1, bool wait=true)
|
inline size_t check(size_t needed, bool wait=true)
|
||||||
{
|
{
|
||||||
size_t nAvail;
|
if (needed > avail())
|
||||||
|
return overrun(needed, wait);
|
||||||
|
|
||||||
if (itemSize == 0 || nItems == 0)
|
return true;
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (itemSize > avail())
|
|
||||||
return overrun(itemSize, nItems, wait);
|
|
||||||
|
|
||||||
// itemSize cannot be zero at this point
|
|
||||||
nAvail = avail() / itemSize;
|
|
||||||
if (nAvail < nItems)
|
|
||||||
return nAvail;
|
|
||||||
|
|
||||||
return nItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNoWait() tries to make sure that the given number of bytes can
|
// checkNoWait() tries to make sure that the given number of bytes can
|
||||||
@ -72,10 +61,7 @@ namespace rdr {
|
|||||||
// otherwise. The length must be "small" (less than the buffer size).
|
// otherwise. The length must be "small" (less than the buffer size).
|
||||||
// If length is zero, checkNoWait() will return true.
|
// If length is zero, checkNoWait() will return true.
|
||||||
|
|
||||||
inline bool checkNoWait(size_t length)
|
inline bool checkNoWait(size_t length) { return check(length, false); }
|
||||||
{
|
|
||||||
return length == 0 || check(length, 1, false) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// readU/SN() methods read unsigned and signed N-bit integers.
|
// readU/SN() methods read unsigned and signed N-bit integers.
|
||||||
|
|
||||||
@ -146,13 +132,12 @@ namespace rdr {
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
// overrun() is implemented by a derived class to cope with buffer overrun.
|
// overrun() is implemented by a derived class to cope with buffer overrun.
|
||||||
// It ensures there are at least itemSize bytes of buffer data. Returns
|
// It ensures there are at least needed bytes of buffer data. Returns true
|
||||||
// the number of items in the buffer (up to a maximum of nItems). itemSize
|
// once the data is available. If wait is false, then instead of blocking
|
||||||
// is supposed to be "small" (a few bytes). If wait is false, then
|
// to wait for the bytes, false is returned if the bytes are not
|
||||||
// instead of blocking to wait for the bytes, zero is returned if the bytes
|
// immediately available.
|
||||||
// are not immediately available.
|
|
||||||
|
|
||||||
virtual size_t overrun(size_t itemSize, size_t nItems, bool wait=true) = 0;
|
virtual bool overrun(size_t needed, bool wait=true) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ namespace rdr {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
size_t overrun(size_t itemSize, size_t nItems, bool wait) { throw EndOfStream(); }
|
bool overrun(size_t needed, bool wait) { throw EndOfStream(); }
|
||||||
const U8* start;
|
const U8* start;
|
||||||
bool deleteWhenDone;
|
bool deleteWhenDone;
|
||||||
};
|
};
|
||||||
|
@ -52,11 +52,11 @@ namespace rdr {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
// overrun() either doubles the buffer or adds enough space for nItems of
|
// overrun() either doubles the buffer or adds enough space for
|
||||||
// size itemSize bytes.
|
// needed bytes.
|
||||||
|
|
||||||
size_t overrun(size_t itemSize, size_t nItems) {
|
virtual void overrun(size_t needed) {
|
||||||
size_t len = ptr - start + itemSize * nItems;
|
size_t len = ptr - start + needed;
|
||||||
if (len < (size_t)(end - start) * 2)
|
if (len < (size_t)(end - start) * 2)
|
||||||
len = (end - start) * 2;
|
len = (end - start) * 2;
|
||||||
|
|
||||||
@ -69,8 +69,6 @@ namespace rdr {
|
|||||||
delete [] start;
|
delete [] start;
|
||||||
start = newStart;
|
start = newStart;
|
||||||
end = newStart + len;
|
end = newStart + len;
|
||||||
|
|
||||||
return nItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
U8* start;
|
U8* start;
|
||||||
|
@ -48,22 +48,12 @@ namespace rdr {
|
|||||||
return end - ptr;
|
return end - ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check() ensures there is buffer space for at least one item of size
|
// check() ensures there is buffer space for at least needed bytes.
|
||||||
// itemSize bytes. Returns the number of items which fit (up to a maximum
|
|
||||||
// of nItems).
|
|
||||||
|
|
||||||
inline size_t check(size_t itemSize, size_t nItems=1)
|
inline void check(size_t needed)
|
||||||
{
|
{
|
||||||
size_t nAvail;
|
if (needed > avail())
|
||||||
|
overrun(needed);
|
||||||
if (itemSize > avail())
|
|
||||||
return overrun(itemSize, nItems);
|
|
||||||
|
|
||||||
nAvail = avail() / itemSize;
|
|
||||||
if (nAvail < nItems)
|
|
||||||
return nAvail;
|
|
||||||
|
|
||||||
return nItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeU/SN() methods write unsigned and signed N-bit integers.
|
// writeU/SN() methods write unsigned and signed N-bit integers.
|
||||||
@ -91,19 +81,14 @@ namespace rdr {
|
|||||||
while (bytes-- > 0) writeU8(0);
|
while (bytes-- > 0) writeU8(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void skip(size_t bytes) {
|
|
||||||
while (bytes > 0) {
|
|
||||||
size_t n = check(1, bytes);
|
|
||||||
ptr += n;
|
|
||||||
bytes -= n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeBytes() writes an exact number of bytes.
|
// writeBytes() writes an exact number of bytes.
|
||||||
|
|
||||||
void writeBytes(const void* data, size_t length) {
|
void writeBytes(const void* data, size_t length) {
|
||||||
while (length > 0) {
|
while (length > 0) {
|
||||||
size_t n = check(1, length);
|
check(1);
|
||||||
|
size_t n = length;
|
||||||
|
if (length > avail())
|
||||||
|
n = avail();
|
||||||
memcpy(ptr, data, n);
|
memcpy(ptr, data, n);
|
||||||
ptr += n;
|
ptr += n;
|
||||||
data = (U8*)data + n;
|
data = (U8*)data + n;
|
||||||
@ -115,7 +100,10 @@ namespace rdr {
|
|||||||
|
|
||||||
void copyBytes(InStream* is, size_t length) {
|
void copyBytes(InStream* is, size_t length) {
|
||||||
while (length > 0) {
|
while (length > 0) {
|
||||||
size_t n = check(1, length);
|
check(1);
|
||||||
|
size_t n = length;
|
||||||
|
if (length > avail())
|
||||||
|
n = avail();
|
||||||
is->readBytes(ptr, n);
|
is->readBytes(ptr, n);
|
||||||
ptr += n;
|
ptr += n;
|
||||||
length -= n;
|
length -= n;
|
||||||
@ -151,11 +139,9 @@ namespace rdr {
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
// overrun() is implemented by a derived class to cope with buffer overrun.
|
// overrun() is implemented by a derived class to cope with buffer overrun.
|
||||||
// It ensures there are at least itemSize bytes of buffer space. Returns
|
// It ensures there are at least needed bytes of buffer space.
|
||||||
// the number of items which fit (up to a maximum of nItems). itemSize is
|
|
||||||
// supposed to be "small" (a few bytes).
|
|
||||||
|
|
||||||
virtual size_t overrun(size_t itemSize, size_t nItems) = 0;
|
virtual void overrun(size_t needed) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ ssize_t TLSInStream::pull(gnutls_transport_ptr_t str, void* data, size_t size)
|
|||||||
InStream *in = self->in;
|
InStream *in = self->in;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!in->check(1, 1, false)) {
|
if (!in->check(1, false)) {
|
||||||
gnutls_transport_set_errno(self->session, EAGAIN);
|
gnutls_transport_set_errno(self->session, EAGAIN);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ size_t TLSInStream::readTLS(U8* buf, size_t len, bool wait)
|
|||||||
int n;
|
int n;
|
||||||
|
|
||||||
if (gnutls_record_check_pending(session) == 0) {
|
if (gnutls_record_check_pending(session) == 0) {
|
||||||
n = in->check(1, 1, wait);
|
n = in->check(1, wait);
|
||||||
if (n == 0)
|
if (n == 0)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -93,19 +93,12 @@ void TLSOutStream::flush()
|
|||||||
out->flush();
|
out->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t TLSOutStream::overrun(size_t itemSize, size_t nItems)
|
void TLSOutStream::overrun(size_t needed)
|
||||||
{
|
{
|
||||||
if (itemSize > bufSize)
|
if (needed > bufSize)
|
||||||
throw Exception("TLSOutStream overrun: max itemSize exceeded");
|
throw Exception("TLSOutStream overrun: buffer size exceeded");
|
||||||
|
|
||||||
flush();
|
flush();
|
||||||
|
|
||||||
size_t nAvail;
|
|
||||||
nAvail = avail() / itemSize;
|
|
||||||
if (nAvail < nItems)
|
|
||||||
return nAvail;
|
|
||||||
|
|
||||||
return nItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t TLSOutStream::writeTLS(const U8* data, size_t length)
|
size_t TLSOutStream::writeTLS(const U8* data, size_t length)
|
||||||
|
@ -39,7 +39,7 @@ namespace rdr {
|
|||||||
size_t length();
|
size_t length();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
size_t overrun(size_t itemSize, size_t nItems);
|
virtual void overrun(size_t needed);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t writeTLS(const U8* data, size_t length);
|
size_t writeTLS(const U8* data, size_t length);
|
||||||
|
@ -94,7 +94,7 @@ bool ZlibInStream::fillBuffer(size_t maxSize, bool wait)
|
|||||||
zs->next_out = (U8*)end;
|
zs->next_out = (U8*)end;
|
||||||
zs->avail_out = maxSize;
|
zs->avail_out = maxSize;
|
||||||
|
|
||||||
size_t n = underlying->check(1, 1, wait);
|
size_t n = underlying->check(1, wait);
|
||||||
if (n == 0) return false;
|
if (n == 0) return false;
|
||||||
zs->next_in = (U8*)underlying->getptr();
|
zs->next_in = (U8*)underlying->getptr();
|
||||||
zs->avail_in = underlying->avail();
|
zs->avail_in = underlying->avail();
|
||||||
|
@ -95,18 +95,18 @@ void ZlibOutStream::flush()
|
|||||||
ptr = start;
|
ptr = start;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ZlibOutStream::overrun(size_t itemSize, size_t nItems)
|
void ZlibOutStream::overrun(size_t needed)
|
||||||
{
|
{
|
||||||
#ifdef ZLIBOUT_DEBUG
|
#ifdef ZLIBOUT_DEBUG
|
||||||
fprintf(stderr,"zos overrun\n");
|
fprintf(stderr,"zos overrun\n");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (itemSize > bufSize)
|
if (needed > bufSize)
|
||||||
throw Exception("ZlibOutStream overrun: max itemSize exceeded");
|
throw Exception("ZlibOutStream overrun: buffer size exceeded");
|
||||||
|
|
||||||
checkCompressionLevel();
|
checkCompressionLevel();
|
||||||
|
|
||||||
while (avail() < itemSize) {
|
while (avail() < needed) {
|
||||||
zs->next_in = start;
|
zs->next_in = start;
|
||||||
zs->avail_in = ptr - start;
|
zs->avail_in = ptr - start;
|
||||||
|
|
||||||
@ -126,13 +126,6 @@ size_t ZlibOutStream::overrun(size_t itemSize, size_t nItems)
|
|||||||
ptr -= zs->next_in - start;
|
ptr -= zs->next_in - start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t nAvail;
|
|
||||||
nAvail = avail() / itemSize;
|
|
||||||
if (nAvail < nItems)
|
|
||||||
return nAvail;
|
|
||||||
|
|
||||||
return nItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZlibOutStream::deflate(int flush)
|
void ZlibOutStream::deflate(int flush)
|
||||||
|
@ -45,7 +45,7 @@ namespace rdr {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
size_t overrun(size_t itemSize, size_t nItems);
|
virtual void overrun(size_t needed);
|
||||||
void deflate(int flush);
|
void deflate(int flush);
|
||||||
void checkCompressionLevel();
|
void checkCompressionLevel();
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ void CMsgWriter::writeSetPixelFormat(const PixelFormat& pf)
|
|||||||
void CMsgWriter::writeSetEncodings(int nEncodings, rdr::U32* encodings)
|
void CMsgWriter::writeSetEncodings(int nEncodings, rdr::U32* encodings)
|
||||||
{
|
{
|
||||||
startMsg(msgTypeSetEncodings);
|
startMsg(msgTypeSetEncodings);
|
||||||
os->skip(1);
|
os->pad(1);
|
||||||
os->writeU16(nEncodings);
|
os->writeU16(nEncodings);
|
||||||
for (int i = 0; i < nEncodings; i++)
|
for (int i = 0; i < nEncodings; i++)
|
||||||
os->writeU32(encodings[i]);
|
os->writeU32(encodings[i]);
|
||||||
|
@ -95,7 +95,7 @@ JpegEmptyOutputBuffer(j_compress_ptr cinfo)
|
|||||||
JpegCompressor *jc = dest->instance;
|
JpegCompressor *jc = dest->instance;
|
||||||
|
|
||||||
jc->setptr(jc->getend());
|
jc->setptr(jc->getend());
|
||||||
jc->overrun(jc->getend() - jc->getstart(), 1);
|
jc->overrun(jc->getend() - jc->getstart());
|
||||||
dest->pub.next_output_byte = jc->getptr();
|
dest->pub.next_output_byte = jc->getptr();
|
||||||
dest->pub.free_in_buffer = jc->avail();
|
dest->pub.free_in_buffer = jc->avail();
|
||||||
|
|
||||||
|
@ -49,8 +49,8 @@ namespace rfb {
|
|||||||
|
|
||||||
inline rdr::U8* getstart() { return start; }
|
inline rdr::U8* getstart() { return start; }
|
||||||
|
|
||||||
virtual inline size_t overrun(size_t itemSize, size_t nItems) {
|
inline virtual void overrun(int needed) {
|
||||||
return MemOutStream::overrun(itemSize, nItems);
|
return MemOutStream::overrun(needed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Loading…
Reference in New Issue
Block a user