mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-18 03:28:07 +00:00
452 lines
18 KiB
C
452 lines
18 KiB
C
/*
|
|
linphone
|
|
Copyright (C) 2012 Belledonne Communications, Grenoble, France
|
|
|
|
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 02110-1301, USA.
|
|
*/
|
|
|
|
#include "sal_impl.h"
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
|
|
void sal_enable_log(){
|
|
sal_set_log_level(ORTP_MESSAGE);
|
|
}
|
|
|
|
void sal_disable_log() {
|
|
sal_set_log_level(ORTP_ERROR);
|
|
}
|
|
|
|
void sal_set_log_level(OrtpLogLevel level) {
|
|
belle_sip_log_level belle_sip_level = BELLE_SIP_LOG_MESSAGE;
|
|
if ((level&ORTP_FATAL) != 0) {
|
|
belle_sip_level = BELLE_SIP_LOG_FATAL;
|
|
}
|
|
if ((level&ORTP_ERROR) != 0) {
|
|
belle_sip_level = BELLE_SIP_LOG_ERROR;
|
|
}
|
|
if ((level&ORTP_WARNING) != 0) {
|
|
belle_sip_level = BELLE_SIP_LOG_WARNING;
|
|
}
|
|
if ((level&ORTP_MESSAGE) != 0) {
|
|
belle_sip_level = BELLE_SIP_LOG_MESSAGE;
|
|
}
|
|
if (((level&ORTP_DEBUG) != 0) || ((level&ORTP_TRACE) != 0)) {
|
|
belle_sip_level = BELLE_SIP_LOG_DEBUG;
|
|
}
|
|
|
|
belle_sip_set_log_level(belle_sip_level);
|
|
}
|
|
static BctbxLogFunc _belle_sip_log_handler = bctbx_logv_out;
|
|
|
|
void sal_set_log_handler(BctbxLogFunc log_handler) {
|
|
_belle_sip_log_handler = log_handler;
|
|
belle_sip_set_log_handler(log_handler);
|
|
}
|
|
|
|
SalAuthInfo* sal_auth_info_create(belle_sip_auth_event_t* event) {
|
|
SalAuthInfo* auth_info = sal_auth_info_new();
|
|
auth_info->realm = ms_strdup(belle_sip_auth_event_get_realm(event));
|
|
auth_info->username = ms_strdup(belle_sip_auth_event_get_username(event));
|
|
auth_info->domain = ms_strdup(belle_sip_auth_event_get_domain(event));
|
|
auth_info->mode = (SalAuthMode)belle_sip_auth_event_get_mode(event);
|
|
auth_info->algorithm = ms_strdup(belle_sip_auth_event_get_algorithm(event));
|
|
return auth_info;
|
|
}
|
|
|
|
SalAuthMode sal_auth_info_get_mode(const SalAuthInfo* auth_info) { return auth_info->mode; }
|
|
belle_sip_signing_key_t *sal_auth_info_get_signing_key(const SalAuthInfo* auth_info) { return auth_info->key; }
|
|
belle_sip_certificates_chain_t *sal_auth_info_get_certificates_chain(const SalAuthInfo* auth_info) { return auth_info->certificates; }
|
|
void sal_auth_info_set_mode(SalAuthInfo* auth_info, SalAuthMode mode) { auth_info->mode = mode; }
|
|
void sal_certificates_chain_delete(belle_sip_certificates_chain_t *chain) {
|
|
belle_sip_object_unref((belle_sip_object_t *)chain);
|
|
}
|
|
void sal_signing_key_delete(belle_sip_signing_key_t *key) {
|
|
belle_sip_object_unref((belle_sip_object_t *)key);
|
|
}
|
|
|
|
int sal_auth_compute_ha1(const char* userid,const char* realm,const char* password, char ha1[33]) {
|
|
return belle_sip_auth_helper_compute_ha1(userid, realm, password, ha1);
|
|
}
|
|
|
|
int sal_auth_compute_ha1_for_algorithm(
|
|
const char *userid,
|
|
const char *realm,
|
|
const char *password,
|
|
char *ha1,
|
|
size_t size,
|
|
const char *algo
|
|
) {
|
|
return belle_sip_auth_helper_compute_ha1_for_algorithm(userid, realm, password, ha1, size, algo);
|
|
}
|
|
|
|
SalCustomHeader *sal_custom_header_ref(SalCustomHeader *ch){
|
|
if (ch == NULL) return NULL;
|
|
belle_sip_object_ref(ch);
|
|
return ch;
|
|
}
|
|
|
|
void sal_custom_header_unref(SalCustomHeader *ch){
|
|
if (ch == NULL) return;
|
|
belle_sip_object_unref(ch);
|
|
}
|
|
|
|
SalCustomHeader *sal_custom_header_append(SalCustomHeader *ch, const char *name, const char *value){
|
|
belle_sip_message_t *msg=(belle_sip_message_t*)ch;
|
|
belle_sip_header_t *h;
|
|
|
|
if (msg==NULL){
|
|
msg=(belle_sip_message_t*)belle_sip_request_new();
|
|
belle_sip_object_ref(msg);
|
|
}
|
|
h=belle_sip_header_create(name,value);
|
|
if (h==NULL){
|
|
belle_sip_error("Fail to parse custom header.");
|
|
return (SalCustomHeader*)msg;
|
|
}
|
|
belle_sip_message_add_header(msg,h);
|
|
return (SalCustomHeader*)msg;
|
|
}
|
|
|
|
const char *sal_custom_header_find(const SalCustomHeader *ch, const char *name){
|
|
if (ch){
|
|
belle_sip_header_t *h=belle_sip_message_get_header((belle_sip_message_t*)ch,name);
|
|
|
|
if (h){
|
|
return belle_sip_header_get_unparsed_value(h);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SalCustomHeader *sal_custom_header_remove(SalCustomHeader *ch, const char *name) {
|
|
belle_sip_message_t *msg=(belle_sip_message_t*)ch;
|
|
if (msg==NULL) return NULL;
|
|
|
|
belle_sip_message_remove_header(msg, name);
|
|
return (SalCustomHeader*)msg;
|
|
}
|
|
|
|
void sal_custom_header_free(SalCustomHeader *ch){
|
|
if (ch==NULL) return;
|
|
belle_sip_object_unref((belle_sip_message_t*)ch);
|
|
}
|
|
|
|
SalCustomHeader *sal_custom_header_clone(const SalCustomHeader *ch){
|
|
if (ch==NULL) return NULL;
|
|
return (SalCustomHeader*)belle_sip_object_ref((belle_sip_message_t*)ch);
|
|
}
|
|
|
|
SalCustomSdpAttribute * sal_custom_sdp_attribute_append(SalCustomSdpAttribute *csa, const char *name, const char *value) {
|
|
belle_sdp_session_description_t *desc = (belle_sdp_session_description_t *)csa;
|
|
belle_sdp_attribute_t *attr;
|
|
|
|
if (desc == NULL) {
|
|
desc = (belle_sdp_session_description_t *)belle_sdp_session_description_new();
|
|
belle_sip_object_ref(desc);
|
|
}
|
|
attr = BELLE_SDP_ATTRIBUTE(belle_sdp_raw_attribute_create(name, value));
|
|
if (attr == NULL) {
|
|
belle_sip_error("Fail to create custom SDP attribute.");
|
|
return (SalCustomSdpAttribute*)desc;
|
|
}
|
|
belle_sdp_session_description_add_attribute(desc, attr);
|
|
return (SalCustomSdpAttribute *)desc;
|
|
}
|
|
|
|
const char * sal_custom_sdp_attribute_find(const SalCustomSdpAttribute *csa, const char *name) {
|
|
if (csa) {
|
|
return belle_sdp_session_description_get_attribute_value((belle_sdp_session_description_t *)csa, name);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void sal_custom_sdp_attribute_free(SalCustomSdpAttribute *csa) {
|
|
if (csa == NULL) return;
|
|
belle_sip_object_unref((belle_sdp_session_description_t *)csa);
|
|
}
|
|
|
|
SalCustomSdpAttribute * sal_custom_sdp_attribute_clone(const SalCustomSdpAttribute *csa) {
|
|
if (csa == NULL) return NULL;
|
|
return (SalCustomSdpAttribute *)belle_sip_object_ref((belle_sdp_session_description_t *)csa);
|
|
}
|
|
|
|
/** Parse a file containing either a certificate chain order in PEM format or a single DER cert
|
|
* @param auth_info structure where to store the result of parsing
|
|
* @param path path to certificate chain file
|
|
* @param format either PEM or DER
|
|
*/
|
|
void sal_certificates_chain_parse_file(SalAuthInfo* auth_info, const char* path, SalCertificateRawFormat format) {
|
|
auth_info->certificates = (belle_sip_certificates_chain_t*) belle_sip_certificates_chain_parse_file(path, (belle_sip_certificate_raw_format_t)format);
|
|
if (auth_info->certificates) belle_sip_object_ref((belle_sip_object_t *) auth_info->certificates);
|
|
}
|
|
|
|
/**
|
|
* Parse a file containing either a private or public rsa key
|
|
* @param auth_info structure where to store the result of parsing
|
|
* @param passwd password (optionnal)
|
|
*/
|
|
void sal_signing_key_parse_file(SalAuthInfo* auth_info, const char* path, const char *passwd) {
|
|
auth_info->key = (belle_sip_signing_key_t *) belle_sip_signing_key_parse_file(path, passwd);
|
|
if (auth_info->key) belle_sip_object_ref((belle_sip_object_t *) auth_info->key);
|
|
}
|
|
|
|
/** Parse a buffer containing either a certificate chain order in PEM format or a single DER cert
|
|
* @param auth_info structure where to store the result of parsing
|
|
* @param buffer the buffer to parse
|
|
* @param format either PEM or DER
|
|
*/
|
|
void sal_certificates_chain_parse(SalAuthInfo* auth_info, const char* buffer, SalCertificateRawFormat format) {
|
|
size_t len = buffer != NULL ? strlen(buffer) : 0;
|
|
auth_info->certificates = (belle_sip_certificates_chain_t*) belle_sip_certificates_chain_parse(buffer, len, (belle_sip_certificate_raw_format_t)format);
|
|
if (auth_info->certificates) belle_sip_object_ref((belle_sip_object_t *) auth_info->certificates);
|
|
}
|
|
|
|
/**
|
|
* Parse a buffer containing either a private or public rsa key
|
|
* @param auth_info structure where to store the result of parsing
|
|
* @param passwd password (optionnal)
|
|
*/
|
|
void sal_signing_key_parse(SalAuthInfo* auth_info, const char* buffer, const char *passwd) {
|
|
size_t len = buffer != NULL ? strlen(buffer) : 0;
|
|
auth_info->key = (belle_sip_signing_key_t *) belle_sip_signing_key_parse(buffer, len, passwd);
|
|
if (auth_info->key) belle_sip_object_ref((belle_sip_object_t *) auth_info->key);
|
|
}
|
|
|
|
/**
|
|
* Parse a directory to get a certificate with the given subject common name
|
|
*
|
|
*/
|
|
void sal_certificates_chain_parse_directory(char **certificate_pem, char **key_pem, char **fingerprint, const char* path, const char *subject, SalCertificateRawFormat format, bool_t generate_certificate, bool_t generate_dtls_fingerprint) {
|
|
belle_sip_certificates_chain_t *certificate = NULL;
|
|
belle_sip_signing_key_t *key = NULL;
|
|
*certificate_pem = NULL;
|
|
*key_pem = NULL;
|
|
if (belle_sip_get_certificate_and_pkey_in_dir(path, subject, &certificate, &key, (belle_sip_certificate_raw_format_t)format) == 0) {
|
|
*certificate_pem = belle_sip_certificates_chain_get_pem(certificate);
|
|
*key_pem = belle_sip_signing_key_get_pem(key);
|
|
ms_message("Retrieve certificate with CN=%s successful\n", subject);
|
|
} else {
|
|
if (generate_certificate == TRUE) {
|
|
if ( belle_sip_generate_self_signed_certificate(path, subject, &certificate, &key) == 0) {
|
|
*certificate_pem = belle_sip_certificates_chain_get_pem(certificate);
|
|
*key_pem = belle_sip_signing_key_get_pem(key);
|
|
ms_message("Generate self-signed certificate with CN=%s successful\n", subject);
|
|
}
|
|
}
|
|
}
|
|
/* generate the fingerprint as described in RFC4572 if needed */
|
|
if ((generate_dtls_fingerprint == TRUE) && (fingerprint != NULL)) {
|
|
if (*fingerprint != NULL) {
|
|
ms_free(*fingerprint);
|
|
}
|
|
*fingerprint = belle_sip_certificates_chain_get_fingerprint(certificate);
|
|
}
|
|
|
|
/* free key and certificate */
|
|
if ( certificate != NULL ) {
|
|
belle_sip_object_unref(certificate);
|
|
}
|
|
if ( key != NULL ) {
|
|
belle_sip_object_unref(key);
|
|
}
|
|
}
|
|
|
|
unsigned char * sal_get_random_bytes(unsigned char *ret, size_t size){
|
|
return belle_sip_random_bytes(ret,size);
|
|
}
|
|
|
|
char *sal_get_random_token(int size){
|
|
return belle_sip_random_token(reinterpret_cast<char *>(ms_malloc((size_t)size)),(size_t)size);
|
|
}
|
|
|
|
unsigned int sal_get_random(void){
|
|
unsigned int ret=0;
|
|
belle_sip_random_bytes((unsigned char*)&ret,4);
|
|
return ret;
|
|
}
|
|
|
|
unsigned long sal_begin_background_task(const char *name, void (*max_time_reached)(void *), void *data){
|
|
return belle_sip_begin_background_task(name, max_time_reached, data);
|
|
}
|
|
|
|
void sal_end_background_task(unsigned long id){
|
|
belle_sip_end_background_task(id);
|
|
}
|
|
|
|
static belle_sip_header_t * sal_body_handler_find_header(const SalBodyHandler *body_handler, const char *header_name) {
|
|
belle_sip_body_handler_t *bsbh = BELLE_SIP_BODY_HANDLER(body_handler);
|
|
const belle_sip_list_t *l = belle_sip_body_handler_get_headers(bsbh);
|
|
for (; l != NULL; l = l->next) {
|
|
belle_sip_header_t *header = BELLE_SIP_HEADER(l->data);
|
|
if (strcmp(belle_sip_header_get_name(header), header_name) == 0) {
|
|
return header;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SalBodyHandler * sal_body_handler_new(void) {
|
|
belle_sip_memory_body_handler_t *body_handler = belle_sip_memory_body_handler_new(NULL, NULL);
|
|
return (SalBodyHandler *)BELLE_SIP_BODY_HANDLER(body_handler);
|
|
}
|
|
|
|
SalBodyHandler * sal_body_handler_ref(SalBodyHandler *body_handler) {
|
|
return (SalBodyHandler *)belle_sip_object_ref(BELLE_SIP_OBJECT(body_handler));
|
|
}
|
|
|
|
void sal_body_handler_unref(SalBodyHandler *body_handler) {
|
|
belle_sip_object_unref(BELLE_SIP_OBJECT(body_handler));
|
|
}
|
|
|
|
const char * sal_body_handler_get_type(const SalBodyHandler *body_handler) {
|
|
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
|
|
if (content_type != NULL) {
|
|
return belle_sip_header_content_type_get_type(content_type);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void sal_body_handler_set_type(SalBodyHandler *body_handler, const char *type) {
|
|
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
|
|
if (content_type == NULL) {
|
|
content_type = belle_sip_header_content_type_new();
|
|
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), BELLE_SIP_HEADER(content_type));
|
|
}
|
|
belle_sip_header_content_type_set_type(content_type, type);
|
|
}
|
|
|
|
const char * sal_body_handler_get_subtype(const SalBodyHandler *body_handler) {
|
|
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
|
|
if (content_type != NULL) {
|
|
return belle_sip_header_content_type_get_subtype(content_type);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void sal_body_handler_set_subtype(SalBodyHandler *body_handler, const char *subtype) {
|
|
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
|
|
if (content_type == NULL) {
|
|
content_type = belle_sip_header_content_type_new();
|
|
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), BELLE_SIP_HEADER(content_type));
|
|
}
|
|
belle_sip_header_content_type_set_subtype(content_type, subtype);
|
|
}
|
|
|
|
const belle_sip_list_t * sal_body_handler_get_content_type_parameters_names(const SalBodyHandler *body_handler) {
|
|
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
|
|
if (content_type != NULL) {
|
|
return belle_sip_parameters_get_parameter_names(BELLE_SIP_PARAMETERS(content_type));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const char * sal_body_handler_get_content_type_parameter(const SalBodyHandler *body_handler, const char *name) {
|
|
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
|
|
if (content_type != NULL) {
|
|
return belle_sip_parameters_get_parameter(BELLE_SIP_PARAMETERS(content_type), name);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void sal_body_handler_set_content_type_parameter(SalBodyHandler *body_handler, const char *paramName, const char *paramValue) {
|
|
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
|
|
if (content_type != NULL) {
|
|
belle_sip_parameters_set_parameter(BELLE_SIP_PARAMETERS(content_type), paramName, paramValue);
|
|
}
|
|
}
|
|
|
|
const char * sal_body_handler_get_encoding(const SalBodyHandler *body_handler) {
|
|
belle_sip_header_t *content_encoding = sal_body_handler_find_header(body_handler, "Content-Encoding");
|
|
if (content_encoding != NULL) {
|
|
return belle_sip_header_get_unparsed_value(content_encoding);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void sal_body_handler_set_encoding(SalBodyHandler *body_handler, const char *encoding) {
|
|
belle_sip_header_t *content_encoding = sal_body_handler_find_header(body_handler, "Content-Encoding");
|
|
if (content_encoding != NULL) {
|
|
belle_sip_body_handler_remove_header_from_ptr(BELLE_SIP_BODY_HANDLER(body_handler), content_encoding);
|
|
}
|
|
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), belle_sip_header_create("Content-Encoding", encoding));
|
|
}
|
|
|
|
void * sal_body_handler_get_data(const SalBodyHandler *body_handler) {
|
|
return belle_sip_memory_body_handler_get_buffer(BELLE_SIP_MEMORY_BODY_HANDLER(body_handler));
|
|
}
|
|
|
|
void sal_body_handler_set_data(SalBodyHandler *body_handler, void *data) {
|
|
belle_sip_memory_body_handler_set_buffer(BELLE_SIP_MEMORY_BODY_HANDLER(body_handler), data);
|
|
}
|
|
|
|
size_t sal_body_handler_get_size(const SalBodyHandler *body_handler) {
|
|
return belle_sip_body_handler_get_size(BELLE_SIP_BODY_HANDLER(body_handler));
|
|
}
|
|
|
|
void sal_body_handler_set_size(SalBodyHandler *body_handler, size_t size) {
|
|
belle_sip_header_content_length_t *content_length = BELLE_SIP_HEADER_CONTENT_LENGTH(sal_body_handler_find_header(body_handler, "Content-Length"));
|
|
if (content_length == NULL) {
|
|
content_length = belle_sip_header_content_length_new();
|
|
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), BELLE_SIP_HEADER(content_length));
|
|
}
|
|
belle_sip_header_content_length_set_content_length(content_length, size);
|
|
belle_sip_body_handler_set_size(BELLE_SIP_BODY_HANDLER(body_handler), size);
|
|
}
|
|
|
|
bool_t sal_body_handler_is_multipart(const SalBodyHandler *body_handler) {
|
|
if (BELLE_SIP_IS_INSTANCE_OF(body_handler, belle_sip_multipart_body_handler_t)) return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
SalBodyHandler * sal_body_handler_get_part(const SalBodyHandler *body_handler, int idx) {
|
|
const belle_sip_list_t *l = belle_sip_multipart_body_handler_get_parts(BELLE_SIP_MULTIPART_BODY_HANDLER(body_handler));
|
|
return (SalBodyHandler *)belle_sip_list_nth_data(l, idx);
|
|
}
|
|
|
|
const belle_sip_list_t * sal_body_handler_get_parts(const SalBodyHandler *body_handler) {
|
|
if (!sal_body_handler_is_multipart(body_handler)) return NULL;
|
|
return belle_sip_multipart_body_handler_get_parts(BELLE_SIP_MULTIPART_BODY_HANDLER(body_handler));
|
|
}
|
|
|
|
SalBodyHandler * sal_body_handler_find_part_by_header(const SalBodyHandler *body_handler, const char *header_name, const char *header_value) {
|
|
const belle_sip_list_t *l = belle_sip_multipart_body_handler_get_parts(BELLE_SIP_MULTIPART_BODY_HANDLER(body_handler));
|
|
for (; l != NULL; l = l->next) {
|
|
belle_sip_body_handler_t *bsbh = BELLE_SIP_BODY_HANDLER(l->data);
|
|
const belle_sip_list_t *headers = belle_sip_body_handler_get_headers(bsbh);
|
|
for (; headers != NULL; headers = headers->next) {
|
|
belle_sip_header_t *header = BELLE_SIP_HEADER(headers->data);
|
|
if ((strcmp(belle_sip_header_get_name(header), header_name) == 0) && (strcmp(belle_sip_header_get_unparsed_value(header), header_value) == 0)) {
|
|
return (SalBodyHandler *)bsbh;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const char * sal_body_handler_get_header(const SalBodyHandler *body_handler, const char *header_name) {
|
|
belle_sip_header_t *header = sal_body_handler_find_header(body_handler, header_name);
|
|
if (header != NULL) {
|
|
return belle_sip_header_get_unparsed_value(header);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const belle_sip_list_t* sal_body_handler_get_headers(const SalBodyHandler *body_handler) {
|
|
return belle_sip_body_handler_get_headers(BELLE_SIP_BODY_HANDLER(body_handler));
|
|
}
|