mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-07-30 07:18:37 -09:00
GP-6886: Fix OOM in ObjectStorageStreamAdapter
This commit is contained in:
@@ -28,6 +28,7 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* {@link GhidraObjectInputFilter} provides global serial input filter for use with Ghidra server
|
||||
@@ -64,11 +65,24 @@ public class GhidraObjectInputFilter implements ObjectInputFilter {
|
||||
private static final String MAXDEPTH = "maxdepth";
|
||||
private static final String MAXBYTES = "maxbytes";
|
||||
|
||||
private static int getMaxArrayFromProperties() {
|
||||
String limitStr = System.getProperty("ghidra.serial.array.limit", "200000");
|
||||
try {
|
||||
return Integer.parseInt(limitStr);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(GhidraObjectInputFilter.class,
|
||||
"Could not parse ghidra.serial.array.limit: %s. A decimal integer is required"
|
||||
.formatted(limitStr));
|
||||
}
|
||||
return 200_000;
|
||||
}
|
||||
|
||||
// NOTE: Be sure to update serialFilterREADME.md if values are updated.
|
||||
private int MAXARRAY_DEFAULT = 200_000;
|
||||
private int MAXREFS_DEFAULT = 10_000;
|
||||
private int MAXDEPTH_DEFAULT = 50;
|
||||
private int MAXBYTES_DEFAULT = 32 * 1024 * 1024; // 32MB
|
||||
public static final int MAXARRAY_DEFAULT = getMaxArrayFromProperties();
|
||||
public static final int MAXREFS_DEFAULT = 10_000;
|
||||
public static final int MAXDEPTH_DEFAULT = 50;
|
||||
public static final int MAXBYTES_DEFAULT = 32 * 1024 * 1024; // 32MB
|
||||
|
||||
private long maxArray;
|
||||
private long maxRefs;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -18,376 +18,454 @@ package ghidra.util;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Implementation for ObjectStorage to save and restore Strings and
|
||||
* Java primitives using an ObjectOutputStream and ObjectInputStream,
|
||||
* respectively.
|
||||
* Implementation for ObjectStorage to save and restore Strings and Java primitives using an
|
||||
* ObjectOutputStream and ObjectInputStream, respectively.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ObjectStorageStreamAdapter implements ObjectStorage {
|
||||
|
||||
private static int getMaxArrayFromProperties() {
|
||||
String limitStr = System.getProperty("ghidra.serial.array.limit", "200000");
|
||||
try {
|
||||
return Integer.parseInt(limitStr);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(ObjectStorageStreamAdapter.class,
|
||||
"Could not parse ghidra.serial.array.limit: %s. A decimal integer is required"
|
||||
.formatted(limitStr));
|
||||
}
|
||||
return 200_000;
|
||||
}
|
||||
|
||||
public static final int MAXARRAY = getMaxArrayFromProperties();
|
||||
|
||||
ObjectOutputStream out;
|
||||
ObjectInputStream in;
|
||||
/**
|
||||
* Constructor for ObjectStorageStreamAdapter.
|
||||
* @param out output stream to write to
|
||||
*/
|
||||
public ObjectStorageStreamAdapter(ObjectOutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
/**
|
||||
* Constructor for new ObjectStorageStreamAdapter
|
||||
* @param in input stream to read from
|
||||
*/
|
||||
public ObjectStorageStreamAdapter(ObjectInputStream in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for ObjectStorageStreamAdapter.
|
||||
*
|
||||
* @param out output stream to write to
|
||||
*/
|
||||
public ObjectStorageStreamAdapter(ObjectOutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for new ObjectStorageStreamAdapter
|
||||
*
|
||||
* @param in input stream to read from
|
||||
*/
|
||||
public ObjectStorageStreamAdapter(ObjectInputStream in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putInt(int value) {
|
||||
try {
|
||||
out.writeInt(value);
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putInt(int value) {
|
||||
try {
|
||||
out.writeInt(value);
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putByte(byte value) {
|
||||
try {
|
||||
out.writeByte(value);
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putByte(byte value) {
|
||||
try {
|
||||
out.writeByte(value);
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putShort(short value) {
|
||||
try {
|
||||
out.writeShort(value);
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putShort(short value) {
|
||||
try {
|
||||
out.writeShort(value);
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putLong(long value) {
|
||||
try {
|
||||
out.writeLong(value);
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putLong(long value) {
|
||||
try {
|
||||
out.writeLong(value);
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putString(String value) {
|
||||
try {
|
||||
out.writeObject(value);
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putString(String value) {
|
||||
try {
|
||||
out.writeObject(value);
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putBoolean(boolean value) {
|
||||
try {
|
||||
out.writeBoolean(value);
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putBoolean(boolean value) {
|
||||
try {
|
||||
out.writeBoolean(value);
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putFloat(float value) {
|
||||
try {
|
||||
out.writeFloat(value);
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putFloat(float value) {
|
||||
try {
|
||||
out.writeFloat(value);
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putDouble(double value) {
|
||||
try {
|
||||
out.writeDouble(value);
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putDouble(double value) {
|
||||
try {
|
||||
out.writeDouble(value);
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt() {
|
||||
try {
|
||||
return in.readInt();
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public int getInt() {
|
||||
try {
|
||||
return in.readInt();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte() {
|
||||
try {
|
||||
return in.readByte();
|
||||
} catch (IOException e) {
|
||||
return (byte)0;
|
||||
}
|
||||
}
|
||||
public byte getByte() {
|
||||
try {
|
||||
return in.readByte();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return (byte) 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort() {
|
||||
try {
|
||||
return in.readShort();
|
||||
} catch (IOException e) {
|
||||
return (short)0;
|
||||
}
|
||||
}
|
||||
public short getShort() {
|
||||
try {
|
||||
return in.readShort();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return (short) 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong() {
|
||||
try {
|
||||
return in.readLong();
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public long getLong() {
|
||||
try {
|
||||
return in.readLong();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean() {
|
||||
try {
|
||||
return in.readBoolean();
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public boolean getBoolean() {
|
||||
try {
|
||||
return in.readBoolean();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString() {
|
||||
try {
|
||||
return (String)in.readObject();
|
||||
}catch(Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public String getString() {
|
||||
try {
|
||||
return (String) in.readObject();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat() {
|
||||
try {
|
||||
return in.readFloat();
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public float getFloat() {
|
||||
try {
|
||||
return in.readFloat();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble() {
|
||||
try {
|
||||
return in.readDouble();
|
||||
} catch (IOException e) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
public double getDouble() {
|
||||
try {
|
||||
return in.readDouble();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkWriteArrayLength(int n) throws IOException {
|
||||
if (n > MAXARRAY) {
|
||||
throw new IOException("Array size %d exceeds max of %d".formatted(n, MAXARRAY));
|
||||
}
|
||||
out.writeInt(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putInts(int[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
out.writeInt(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeInt(value[i]);
|
||||
}
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putInts(int[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
checkWriteArrayLength(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeInt(value[i]);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putBytes(byte[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
out.writeInt(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeByte(value[i]);
|
||||
}
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putBytes(byte[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
checkWriteArrayLength(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeByte(value[i]);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putShorts(short[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
out.writeInt(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeShort(value[i]);
|
||||
}
|
||||
} catch (IOException e) {}
|
||||
public void putShorts(short[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
checkWriteArrayLength(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeShort(value[i]);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putLongs(long[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
out.writeInt(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeLong(value[i]);
|
||||
}
|
||||
} catch (IOException e) {}
|
||||
public void putLongs(long[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
checkWriteArrayLength(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeLong(value[i]);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putFloats(float[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
out.writeInt(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeFloat(value[i]);
|
||||
}
|
||||
} catch (IOException e) {}
|
||||
|
||||
}
|
||||
public void putFloats(float[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
checkWriteArrayLength(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeFloat(value[i]);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putDoubles(double[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
out.writeInt(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeDouble(value[i]);
|
||||
}
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putDoubles(double[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
checkWriteArrayLength(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeDouble(value[i]);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putStrings(String[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
out.writeInt(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeObject(value[i]);
|
||||
}
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
public void putStrings(String[] value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
checkWriteArrayLength(value.length);
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
out.writeObject(value[i]);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
protected int checkReadArrayLength() throws IOException {
|
||||
int n = in.readInt();
|
||||
if (n > MAXARRAY) {
|
||||
throw new IOException("Array size %d exceeds max of %d".formatted(n, MAXARRAY));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getInts() {
|
||||
try {
|
||||
int n = in.readInt();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
int[] r = new int[n];
|
||||
for(int i=0;i<n;i++) {
|
||||
r[i] = in.readInt();
|
||||
}
|
||||
return r;
|
||||
} catch (IOException e) {
|
||||
return new int[0];
|
||||
}
|
||||
}
|
||||
public int[] getInts() {
|
||||
try {
|
||||
int n = checkReadArrayLength();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
int[] r = new int[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = in.readInt();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return new int[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBytes() {
|
||||
try {
|
||||
int n = in.readInt();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
byte[] r = new byte[n];
|
||||
for(int i=0;i<n;i++) {
|
||||
r[i] = in.readByte();
|
||||
}
|
||||
return r;
|
||||
} catch (IOException e) {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
public byte[] getBytes() {
|
||||
try {
|
||||
int n = checkReadArrayLength();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
byte[] r = new byte[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = in.readByte();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public short[] getShorts() {
|
||||
try {
|
||||
int n = in.readInt();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
short[] r = new short[n];
|
||||
for(int i=0;i<n;i++) {
|
||||
r[i] = in.readShort();
|
||||
}
|
||||
return r;
|
||||
} catch (IOException e) {
|
||||
return new short[0];
|
||||
}
|
||||
}
|
||||
public short[] getShorts() {
|
||||
try {
|
||||
int n = checkReadArrayLength();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
short[] r = new short[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = in.readShort();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return new short[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getLongs() {
|
||||
try {
|
||||
int n = in.readInt();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
long[] r = new long[n];
|
||||
for(int i=0;i<n;i++) {
|
||||
r[i] = in.readLong();
|
||||
}
|
||||
return r;
|
||||
} catch (IOException e) {
|
||||
return new long[0];
|
||||
}
|
||||
}
|
||||
public long[] getLongs() {
|
||||
try {
|
||||
int n = checkReadArrayLength();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
long[] r = new long[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = in.readLong();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return new long[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getFloats() {
|
||||
try {
|
||||
int n = in.readInt();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
float[] r = new float[n];
|
||||
for(int i=0;i<n;i++) {
|
||||
r[i] = in.readFloat();
|
||||
}
|
||||
return r;
|
||||
} catch (IOException e) {
|
||||
return new float[0];
|
||||
}
|
||||
}
|
||||
public float[] getFloats() {
|
||||
try {
|
||||
int n = checkReadArrayLength();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
float[] r = new float[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = in.readFloat();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return new float[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] getDoubles() {
|
||||
try {
|
||||
int n = in.readInt();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
double[] r = new double[n];
|
||||
for(int i=0;i<n;i++) {
|
||||
r[i] = in.readDouble();
|
||||
}
|
||||
return r;
|
||||
} catch (IOException e) {
|
||||
return new double[0];
|
||||
}
|
||||
}
|
||||
public double[] getDoubles() {
|
||||
try {
|
||||
int n = checkReadArrayLength();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
double[] r = new double[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = in.readDouble();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return new double[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getStrings() {
|
||||
try {
|
||||
int n = in.readInt();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
String[] r = new String[n];
|
||||
for(int i=0;i<n;i++) {
|
||||
r[i] = (String)in.readObject();
|
||||
}
|
||||
return r;
|
||||
} catch (Exception e) {
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getStrings() {
|
||||
try {
|
||||
int n = checkReadArrayLength();
|
||||
if (n < 0) {
|
||||
return null;
|
||||
}
|
||||
String[] r = new String[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = (String) in.readObject();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
catch (Exception e) {
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user