pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Fix conditional.
release-2.x.y
2014-01-13, Mark Doliner
c03eefbf89a8
Fix conditional.
I think this would have incorrectly found a Connection: header in the
XML body. No idea what the harm is.
/**
* @file httpconn.c HTTP connection method
*
* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include
"msn.h"
#include
"debug.h"
#include
"httpconn.h"
typedef
struct
{
MsnHttpConn
*
httpconn
;
char
*
body
;
size_t
body_len
;
}
MsnHttpQueueData
;
static
void
msn_httpconn_process_queue
(
MsnHttpConn
*
httpconn
)
{
httpconn
->
waiting_response
=
FALSE
;
if
(
httpconn
->
queue
!=
NULL
)
{
MsnHttpQueueData
*
queue_data
;
queue_data
=
(
MsnHttpQueueData
*
)
httpconn
->
queue
->
data
;
httpconn
->
queue
=
g_list_remove
(
httpconn
->
queue
,
queue_data
);
msn_httpconn_write
(
queue_data
->
httpconn
,
queue_data
->
body
,
queue_data
->
body_len
);
g_free
(
queue_data
->
body
);
g_free
(
queue_data
);
}
}
static
gboolean
msn_httpconn_parse_data
(
MsnHttpConn
*
httpconn
,
const
char
*
buf
,
size_t
size
,
char
**
ret_buf
,
size_t
*
ret_size
,
gboolean
*
error
)
{
const
char
*
s
,
*
c
;
char
*
header
,
*
body
;
const
char
*
body_start
;
char
*
tmp
;
size_t
body_len
=
0
;
g_return_val_if_fail
(
httpconn
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
buf
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
size
>
0
,
FALSE
);
g_return_val_if_fail
(
ret_buf
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
ret_size
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
error
!=
NULL
,
FALSE
);
#if 0
purple_debug_info("msn", "HTTP: parsing data {%s}\n", buf);
#endif
/* Healthy defaults. */
body
=
NULL
;
*
ret_buf
=
NULL
;
*
ret_size
=
0
;
*
error
=
FALSE
;
/* First, some tests to see if we have a full block of stuff. */
if
(((
strncmp
(
buf
,
"HTTP/1.1 200 OK
\r\n
"
,
17
)
!=
0
)
&&
(
strncmp
(
buf
,
"HTTP/1.1 100 Continue
\r\n
"
,
23
)
!=
0
))
&&
((
strncmp
(
buf
,
"HTTP/1.0 200 OK
\r\n
"
,
17
)
!=
0
)
&&
(
strncmp
(
buf
,
"HTTP/1.0 100 Continue
\r\n
"
,
23
)
!=
0
)))
{
*
error
=
TRUE
;
return
FALSE
;
}
if
(
strncmp
(
buf
,
"HTTP/1.1 100 Continue
\r\n
"
,
23
)
==
0
)
{
if
((
s
=
strstr
(
buf
,
"
\r\n\r\n
"
))
==
NULL
)
return
FALSE
;
s
+=
4
;
if
(
*
s
==
'\0'
)
{
*
ret_buf
=
g_strdup
(
""
);
*
ret_size
=
0
;
msn_httpconn_process_queue
(
httpconn
);
return
TRUE
;
}
size
-=
(
s
-
buf
);
buf
=
s
;
}
if
((
s
=
strstr
(
buf
,
"
\r\n\r\n
"
))
==
NULL
)
/* Need to wait for the full HTTP header to arrive */
return
FALSE
;
s
+=
4
;
/* Skip \r\n\r\n */
header
=
g_strndup
(
buf
,
s
-
buf
);
body_start
=
s
;
body_len
=
size
-
(
body_start
-
buf
);
if
((
s
=
purple_strcasestr
(
header
,
"Content-Length: "
))
!=
NULL
)
{
int
tmp_len
;
s
+=
strlen
(
"Content-Length: "
);
if
((
c
=
strchr
(
s
,
'\r'
))
==
NULL
)
{
g_free
(
header
);
return
FALSE
;
}
tmp
=
g_strndup
(
s
,
c
-
s
);
tmp_len
=
atoi
(
tmp
);
g_free
(
tmp
);
if
(
body_len
!=
tmp_len
)
{
/* Need to wait for the full packet to arrive */
g_free
(
header
);
#if 0
purple_debug_warning("msn",
"body length (%d) != content length (%d)\n",
body_len, tmp_len);
#endif
return
FALSE
;
}
}
body
=
g_malloc
(
body_len
+
1
);
memcpy
(
body
,
body_start
,
body_len
);
body
[
body_len
]
=
'\0'
;
if
(
purple_debug_is_verbose
())
purple_debug_misc
(
"msn"
,
"Incoming HTTP buffer (header): {%s}
\n
"
,
header
);
/* Now we should be able to process the data. */
if
((
s
=
purple_strcasestr
(
header
,
"X-MSN-Messenger: "
))
!=
NULL
)
{
gchar
*
full_session_id
=
NULL
,
*
gw_ip
=
NULL
,
*
session_action
=
NULL
;
char
*
t
,
*
session_id
;
char
**
elems
,
**
cur
,
**
tokens
;
full_session_id
=
gw_ip
=
session_action
=
NULL
;
s
+=
strlen
(
"X-MSN-Messenger: "
);
if
((
c
=
strchr
(
s
,
'\r'
))
==
NULL
)
{
msn_session_set_error
(
httpconn
->
session
,
MSN_ERROR_HTTP_MALFORMED
,
NULL
);
purple_debug_error
(
"msn"
,
"Malformed X-MSN-Messenger field.
\n
{%s}
\n
"
,
buf
);
g_free
(
header
);
g_free
(
body
);
return
FALSE
;
}
tmp
=
g_strndup
(
s
,
c
-
s
);
elems
=
g_strsplit
(
tmp
,
"; "
,
0
);
for
(
cur
=
elems
;
*
cur
!=
NULL
;
cur
++
)
{
tokens
=
g_strsplit
(
*
cur
,
"="
,
2
);
if
(
strcmp
(
tokens
[
0
],
"SessionID"
)
==
0
)
{
g_free
(
full_session_id
);
full_session_id
=
tokens
[
1
];
}
else
if
(
strcmp
(
tokens
[
0
],
"GW-IP"
)
==
0
)
{
g_free
(
gw_ip
);
gw_ip
=
tokens
[
1
];
}
else
if
(
strcmp
(
tokens
[
0
],
"Session"
)
==
0
)
{
g_free
(
session_action
);
session_action
=
tokens
[
1
];
}
else
g_free
(
tokens
[
1
]);
g_free
(
tokens
[
0
]);
/* Don't free each of the tokens, only the array. */
g_free
(
tokens
);
}
g_strfreev
(
elems
);
g_free
(
tmp
);
t
=
full_session_id
?
strchr
(
full_session_id
,
'.'
)
:
NULL
;
if
(
t
!=
NULL
)
session_id
=
g_strndup
(
full_session_id
,
t
-
full_session_id
);
else
{
purple_debug_error
(
"msn"
,
"Malformed full_session_id[%s]
\n
"
,
full_session_id
?
full_session_id
:
NULL
);
session_id
=
g_strdup
(
full_session_id
);
}
if
(
session_action
==
NULL
||
strcmp
(
session_action
,
"close"
)
!=
0
)
{
g_free
(
httpconn
->
full_session_id
);
httpconn
->
full_session_id
=
full_session_id
;
g_free
(
httpconn
->
session_id
);
httpconn
->
session_id
=
session_id
;
g_free
(
httpconn
->
host
);
httpconn
->
host
=
gw_ip
;
}
else
{
/* I'll be honest, I don't fully understand all this, but this
* causes crashes, Stu. */
#if 0
MsnServConn *servconn;
/* It's going to die. */
/* poor thing */
servconn = httpconn->servconn;
if (servconn != NULL)
servconn->wasted = TRUE;
#endif
g_free
(
full_session_id
);
g_free
(
session_id
);
g_free
(
gw_ip
);
}
g_free
(
session_action
);
}
g_free
(
header
);
*
ret_buf
=
body
;
*
ret_size
=
body_len
;
msn_httpconn_process_queue
(
httpconn
);
return
TRUE
;
}
static
void
read_cb
(
gpointer
data
,
gint
source
,
PurpleInputCondition
cond
)
{
MsnHttpConn
*
httpconn
;
MsnServConn
*
servconn
;
char
buf
[
MSN_BUF_LEN
];
gssize
len
;
char
*
result_msg
=
NULL
;
size_t
result_len
=
0
;
gboolean
error
=
FALSE
;
httpconn
=
data
;
servconn
=
httpconn
->
servconn
;
if
(
servconn
->
type
==
MSN_SERVCONN_NS
)
servconn
->
session
->
account
->
gc
->
last_received
=
time
(
NULL
);
len
=
read
(
httpconn
->
fd
,
buf
,
sizeof
(
buf
)
-
1
);
if
(
len
<
0
&&
errno
==
EAGAIN
)
return
;
if
(
len
<=
0
)
{
purple_debug_error
(
"msn"
,
"HTTP: servconn %03d read error, "
"len: %"
G_GSSIZE_FORMAT
", errno: %d, error: %s
\n
"
,
servconn
->
num
,
len
,
error
,
g_strerror
(
errno
));
msn_servconn_got_error
(
servconn
,
MSN_SERVCONN_ERROR_READ
,
NULL
);
return
;
}
buf
[
len
]
=
'\0'
;
httpconn
->
rx_buf
=
g_realloc
(
httpconn
->
rx_buf
,
len
+
httpconn
->
rx_len
+
1
);
memcpy
(
httpconn
->
rx_buf
+
httpconn
->
rx_len
,
buf
,
len
+
1
);
httpconn
->
rx_len
+=
len
;
if
(
!
msn_httpconn_parse_data
(
httpconn
,
httpconn
->
rx_buf
,
httpconn
->
rx_len
,
&
result_msg
,
&
result_len
,
&
error
))
{
/* Either we must wait for more input, or something went wrong */
if
(
error
)
msn_servconn_got_error
(
servconn
,
MSN_SERVCONN_ERROR_READ
,
NULL
);
return
;
}
if
(
error
)
{
purple_debug_error
(
"msn"
,
"HTTP: Special error
\n
"
);
msn_servconn_got_error
(
servconn
,
MSN_SERVCONN_ERROR_READ
,
NULL
);
return
;
}
g_free
(
httpconn
->
rx_buf
);
httpconn
->
rx_buf
=
NULL
;
httpconn
->
rx_len
=
0
;
if
(
result_len
==
0
)
{
/* Nothing to do here */
#if 0
purple_debug_info("msn", "HTTP: nothing to do here\n");
#endif
g_free
(
result_msg
);
return
;
}
g_free
(
servconn
->
rx_buf
);
servconn
->
rx_buf
=
result_msg
;
servconn
->
rx_len
=
result_len
;
msn_servconn_process_data
(
servconn
);
}
static
void
httpconn_write_cb
(
gpointer
data
,
gint
source
,
PurpleInputCondition
cond
)
{
MsnHttpConn
*
httpconn
;
gssize
ret
;
int
writelen
;
httpconn
=
data
;
writelen
=
purple_circ_buffer_get_max_read
(
httpconn
->
tx_buf
);
if
(
writelen
==
0
)
{
purple_input_remove
(
httpconn
->
tx_handler
);
httpconn
->
tx_handler
=
0
;
return
;
}
ret
=
write
(
httpconn
->
fd
,
httpconn
->
tx_buf
->
outptr
,
writelen
);
if
(
ret
<=
0
)
{
if
((
errno
==
EAGAIN
)
||
(
errno
==
EWOULDBLOCK
))
/* No worries */
return
;
/* Error! */
msn_servconn_got_error
(
httpconn
->
servconn
,
MSN_SERVCONN_ERROR_WRITE
,
NULL
);
return
;
}
purple_circ_buffer_mark_read
(
httpconn
->
tx_buf
,
ret
);
/* TODO: I don't think these 2 lines are needed. Remove them? */
if
(
ret
==
writelen
)
httpconn_write_cb
(
data
,
source
,
cond
);
}
static
gboolean
write_raw
(
MsnHttpConn
*
httpconn
,
const
char
*
data
,
size_t
data_len
)
{
gssize
res
;
/* result of the write operation */
if
(
httpconn
->
tx_handler
==
0
)
res
=
write
(
httpconn
->
fd
,
data
,
data_len
);
else
{
res
=
-1
;
errno
=
EAGAIN
;
}
if
((
res
<=
0
)
&&
((
errno
!=
EAGAIN
)
&&
(
errno
!=
EWOULDBLOCK
)))
{
msn_servconn_got_error
(
httpconn
->
servconn
,
MSN_SERVCONN_ERROR_WRITE
,
NULL
);
return
FALSE
;
}
if
(
res
<
0
||
res
<
data_len
)
{
if
(
res
<
0
)
res
=
0
;
if
(
httpconn
->
tx_handler
==
0
&&
httpconn
->
fd
)
httpconn
->
tx_handler
=
purple_input_add
(
httpconn
->
fd
,
PURPLE_INPUT_WRITE
,
httpconn_write_cb
,
httpconn
);
purple_circ_buffer_append
(
httpconn
->
tx_buf
,
data
+
res
,
data_len
-
res
);
}
return
TRUE
;
}
static
char
*
msn_httpconn_proxy_auth
(
MsnHttpConn
*
httpconn
)
{
PurpleAccount
*
account
;
PurpleProxyInfo
*
gpi
;
const
char
*
username
,
*
password
;
char
*
auth
=
NULL
;
account
=
httpconn
->
session
->
account
;
gpi
=
purple_proxy_get_setup
(
account
);
if
(
gpi
==
NULL
||
!
(
purple_proxy_info_get_type
(
gpi
)
==
PURPLE_PROXY_HTTP
||
purple_proxy_info_get_type
(
gpi
)
==
PURPLE_PROXY_USE_ENVVAR
))
return
NULL
;
username
=
purple_proxy_info_get_username
(
gpi
);
password
=
purple_proxy_info_get_password
(
gpi
);
if
(
username
!=
NULL
)
{
char
*
tmp
;
auth
=
g_strdup_printf
(
"%s:%s"
,
username
,
password
?
password
:
""
);
tmp
=
purple_base64_encode
((
const
guchar
*
)
auth
,
strlen
(
auth
));
g_free
(
auth
);
auth
=
g_strdup_printf
(
"Proxy-Authorization: Basic %s
\r\n
"
,
tmp
);
g_free
(
tmp
);
}
return
auth
;
}
static
gboolean
msn_httpconn_poll
(
gpointer
data
)
{
MsnHttpConn
*
httpconn
;
char
*
header
;
char
*
auth
;
httpconn
=
data
;
g_return_val_if_fail
(
httpconn
!=
NULL
,
FALSE
);
if
((
httpconn
->
host
==
NULL
)
||
(
httpconn
->
full_session_id
==
NULL
))
{
/* There's no need to poll if the session is not fully established */
return
TRUE
;
}
if
(
httpconn
->
waiting_response
)
{
/* There's no need to poll if we're already waiting for a response */
return
TRUE
;
}
auth
=
msn_httpconn_proxy_auth
(
httpconn
);
header
=
g_strdup_printf
(
"POST http://%s/gateway/gateway.dll?Action=poll&SessionID=%s HTTP/1.1
\r\n
"
"Accept: */*
\r\n
"
"Accept-Language: en-us
\r\n
"
"User-Agent: MSMSGS
\r\n
"
"Host: %s
\r\n
"
"Proxy-Connection: Keep-Alive
\r\n
"
"%s"
/* Proxy auth */
"Connection: Keep-Alive
\r\n
"
"Pragma: no-cache
\r\n
"
"Content-Type: application/x-msn-messenger
\r\n
"
"Content-Length: 0
\r\n\r\n
"
,
httpconn
->
host
,
httpconn
->
full_session_id
,
httpconn
->
host
,
auth
?
auth
:
""
);
g_free
(
auth
);
if
(
write_raw
(
httpconn
,
header
,
strlen
(
header
)))
httpconn
->
waiting_response
=
TRUE
;
g_free
(
header
);
return
TRUE
;
}
gssize
msn_httpconn_write
(
MsnHttpConn
*
httpconn
,
const
char
*
body
,
size_t
body_len
)
{
char
*
params
;
char
*
data
;
int
header_len
;
char
*
auth
;
const
char
*
server_types
[]
=
{
"NS"
,
"SB"
};
const
char
*
server_type
;
char
*
host
;
MsnServConn
*
servconn
;
/* TODO: remove http data from servconn */
g_return_val_if_fail
(
httpconn
!=
NULL
,
0
);
g_return_val_if_fail
(
body
!=
NULL
,
0
);
g_return_val_if_fail
(
body_len
>
0
,
0
);
servconn
=
httpconn
->
servconn
;
if
(
httpconn
->
waiting_response
)
{
MsnHttpQueueData
*
queue_data
=
g_new0
(
MsnHttpQueueData
,
1
);
queue_data
->
httpconn
=
httpconn
;
queue_data
->
body
=
g_memdup
(
body
,
body_len
);
queue_data
->
body_len
=
body_len
;
httpconn
->
queue
=
g_list_append
(
httpconn
->
queue
,
queue_data
);
return
body_len
;
}
server_type
=
server_types
[
servconn
->
type
];
if
(
httpconn
->
virgin
)
{
/* QuLogic: This doesn't look right to me, but it still seems to work */
host
=
MSN_HTTPCONN_SERVER
;
/* The first time servconn->host is the host we should connect to. */
params
=
g_strdup_printf
(
"Action=open&Server=%s&IP=%s"
,
server_type
,
servconn
->
host
);
httpconn
->
virgin
=
FALSE
;
}
else
{
/* The rest of the times servconn->host is the gateway host. */
host
=
httpconn
->
host
;
if
(
host
==
NULL
||
httpconn
->
full_session_id
==
NULL
)
{
purple_debug_warning
(
"msn"
,
"Attempted HTTP write before session is established
\n
"
);
return
-1
;
}
params
=
g_strdup_printf
(
"SessionID=%s"
,
httpconn
->
full_session_id
);
}
auth
=
msn_httpconn_proxy_auth
(
httpconn
);
data
=
g_strdup_printf
(
"POST http://%s/gateway/gateway.dll?%s HTTP/1.1
\r\n
"
"Accept: */*
\r\n
"
"Accept-Language: en-us
\r\n
"
"User-Agent: MSMSGS
\r\n
"
"Host: %s
\r\n
"
"Proxy-Connection: Keep-Alive
\r\n
"
"%s"
/* Proxy auth */
"Connection: Keep-Alive
\r\n
"
"Pragma: no-cache
\r\n
"
"Content-Type: application/x-msn-messenger
\r\n
"
"Content-Length: %d
\r\n\r\n
"
,
host
,
params
,
host
,
auth
?
auth
:
""
,
(
int
)
body_len
);
g_free
(
params
);
g_free
(
auth
);
header_len
=
strlen
(
data
);
data
=
g_realloc
(
data
,
header_len
+
body_len
);
memcpy
(
data
+
header_len
,
body
,
body_len
);
if
(
write_raw
(
httpconn
,
data
,
header_len
+
body_len
))
httpconn
->
waiting_response
=
TRUE
;
g_free
(
data
);
return
body_len
;
}
MsnHttpConn
*
msn_httpconn_new
(
MsnServConn
*
servconn
)
{
MsnHttpConn
*
httpconn
;
g_return_val_if_fail
(
servconn
!=
NULL
,
NULL
);
httpconn
=
g_new0
(
MsnHttpConn
,
1
);
purple_debug_info
(
"msn"
,
"new httpconn (%p)
\n
"
,
httpconn
);
/* TODO: Remove this */
httpconn
->
session
=
servconn
->
session
;
httpconn
->
servconn
=
servconn
;
httpconn
->
tx_buf
=
purple_circ_buffer_new
(
MSN_BUF_LEN
);
httpconn
->
tx_handler
=
0
;
httpconn
->
fd
=
-1
;
return
httpconn
;
}
void
msn_httpconn_destroy
(
MsnHttpConn
*
httpconn
)
{
g_return_if_fail
(
httpconn
!=
NULL
);
purple_debug_info
(
"msn"
,
"destroy httpconn (%p)
\n
"
,
httpconn
);
if
(
httpconn
->
connected
)
msn_httpconn_disconnect
(
httpconn
);
g_free
(
httpconn
->
full_session_id
);
g_free
(
httpconn
->
session_id
);
g_free
(
httpconn
->
host
);
while
(
httpconn
->
queue
!=
NULL
)
{
MsnHttpQueueData
*
queue_data
;
queue_data
=
(
MsnHttpQueueData
*
)
httpconn
->
queue
->
data
;
httpconn
->
queue
=
g_list_delete_link
(
httpconn
->
queue
,
httpconn
->
queue
);
g_free
(
queue_data
->
body
);
g_free
(
queue_data
);
}
purple_circ_buffer_destroy
(
httpconn
->
tx_buf
);
if
(
httpconn
->
tx_handler
>
0
)
purple_input_remove
(
httpconn
->
tx_handler
);
g_free
(
httpconn
);
}
static
void
connect_cb
(
gpointer
data
,
gint
source
,
const
gchar
*
error_message
)
{
MsnHttpConn
*
httpconn
;
httpconn
=
data
;
httpconn
->
connect_data
=
NULL
;
httpconn
->
fd
=
source
;
if
(
source
>=
0
)
{
httpconn
->
inpa
=
purple_input_add
(
httpconn
->
fd
,
PURPLE_INPUT_READ
,
read_cb
,
data
);
httpconn
->
timer
=
purple_timeout_add_seconds
(
2
,
msn_httpconn_poll
,
httpconn
);
msn_httpconn_process_queue
(
httpconn
);
}
else
{
purple_debug_error
(
"msn"
,
"HTTP: Connection error: %s
\n
"
,
error_message
?
error_message
:
"(null)"
);
msn_servconn_got_error
(
httpconn
->
servconn
,
MSN_SERVCONN_ERROR_CONNECT
,
error_message
);
}
}
gboolean
msn_httpconn_connect
(
MsnHttpConn
*
httpconn
,
const
char
*
host
,
int
port
)
{
g_return_val_if_fail
(
httpconn
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
host
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
port
>
0
,
FALSE
);
if
(
httpconn
->
connected
)
msn_httpconn_disconnect
(
httpconn
);
httpconn
->
connect_data
=
purple_proxy_connect
(
NULL
,
httpconn
->
session
->
account
,
host
,
80
,
connect_cb
,
httpconn
);
if
(
httpconn
->
connect_data
!=
NULL
)
{
httpconn
->
waiting_response
=
TRUE
;
httpconn
->
connected
=
TRUE
;
}
return
httpconn
->
connected
;
}
void
msn_httpconn_disconnect
(
MsnHttpConn
*
httpconn
)
{
g_return_if_fail
(
httpconn
!=
NULL
);
if
(
!
httpconn
->
connected
)
return
;
if
(
httpconn
->
connect_data
!=
NULL
)
{
purple_proxy_connect_cancel
(
httpconn
->
connect_data
);
httpconn
->
connect_data
=
NULL
;
}
if
(
httpconn
->
timer
)
{
purple_timeout_remove
(
httpconn
->
timer
);
httpconn
->
timer
=
0
;
}
if
(
httpconn
->
inpa
>
0
)
{
purple_input_remove
(
httpconn
->
inpa
);
httpconn
->
inpa
=
0
;
}
close
(
httpconn
->
fd
);
httpconn
->
fd
=
-1
;
g_free
(
httpconn
->
rx_buf
);
httpconn
->
rx_buf
=
NULL
;
httpconn
->
rx_len
=
0
;
httpconn
->
connected
=
FALSE
;
/* msn_servconn_disconnect(httpconn->servconn); */
}