Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
wine
Manage
Activity
Members
Labels
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container Registry
Model registry
Operate
Terraform modules
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Sebastian Mayr
wine
Commits
1ff40d15
Commit
1ff40d15
authored
17 years ago
by
James Hawkins
Committed by
Alexandre Julliard
17 years ago
Browse files
Options
Downloads
Patches
Plain Diff
msi: Add tests for MsiSourceListGetInfo.
parent
6cc2f0f4
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
dlls/msi/tests/Makefile.in
+1
-0
1 addition, 0 deletions
dlls/msi/tests/Makefile.in
dlls/msi/tests/msi.c
+1
-1
1 addition, 1 deletion
dlls/msi/tests/msi.c
dlls/msi/tests/source.c
+361
-0
361 additions, 0 deletions
dlls/msi/tests/source.c
include/winerror.h
+1
-0
1 addition, 0 deletions
include/winerror.h
with
364 additions
and
1 deletion
dlls/msi/tests/Makefile.in
+
1
−
0
View file @
1ff40d15
...
...
@@ -14,6 +14,7 @@ CTESTS = \
msi.c
\
package.c
\
record.c
\
source.c
\
suminfo.c
@MAKE_TEST_RULES@
...
...
This diff is collapsed.
Click to expand it.
dlls/msi/tests/msi.c
+
1
−
1
View file @
1ff40d15
...
...
@@ -262,7 +262,7 @@ static void test_filehash(void)
}
/* copied from dlls/msi/registry.c */
BOOL
squash_guid
(
LPCWSTR
in
,
LPWSTR
out
)
static
BOOL
squash_guid
(
LPCWSTR
in
,
LPWSTR
out
)
{
DWORD
i
,
n
=
1
;
GUID
guid
;
...
...
This diff is collapsed.
Click to expand it.
dlls/msi/tests/source.c
0 → 100644
+
361
−
0
View file @
1ff40d15
/*
* Tests for MSI Source functions
*
* Copyright (C) 2006 James Hawkins
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include
<stdio.h>
#include
<windows.h>
#include
<msiquery.h>
#include
<msidefs.h>
#include
<msi.h>
#include
<sddl.h>
#include
"wine/test.h"
/* copied from dlls/msi/registry.c */
static
BOOL
squash_guid
(
LPCWSTR
in
,
LPWSTR
out
)
{
DWORD
i
,
n
=
1
;
GUID
guid
;
if
(
FAILED
(
CLSIDFromString
((
LPOLESTR
)
in
,
&
guid
)))
return
FALSE
;
for
(
i
=
0
;
i
<
8
;
i
++
)
out
[
7
-
i
]
=
in
[
n
++
];
n
++
;
for
(
i
=
0
;
i
<
4
;
i
++
)
out
[
11
-
i
]
=
in
[
n
++
];
n
++
;
for
(
i
=
0
;
i
<
4
;
i
++
)
out
[
15
-
i
]
=
in
[
n
++
];
n
++
;
for
(
i
=
0
;
i
<
2
;
i
++
)
{
out
[
17
+
i
*
2
]
=
in
[
n
++
];
out
[
16
+
i
*
2
]
=
in
[
n
++
];
}
n
++
;
for
(
;
i
<
8
;
i
++
)
{
out
[
17
+
i
*
2
]
=
in
[
n
++
];
out
[
16
+
i
*
2
]
=
in
[
n
++
];
}
out
[
32
]
=
0
;
return
TRUE
;
}
static
void
create_test_guid
(
LPSTR
prodcode
,
LPSTR
squashed
)
{
WCHAR
guidW
[
MAX_PATH
];
WCHAR
squashedW
[
MAX_PATH
];
GUID
guid
;
HRESULT
hr
;
int
size
;
hr
=
CoCreateGuid
(
&
guid
);
ok
(
hr
==
S_OK
,
"Expected S_OK, got %d
\n
"
,
hr
);
size
=
StringFromGUID2
(
&
guid
,
(
LPOLESTR
)
guidW
,
MAX_PATH
);
ok
(
size
==
39
,
"Expected 39, got %d
\n
"
,
hr
);
WideCharToMultiByte
(
CP_ACP
,
0
,
guidW
,
size
,
prodcode
,
MAX_PATH
,
NULL
,
NULL
);
squash_guid
(
guidW
,
squashedW
);
WideCharToMultiByte
(
CP_ACP
,
0
,
squashedW
,
-
1
,
squashed
,
MAX_PATH
,
NULL
,
NULL
);
}
static
void
get_user_sid
(
LPSTR
*
usersid
)
{
HANDLE
token
;
BYTE
buf
[
1024
];
DWORD
size
;
PTOKEN_USER
user
;
OpenProcessToken
(
GetCurrentProcess
(),
TOKEN_QUERY
,
&
token
);
size
=
sizeof
(
buf
);
GetTokenInformation
(
token
,
TokenUser
,
(
void
*
)
buf
,
size
,
&
size
);
user
=
(
PTOKEN_USER
)
buf
;
ConvertSidToStringSid
(
user
->
User
.
Sid
,
usersid
);
}
static
void
test_MsiSourceListGetInfo
(
void
)
{
CHAR
prodcode
[
MAX_PATH
];
CHAR
prod_squashed
[
MAX_PATH
];
CHAR
keypath
[
MAX_PATH
*
2
];
CHAR
value
[
MAX_PATH
];
LPSTR
usersid
;
LPCSTR
data
;
LONG
res
;
UINT
r
;
HKEY
userkey
,
hkey
;
DWORD
size
;
create_test_guid
(
prodcode
,
prod_squashed
);
get_user_sid
(
&
usersid
);
/* NULL szProductCodeOrPatchCode */
r
=
MsiSourceListGetInfoA
(
NULL
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
/* empty szProductCodeOrPatchCode */
r
=
MsiSourceListGetInfoA
(
""
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
todo_wine
{
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
}
/* garbage szProductCodeOrPatchCode */
r
=
MsiSourceListGetInfoA
(
"garbage"
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
todo_wine
{
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
}
/* szProductCodeOrPatchCode */
r
=
MsiSourceListGetInfoA
(
"garbage"
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
todo_wine
{
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
}
/* guid without brackets */
r
=
MsiSourceListGetInfoA
(
"51CD2AD5-0482-4C46-8DDD-0ED1022AA1AA"
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
todo_wine
{
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
}
/* guid with brackets */
r
=
MsiSourceListGetInfoA
(
"{51CD2AD5-0482-4C46-8DDD-0ED1022AA1AA}"
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
ok
(
r
==
ERROR_UNKNOWN_PRODUCT
,
"Expected ERROR_UNKNOWN_PRODUCT, got %d
\n
"
,
r
);
/* same length as guid, but random */
r
=
MsiSourceListGetInfoA
(
"ADKD-2KSDFF2-DKK1KNFJASD9GLKWME-1I3KAD"
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
todo_wine
{
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
}
/* invalid context */
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_NONE
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
todo_wine
{
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
}
/* another invalid context */
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_ALLUSERMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
todo_wine
{
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
}
/* yet another invalid context */
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_ALL
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
todo_wine
{
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
}
/* mix two valid contexts */
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERMANAGED
|
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
todo_wine
{
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
}
/* invalid option */
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
4
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
NULL
);
ok
(
r
==
ERROR_UNKNOWN_PRODUCT
,
"Expected ERROR_UNKNOWN_PRODUCT, got %d
\n
"
,
r
);
/* NULL property */
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
NULL
,
NULL
,
NULL
);
todo_wine
{
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
}
/* empty property */
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
""
,
NULL
,
NULL
);
ok
(
r
==
ERROR_UNKNOWN_PRODUCT
,
"Expected ERROR_UNKNOWN_PRODUCT, got %d
\n
"
,
r
);
/* value is non-NULL while size is NULL */
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
value
,
NULL
);
ok
(
r
==
ERROR_INVALID_PARAMETER
,
"Expected ERROR_INVALID_PARAMETER, got %d
\n
"
,
r
);
/* size is non-NULL while value is NULL */
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
&
size
);
ok
(
r
==
ERROR_UNKNOWN_PRODUCT
,
"Expected ERROR_UNKNOWN_PRODUCT, got %d
\n
"
,
r
);
lstrcpyA
(
keypath
,
"Software
\\
Microsoft
\\
Installer
\\
Products
\\
"
);
lstrcatA
(
keypath
,
prod_squashed
);
res
=
RegCreateKeyA
(
HKEY_CURRENT_USER
,
keypath
,
&
userkey
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
/* user product key exists */
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
&
size
);
todo_wine
{
ok
(
r
==
ERROR_BAD_CONFIGURATION
,
"Expected ERROR_BAD_CONFIGURATION, got %d
\n
"
,
r
);
}
res
=
RegCreateKeyA
(
userkey
,
"SourceList"
,
&
hkey
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
/* SourceList key exists */
size
=
0xdeadbeef
;
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
&
size
);
todo_wine
{
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
r
);
ok
(
size
==
0
,
"Expected 0, got %d
\n
"
,
size
);
}
data
=
"msitest.msi"
;
res
=
RegSetValueExA
(
hkey
,
"PackageName"
,
0
,
REG_SZ
,
(
const
BYTE
*
)
data
,
lstrlenA
(
data
)
+
1
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
/* PackageName value exists */
size
=
0xdeadbeef
;
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
&
size
);
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
r
);
ok
(
size
==
11
,
"Expected 11, got %d
\n
"
,
size
);
/* read the value, don't change size */
lstrcpyA
(
value
,
"aaa"
);
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
value
,
&
size
);
ok
(
r
==
ERROR_MORE_DATA
,
"Expected ERROR_MORE_DATA, got %d
\n
"
,
r
);
ok
(
!
lstrcmpA
(
value
,
"aaa"
),
"Expected 'aaa', got %s
\n
"
,
value
);
ok
(
size
==
11
,
"Expected 11, got %d
\n
"
,
size
);
/* read the value, fix size */
size
++
;
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
INSTALLPROPERTY_PACKAGENAME
,
value
,
&
size
);
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
r
);
ok
(
!
lstrcmpA
(
value
,
"msitest.msi"
),
"Expected 'msitest.msi', got %s
\n
"
,
value
);
ok
(
size
==
11
,
"Expected 11, got %d
\n
"
,
size
);
/* empty property now that product key exists */
size
=
0xdeadbeef
;
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
""
,
NULL
,
&
size
);
ok
(
r
==
ERROR_UNKNOWN_PROPERTY
,
"Expected ERROR_UNKNOWN_PROPERTY, got %d
\n
"
,
r
);
ok
(
size
==
0xdeadbeef
,
"Expected 0xdeadbeef, got %d
\n
"
,
size
);
/* nonexistent property now that product key exists */
size
=
0xdeadbeef
;
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
"nonexistent"
,
NULL
,
&
size
);
ok
(
r
==
ERROR_UNKNOWN_PROPERTY
,
"Expected ERROR_UNKNOWN_PROPERTY, got %d
\n
"
,
r
);
ok
(
size
==
0xdeadbeef
,
"Expected 0xdeadbeef, got %d
\n
"
,
size
);
data
=
"tester"
;
res
=
RegSetValueExA
(
hkey
,
"nonexistent"
,
0
,
REG_SZ
,
(
const
BYTE
*
)
data
,
lstrlenA
(
data
)
+
1
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
/* nonexistent property now that nonexistent value exists */
size
=
0xdeadbeef
;
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PRODUCT
,
"nonexistent"
,
NULL
,
&
size
);
ok
(
r
==
ERROR_UNKNOWN_PROPERTY
,
"Expected ERROR_UNKNOWN_PROPERTY, got %d
\n
"
,
r
);
ok
(
size
==
0xdeadbeef
,
"Expected 0xdeadbeef, got %d
\n
"
,
size
);
/* invalid option now that product key exists */
size
=
0xdeadbeef
;
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
4
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
&
size
);
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
r
);
ok
(
size
==
11
,
"Expected 11, got %d
\n
"
,
size
);
RegDeleteValueA
(
hkey
,
"nonexistent"
);
RegDeleteValueA
(
hkey
,
"PackageName"
);
RegDeleteKeyA
(
hkey
,
""
);
RegDeleteKeyA
(
userkey
,
""
);
RegCloseKey
(
hkey
);
RegCloseKey
(
userkey
);
/* try a patch */
size
=
0xdeadbeef
;
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PATCH
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
&
size
);
todo_wine
{
ok
(
r
==
ERROR_UNKNOWN_PATCH
,
"Expected ERROR_UNKNOWN_PATCH, got %d
\n
"
,
r
);
}
ok
(
size
==
0xdeadbeef
,
"Expected 0xdeadbeef, got %d
\n
"
,
size
);
lstrcpyA
(
keypath
,
"Software
\\
Microsoft
\\
Installer
\\
Patches
\\
"
);
lstrcatA
(
keypath
,
prod_squashed
);
res
=
RegCreateKeyA
(
HKEY_CURRENT_USER
,
keypath
,
&
userkey
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
/* patch key exists
* NOTE: using prodcode guid, but it really doesn't matter
*/
size
=
0xdeadbeef
;
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PATCH
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
&
size
);
todo_wine
{
ok
(
r
==
ERROR_BAD_CONFIGURATION
,
"Expected ERROR_BAD_CONFIGURATION, got %d
\n
"
,
r
);
}
ok
(
size
==
0xdeadbeef
,
"Expected 0xdeadbeef, got %d
\n
"
,
size
);
res
=
RegCreateKeyA
(
userkey
,
"SourceList"
,
&
hkey
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
/* SourceList key exists */
size
=
0xdeadbeef
;
r
=
MsiSourceListGetInfoA
(
prodcode
,
usersid
,
MSIINSTALLCONTEXT_USERUNMANAGED
,
MSICODE_PATCH
,
INSTALLPROPERTY_PACKAGENAME
,
NULL
,
&
size
);
todo_wine
{
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
r
);
ok
(
size
==
0
,
"Expected 0, got %d
\n
"
,
size
);
}
}
START_TEST
(
source
)
{
test_MsiSourceListGetInfo
();
}
This diff is collapsed.
Click to expand it.
include/winerror.h
+
1
−
0
View file @
1ff40d15
...
...
@@ -707,6 +707,7 @@ static inline HRESULT HRESULT_FROM_WIN32(unsigned long x)
#define ERROR_INVALID_COMMAND_LINE 1639
#define ERROR_INSTALL_REMOTE_DISALLOWED 1640
#define ERROR_SUCCESS_REBOOT_INITIATED 1641
#define ERROR_UNKNOWN_PATCH 1647
#define RPC_S_INVALID_STRING_BINDING 1700
#define RPC_S_WRONG_KIND_OF_BINDING 1701
#define RPC_S_INVALID_BINDING 1702
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment