Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
onlyoffice_core
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boris Kocherov
onlyoffice_core
Commits
11c9d74e
Commit
11c9d74e
authored
Jun 21, 2017
by
ElenaSubbotina
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix bug #35161
parent
d61a955c
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
139 additions
and
78 deletions
+139
-78
ASCOfficePPTFile/PPTFormatLib/PPTFormatLib.cpp
ASCOfficePPTFile/PPTFormatLib/PPTFormatLib.cpp
+13
-8
ASCOfficePPTFile/PPTFormatLib/Reader/PPTDocumentInfo.h
ASCOfficePPTFile/PPTFormatLib/Reader/PPTDocumentInfo.h
+14
-9
ASCOfficePPTFile/PPTFormatLib/Reader/PPTDocumentInfoOneUser.cpp
...icePPTFile/PPTFormatLib/Reader/PPTDocumentInfoOneUser.cpp
+65
-26
ASCOfficePPTFile/PPTFormatLib/Reader/PPTDocumentInfoOneUser.h
...fficePPTFile/PPTFormatLib/Reader/PPTDocumentInfoOneUser.h
+3
-1
ASCOfficePPTFile/PPTFormatLib/Reader/PPTFileReader.h
ASCOfficePPTFile/PPTFormatLib/Reader/PPTFileReader.h
+38
-31
ASCOfficePPTFile/PPTFormatLib/Reader/Records.h
ASCOfficePPTFile/PPTFormatLib/Reader/Records.h
+5
-2
Common/3dParty/cryptopp/arc4.h
Common/3dParty/cryptopp/arc4.h
+1
-1
No files found.
ASCOfficePPTFile/PPTFormatLib/PPTFormatLib.cpp
View file @
11c9d74e
...
...
@@ -64,7 +64,8 @@ long COfficePPTFile::OpenFile(const std::wstring & sFileName, const std::wstring
m_pReader
=
new
CPPTFileReader
(
pStgFrom
,
m_strTempDirectory
);
CPPTFileReader
*
pptReader
=
(
CPPTFileReader
*
)
m_pReader
;
pptReader
->
m_oDocumentInfo
.
m_strFileDirectory
=
GetDirectory
(
sFileName
.
c_str
());
pptReader
->
m_oDocumentInfo
.
m_strFileDirectory
=
GetDirectory
(
sFileName
.
c_str
());
pptReader
->
m_oDocumentInfo
.
m_strPassword
=
password
;
if
(
pptReader
->
IsPowerPoint
()
==
false
)
{
...
...
@@ -73,14 +74,14 @@ long COfficePPTFile::OpenFile(const std::wstring & sFileName, const std::wstring
return
AVS_ERROR_FILEFORMAT
;
}
if
(
pptReader
->
ReadPersistDirectory
()
==
false
)
return
AVS_ERROR_FILEFORMAT
;
if
(
pptReader
->
ReadPersists
()
==
false
)
return
AVS_ERROR_FILEFORMAT
;
if
(
pptReader
->
IsEncrypted
())
{
CEncryptionHeader
*
pEncryptionHeader
=
pptReader
->
GetEncryptionHeader
();
if
(
password
.
empty
())
return
AVS_ERROR_DRM
;
if
(
!
pEncryptionHeader
)
return
AVS_ERROR_FILEFORMAT
;
if
(
password
.
empty
())
return
AVS_ERROR_DRM
;
if
(
pEncryptionHeader
->
bStandard
)
{
...
...
@@ -92,7 +93,8 @@ long COfficePPTFile::OpenFile(const std::wstring & sFileName, const std::wstring
}
if
(
DecryptOfficeFile
(
&
Decryptor
)
==
false
)
return
AVS_ERROR_DRM
;
return
OpenFile
(
m_sTempDecryptFileName
,
L""
);
return
AVS_ERROR_PASSWORD
;
//return OpenFile(m_sTempDecryptFileName, L"");
}
else
{
...
...
@@ -106,12 +108,15 @@ long COfficePPTFile::OpenFile(const std::wstring & sFileName, const std::wstring
}
if
(
DecryptOfficeFile
(
&
Decryptor
)
==
false
)
return
AVS_ERROR_DRM
;
return
OpenFile
(
m_sTempDecryptFileName
,
L""
);
return
AVS_ERROR_PASSWORD
;
pptReader
->
ReadDocument
(
&
Decryptor
);
//return OpenFile(m_sTempDecryptFileName, L"");
}
}
else
{
pptReader
->
ReadSlideList
();
pptReader
->
ReadDocument
(
NULL
);
m_Status
=
READMODE
;
}
...
...
ASCOfficePPTFile/PPTFormatLib/Reader/PPTDocumentInfo.h
View file @
11c9d74e
...
...
@@ -40,6 +40,7 @@ public:
std
::
vector
<
CPPTUserInfo
*>
m_arUsers
;
std
::
wstring
m_strFileDirectory
;
std
::
map
<
int
,
std
::
wstring
>
m_mapStoreImageFile
;
std
::
wstring
m_strPassword
;
CPPTDocumentInfo
()
:
m_oCurrentUser
(),
m_arUsers
()
{
...
...
@@ -63,7 +64,7 @@ public:
}
}
bool
ReadFromStream
(
CRecordCurrentUserAtom
*
pCurrentUser
,
POLE
::
Stream
*
pStream
,
std
::
wstring
strFolderMem
)
bool
ReadFromStream
(
CRecordCurrentUserAtom
*
pCurrentUser
,
POLE
::
Stream
*
pStream
)
{
m_oCurrentUser
.
FromAtom
(
pCurrentUser
);
...
...
@@ -86,8 +87,9 @@ public:
pInfo
->
m_strFileDirectory
=
m_strFileDirectory
;
pInfo
->
m_bEncrypt
=
m_oCurrentUser
.
m_bIsEncrypt
;
pInfo
->
m_strPassword
=
m_strPassword
;
bool
bResult
=
pInfo
->
ReadFromStream
(
&
oUserAtom
,
pStream
,
strFolderMem
);
bool
bResult
=
pInfo
->
ReadFromStream
(
&
oUserAtom
,
pStream
);
offsetToEdit
=
pInfo
->
m_oUser
.
m_nOffsetLastEdit
;
...
...
@@ -104,13 +106,16 @@ public:
pInfo
=
NULL
;
}
if
(
m_arUsers
.
empty
()
==
false
)
{
if
(
m_arUsers
[
0
]
->
m_bEncrypt
==
false
)
{
m_arUsers
[
0
]
->
FromDocument
();
}
}
return
true
;
}
bool
LoadDocument
(
std
::
wstring
strFolderMem
)
{
if
(
m_arUsers
.
empty
())
return
false
;
m_arUsers
[
0
]
->
ReadExtenalObjects
(
strFolderMem
);
m_arUsers
[
0
]
->
FromDocument
();
return
true
;
}
};
ASCOfficePPTFile/PPTFormatLib/Reader/PPTDocumentInfoOneUser.cpp
View file @
11c9d74e
...
...
@@ -47,6 +47,7 @@ CPPTUserInfo::CPPTUserInfo() : CDocument(),
m_mapMasters
(),
m_mapNotes
(),
m_mapSlides
(),
m_bEncrypt
(
false
),
m_arOffsetPictures
()
{
m_pDocumentInfo
=
NULL
;
...
...
@@ -128,7 +129,7 @@ void CPPTUserInfo::Clear()
m_arOffsetPictures
.
clear
();
}
bool
CPPTUserInfo
::
ReadFromStream
(
CRecordUserEditAtom
*
pUser
,
POLE
::
Stream
*
pStream
,
std
::
wstring
strFolderMem
)
bool
CPPTUserInfo
::
ReadFromStream
(
CRecordUserEditAtom
*
pUser
,
POLE
::
Stream
*
pStream
)
{
m_oUser
.
FromAtom
(
pUser
);
...
...
@@ -149,8 +150,9 @@ bool CPPTUserInfo::ReadFromStream(CRecordUserEditAtom* pUser, POLE::Stream* pStr
oPersist
.
ReadFromStream
(
oHeader
,
pStream
);
oPersist
.
ToMap
(
&
m_mapOffsetInPIDs
);
//--------------------------------------------------------------------------------------------------
CRYPT
::
ECMADecryptor
*
pDecryptor
=
NULL
;
std
::
map
<
DWORD
,
DWORD
>::
iterator
pPair
=
m_mapOffsetInPIDs
.
find
(
m_oUser
.
m_nEncryptRef
);
if
(
pPair
!=
m_mapOffsetInPIDs
.
end
())
{
StreamUtils
::
StreamSeek
(
pPair
->
second
,
pStream
);
...
...
@@ -161,7 +163,17 @@ bool CPPTUserInfo::ReadFromStream(CRecordUserEditAtom* pUser, POLE::Stream* pStr
m_bEncrypt
=
true
;
m_oEncryptionHeader
.
ReadFromStream
(
oHeader
,
pStream
);
return
true
;
pDecryptor
=
new
CRYPT
::
ECMADecryptor
();
pDecryptor
->
SetCryptData
(
m_oEncryptionHeader
.
crypt_data_aes
);
if
(
pDecryptor
->
SetPassword
(
m_strPassword
)
==
false
)
{
delete
pDecryptor
;
pDecryptor
=
NULL
;
//return true;
}
return
true
;
//read persis decrypt
}
}
//--------------------------------------------------------------------------------------------------
...
...
@@ -170,15 +182,45 @@ bool CPPTUserInfo::ReadFromStream(CRecordUserEditAtom* pUser, POLE::Stream* pStr
if
(
pPair
==
m_mapOffsetInPIDs
.
end
())
return
false
;
StreamUtils
::
StreamSeek
(
pPair
->
second
,
pStream
);
DWORD
offset_stream
=
pPair
->
second
;
StreamUtils
::
StreamSeek
(
offset_stream
,
pStream
);
oHeader
.
ReadFromStream
(
pStream
);
if
(
RECORD_TYPE_DOCUMENT
!=
oHeader
.
RecType
)
if
(
pDecryptor
)
{
return
false
;
}
POLE
::
Storage
*
pStorageOut
=
NULL
;
POLE
::
Stream
*
pStreamTmp
=
NULL
;
std
::
wstring
sTemp
=
m_strFileDirectory
+
FILE_SEPARATOR_STR
+
L"~tempFile.ppt"
;
pStorageOut
=
new
POLE
::
Storage
(
sTemp
.
c_str
());
pStorageOut
->
open
(
true
,
true
);
pStreamTmp
=
new
POLE
::
Stream
(
pStorageOut
,
"Tmp"
,
true
,
oHeader
.
RecLen
);
unsigned
char
*
data_stream
=
new
unsigned
char
[
oHeader
.
RecLen
];
pStream
->
read
(
data_stream
,
oHeader
.
RecLen
);
pDecryptor
->
Decrypt
((
char
*
)
data_stream
,
oHeader
.
RecLen
,
m_oUser
.
m_nDocumentRef
);
pStreamTmp
->
write
(
data_stream
,
oHeader
.
RecLen
);
pStreamTmp
->
flush
();
pStreamTmp
->
seek
(
0
);
m_oDocument
.
ReadFromStream
(
oHeader
,
pStreamTmp
);
m_oDocument
.
ReadFromStream
(
oHeader
,
pStream
);
delete
pStream
;
delete
pStorageOut
;
//NSFile::DeleteFile(sTemp);
}
else
{
if
(
RECORD_TYPE_DOCUMENT
!=
oHeader
.
RecType
)
{
return
false
;
}
m_oDocument
.
ReadFromStream
(
oHeader
,
pStream
);
}
Clear
();
...
...
@@ -190,11 +232,12 @@ bool CPPTUserInfo::ReadFromStream(CRecordUserEditAtom* pUser, POLE::Stream* pStr
if
(
nIndexPsrRef
!=
m_mapOffsetInPIDs
.
end
())
{
long
offset
=
(
long
)
nIndexPsrRef
->
second
;
offset_stream
=
nIndexPsrRef
->
second
;
StreamUtils
::
StreamSeek
(
offset
,
pStream
);
StreamUtils
::
StreamSeek
(
offset
_stream
,
pStream
);
oHeader
.
ReadFromStream
(
pStream
);
CRecordSlide
*
pSlide
=
new
CRecordSlide
();
pSlide
->
ReadFromStream
(
oHeader
,
pStream
);
pSlide
->
m_oPersist
=
m_oDocument
.
m_arMasterPersists
[
index
];
...
...
@@ -215,8 +258,8 @@ bool CPPTUserInfo::ReadFromStream(CRecordUserEditAtom* pUser, POLE::Stream* pStr
if
(
m_mapOffsetInPIDs
.
end
()
!=
nIndexPsrRef
)
{
long
offset
=
(
long
)
nIndexPsrRef
->
second
;
StreamUtils
::
StreamSeek
(
offset
,
pStream
);
offset_stream
=
nIndexPsrRef
->
second
;
StreamUtils
::
StreamSeek
(
offset
_stream
,
pStream
);
oHeader
.
ReadFromStream
(
pStream
);
CRecordSlide
*
pSlide
=
new
CRecordSlide
();
...
...
@@ -240,9 +283,9 @@ bool CPPTUserInfo::ReadFromStream(CRecordUserEditAtom* pUser, POLE::Stream* pStr
if
(
m_mapOffsetInPIDs
.
end
()
!=
nIndexPsrRef
)
{
long
offset
=
(
long
)
nIndexPsrRef
->
second
;
offset_stream
=
(
long
)
nIndexPsrRef
->
second
;
StreamUtils
::
StreamSeek
(
offset
,
pStream
);
StreamUtils
::
StreamSeek
(
offset
_stream
,
pStream
);
oHeader
.
ReadFromStream
(
pStream
);
...
...
@@ -283,9 +326,9 @@ bool CPPTUserInfo::ReadFromStream(CRecordUserEditAtom* pUser, POLE::Stream* pStr
if
(
m_mapOffsetInPIDs
.
end
()
!=
nIndexPsrRef
)
{
long
offset
=
(
long
)
nIndexPsrRef
->
second
;
offset_stream
=
nIndexPsrRef
->
second
;
StreamUtils
::
StreamSeek
(
offset
,
pStream
);
StreamUtils
::
StreamSeek
(
offset
_stream
,
pStream
);
oHeader
.
ReadFromStream
(
pStream
);
...
...
@@ -300,9 +343,9 @@ bool CPPTUserInfo::ReadFromStream(CRecordUserEditAtom* pUser, POLE::Stream* pStr
if
(
m_mapOffsetInPIDs
.
end
()
!=
nIndexPsrRef
)
{
long
offset
=
(
long
)
nIndexPsrRef
->
second
;
offset_stream
=
nIndexPsrRef
->
second
;
StreamUtils
::
StreamSeek
(
offset
,
pStream
);
StreamUtils
::
StreamSeek
(
offset
_stream
,
pStream
);
oHeader
.
ReadFromStream
(
pStream
);
...
...
@@ -314,7 +357,11 @@ bool CPPTUserInfo::ReadFromStream(CRecordUserEditAtom* pUser, POLE::Stream* pStr
m_mapHandoutMasters
.
insert
(
std
::
pair
<
DWORD
,
CRecordSlide
*>
(
0
,
pSlide
));
}
}
return
true
;
}
//--------------------------------------------------------------------------------------------
void
CPPTUserInfo
::
ReadExtenalObjects
(
std
::
wstring
strFolderMem
)
{
// так... теперь берем всю инфу о ExObject -----------------------------
m_oExMedia
.
m_strPresentationDirectory
=
strFolderMem
;
m_oExMedia
.
m_strSourceDirectory
=
m_strFileDirectory
;
...
...
@@ -364,20 +411,12 @@ bool CPPTUserInfo::ReadFromStream(CRecordUserEditAtom* pUser, POLE::Stream* pStr
m_arrFonts
.
push_back
(
oFont
);
}
//FromDocument();
// FromDocument - должен вызываться после того, как загрузятся все (!!!) юзеры
// теперь заполним пустые картинки
//std::vector<CRecordBlipStoreContainer*> oArray;
m_oDocument
.
GetRecordsByType
(
&
m_arrBlipStore
,
true
,
true
);
if
(
0
<
m_arrBlipStore
.
size
())
{
m_bIsSetupEmpty
=
TRUE
;
m_arrBlipStore
[
0
]
->
SetUpPicturesInfos
(
&
m_arOffsetPictures
);
}
return
TRUE
;
}
void
CPPTUserInfo
::
FromDocument
()
...
...
ASCOfficePPTFile/PPTFormatLib/Reader/PPTDocumentInfoOneUser.h
View file @
11c9d74e
...
...
@@ -50,6 +50,7 @@ public:
CEncryptionHeader
m_oEncryptionHeader
;
bool
m_bEncrypt
;
std
::
wstring
m_strPassword
;
std
::
map
<
DWORD
,
CRecordSlide
*>
m_mapSlides
;
std
::
map
<
DWORD
,
CRecordSlide
*>
m_mapMasters
;
...
...
@@ -122,7 +123,8 @@ public:
void
Clear
();
bool
ReadFromStream
(
CRecordUserEditAtom
*
pUser
,
POLE
::
Stream
*
pStream
,
std
::
wstring
strFolderMem
);
bool
ReadFromStream
(
CRecordUserEditAtom
*
pUser
,
POLE
::
Stream
*
pStream
);
void
ReadExtenalObjects
(
std
::
wstring
strFolderMem
);
void
FromDocument
();
void
NormalizeCoords
(
long
lWidth
,
long
lHeight
);
...
...
ASCOfficePPTFile/PPTFormatLib/Reader/PPTFileReader.h
View file @
11c9d74e
...
...
@@ -56,7 +56,6 @@ public:
m_bIsPPTFile
(
false
),
m_pDocStream
(
NULL
),
m_pPictureStream
(
NULL
),
m_lImagesCount
(
0
),
m_strMemoryForder
(
strTemp
),
m_oDocumentInfo
()
{
...
...
@@ -114,7 +113,7 @@ public:
if
(
m_oDocumentInfo
.
m_arUsers
.
empty
())
return
m_oDocumentInfo
.
m_oCurrentUser
.
m_bIsEncrypt
;
//wps не выставляет флаг!
return
&
m_oDocumentInfo
.
m_arUsers
[
0
]
->
m_bEncrypt
;
return
m_oDocumentInfo
.
m_arUsers
[
0
]
->
m_bEncrypt
;
}
CEncryptionHeader
*
GetEncryptionHeader
()
{
...
...
@@ -122,26 +121,14 @@ public:
return
&
m_oDocumentInfo
.
m_arUsers
[
0
]
->
m_oEncryptionHeader
;
}
bool
ReadPersist
Directory
()
bool
ReadPersist
s
()
{
// нужно вызывать РОВНО один раз...
bool
bRes
=
SavePictures
();
return
m_oDocumentInfo
.
ReadFromStream
(
&
m_oCurrentUser
,
GetDocStream
(),
m_strMemoryForder
);
return
m_oDocumentInfo
.
ReadFromStream
(
&
m_oCurrentUser
,
GetDocStream
());
}
void
ReadSlideList
()
void
ReadDocument
(
CRYPT
::
ECMADecryptor
*
pDecryptor
)
{
if
(
m_oDocumentInfo
.
m_arUsers
.
size
()
>
0
)
{
DWORD
nPID
=
m_oDocumentInfo
.
m_arUsers
[
0
]
->
m_oUser
.
m_nDocumentRef
;
std
::
map
<
DWORD
,
DWORD
>::
iterator
pPair
=
m_oDocumentInfo
.
m_arUsers
[
0
]
->
m_mapOffsetInPIDs
.
find
(
nPID
);
if
(
pPair
==
m_oDocumentInfo
.
m_arUsers
[
0
]
->
m_mapOffsetInPIDs
.
end
())
return
;
DWORD
offset
=
pPair
->
second
;
StreamUtils
::
StreamSeek
((
long
)
offset
,
GetDocStream
());
}
ReadPictures
(
pDecryptor
);
m_oDocumentInfo
.
LoadDocument
(
m_strMemoryForder
);
}
protected:
...
...
@@ -194,20 +181,13 @@ protected:
return
m_pPictureStream
;
}
bool
SavePictures
(
)
void
ReadPictures
(
CRYPT
::
ECMADecryptor
*
pDecryptor
)
{
POLE
::
Stream
*
pStream
=
GetPictureStream
();
if
(
NULL
==
pStream
)
return
;
if
(
NULL
==
pStream
)
{
return
false
;
}
SRecordHeader
oHeader
;
ULONG
nRd
=
0
;
m_lImagesCount
=
0
;
// удаление картинок при завершении программы
while
(
true
)
{
...
...
@@ -220,11 +200,39 @@ protected:
art_blip
.
m_strMemoryForder
=
m_strMemoryForder
;
art_blip
.
m_oDocumentInfo
=
&
m_oDocumentInfo
;
art_blip
.
ReadFromStream
(
oHeader
,
pStream
);
if
(
pDecryptor
)
{
POLE
::
Storage
*
pStorageOut
=
NULL
;
POLE
::
Stream
*
pStreamTmp
=
NULL
;
std
::
wstring
sTemp
=
m_strMemoryForder
+
FILE_SEPARATOR_STR
+
L"~tempFile.ppt"
;
pStorageOut
=
new
POLE
::
Storage
(
sTemp
.
c_str
());
pStorageOut
->
open
(
true
,
true
);
pStreamTmp
=
new
POLE
::
Stream
(
pStorageOut
,
"Tmp"
,
true
,
oHeader
.
RecLen
);
unsigned
char
*
data_stream
=
new
unsigned
char
[
oHeader
.
RecLen
];
pStream
->
read
(
data_stream
,
oHeader
.
RecLen
);
pDecryptor
->
Decrypt
((
char
*
)
data_stream
,
oHeader
.
RecLen
,
0
);
pStreamTmp
->
write
(
data_stream
,
oHeader
.
RecLen
);
pStreamTmp
->
flush
();
pStreamTmp
->
seek
(
0
);
art_blip
.
ReadFromStream
(
oHeader
,
pStreamTmp
);
delete
pStream
;
delete
pStorageOut
;
//NSFile::DeleteFile(sTemp);
}
else
{
art_blip
.
ReadFromStream
(
oHeader
,
pStream
);
}
}
return
true
;
}
private:
bool
m_bDualStorage
;
...
...
@@ -239,7 +247,6 @@ public:
std
::
wstring
m_strMemoryForder
;
std
::
vector
<
bool
>
m_arLoadImageFlags
;
DWORD
m_lImagesCount
;
CPPTDocumentInfo
m_oDocumentInfo
;
};
ASCOfficePPTFile/PPTFormatLib/Reader/Records.h
View file @
11c9d74e
...
...
@@ -35,11 +35,13 @@
#include "../Reader/ReadStructures.h"
#include "../../../ASCOfficePPTXFile/Editor/Drawing/Shapes/BaseShape/PPTShape/Enums.h"
#include "../../../Common/3dParty/pole/pole.h"
#include "../../../OfficeCryptReader/source/CryptTransform.h"
using
namespace
NSPresentationEditor
;
struct
SRecordHeader
class
SRecordHeader
{
public:
BYTE
RecVersion
;
USHORT
RecInstance
;
USHORT
RecType
;
...
...
@@ -48,7 +50,8 @@ struct SRecordHeader
SRecordHeader
()
{
RecVersion
=
0
;
RecInstance
=
RecType
=
0
;
RecInstance
=
0
;
RecType
=
0
;
RecLen
=
0
;
}
...
...
Common/3dParty/cryptopp/arc4.h
View file @
11c9d74e
...
...
@@ -41,7 +41,7 @@ protected:
void
UncheckedSetKey
(
const
byte
*
key
,
unsigned
int
length
,
const
NameValuePairs
&
params
);
virtual
unsigned
int
GetDefaultDiscardBytes
()
const
{
return
0
;}
FixedSizeSecBlock
<
byte
,
512
>
m_state
;
FixedSizeSecBlock
<
byte
,
256
>
m_state
;
byte
m_x
,
m_y
;
};
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment