Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
f3d65911
Commit
f3d65911
authored
Aug 31, 2016
by
Hans Leidekker
Committed by
Alexandre Julliard
Aug 31, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
webservices: Store standard headers in an XML buffer.
Signed-off-by:
Hans Leidekker
<
hans@codeweavers.com
>
Signed-off-by:
Alexandre Julliard
<
julliard@winehq.org
>
parent
31963d7d
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
194 additions
and
120 deletions
+194
-120
msg.c
dlls/webservices/msg.c
+194
-120
No files found.
dlls/webservices/msg.c
View file @
f3d65911
...
...
@@ -26,6 +26,7 @@
#include "wine/debug.h"
#include "wine/list.h"
#include "wine/unicode.h"
#include "webservices_private.h"
WINE_DEFAULT_DEBUG_CHANNEL
(
webservices
);
...
...
@@ -50,10 +51,15 @@ static const struct prop_desc msg_props[] =
struct
header
{
WS_HEADER_TYPE
type
;
WS_XML_STRING
name
;
BOOL
mapped
;
WS_XML_UTF8_TEXT
text
;
WS_HEADER_TYPE
type
;
BOOL
mapped
;
WS_XML_STRING
name
;
WS_XML_STRING
ns
;
union
{
WS_XML_BUFFER
*
buf
;
WS_XML_STRING
*
text
;
}
u
;
};
struct
msg
...
...
@@ -99,6 +105,8 @@ static struct msg *alloc_msg(void)
static
void
free_header
(
struct
header
*
header
)
{
heap_free
(
header
->
name
.
bytes
);
heap_free
(
header
->
ns
.
bytes
);
if
(
header
->
mapped
)
heap_free
(
header
->
u
.
text
);
heap_free
(
header
);
}
...
...
@@ -385,22 +393,6 @@ static const WS_XML_STRING *get_header_name( WS_HEADER_TYPE type )
}
}
static
HRESULT
write_header
(
WS_XML_WRITER
*
writer
,
const
struct
header
*
header
)
{
static
const
WS_XML_STRING
prefix_s
=
{
1
,
(
BYTE
*
)
"s"
},
prefix_a
=
{
1
,
(
BYTE
*
)
"a"
};
static
const
WS_XML_STRING
understand
=
{
14
,
(
BYTE
*
)
"mustUnderstand"
},
ns
=
{
0
,
NULL
};
const
WS_XML_STRING
*
localname
=
get_header_name
(
header
->
type
);
WS_XML_INT32_TEXT
one
=
{{
WS_XML_TEXT_TYPE_INT32
},
1
};
HRESULT
hr
;
if
((
hr
=
WsWriteStartElement
(
writer
,
&
prefix_a
,
localname
,
&
ns
,
NULL
))
!=
S_OK
)
return
hr
;
if
((
hr
=
WsWriteStartAttribute
(
writer
,
&
prefix_s
,
&
understand
,
&
ns
,
FALSE
,
NULL
))
!=
S_OK
)
return
hr
;
if
((
hr
=
WsWriteText
(
writer
,
&
one
.
text
,
NULL
))
!=
S_OK
)
return
hr
;
if
((
hr
=
WsWriteEndAttribute
(
writer
,
NULL
))
!=
S_OK
)
return
hr
;
if
((
hr
=
WsWriteText
(
writer
,
&
header
->
text
.
text
,
NULL
))
!=
S_OK
)
return
hr
;
return
WsWriteEndElement
(
writer
,
NULL
);
}
static
HRESULT
write_envelope_start
(
struct
msg
*
msg
,
WS_XML_WRITER
*
writer
)
{
static
const
char
anonymous
[]
=
"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"
;
...
...
@@ -432,7 +424,8 @@ static HRESULT write_envelope_start( struct msg *msg, WS_XML_WRITER *writer )
for
(
i
=
0
;
i
<
msg
->
header_count
;
i
++
)
{
if
(
!
msg
->
header
[
i
]
->
mapped
&&
(
hr
=
write_header
(
writer
,
msg
->
header
[
i
]
))
!=
S_OK
)
return
hr
;
if
(
msg
->
header
[
i
]
->
mapped
)
continue
;
if
((
hr
=
WsWriteXmlBuffer
(
writer
,
msg
->
header
[
i
]
->
u
.
buf
,
NULL
))
!=
S_OK
)
return
hr
;
}
if
(
msg
->
version_addr
==
WS_ADDRESSING_VERSION_0_9
)
...
...
@@ -452,7 +445,7 @@ static HRESULT write_envelope_start( struct msg *msg, WS_XML_WRITER *writer )
return
WsWriteStartElement
(
writer
,
&
prefix_s
,
&
body
,
&
ns_env
,
NULL
);
/* <s:Body> */
}
static
HRESULT
write_envelope_end
(
struct
msg
*
msg
,
WS_XML_WRITER
*
writer
)
static
HRESULT
write_envelope_end
(
WS_XML_WRITER
*
writer
)
{
HRESULT
hr
;
if
((
hr
=
WsWriteEndElement
(
writer
,
NULL
))
!=
S_OK
)
return
hr
;
/* </s:Body> */
...
...
@@ -466,7 +459,7 @@ static HRESULT write_envelope( struct msg *msg )
if
(
!
msg
->
buf
&&
(
hr
=
WsCreateXmlBuffer
(
msg
->
heap
,
NULL
,
0
,
&
msg
->
buf
,
NULL
))
!=
S_OK
)
return
hr
;
if
((
hr
=
WsSetOutputToBuffer
(
msg
->
writer
,
msg
->
buf
,
NULL
,
0
,
NULL
))
!=
S_OK
)
return
hr
;
if
((
hr
=
write_envelope_start
(
msg
,
msg
->
writer
))
!=
S_OK
)
return
hr
;
return
write_envelope_end
(
msg
,
msg
->
writer
);
return
write_envelope_end
(
msg
->
writer
);
}
/**************************************************************************
...
...
@@ -511,7 +504,7 @@ HRESULT WINAPI WsWriteEnvelopeEnd( WS_MESSAGE *handle, WS_ERROR *error )
if
(
!
handle
)
return
E_INVALIDARG
;
if
(
msg
->
state
!=
WS_MESSAGE_STATE_WRITING
)
return
WS_E_INVALID_OPERATION
;
if
((
hr
=
write_envelope_end
(
msg
,
msg
->
writer_body
))
!=
S_OK
)
return
hr
;
if
((
hr
=
write_envelope_end
(
msg
->
writer_body
))
!=
S_OK
)
return
hr
;
msg
->
state
=
WS_MESSAGE_STATE_DONE
;
return
S_OK
;
...
...
@@ -567,87 +560,89 @@ HRESULT WINAPI WsInitializeMessage( WS_MESSAGE *handle, WS_MESSAGE_INITIALIZATIO
return
write_envelope
(
msg
);
}
static
inline
void
set_utf8_text
(
WS_XML_UTF8_TEXT
*
text
,
BYTE
*
bytes
,
ULONG
len
)
static
HRESULT
grow_header_array
(
struct
msg
*
msg
,
ULONG
size
)
{
text
->
text
.
textType
=
WS_XML_TEXT_TYPE_UTF8
;
text
->
value
.
bytes
=
bytes
;
text
->
value
.
length
=
len
;
struct
header
**
tmp
;
if
(
size
<=
msg
->
header_size
)
return
S_OK
;
if
(
!
(
tmp
=
heap_realloc
(
msg
->
header
,
2
*
msg
->
header_size
*
sizeof
(
struct
header
*
)
)))
return
E_OUTOFMEMORY
;
msg
->
header
=
tmp
;
msg
->
header_size
*=
2
;
return
S_OK
;
}
static
HRESULT
alloc_header
(
WS_HEADER_TYPE
type
,
const
WS_XML_STRING
*
name
,
BOOL
mapped
,
WS_TYPE
value_typ
e
,
WS_WRITE_OPTION
option
,
const
void
*
value
,
ULONG
size
,
struct
header
**
ret
)
static
struct
header
*
alloc_header
(
WS_HEADER_TYPE
type
,
BOOL
mapped
,
const
WS_XML_STRING
*
nam
e
,
const
WS_XML_STRING
*
ns
)
{
struct
header
*
header
;
switch
(
value_type
)
{
case
WS_WSZ_TYPE
:
{
int
len
;
const
WCHAR
*
src
;
if
(
option
!=
WS_WRITE_REQUIRED_POINTER
||
size
!=
sizeof
(
WCHAR
*
))
return
E_INVALIDARG
;
src
=
*
(
const
WCHAR
**
)
value
;
len
=
WideCharToMultiByte
(
CP_UTF8
,
0
,
src
,
-
1
,
NULL
,
0
,
NULL
,
NULL
)
-
1
;
if
(
!
(
header
=
heap_alloc_zero
(
sizeof
(
*
header
)
+
len
)))
return
E_OUTOFMEMORY
;
set_utf8_text
(
&
header
->
text
,
(
BYTE
*
)(
header
+
1
),
len
);
WideCharToMultiByte
(
CP_UTF8
,
0
,
src
,
-
1
,
(
char
*
)
header
->
text
.
value
.
bytes
,
len
,
NULL
,
NULL
);
break
;
}
case
WS_XML_STRING_TYPE
:
struct
header
*
ret
;
if
(
!
(
ret
=
heap_alloc_zero
(
sizeof
(
*
ret
)
)))
return
NULL
;
if
(
name
&&
name
->
length
)
{
const
WS_XML_STRING
*
str
=
value
;
if
(
option
!=
WS_WRITE_REQUIRED_VALUE
)
if
(
!
(
ret
->
name
.
bytes
=
heap_alloc
(
name
->
length
)))
{
FIXME
(
"unhandled write option %u
\n
"
,
option
);
return
E_NOTIMP
L
;
free_header
(
ret
);
return
NUL
L
;
}
if
(
size
!=
sizeof
(
*
str
))
return
E_INVALIDARG
;
if
(
!
(
header
=
heap_alloc_zero
(
sizeof
(
*
header
)
+
str
->
length
)))
return
E_OUTOFMEMORY
;
set_utf8_text
(
&
header
->
text
,
(
BYTE
*
)(
header
+
1
),
str
->
length
);
memcpy
(
header
->
text
.
value
.
bytes
,
str
->
bytes
,
str
->
length
);
break
;
memcpy
(
ret
->
name
.
bytes
,
name
->
bytes
,
name
->
length
);
ret
->
name
.
length
=
name
->
length
;
}
case
WS_STRING_TYPE
:
if
(
ns
&&
ns
->
length
)
{
int
len
;
const
WS_STRING
*
str
=
value
;
if
(
option
!=
WS_WRITE_REQUIRED_VALUE
)
if
(
!
(
ret
->
ns
.
bytes
=
heap_alloc
(
ns
->
length
)))
{
FIXME
(
"unhandled write option %u
\n
"
,
option
);
return
E_NOTIMP
L
;
free_header
(
ret
);
return
NUL
L
;
}
if
(
size
!=
sizeof
(
*
str
))
return
E_INVALIDARG
;
len
=
WideCharToMultiByte
(
CP_UTF8
,
0
,
str
->
chars
,
str
->
length
,
NULL
,
0
,
NULL
,
NULL
);
if
(
!
(
header
=
heap_alloc_zero
(
sizeof
(
*
header
)
+
len
)))
return
E_OUTOFMEMORY
;
set_utf8_text
(
&
header
->
text
,
(
BYTE
*
)(
header
+
1
),
len
);
WideCharToMultiByte
(
CP_UTF8
,
0
,
str
->
chars
,
str
->
length
,
(
char
*
)
header
->
text
.
value
.
bytes
,
len
,
NULL
,
NULL
);
break
;
}
default:
FIXME
(
"unhandled type %u
\n
"
,
value_type
);
return
E_NOTIMPL
;
memcpy
(
ret
->
ns
.
bytes
,
ns
->
bytes
,
ns
->
length
);
ret
->
ns
.
length
=
ns
->
length
;
}
ret
->
type
=
type
;
ret
->
mapped
=
mapped
;
return
ret
;
}
if
(
name
&&
name
->
length
)
{
if
(
!
(
header
->
name
.
bytes
=
heap_alloc
(
name
->
length
)))
{
heap_free
(
header
);
return
E_OUTOFMEMORY
;
}
memcpy
(
header
->
name
.
bytes
,
name
->
bytes
,
name
->
length
);
header
->
name
.
length
=
name
->
length
;
}
header
->
type
=
type
;
header
->
mapped
=
mapped
;
static
HRESULT
write_standard_header
(
WS_XML_WRITER
*
writer
,
const
WS_XML_STRING
*
name
,
WS_TYPE
value_type
,
WS_WRITE_OPTION
option
,
const
void
*
value
,
ULONG
size
)
{
static
const
WS_XML_STRING
prefix_s
=
{
1
,
(
BYTE
*
)
"s"
},
prefix_a
=
{
1
,
(
BYTE
*
)
"a"
};
static
const
WS_XML_STRING
understand
=
{
14
,
(
BYTE
*
)
"mustUnderstand"
},
ns
=
{
0
,
NULL
};
WS_XML_INT32_TEXT
one
=
{{
WS_XML_TEXT_TYPE_INT32
},
1
};
HRESULT
hr
;
*
ret
=
header
;
return
S_OK
;
if
((
hr
=
WsWriteStartElement
(
writer
,
&
prefix_a
,
name
,
&
ns
,
NULL
))
!=
S_OK
)
return
hr
;
if
((
hr
=
WsWriteStartAttribute
(
writer
,
&
prefix_s
,
&
understand
,
&
ns
,
FALSE
,
NULL
))
!=
S_OK
)
return
hr
;
if
((
hr
=
WsWriteText
(
writer
,
&
one
.
text
,
NULL
))
!=
S_OK
)
return
hr
;
if
((
hr
=
WsWriteEndAttribute
(
writer
,
NULL
))
!=
S_OK
)
return
hr
;
if
((
hr
=
WsWriteType
(
writer
,
WS_ELEMENT_CONTENT_TYPE_MAPPING
,
value_type
,
NULL
,
option
,
value
,
size
,
NULL
))
!=
S_OK
)
return
hr
;
return
WsWriteEndElement
(
writer
,
NULL
);
}
static
HRESULT
build_standard_header
(
WS_HEAP
*
heap
,
WS_HEADER_TYPE
type
,
WS_TYPE
value_type
,
WS_WRITE_OPTION
option
,
const
void
*
value
,
ULONG
size
,
struct
header
**
ret
)
{
const
WS_XML_STRING
*
name
=
get_header_name
(
type
);
struct
header
*
header
;
WS_XML_WRITER
*
writer
;
WS_XML_BUFFER
*
buf
;
HRESULT
hr
;
if
(
!
(
header
=
alloc_header
(
type
,
FALSE
,
name
,
NULL
)))
return
E_OUTOFMEMORY
;
if
((
hr
=
WsCreateWriter
(
NULL
,
0
,
&
writer
,
NULL
))
!=
S_OK
)
goto
done
;
if
((
hr
=
WsCreateXmlBuffer
(
heap
,
NULL
,
0
,
&
buf
,
NULL
))
!=
S_OK
)
goto
done
;
if
((
hr
=
WsSetOutputToBuffer
(
writer
,
buf
,
NULL
,
0
,
NULL
))
!=
S_OK
)
goto
done
;
if
((
hr
=
write_standard_header
(
writer
,
name
,
value_type
,
option
,
value
,
size
))
!=
S_OK
)
goto
done
;
header
->
u
.
buf
=
buf
;
done:
if
(
hr
!=
S_OK
)
free_header
(
header
);
else
*
ret
=
header
;
WsFreeWriter
(
writer
);
return
hr
;
}
/**************************************************************************
...
...
@@ -679,25 +674,27 @@ HRESULT WINAPI WsSetHeader( WS_MESSAGE *handle, WS_HEADER_TYPE type, WS_TYPE val
if
(
!
found
)
{
if
(
msg
->
header_count
==
msg
->
header_size
)
{
struct
header
**
tmp
;
if
(
!
(
tmp
=
heap_realloc
(
msg
->
header
,
2
*
msg
->
header_size
*
sizeof
(
struct
header
*
)
)))
return
E_OUTOFMEMORY
;
msg
->
header
=
tmp
;
msg
->
header_size
*=
2
;
}
i
=
msg
->
header_count
++
;
if
((
hr
=
grow_header_array
(
msg
,
msg
->
header_count
+
1
))
!=
S_OK
)
return
hr
;
i
=
msg
->
header_count
;
}
if
((
hr
=
alloc_header
(
type
,
NULL
,
FALSE
,
value_type
,
option
,
value
,
size
,
&
header
))
!=
S_OK
)
if
((
hr
=
build_standard_header
(
msg
->
heap
,
type
,
value_type
,
option
,
value
,
size
,
&
header
))
!=
S_OK
)
return
hr
;
if
(
found
)
free_header
(
msg
->
header
[
i
]
);
if
(
!
found
)
msg
->
header_count
++
;
else
free_header
(
msg
->
header
[
i
]
);
msg
->
header
[
i
]
=
header
;
return
write_envelope
(
msg
);
}
static
void
remove_header
(
struct
msg
*
msg
,
ULONG
i
)
{
free_header
(
msg
->
header
[
i
]
);
memmove
(
&
msg
->
header
[
i
],
&
msg
->
header
[
i
+
1
],
(
msg
->
header_count
-
i
)
*
sizeof
(
struct
header
*
)
);
msg
->
header_count
--
;
}
/**************************************************************************
* WsRemoveHeader [webservices.@]
*/
...
...
@@ -718,9 +715,7 @@ HRESULT WINAPI WsRemoveHeader( WS_MESSAGE *handle, WS_HEADER_TYPE type, WS_ERROR
{
if
(
msg
->
header
[
i
]
->
type
==
type
)
{
free_header
(
msg
->
header
[
i
]
);
memmove
(
&
msg
->
header
[
i
],
&
msg
->
header
[
i
+
1
],
(
msg
->
header_count
-
i
)
*
sizeof
(
struct
header
*
)
);
msg
->
header_count
--
;
remove_header
(
msg
,
i
);
removed
=
TRUE
;
break
;
}
...
...
@@ -730,6 +725,93 @@ HRESULT WINAPI WsRemoveHeader( WS_MESSAGE *handle, WS_HEADER_TYPE type, WS_ERROR
return
S_OK
;
}
static
HRESULT
build_mapped_header
(
const
WS_XML_STRING
*
name
,
WS_TYPE
type
,
WS_WRITE_OPTION
option
,
const
void
*
value
,
ULONG
size
,
struct
header
**
ret
)
{
struct
header
*
header
;
if
(
!
(
header
=
alloc_header
(
0
,
TRUE
,
name
,
NULL
)))
return
E_OUTOFMEMORY
;
switch
(
type
)
{
case
WS_WSZ_TYPE
:
{
int
len
;
const
WCHAR
*
src
;
if
(
option
!=
WS_WRITE_REQUIRED_POINTER
||
size
!=
sizeof
(
WCHAR
*
))
{
free_header
(
header
);
return
E_INVALIDARG
;
}
src
=
*
(
const
WCHAR
**
)
value
;
len
=
WideCharToMultiByte
(
CP_UTF8
,
0
,
src
,
-
1
,
NULL
,
0
,
NULL
,
NULL
)
-
1
;
if
(
!
(
header
->
u
.
text
=
alloc_xml_string
(
NULL
,
len
)))
{
free_header
(
header
);
return
E_OUTOFMEMORY
;
}
WideCharToMultiByte
(
CP_UTF8
,
0
,
src
,
-
1
,
(
char
*
)
header
->
u
.
text
->
bytes
,
len
,
NULL
,
NULL
);
break
;
}
case
WS_XML_STRING_TYPE
:
{
const
WS_XML_STRING
*
str
=
value
;
if
(
option
!=
WS_WRITE_REQUIRED_VALUE
)
{
FIXME
(
"unhandled write option %u
\n
"
,
option
);
free_header
(
header
);
return
E_NOTIMPL
;
}
if
(
size
!=
sizeof
(
*
str
))
{
free_header
(
header
);
return
E_INVALIDARG
;
}
if
(
!
(
header
->
u
.
text
=
alloc_xml_string
(
NULL
,
str
->
length
)))
{
free_header
(
header
);
return
E_OUTOFMEMORY
;
}
memcpy
(
header
->
u
.
text
->
bytes
,
str
->
bytes
,
str
->
length
);
break
;
}
case
WS_STRING_TYPE
:
{
int
len
;
const
WS_STRING
*
str
=
value
;
if
(
option
!=
WS_WRITE_REQUIRED_VALUE
)
{
FIXME
(
"unhandled write option %u
\n
"
,
option
);
free_header
(
header
);
return
E_NOTIMPL
;
}
if
(
size
!=
sizeof
(
*
str
))
{
free_header
(
header
);
return
E_INVALIDARG
;
}
len
=
WideCharToMultiByte
(
CP_UTF8
,
0
,
str
->
chars
,
str
->
length
,
NULL
,
0
,
NULL
,
NULL
);
if
(
!
(
header
->
u
.
text
=
alloc_xml_string
(
NULL
,
len
)))
{
free_header
(
header
);
return
E_OUTOFMEMORY
;
}
WideCharToMultiByte
(
CP_UTF8
,
0
,
str
->
chars
,
str
->
length
,
(
char
*
)
header
->
u
.
text
->
bytes
,
len
,
NULL
,
NULL
);
break
;
}
default:
FIXME
(
"unhandled type %u
\n
"
,
type
);
free_header
(
header
);
return
E_NOTIMPL
;
}
*
ret
=
header
;
return
S_OK
;
}
/**************************************************************************
* WsAddMappedHeader [webservices.@]
*/
...
...
@@ -760,21 +842,15 @@ HRESULT WINAPI WsAddMappedHeader( WS_MESSAGE *handle, const WS_XML_STRING *name,
if
(
!
found
)
{
if
(
msg
->
header_count
==
msg
->
header_size
)
{
struct
header
**
tmp
;
if
(
!
(
tmp
=
heap_realloc
(
msg
->
header
,
2
*
msg
->
header_size
*
sizeof
(
struct
header
*
)
)))
return
E_OUTOFMEMORY
;
msg
->
header
=
tmp
;
msg
->
header_size
*=
2
;
}
i
=
msg
->
header_count
++
;
if
((
hr
=
grow_header_array
(
msg
,
msg
->
header_count
+
1
))
!=
S_OK
)
return
hr
;
i
=
msg
->
header_count
;
}
if
((
hr
=
alloc_header
(
0
,
name
,
TRUE
,
WS_XML_STRING_TYPE
,
option
,
value
,
size
,
&
header
))
!=
S_OK
)
return
hr
;
if
((
hr
=
build_mapped_header
(
name
,
type
,
option
,
value
,
size
,
&
header
))
!=
S_OK
)
return
hr
;
if
(
!
found
)
msg
->
header_count
++
;
else
free_header
(
msg
->
header
[
i
]
);
if
(
found
)
free_header
(
msg
->
header
[
i
]
);
msg
->
header
[
i
]
=
header
;
return
S_OK
;
}
...
...
@@ -798,9 +874,7 @@ HRESULT WINAPI WsRemoveMappedHeader( WS_MESSAGE *handle, const WS_XML_STRING *na
if
(
msg
->
header
[
i
]
->
type
||
!
msg
->
header
[
i
]
->
mapped
)
continue
;
if
(
WsXmlStringEquals
(
name
,
&
msg
->
header
[
i
]
->
name
,
NULL
)
==
S_OK
)
{
free_header
(
msg
->
header
[
i
]
);
memmove
(
&
msg
->
header
[
i
],
&
msg
->
header
[
i
+
1
],
(
msg
->
header_count
-
i
)
*
sizeof
(
struct
header
*
)
);
msg
->
header_count
--
;
remove_header
(
msg
,
i
);
break
;
}
}
...
...
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