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
2cba43e6
Commit
2cba43e6
authored
May 11, 2017
by
Oleg Korshul
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
.
parent
abcf8d02
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
305 additions
and
173 deletions
+305
-173
DesktopEditor/common/BigInteger.h
DesktopEditor/common/BigInteger.h
+4
-4
DesktopEditor/xml/build/qt/libxml2.pro
DesktopEditor/xml/build/qt/libxml2.pro
+13
-0
DesktopEditor/xmlsec/src/OOXMLSigner.h
DesktopEditor/xmlsec/src/OOXMLSigner.h
+1
-161
DesktopEditor/xmlsec/src/XmlCanonicalizator.h
DesktopEditor/xmlsec/src/XmlCanonicalizator.h
+11
-7
DesktopEditor/xmlsec/src/XmlRels.h
DesktopEditor/xmlsec/src/XmlRels.h
+176
-0
DesktopEditor/xmlsec/src/XmlTransform.h
DesktopEditor/xmlsec/src/XmlTransform.h
+60
-0
DesktopEditor/xmlsec/test/windows/test.pro
DesktopEditor/xmlsec/test/windows/test.pro
+3
-1
DesktopEditor/xmlsec/test/windows_list_serts/main.cpp
DesktopEditor/xmlsec/test/windows_list_serts/main.cpp
+37
-0
No files found.
DesktopEditor/common/BigInteger.h
View file @
2cba43e6
...
...
@@ -326,7 +326,7 @@ private:
if
(
differenceInLength
<
0
)
differenceInLength
=
-
differenceInLength
;
for
(
int
i
=
number1
.
size
(
)
-
1
;
i
>=
0
;
--
i
)
for
(
int
i
=
(
int
)(
number1
.
size
()
)
-
1
;
i
>=
0
;
--
i
)
{
add
[
i
]
=
((
carry
-
'0'
)
+
(
number1
[
i
]
-
'0'
)
+
(
number2
[
i
]
-
'0'
))
+
'0'
;
...
...
@@ -364,7 +364,7 @@ private:
if
(
differenceInLength
<
0
)
differenceInLength
=
-
differenceInLength
;
for
(
int
i
=
number1
.
length
(
)
-
1
;
i
>=
0
;
--
i
)
for
(
int
i
=
(
int
)(
number1
.
length
()
)
-
1
;
i
>=
0
;
--
i
)
{
if
(
number1
[
i
]
<
number2
[
i
])
{
...
...
@@ -386,13 +386,13 @@ private:
n1
.
swap
(
n2
);
std
::
string
res
=
"0"
;
for
(
int
i
=
n1
.
length
(
)
-
1
;
i
>=
0
;
--
i
)
for
(
int
i
=
(
int
)(
n1
.
length
()
)
-
1
;
i
>=
0
;
--
i
)
{
std
::
string
temp
=
n2
;
int
currentDigit
=
n1
[
i
]
-
'0'
;
int
carry
=
0
;
for
(
int
j
=
temp
.
length
(
)
-
1
;
j
>=
0
;
--
j
)
for
(
int
j
=
(
int
)(
temp
.
length
()
)
-
1
;
j
>=
0
;
--
j
)
{
temp
[
j
]
=
((
temp
[
j
]
-
'0'
)
*
currentDigit
)
+
carry
;
...
...
DesktopEditor/xml/build/qt/libxml2.pro
View file @
2cba43e6
...
...
@@ -11,6 +11,19 @@ TEMPLATE = lib
CONFIG
+=
staticlib
QMAKE_CXXFLAGS
+=
-
Wall
-
g
DEFINES
+=
\
LIBXML_READER_ENABLED
\
LIBXML_PUSH_ENABLED
\
LIBXML_HTML_ENABLED
\
LIBXML_XPATH_ENABLED
\
LIBXML_OUTPUT_ENABLED
\
LIBXML_C14N_ENABLED
\
LIBXML_SAX1_ENABLED
\
LIBXML_TREE_ENABLED
\
LIBXML_XPTR_ENABLED
\
IN_LIBXML
\
LIBXML_STATIC
CORE_ROOT_DIR
=
$$
PWD
/../../../..
PWD_ROOT_DIR
=
$$
PWD
include
(..
/../../../
Common
/
base
.
pri
)
...
...
DesktopEditor/xmlsec/src/OOXMLSigner.h
View file @
2cba43e6
...
...
@@ -3,6 +3,7 @@
#include "./XmlCanonicalizator.h"
#include "./XmlSignerBase.h"
#include "./XmlTransform.h"
class
COOXMLSigner
{
...
...
@@ -23,167 +24,6 @@ public:
std
::
wstring
m_guid
;
public:
class
COOXMLRelationship
{
public:
std
::
wstring
rid
;
std
::
wstring
type
;
std
::
wstring
target
;
std
::
wstring
target_mode
;
public:
COOXMLRelationship
()
{
}
COOXMLRelationship
(
XmlUtils
::
CXmlNode
&
node
)
{
rid
=
node
.
GetAttribute
(
"Id"
);
type
=
node
.
GetAttribute
(
"Type"
);
target
=
node
.
GetAttribute
(
"Target"
);
CheckTargetMode
();
}
std
::
wstring
GetXml
()
{
NSStringUtils
::
CStringBuilder
builder
;
builder
.
WriteString
(
L"<Relationship Id=
\"
"
);
builder
.
WriteEncodeXmlString
(
rid
);
builder
.
WriteString
(
L"
\"
Type=
\"
"
);
builder
.
WriteEncodeXmlString
(
type
);
builder
.
WriteString
(
L"
\"
Target=
\"
"
);
builder
.
WriteEncodeXmlString
(
target
);
builder
.
WriteString
(
L"
\"
TargetMode=
\"
"
);
builder
.
WriteEncodeXmlString
(
target_mode
);
builder
.
WriteString
(
L"
\"
/>"
);
return
builder
.
GetData
();
}
static
bool
Compare
(
const
COOXMLRelationship
&
i
,
const
COOXMLRelationship
&
j
)
{
return
i
.
rid
<
j
.
rid
;
}
protected:
void
CheckTargetMode
()
{
if
(
0
==
target
.
find
(
L"http"
)
||
0
==
target
.
find
(
L"www"
)
||
0
==
target
.
find
(
L"ftp"
))
target_mode
=
L"External"
;
else
target_mode
=
L"Internal"
;
}
};
class
COOXMLRelationships
{
public:
std
::
vector
<
COOXMLRelationship
>
rels
;
public:
COOXMLRelationships
()
{
}
COOXMLRelationships
(
std
::
wstring
&
file
)
{
XmlUtils
::
CXmlNode
oNode
;
if
(
!
oNode
.
FromXmlFile
(
file
))
return
;
XmlUtils
::
CXmlNodes
oNodes
;
if
(
!
oNode
.
GetNodes
(
L"Relationship"
,
oNodes
))
return
;
int
nCount
=
oNodes
.
GetCount
();
for
(
int
i
=
0
;
i
<
nCount
;
++
i
)
{
XmlUtils
::
CXmlNode
oRel
;
oNodes
.
GetAt
(
i
,
oRel
);
rels
.
push_back
(
COOXMLRelationship
(
oRel
));
}
}
std
::
wstring
GetXml
()
{
NSStringUtils
::
CStringBuilder
builder
;
builder
.
WriteString
(
L"<Relationships xmlns=
\"
http://schemas.openxmlformats.org/package/2006/relationships
\"
>"
);
// sort by rId
std
::
sort
(
rels
.
begin
(),
rels
.
end
(),
COOXMLRelationship
::
Compare
);
for
(
std
::
vector
<
COOXMLRelationship
>::
iterator
i
=
rels
.
begin
();
i
!=
rels
.
end
();
i
++
)
builder
.
WriteString
(
i
->
GetXml
());
builder
.
WriteString
(
L"</Relationships>"
);
return
builder
.
GetData
();
}
std
::
wstring
GetTransforms
()
{
NSStringUtils
::
CStringBuilder
builder
;
builder
.
WriteString
(
L"<Transforms><Transform Algorithm=
\"
http://schemas.openxmlformats.org/package/2006/RelationshipTransform
\"
>"
);
for
(
std
::
vector
<
COOXMLRelationship
>::
iterator
i
=
rels
.
begin
();
i
!=
rels
.
end
();
i
++
)
{
builder
.
WriteString
(
L"<mdssi:RelationshipReference xmlns:mdssi=
\"
http://schemas.openxmlformats.org/package/2006/digital-signature
\"
SourceId=
\"
"
);
builder
.
WriteEncodeXmlString
(
i
->
rid
);
builder
.
WriteString
(
L"
\"
/>"
);
}
builder
.
WriteString
(
L"</Transform><Transform Algorithm=
\"
http://www.w3.org/TR/2001/REC-xml-c14n-20010315
\"
/></Transforms>"
);
return
builder
.
GetData
();
}
void
CheckOriginSigs
(
std
::
wstring
&
file
)
{
int
rId
=
0
;
std
::
vector
<
COOXMLRelationship
>::
iterator
i
=
rels
.
begin
();
while
(
i
!=
rels
.
end
())
{
if
(
0
==
i
->
target
.
find
(
L"_xmlsignatures/"
))
return
;
std
::
wstring
rid
=
i
->
rid
;
rid
=
rid
.
substr
(
3
);
int
nTemp
=
std
::
stoi
(
rid
);
if
(
nTemp
>
rId
)
rId
=
nTemp
;
i
++
;
}
std
::
string
sXmlA
;
NSFile
::
CFileBinary
::
ReadAllTextUtf8A
(
file
,
sXmlA
);
std
::
string
::
size_type
pos
=
sXmlA
.
rfind
(
"</Relationships>"
);
if
(
pos
==
std
::
string
::
npos
)
return
;
rId
++
;
std
::
string
sRet
=
sXmlA
.
substr
(
0
,
pos
);
sRet
+=
(
"<Relationship Id=
\"
rId"
+
std
::
to_string
(
rId
)
+
"
\"
\
Type=
\"
http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin
\"
Target=
\"
_xmlsignatures/origin.sigs
\"
/>\
</Relationships>"
);
NSFile
::
CFileBinary
::
Remove
(
file
);
NSFile
::
CFileBinary
oFile
;
oFile
.
CreateFileW
(
file
);
oFile
.
WriteFile
((
BYTE
*
)
sRet
.
c_str
(),
(
DWORD
)
sRet
.
length
());
oFile
.
CloseFile
();
}
};
public:
COOXMLSigner
(
const
std
::
wstring
&
sFolder
,
ICertificate
*
pContext
)
{
...
...
DesktopEditor/xmlsec/src/XmlCanonicalizator.h
View file @
2cba43e6
...
...
@@ -8,6 +8,10 @@
#include "../../xml/include/xmlutils.h"
#include "../../xml/libxml2/include/libxml/c14n.h"
#ifndef XML_UNUSED
#define XML_UNUSED( arg ) ( (arg) = (arg) )
#endif
class
CXmlCanonicalizator
{
private:
...
...
@@ -33,11 +37,14 @@ private:
static
int
buffer_xmlBufferIOClose
(
CXmlBuffer
*
buf
)
{
XML_UNUSED
(
buf
);
return
0
;
}
static
int
buffer_xmlC14NIsVisibleCallback
(
void
*
user_data
,
xmlNodePtr
node
,
xmlNodePtr
parent
)
{
XML_UNUSED
(
user_data
);
XML_UNUSED
(
parent
);
if
(
node
->
type
==
XML_TEXT_NODE
)
{
const
char
*
cur
=
(
char
*
)
node
->
content
;
...
...
@@ -53,7 +60,7 @@ private:
}
public:
static
std
::
string
Execute
(
const
std
::
string
&
sXml
,
int
mode
)
static
std
::
string
Execute
(
const
std
::
string
&
sXml
,
int
mode
=
XML_C14N_1_0
,
bool
withComments
=
false
)
{
xmlDocPtr
xmlDoc
=
xmlParseMemory
((
char
*
)
sXml
.
c_str
(),
(
int
)
sXml
.
length
());
...
...
@@ -63,21 +70,18 @@ public:
&
bufferC14N
,
NULL
);
xmlC14NExecute
(
xmlDoc
,
buffer_xmlC14NIsVisibleCallback
,
NULL
,
mode
,
NULL
,
0
,
_buffer
);
xmlC14NExecute
(
xmlDoc
,
buffer_xmlC14NIsVisibleCallback
,
NULL
,
mode
,
NULL
,
withComments
?
1
:
0
,
_buffer
);
xmlOutputBufferClose
(
_buffer
);
return
bufferC14N
.
builder
.
GetData
();
}
static
std
::
string
Execute
(
const
std
::
wstring
&
sXmlFile
,
int
mode
)
static
std
::
string
Execute
(
const
std
::
wstring
&
sXmlFile
,
int
mode
=
XML_C14N_1_0
,
bool
withComments
=
false
)
{
std
::
string
sXml
;
NSFile
::
CFileBinary
::
ReadAllTextUtf8A
(
sXmlFile
,
sXml
);
xmlDocPtr
xmlDoc
=
xmlParseMemory
((
char
*
)
sXml
.
c_str
(),
(
int
)
sXml
.
length
());
return
Execute
(
sXml
,
mode
);
return
Execute
(
sXml
,
mode
,
withComments
);
}
};
...
...
DesktopEditor/xmlsec/src/XmlRels.h
0 → 100644
View file @
2cba43e6
#ifndef _XML_RELS_H_
#define _XML_RELS_H_
#include "./XmlCanonicalizator.h"
class
COOXMLRelationship
{
public:
std
::
wstring
rid
;
std
::
wstring
type
;
std
::
wstring
target
;
std
::
wstring
target_mode
;
public:
COOXMLRelationship
()
{
}
COOXMLRelationship
(
XmlUtils
::
CXmlNode
&
node
)
{
rid
=
node
.
GetAttribute
(
"Id"
);
type
=
node
.
GetAttribute
(
"Type"
);
target
=
node
.
GetAttribute
(
"Target"
);
CheckTargetMode
();
}
std
::
wstring
GetXml
()
{
NSStringUtils
::
CStringBuilder
builder
;
builder
.
WriteString
(
L"<Relationship Id=
\"
"
);
builder
.
WriteEncodeXmlString
(
rid
);
builder
.
WriteString
(
L"
\"
Type=
\"
"
);
builder
.
WriteEncodeXmlString
(
type
);
builder
.
WriteString
(
L"
\"
Target=
\"
"
);
builder
.
WriteEncodeXmlString
(
target
);
builder
.
WriteString
(
L"
\"
TargetMode=
\"
"
);
builder
.
WriteEncodeXmlString
(
target_mode
);
builder
.
WriteString
(
L"
\"
/>"
);
return
builder
.
GetData
();
}
static
bool
Compare
(
const
COOXMLRelationship
&
i
,
const
COOXMLRelationship
&
j
)
{
return
i
.
rid
<
j
.
rid
;
}
protected:
void
CheckTargetMode
()
{
if
(
0
==
target
.
find
(
L"http"
)
||
0
==
target
.
find
(
L"www"
)
||
0
==
target
.
find
(
L"ftp"
))
target_mode
=
L"External"
;
else
target_mode
=
L"Internal"
;
}
};
class
COOXMLRelationships
{
public:
std
::
vector
<
COOXMLRelationship
>
rels
;
public:
COOXMLRelationships
()
{
}
COOXMLRelationships
(
const
std
::
wstring
&
file
,
std
::
map
<
std
::
wstring
,
bool
>*
check_need
=
NULL
)
{
XmlUtils
::
CXmlNode
oNode
;
if
(
!
oNode
.
FromXmlFile
(
file
))
return
;
XmlUtils
::
CXmlNodes
oNodes
;
if
(
!
oNode
.
GetNodes
(
L"Relationship"
,
oNodes
))
return
;
int
nCount
=
oNodes
.
GetCount
();
for
(
int
i
=
0
;
i
<
nCount
;
++
i
)
{
XmlUtils
::
CXmlNode
oRel
;
oNodes
.
GetAt
(
i
,
oRel
);
if
(
NULL
==
check_need
)
{
rels
.
push_back
(
COOXMLRelationship
(
oRel
));
}
else
{
std
::
wstring
sRid
=
oRel
.
GetAttribute
(
"Id"
);
if
(
check_need
->
find
(
sRid
)
!=
check_need
->
end
())
rels
.
push_back
(
COOXMLRelationship
(
oRel
));
}
}
}
std
::
wstring
GetXml
()
{
NSStringUtils
::
CStringBuilder
builder
;
builder
.
WriteString
(
L"<Relationships xmlns=
\"
http://schemas.openxmlformats.org/package/2006/relationships
\"
>"
);
// sort by rId
std
::
sort
(
rels
.
begin
(),
rels
.
end
(),
COOXMLRelationship
::
Compare
);
for
(
std
::
vector
<
COOXMLRelationship
>::
iterator
i
=
rels
.
begin
();
i
!=
rels
.
end
();
i
++
)
builder
.
WriteString
(
i
->
GetXml
());
builder
.
WriteString
(
L"</Relationships>"
);
return
builder
.
GetData
();
}
std
::
wstring
GetTransforms
()
{
NSStringUtils
::
CStringBuilder
builder
;
builder
.
WriteString
(
L"<Transforms><Transform Algorithm=
\"
http://schemas.openxmlformats.org/package/2006/RelationshipTransform
\"
>"
);
for
(
std
::
vector
<
COOXMLRelationship
>::
iterator
i
=
rels
.
begin
();
i
!=
rels
.
end
();
i
++
)
{
builder
.
WriteString
(
L"<mdssi:RelationshipReference xmlns:mdssi=
\"
http://schemas.openxmlformats.org/package/2006/digital-signature
\"
SourceId=
\"
"
);
builder
.
WriteEncodeXmlString
(
i
->
rid
);
builder
.
WriteString
(
L"
\"
/>"
);
}
builder
.
WriteString
(
L"</Transform><Transform Algorithm=
\"
http://www.w3.org/TR/2001/REC-xml-c14n-20010315
\"
/></Transforms>"
);
return
builder
.
GetData
();
}
void
CheckOriginSigs
(
std
::
wstring
&
file
)
{
int
rId
=
0
;
std
::
vector
<
COOXMLRelationship
>::
iterator
i
=
rels
.
begin
();
while
(
i
!=
rels
.
end
())
{
if
(
0
==
i
->
target
.
find
(
L"_xmlsignatures/"
))
return
;
std
::
wstring
rid
=
i
->
rid
;
rid
=
rid
.
substr
(
3
);
int
nTemp
=
std
::
stoi
(
rid
);
if
(
nTemp
>
rId
)
rId
=
nTemp
;
i
++
;
}
std
::
string
sXmlA
;
NSFile
::
CFileBinary
::
ReadAllTextUtf8A
(
file
,
sXmlA
);
std
::
string
::
size_type
pos
=
sXmlA
.
rfind
(
"</Relationships>"
);
if
(
pos
==
std
::
string
::
npos
)
return
;
rId
++
;
std
::
string
sRet
=
sXmlA
.
substr
(
0
,
pos
);
sRet
+=
(
"<Relationship Id=
\"
rId"
+
std
::
to_string
(
rId
)
+
"
\"
\
Type=
\"
http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin
\"
Target=
\"
_xmlsignatures/origin.sigs
\"
/>\
</Relationships>"
);
NSFile
::
CFileBinary
::
Remove
(
file
);
NSFile
::
CFileBinary
oFile
;
oFile
.
CreateFileW
(
file
);
oFile
.
WriteFile
((
BYTE
*
)
sRet
.
c_str
(),
(
DWORD
)
sRet
.
length
());
oFile
.
CloseFile
();
}
};
#endif //_XML_RELS_H_
DesktopEditor/xmlsec/src/XmlTransform.h
0 → 100644
View file @
2cba43e6
#ifndef _XML_TRANSFORM_H_
#define _XML_TRANSFORM_H_
#include "./XmlRels.h"
class
IXmlTransform
{
protected:
std
::
string
m_algorithm
;
public:
IXmlTransform
()
{
m_algorithm
=
""
;
}
virtual
~
IXmlTransform
()
{
}
public:
virtual
std
::
string
Transform
(
const
std
::
string
&
sXml
)
=
0
;
virtual
void
LoadFromXml
(
XmlUtils
::
CXmlNode
&
node
)
=
0
;
};
class
CXmlTransformRelationship
:
public
IXmlTransform
{
protected:
std
::
map
<
std
::
wstring
,
bool
>
m_arIds
;
public:
CXmlTransformRelationship
()
:
IXmlTransform
()
{
m_algorithm
=
"http://schemas.openxmlformats.org/package/2006/RelationshipTransform"
;
}
virtual
std
::
string
Transform
(
const
std
::
wstring
&
sFile
)
{
COOXMLRelationships
_rels
(
sFile
,
&
m_arIds
);
return
U_TO_UTF8
(
_rels
.
GetXml
());
}
virtual
void
LoadFromXml
(
XmlUtils
::
CXmlNode
&
node
)
{
XmlUtils
::
CXmlNodes
oNodesIds
;
node
.
GetChilds
(
oNodesIds
);
int
nCount
=
oNodesIds
.
GetCount
();
for
(
int
i
=
0
;
i
<
nCount
;
++
i
)
{
XmlUtils
::
CXmlNode
_node
;
oNodesIds
.
GetAt
(
i
,
_node
);
std
::
wstring
sType
=
_node
.
GetAttribute
(
"SourceId"
);
if
(
!
sType
.
empty
())
m_arIds
.
insert
(
std
::
pair
<
std
::
wstring
,
bool
>
(
sType
,
true
));
}
}
};
#endif //_XML_TRANSFORM_H_
DesktopEditor/xmlsec/test/windows/test.pro
View file @
2cba43e6
...
...
@@ -6,7 +6,9 @@ TEMPLATE = app
CONFIG
+=
console
CONFIG
-=
app_bundle
DEFINES
+=
UNICODE
DEFINES
-=
\
UNICODE
\
_UNICODE
CORE_ROOT_DIR
=
$$
PWD
/../../../..
PWD_ROOT_DIR
=
$$
PWD
...
...
DesktopEditor/xmlsec/test/windows_list_serts/main.cpp
View file @
2cba43e6
...
...
@@ -8,6 +8,43 @@
void
main
(
void
)
{
if
(
false
)
{
BYTE
*
pData
=
NULL
;
DWORD
dwDataLen
=
0
;
bool
bRes
=
NSFile
::
CFileBinary
::
ReadAllBytes
(
L"D:
\\
cert2.bin"
,
&
pData
,
dwDataLen
);
if
(
!
bRes
)
return
;
PCCERT_CONTEXT
context
=
CertCreateCertificateContext
(
X509_ASN_ENCODING
|
PKCS_7_ASN_ENCODING
,
pData
,
dwDataLen
);
if
(
!
context
)
{
RELEASEARRAYOBJECTS
(
pData
);
return
;
}
BOOL
result
=
CryptUIDlgViewContext
(
CERT_STORE_CERTIFICATE_CONTEXT
,
context
,
NULL
,
NULL
,
0
,
NULL
);
result
;
if
(
context
)
CertFreeCertificateContext
(
context
);
RELEASEARRAYOBJECTS
(
pData
);
return
;
}
if
(
false
)
{
xmlLoadExtDtdDefaultValue
=
XML_DETECT_IDS
|
XML_COMPLETE_ATTRS
;
xmlSubstituteEntitiesDefault
(
1
);
std
::
string
sXmlCan
=
CXmlCanonicalizator
::
Execute
(
L"D:
\\
1.xml"
,
XML_C14N_1_0
);
NSFile
::
CFileBinary
::
SaveToFile
(
L"D:
\\
2.xml"
,
UTF8_TO_U
(
sXmlCan
));
return
;
}
//std::wstring sFolderOOOXML = NSFile::GetProcessDirectory() + L"/ImageStamp";
//std::wstring sSignId = L"{39B6B9C7-60AD-45A2-9F61-40C74A24042E}";
...
...
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