mirror of
https://github.com/ONLYOFFICE/core.git
synced 2026-04-07 13:55:33 +08:00
Create ChangePassword for pdf
This commit is contained in:
@ -54,6 +54,7 @@
|
|||||||
#include "SrcWriter/Info.h"
|
#include "SrcWriter/Info.h"
|
||||||
#include "SrcWriter/Annotation.h"
|
#include "SrcWriter/Annotation.h"
|
||||||
#include "SrcWriter/ResourcesDictionary.h"
|
#include "SrcWriter/ResourcesDictionary.h"
|
||||||
|
#include "SrcWriter/Streams.h"
|
||||||
|
|
||||||
#define AddToObject(oVal)\
|
#define AddToObject(oVal)\
|
||||||
{\
|
{\
|
||||||
@ -1441,6 +1442,191 @@ void CPdfFile::AddMetaData(const std::wstring& sMetaName, BYTE* pMetaData, DWORD
|
|||||||
return;
|
return;
|
||||||
m_pInternal->pWriter->AddMetaData(sMetaName, pMetaData, nMetaLength);
|
m_pInternal->pWriter->AddMetaData(sMetaName, pMetaData, nMetaLength);
|
||||||
}
|
}
|
||||||
|
HRESULT CPdfFile::ChangePassword(const std::wstring& wsPath, const std::wstring& wsPassword)
|
||||||
|
{
|
||||||
|
RELEASEOBJECT(m_pInternal->pWriter);
|
||||||
|
m_pInternal->pWriter = new CPdfWriter(m_pInternal->pAppFonts, false, this, false);
|
||||||
|
|
||||||
|
PDFDoc* pPDFDocument = m_pInternal->pReader->GetPDFDocument();
|
||||||
|
if (!pPDFDocument)
|
||||||
|
return S_FALSE;
|
||||||
|
|
||||||
|
XRef* xref = pPDFDocument->getXRef();
|
||||||
|
if (!xref)
|
||||||
|
return S_FALSE;
|
||||||
|
Object* trailerDict = xref->getTrailerDict();
|
||||||
|
if (!trailerDict)
|
||||||
|
return S_FALSE;
|
||||||
|
|
||||||
|
PdfWriter::CDocument* pDoc = m_pInternal->pWriter->m_pDocument;
|
||||||
|
PdfWriter::CXref* pXref = new PdfWriter::CXref(pDoc, 0);
|
||||||
|
PdfWriter::CXref* m_pXref = new PdfWriter::CXref(pDoc, xref->getNumObjects()); // Для новых объектов
|
||||||
|
if (!xref || !pDoc || !pXref || !m_pXref)
|
||||||
|
{
|
||||||
|
RELEASEOBJECT(pXref);
|
||||||
|
RELEASEOBJECT(m_pXref);
|
||||||
|
return S_FALSE;
|
||||||
|
}
|
||||||
|
pXref->SetPrev(m_pXref);
|
||||||
|
|
||||||
|
for (int i = 0; i < xref->getSize(); ++i)
|
||||||
|
{
|
||||||
|
XRefEntry* pEntry = xref->getEntry(i);
|
||||||
|
if (pEntry->type == xrefEntryFree)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (i != pXref->GetSizeXRef())
|
||||||
|
{
|
||||||
|
PdfWriter::CXref* pXref2 = new PdfWriter::CXref(pDoc, i);
|
||||||
|
pXref2->SetPrev(pXref);
|
||||||
|
pXref = pXref2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object oTemp;
|
||||||
|
xref->fetch(i, pEntry->gen, &oTemp);
|
||||||
|
PdfWriter::CObjectBase* pObj = NULL;
|
||||||
|
|
||||||
|
switch (oTemp.getType())
|
||||||
|
{
|
||||||
|
case objBool:
|
||||||
|
{
|
||||||
|
pObj = new PdfWriter::CBoolObject(oTemp.getBool());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case objInt:
|
||||||
|
{
|
||||||
|
pObj = new PdfWriter::CNumberObject(oTemp.getInt());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case objReal:
|
||||||
|
{
|
||||||
|
pObj = new PdfWriter::CRealObject(oTemp.getReal());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case objString:
|
||||||
|
{
|
||||||
|
TextString* s = new TextString(oTemp.getString());
|
||||||
|
std::string sValue = NSStringExt::CConverter::GetUtf8FromUTF32(s->getUnicode(), s->getLength());
|
||||||
|
pObj = new PdfWriter::CStringObject(sValue.c_str());
|
||||||
|
delete s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case objName:
|
||||||
|
{
|
||||||
|
pObj = new PdfWriter::CNameObject(oTemp.getName());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case objNull:
|
||||||
|
{
|
||||||
|
pObj = new PdfWriter::CNullObject();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case objArray:
|
||||||
|
{
|
||||||
|
pObj = new PdfWriter::CArrayObject();
|
||||||
|
|
||||||
|
for (int nIndex = 0; nIndex < oTemp.arrayGetLength(); ++nIndex)
|
||||||
|
{
|
||||||
|
Object oT;
|
||||||
|
oTemp.arrayGetNF(nIndex, &oT);
|
||||||
|
DictToCDictObject(&oT, pObj, false, "");
|
||||||
|
oT.free();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case objDict:
|
||||||
|
{
|
||||||
|
pObj = new PdfWriter::CDictObject();
|
||||||
|
|
||||||
|
for (int nIndex = 0; nIndex < oTemp.dictGetLength(); ++nIndex)
|
||||||
|
{
|
||||||
|
Object oT;
|
||||||
|
char* chKey = oTemp.dictGetKey(nIndex);
|
||||||
|
oTemp.dictGetValNF(nIndex, &oT);
|
||||||
|
DictToCDictObject(&oT, pObj, false, chKey);
|
||||||
|
oT.free();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case objRef:
|
||||||
|
{
|
||||||
|
PdfWriter::CObjectBase* pBase = new PdfWriter::CObjectBase();
|
||||||
|
pBase->SetRef(oTemp.getRefNum(), oTemp.getRefGen());
|
||||||
|
pObj = new PdfWriter::CProxyObject(pBase, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case objStream:
|
||||||
|
{
|
||||||
|
Dict* pDict = oTemp.streamGetDict();
|
||||||
|
Object oObjStm;
|
||||||
|
if (pDict->lookup("Type", &oObjStm)->isName("ObjStm"))
|
||||||
|
{
|
||||||
|
oObjStm.free();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
oObjStm.free();
|
||||||
|
|
||||||
|
PdfWriter::CDictObject* pDObj = new PdfWriter::CDictObject();
|
||||||
|
pObj = pDObj;
|
||||||
|
|
||||||
|
int nLength = 0;
|
||||||
|
for (int nIndex = 0; nIndex < pDict->getLength(); ++nIndex)
|
||||||
|
{
|
||||||
|
Object oT;
|
||||||
|
char* chKey = pDict->getKey(nIndex);
|
||||||
|
if (strcmp("Length", chKey) == 0)
|
||||||
|
{
|
||||||
|
Object oLength;
|
||||||
|
nLength = pDict->getVal(nIndex, &oLength)->isInt() ? oLength.getInt() : 0;
|
||||||
|
oLength.free();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pDict->getValNF(nIndex, &oT);
|
||||||
|
DictToCDictObject(&oT, pObj, false, chKey);
|
||||||
|
oT.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
PdfWriter::CStream* pStream = new PdfWriter::CMemoryStream();
|
||||||
|
pDObj->SetStream(m_pXref, pStream, false);
|
||||||
|
|
||||||
|
Stream* pImage = oTemp.getStream()->getUndecodedStream();
|
||||||
|
pImage->reset();
|
||||||
|
for (int nI = 0; nI < nLength; ++nI)
|
||||||
|
pStream->WriteChar(pImage->getChar());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case objNone:
|
||||||
|
case objCmd:
|
||||||
|
case objError:
|
||||||
|
case objEOF:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
oTemp.free();
|
||||||
|
|
||||||
|
if (pObj)
|
||||||
|
pXref->Add(pObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
PdfWriter::CDictObject* pTrailer = pXref->GetTrailer();
|
||||||
|
for (int nIndex = 0; nIndex < trailerDict->dictGetLength(); ++nIndex)
|
||||||
|
{
|
||||||
|
Object oTemp;
|
||||||
|
char* chKey = trailerDict->dictGetKey(nIndex);
|
||||||
|
if (strcmp("Root", chKey) == 0 || strcmp("Info", chKey) == 0)
|
||||||
|
{
|
||||||
|
trailerDict->dictGetValNF(nIndex, &oTemp);
|
||||||
|
DictToCDictObject(&oTemp, pTrailer, true, chKey);
|
||||||
|
}
|
||||||
|
oTemp.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bRes = pDoc->SaveNewWithPassword(pXref, m_pXref, wsPath, wsPassword, wsPassword, pTrailer);
|
||||||
|
|
||||||
|
RELEASEOBJECT(pXref);
|
||||||
|
|
||||||
|
return bRes ? S_OK : S_FALSE;
|
||||||
|
}
|
||||||
HRESULT CPdfFile::OnlineWordToPdf(const std::wstring& wsSrcFile, const std::wstring& wsDstFile, CConvertFromBinParams* pParams)
|
HRESULT CPdfFile::OnlineWordToPdf(const std::wstring& wsSrcFile, const std::wstring& wsDstFile, CConvertFromBinParams* pParams)
|
||||||
{
|
{
|
||||||
#ifndef BUILDING_WASM_MODULE
|
#ifndef BUILDING_WASM_MODULE
|
||||||
|
|||||||
@ -150,6 +150,7 @@ public:
|
|||||||
void SetDocumentInfo(const std::wstring& wsTitle, const std::wstring& wsCreator, const std::wstring& wsSubject, const std::wstring& wsKeywords);
|
void SetDocumentInfo(const std::wstring& wsTitle, const std::wstring& wsCreator, const std::wstring& wsSubject, const std::wstring& wsKeywords);
|
||||||
void AddMetaData(const std::wstring& sMetaName, BYTE* pMetaData, DWORD nMetaLength);
|
void AddMetaData(const std::wstring& sMetaName, BYTE* pMetaData, DWORD nMetaLength);
|
||||||
|
|
||||||
|
HRESULT ChangePassword(const std::wstring& wsPath, const std::wstring& wsPassword = L"");
|
||||||
HRESULT OnlineWordToPdf (const std::wstring& wsSrcFile, const std::wstring& wsDstFile, CConvertFromBinParams* pParams = NULL);
|
HRESULT OnlineWordToPdf (const std::wstring& wsSrcFile, const std::wstring& wsDstFile, CConvertFromBinParams* pParams = NULL);
|
||||||
HRESULT OnlineWordToPdfFromBinary(const std::wstring& wsSrcFile, const std::wstring& wsDstFile, CConvertFromBinParams* pParams = NULL);
|
HRESULT OnlineWordToPdfFromBinary(const std::wstring& wsSrcFile, const std::wstring& wsDstFile, CConvertFromBinParams* pParams = NULL);
|
||||||
HRESULT AddToPdfFromBinary(BYTE* pBuffer, unsigned int nLen, CConvertFromBinParams* pParams = NULL);
|
HRESULT AddToPdfFromBinary(BYTE* pBuffer, unsigned int nLen, CConvertFromBinParams* pParams = NULL);
|
||||||
|
|||||||
@ -94,7 +94,7 @@ static const long c_BrushTypeRadialGradient = 8002;
|
|||||||
// CPdfRenderer
|
// CPdfRenderer
|
||||||
//
|
//
|
||||||
//----------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------
|
||||||
CPdfWriter::CPdfWriter(NSFonts::IApplicationFonts* pAppFonts, bool isPDFA, IRenderer* pRenderer) : m_oCommandManager(this)
|
CPdfWriter::CPdfWriter(NSFonts::IApplicationFonts* pAppFonts, bool isPDFA, IRenderer* pRenderer, bool bCreate) : m_oCommandManager(this)
|
||||||
{
|
{
|
||||||
// Создаем менеджер шрифтов с собственным кэшем
|
// Создаем менеджер шрифтов с собственным кэшем
|
||||||
m_pFontManager = pAppFonts->GenerateFontManager();
|
m_pFontManager = pAppFonts->GenerateFontManager();
|
||||||
@ -108,7 +108,7 @@ CPdfWriter::CPdfWriter(NSFonts::IApplicationFonts* pAppFonts, bool isPDFA, IRend
|
|||||||
if (isPDFA)
|
if (isPDFA)
|
||||||
m_pDocument->SetPDFAConformanceMode(true);
|
m_pDocument->SetPDFAConformanceMode(true);
|
||||||
|
|
||||||
if (!m_pDocument || !m_pDocument->CreateNew())
|
if (!m_pDocument || (bCreate && !m_pDocument->CreateNew()))
|
||||||
{
|
{
|
||||||
SetError();
|
SetError();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -66,7 +66,7 @@ namespace Aggplus
|
|||||||
class CPdfWriter
|
class CPdfWriter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CPdfWriter(NSFonts::IApplicationFonts* pAppFonts, bool isPDFA = false, IRenderer* pRenderer = NULL);
|
CPdfWriter(NSFonts::IApplicationFonts* pAppFonts, bool isPDFA = false, IRenderer* pRenderer = NULL, bool bCreate = true);
|
||||||
~CPdfWriter();
|
~CPdfWriter();
|
||||||
int SaveToFile(const std::wstring& wsPath);
|
int SaveToFile(const std::wstring& wsPath);
|
||||||
void SetPassword(const std::wstring& wsPassword);
|
void SetPassword(const std::wstring& wsPassword);
|
||||||
|
|||||||
@ -218,10 +218,10 @@ namespace PdfWriter
|
|||||||
m_pFreeTypeLibrary = NULL;
|
m_pFreeTypeLibrary = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool CDocument::SaveToFile(const std::wstring& wsPath, bool bAdd)
|
bool CDocument::SaveToFile(const std::wstring& wsPath)
|
||||||
{
|
{
|
||||||
CFileStream* pStream = new CFileStream();
|
CFileStream* pStream = new CFileStream();
|
||||||
if (!pStream || !pStream->OpenFile(wsPath, bAdd))
|
if (!pStream || !pStream->OpenFile(wsPath, true))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (m_pJbig2)
|
if (m_pJbig2)
|
||||||
@ -264,6 +264,32 @@ namespace PdfWriter
|
|||||||
|
|
||||||
m_pXref->WriteToStream(pStream, pEncrypt);
|
m_pXref->WriteToStream(pStream, pEncrypt);
|
||||||
}
|
}
|
||||||
|
bool CDocument::SaveNewWithPassword(CXref* pXref, CXref* _pXref, const std::wstring& wsPath, const std::wstring& wsOwnerPassword, const std::wstring& wsUserPassword, CDictObject* pTrailer)
|
||||||
|
{
|
||||||
|
if (!pXref || !pTrailer || !_pXref)
|
||||||
|
return false;
|
||||||
|
m_pTrailer = pTrailer;
|
||||||
|
|
||||||
|
CEncrypt* pEncrypt = NULL;
|
||||||
|
if (!wsOwnerPassword.empty())
|
||||||
|
{
|
||||||
|
m_pEncryptDict = new CEncryptDict(_pXref);
|
||||||
|
m_pEncryptDict->SetPasswords(wsOwnerPassword, wsUserPassword);
|
||||||
|
m_pTrailer->Add("Encrypt", m_pEncryptDict);
|
||||||
|
|
||||||
|
pEncrypt = m_pEncryptDict->GetEncrypt();
|
||||||
|
PrepareEncryption();
|
||||||
|
}
|
||||||
|
|
||||||
|
CFileStream* pStream = new CFileStream();
|
||||||
|
if (!pStream || !pStream->OpenFile(wsPath, true))
|
||||||
|
return false;
|
||||||
|
pStream->WriteStr(c_sPdfHeader);
|
||||||
|
|
||||||
|
pXref->WriteToStream(pStream, pEncrypt);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
void CDocument::PrepareEncryption()
|
void CDocument::PrepareEncryption()
|
||||||
{
|
{
|
||||||
CEncrypt* pEncrypt = m_pEncryptDict->GetEncrypt();
|
CEncrypt* pEncrypt = m_pEncryptDict->GetEncrypt();
|
||||||
|
|||||||
@ -103,7 +103,8 @@ namespace PdfWriter
|
|||||||
|
|
||||||
bool CreateNew();
|
bool CreateNew();
|
||||||
void Close();
|
void Close();
|
||||||
bool SaveToFile(const std::wstring& wsPath, bool bAdd = true);
|
bool SaveToFile(const std::wstring& wsPath);
|
||||||
|
bool SaveNewWithPassword(CXref* pXref, CXref* _pXref, const std::wstring& wsPath, const std::wstring& wsOwnerPassword, const std::wstring& wsUserPassword, CDictObject* pTrailer);
|
||||||
|
|
||||||
void SetPasswords(const std::wstring & wsOwnerPassword, const std::wstring & wsUserPassword);
|
void SetPasswords(const std::wstring & wsOwnerPassword, const std::wstring & wsUserPassword);
|
||||||
void SetPermission(unsigned int unPermission);
|
void SetPermission(unsigned int unPermission);
|
||||||
|
|||||||
@ -651,7 +651,7 @@ namespace PdfWriter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void CDictObject::SetStream(CXref* pXref, CStream* pStream)
|
void CDictObject::SetStream(CXref* pXref, CStream* pStream, bool bThis)
|
||||||
{
|
{
|
||||||
if (m_pStream)
|
if (m_pStream)
|
||||||
delete m_pStream;
|
delete m_pStream;
|
||||||
@ -661,7 +661,8 @@ namespace PdfWriter
|
|||||||
CNumberObject* pLength = new CNumberObject(0);
|
CNumberObject* pLength = new CNumberObject(0);
|
||||||
|
|
||||||
// Только stream object добавляются в таблицу xref автоматически
|
// Только stream object добавляются в таблицу xref автоматически
|
||||||
pXref->Add((CObjectBase*)this);
|
if (bThis)
|
||||||
|
pXref->Add((CObjectBase*)this);
|
||||||
pXref->Add((CObjectBase*)pLength);
|
pXref->Add((CObjectBase*)pLength);
|
||||||
|
|
||||||
Add("Length", (CObjectBase*)pLength);
|
Add("Length", (CObjectBase*)pLength);
|
||||||
@ -852,7 +853,7 @@ namespace PdfWriter
|
|||||||
unsigned int unMaxObjId = m_pPrev ? pPrev->m_arrEntries.size() + pPrev->m_unStartOffset : m_arrEntries.size() + m_unStartOffset;
|
unsigned int unMaxObjId = m_pPrev ? pPrev->m_arrEntries.size() + pPrev->m_unStartOffset : m_arrEntries.size() + m_unStartOffset;
|
||||||
|
|
||||||
m_pTrailer->Add("Size", unMaxObjId);
|
m_pTrailer->Add("Size", unMaxObjId);
|
||||||
if (m_pPrev)
|
if (m_pPrev && pPrev->m_unAddr)
|
||||||
m_pTrailer->Add("Prev", pPrev->m_unAddr);
|
m_pTrailer->Add("Prev", pPrev->m_unAddr);
|
||||||
|
|
||||||
pStream->WriteStr("trailer\012");
|
pStream->WriteStr("trailer\012");
|
||||||
@ -916,7 +917,7 @@ namespace PdfWriter
|
|||||||
CDictObject* pTrailer = m_pTrailer;
|
CDictObject* pTrailer = m_pTrailer;
|
||||||
pTrailer->Add("Type", "XRef");
|
pTrailer->Add("Type", "XRef");
|
||||||
pTrailer->Add("Size", unMaxObjId + 1);
|
pTrailer->Add("Size", unMaxObjId + 1);
|
||||||
if (m_pPrev)
|
if (m_pPrev && pPrev->m_unAddr)
|
||||||
pTrailer->Add("Prev", pPrev->m_unAddr);
|
pTrailer->Add("Prev", pPrev->m_unAddr);
|
||||||
CArrayObject* pW = new CArrayObject();
|
CArrayObject* pW = new CArrayObject();
|
||||||
pTrailer->Add("W", pW);
|
pTrailer->Add("W", pW);
|
||||||
|
|||||||
@ -468,7 +468,7 @@ namespace PdfWriter
|
|||||||
{
|
{
|
||||||
m_unFilter = unFiler;
|
m_unFilter = unFiler;
|
||||||
}
|
}
|
||||||
void SetStream(CXref* pXref, CStream* pStream);
|
void SetStream(CXref* pXref, CStream* pStream, bool bThis = true);
|
||||||
|
|
||||||
virtual void BeforeWrite(){}
|
virtual void BeforeWrite(){}
|
||||||
virtual void Write(CStream* pStream){}
|
virtual void Write(CStream* pStream){}
|
||||||
|
|||||||
@ -589,7 +589,6 @@ namespace PdfWriter
|
|||||||
// Добавляем запись Filter
|
// Добавляем запись Filter
|
||||||
if (pDict->GetStream())
|
if (pDict->GetStream())
|
||||||
{
|
{
|
||||||
pDict->Remove("Filter");
|
|
||||||
unsigned int unFilter = pDict->GetFilter();
|
unsigned int unFilter = pDict->GetFilter();
|
||||||
if (STREAM_FILTER_NONE != unFilter)
|
if (STREAM_FILTER_NONE != unFilter)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -281,7 +281,7 @@ TEST_F(CPdfFileTest, EditPdf)
|
|||||||
|
|
||||||
TEST_F(CPdfFileTest, EditPdfFromBase64)
|
TEST_F(CPdfFileTest, EditPdfFromBase64)
|
||||||
{
|
{
|
||||||
// GTEST_SKIP();
|
GTEST_SKIP();
|
||||||
|
|
||||||
LoadFromFile();
|
LoadFromFile();
|
||||||
ASSERT_TRUE(pdfFile->EditPdf(wsDstFile));
|
ASSERT_TRUE(pdfFile->EditPdf(wsDstFile));
|
||||||
@ -368,3 +368,19 @@ TEST_F(CPdfFileTest, EditPdfSign)
|
|||||||
|
|
||||||
RELEASEOBJECT(pCertificate);
|
RELEASEOBJECT(pCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(CPdfFileTest, ChangePasswordToEmpty)
|
||||||
|
{
|
||||||
|
// GTEST_SKIP();
|
||||||
|
|
||||||
|
LoadFromFile();
|
||||||
|
EXPECT_HRESULT_SUCCEEDED(pdfFile->ChangePassword(wsDstFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CPdfFileTest, ChangePasswordToPassword)
|
||||||
|
{
|
||||||
|
GTEST_SKIP();
|
||||||
|
|
||||||
|
LoadFromFile();
|
||||||
|
EXPECT_HRESULT_SUCCEEDED(pdfFile->ChangePassword(wsDstFile, L"123456"));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user