int web_server_set_alias(const char *alias_name, const char *alias_content, size_t alias_content_length, time_t last_modified) { int ret_code; struct xml_alias_t alias; alias_release(&gAliasDoc); if (alias_name == NULL) { /* don't serve aliased doc anymore */ return 0; } assert(alias_content != NULL); membuffer_init(&alias.doc); membuffer_init(&alias.name); alias.ct = NULL; do { /* insert leading /, if missing */ if (*alias_name != '/') if (membuffer_assign_str(&alias.name, "/") != 0) break; /* error; out of mem */ ret_code = membuffer_append_str(&alias.name, alias_name); if (ret_code != 0) break; /* error */ if ((alias.ct = (int *)malloc(sizeof(int))) == NULL) break; /* error */ *alias.ct = 1; membuffer_attach(&alias.doc, (char *)alias_content, alias_content_length); alias.last_modified = last_modified; /* save in module var */ ithread_mutex_lock(&gWebMutex); gAliasDoc = alias; ithread_mutex_unlock(&gWebMutex); return 0; } while (FALSE); /* error handler */ /* free temp alias */ membuffer_destroy(&alias.name); membuffer_destroy(&alias.doc); free(alias.ct); return UPNP_E_OUTOF_MEMORY; }
/**************************************************************************** * Function : get_request_type * * Parameters : * IN http_message_t* request : HTTP request * OUT memptr* action_name : SOAP action name * * Description : This function retrives the name of the SOAP action * * Return : int * 0 if successful else returns appropriate error. * Note : ****************************************************************************/ static XINLINE int get_request_type( IN http_message_t * request, OUT memptr * action_name ) { memptr value; memptr ns_value, dummy_quote; http_header_t *hdr; char save_char; char *s; membuffer soap_action_name; // find soapaction header // if( request->method == SOAPMETHOD_POST ) { if( httpmsg_find_hdr( request, HDR_SOAPACTION, &value ) == NULL ) { return SREQ_HDR_NOT_FOUND; } } else // M-POST { // get NS value from MAN header hdr = httpmsg_find_hdr( request, HDR_MAN, &value ); if( hdr == NULL ) { return SREQ_HDR_NOT_FOUND; } if( matchstr( value.buf, value.length, "%q%i ; ns = %s", &dummy_quote, &ns_value ) != 0 ) { return SREQ_BAD_HDR_FORMAT; } // create soapaction name header membuffer_init( &soap_action_name ); if( ( membuffer_assign( &soap_action_name, ns_value.buf, ns_value.length ) == UPNP_E_OUTOF_MEMORY ) || ( membuffer_append_str( &soap_action_name, "-SOAPACTION" ) == UPNP_E_OUTOF_MEMORY ) ) { membuffer_destroy( &soap_action_name ); return UPNP_E_OUTOF_MEMORY; } hdr = httpmsg_find_hdr_str( request, soap_action_name.buf ); membuffer_destroy( &soap_action_name ); if( hdr == NULL ) { return SREQ_HDR_NOT_FOUND; } value.buf = hdr->value.buf; value.length = hdr->value.length; } // determine type // save_char = value.buf[value.length]; value.buf[value.length] = '\0'; s = strchr( value.buf, '#' ); if( s == NULL ) { value.buf[value.length] = save_char; return SREQ_BAD_HDR_FORMAT; } s++; // move to value if( matchstr( s, value.length - ( s - value.buf ), "%s", action_name ) != PARSE_OK ) { value.buf[value.length] = save_char; return SREQ_BAD_HDR_FORMAT; } // action name or variable ? if( memptr_cmp( action_name, "QueryStateVariable" ) == 0 ) { // query variable action_name->buf = NULL; action_name->length = 0; } value.buf[value.length] = save_char; // restore return 0; }
/*! * \brief Processes the request and returns the result in the output parameters. * * \return * \li \c HTTP_BAD_REQUEST * \li \c HTTP_INTERNAL_SERVER_ERROR * \li \c HTTP_REQUEST_RANGE_NOT_SATISFIABLE * \li \c HTTP_FORBIDDEN * \li \c HTTP_NOT_FOUND * \li \c HTTP_NOT_ACCEPTABLE * \li \c HTTP_OK */ static int process_request( /*! [in] HTTP Request message. */ http_message_t *req, /*! [out] Tpye of response. */ enum resp_type *rtype, /*! [out] Headers. */ membuffer *headers, /*! [out] Get filename from request document. */ membuffer *filename, /*! [out] Xml alias document from the request document. */ struct xml_alias_t *alias, /*! [out] Send Instruction object where the response is set up. */ struct SendInstruction *RespInstr) { int code; int err_code; char *request_doc; struct File_Info finfo; int using_alias; int using_virtual_dir; uri_type *url; const char *temp_str; int resp_major; int resp_minor; int alias_grabbed; size_t dummy; const char *extra_headers = NULL; print_http_headers(req); url = &req->uri; assert(req->method == HTTPMETHOD_GET || req->method == HTTPMETHOD_HEAD || req->method == HTTPMETHOD_POST || req->method == HTTPMETHOD_SIMPLEGET); /* init */ memset(&finfo, 0, sizeof(finfo)); request_doc = NULL; finfo.content_type = NULL; alias_grabbed = FALSE; err_code = HTTP_INTERNAL_SERVER_ERROR; /* default error */ using_virtual_dir = FALSE; using_alias = FALSE; http_CalcResponseVersion(req->major_version, req->minor_version, &resp_major, &resp_minor); /* */ /* remove dots */ /* */ request_doc = malloc(url->pathquery.size + 1); if (request_doc == NULL) { goto error_handler; /* out of mem */ } memcpy(request_doc, url->pathquery.buff, url->pathquery.size); request_doc[url->pathquery.size] = '\0'; dummy = url->pathquery.size; remove_escaped_chars(request_doc, &dummy); code = remove_dots(request_doc, url->pathquery.size); if (code != 0) { err_code = HTTP_FORBIDDEN; goto error_handler; } if (*request_doc != '/') { /* no slash */ err_code = HTTP_BAD_REQUEST; goto error_handler; } if (isFileInVirtualDir(request_doc)) { using_virtual_dir = TRUE; RespInstr->IsVirtualFile = 1; if (membuffer_assign_str(filename, request_doc) != 0) { goto error_handler; } } else { /* try using alias */ if (is_valid_alias(&gAliasDoc)) { alias_grab(alias); alias_grabbed = TRUE; using_alias = get_alias(request_doc, alias, &finfo); if (using_alias == TRUE) { finfo.content_type = ixmlCloneDOMString("text/xml"); if (finfo.content_type == NULL) { goto error_handler; } } } } if (using_virtual_dir) { if (req->method != HTTPMETHOD_POST) { /* get file info */ if (virtualDirCallback. get_info(filename->buf, &finfo) != 0) { err_code = HTTP_NOT_FOUND; goto error_handler; } /* try index.html if req is a dir */ if (finfo.is_directory) { if (filename->buf[filename->length - 1] == '/') { temp_str = "index.html"; } else { temp_str = "/index.html"; } if (membuffer_append_str(filename, temp_str) != 0) { goto error_handler; } /* get info */ if ((virtualDirCallback. get_info(filename->buf, &finfo) != UPNP_E_SUCCESS) || finfo.is_directory) { err_code = HTTP_NOT_FOUND; goto error_handler; } } /* not readable */ if (!finfo.is_readable) { err_code = HTTP_FORBIDDEN; goto error_handler; } /* finally, get content type */ /* if ( get_content_type(filename->buf, &content_type) != 0 ) */ /*{ */ /* goto error_handler; */ /* } */ } } else if (!using_alias) { if (gDocumentRootDir.length == 0) { goto error_handler; } /* */ /* get file name */ /* */ /* filename str */ if (membuffer_assign_str(filename, gDocumentRootDir.buf) != 0 || membuffer_append_str(filename, request_doc) != 0) { goto error_handler; /* out of mem */ } /* remove trailing slashes */ while (filename->length > 0 && filename->buf[filename->length - 1] == '/') { membuffer_delete(filename, filename->length - 1, 1); } if (req->method != HTTPMETHOD_POST) { /* get info on file */ if (get_file_info(filename->buf, &finfo) != 0) { err_code = HTTP_NOT_FOUND; goto error_handler; } /* try index.html if req is a dir */ if (finfo.is_directory) { if (filename->buf[filename->length - 1] == '/') { temp_str = "index.html"; } else { temp_str = "/index.html"; } if (membuffer_append_str(filename, temp_str) != 0) { goto error_handler; } /* get info */ if (get_file_info(filename->buf, &finfo) != 0 || finfo.is_directory) { err_code = HTTP_NOT_FOUND; goto error_handler; } } /* not readable */ if (!finfo.is_readable) { err_code = HTTP_FORBIDDEN; goto error_handler; } } /* finally, get content type */ /* if ( get_content_type(filename->buf, &content_type) != 0 ) */ /* { */ /* goto error_handler; */ /* } */ } RespInstr->ReadSendSize = finfo.file_length; /* Check other header field. */ if ((code = CheckOtherHTTPHeaders(req, RespInstr, finfo.file_length)) != HTTP_OK) { err_code = code; goto error_handler; } if (req->method == HTTPMETHOD_POST) { *rtype = RESP_POST; err_code = HTTP_OK; goto error_handler; } /*extra_headers = UpnpFileInfo_get_ExtraHeaders(finfo); */ if (!extra_headers) { extra_headers = ""; } /* Check if chunked encoding should be used. */ if (using_virtual_dir && finfo.file_length == UPNP_USING_CHUNKED) { /* Chunked encoding is only supported by HTTP 1.1 clients */ if (resp_major == 1 && resp_minor == 1) { RespInstr->IsChunkActive = 1; } else { /* The virtual callback indicates that we should use * chunked encoding however the client doesn't support * it. Return with an internal server error. */ err_code = HTTP_NOT_ACCEPTABLE; goto error_handler; } } if (RespInstr->IsRangeActive && RespInstr->IsChunkActive) { /* Content-Range: bytes 222-3333/4000 HTTP_PARTIAL_CONTENT */ /* Transfer-Encoding: chunked */ if (http_MakeMessage(headers, resp_major, resp_minor, "R" "T" "GKLD" "s" "tcS" "Xc" "sCc", HTTP_PARTIAL_CONTENT, /* status code */ finfo.content_type, /* content type */ RespInstr, /* range info */ RespInstr, /* language info */ "LAST-MODIFIED: ", &finfo.last_modified, X_USER_AGENT, extra_headers) != 0) { goto error_handler; } } else if (RespInstr->IsRangeActive && !RespInstr->IsChunkActive) { /* Content-Range: bytes 222-3333/4000 HTTP_PARTIAL_CONTENT */ if (http_MakeMessage(headers, resp_major, resp_minor, "R" "N" "T" "GLD" "s" "tcS" "Xc" "sCc", HTTP_PARTIAL_CONTENT, /* status code */ RespInstr->ReadSendSize, /* content length */ finfo.content_type, /* content type */ RespInstr, /* range info */ RespInstr, /* language info */ "LAST-MODIFIED: ", &finfo.last_modified, X_USER_AGENT, extra_headers) != 0) { goto error_handler; } } else if (!RespInstr->IsRangeActive && RespInstr->IsChunkActive) { /* Transfer-Encoding: chunked */ if (http_MakeMessage(headers, resp_major, resp_minor, "RK" "TLD" "s" "tcS" "Xc" "sCc", HTTP_OK, /* status code */ finfo.content_type, /* content type */ RespInstr, /* language info */ "LAST-MODIFIED: ", &finfo.last_modified, X_USER_AGENT, extra_headers) != 0) { goto error_handler; } } else { /* !RespInstr->IsRangeActive && !RespInstr->IsChunkActive */ if (RespInstr->ReadSendSize >= 0) { if (http_MakeMessage(headers, resp_major, resp_minor, "R" "N" "TLD" "s" "tcS" "Xc" "sCc", HTTP_OK, /* status code */ RespInstr->ReadSendSize, /* content length */ finfo.content_type, /* content type */ RespInstr, /* language info */ "LAST-MODIFIED: ", &finfo.last_modified, X_USER_AGENT, extra_headers) != 0) { goto error_handler; } } else { if (http_MakeMessage(headers, resp_major, resp_minor, "R" "TLD" "s" "tcS" "Xc" "sCc", HTTP_OK, /* status code */ finfo.content_type, /* content type */ RespInstr, /* language info */ "LAST-MODIFIED: ", &finfo.last_modified, X_USER_AGENT, extra_headers) != 0) { goto error_handler; } } } if (req->method == HTTPMETHOD_HEAD) { *rtype = RESP_HEADERS; } else if (using_alias) { /* GET xml */ *rtype = RESP_XMLDOC; } else if (using_virtual_dir) { *rtype = RESP_WEBDOC; } else { /* GET filename */ *rtype = RESP_FILEDOC; } /* simple get http 0.9 as specified in http 1.0 */ /* don't send headers */ if (req->method == HTTPMETHOD_SIMPLEGET) { membuffer_destroy(headers); } err_code = HTTP_OK; error_handler: free(request_doc); ixmlFreeDOMString(finfo.content_type); if (err_code != HTTP_OK && alias_grabbed) { alias_release(alias); } return err_code; }
/*! * \brief This function retrives the name of the SOAP action. * * \return 0 if successful else returns appropriate error. */ static UPNP_INLINE int get_request_type( /*! [in] HTTP request. */ http_message_t *request, /*! [out] SOAP action name. */ memptr *action_name) { memptr value; memptr ns_value, dummy_quote; http_header_t *hdr; char save_char; char *s; membuffer soap_action_name; size_t n; /* find soapaction header */ if (request->method == SOAPMETHOD_POST) { if (!httpmsg_find_hdr(request, HDR_SOAPACTION, &value)) return SREQ_HDR_NOT_FOUND; } else { /* M-POST */ /* get NS value from MAN header */ hdr = httpmsg_find_hdr(request, HDR_MAN, &value); if (hdr == NULL) return SREQ_HDR_NOT_FOUND; if (matchstr(value.buf, value.length, "%q%i ; ns = %s", &dummy_quote, &ns_value) != 0) return SREQ_BAD_HDR_FORMAT; /* create soapaction name header */ membuffer_init(&soap_action_name); if (membuffer_assign(&soap_action_name, ns_value.buf, ns_value.length) == UPNP_E_OUTOF_MEMORY || membuffer_append_str(&soap_action_name, "-SOAPACTION") == UPNP_E_OUTOF_MEMORY) { membuffer_destroy(&soap_action_name); return UPNP_E_OUTOF_MEMORY; } hdr = httpmsg_find_hdr_str(request, soap_action_name.buf); membuffer_destroy(&soap_action_name); if (!hdr) return SREQ_HDR_NOT_FOUND; value.buf = hdr->value.buf; value.length = hdr->value.length; } /* determine type */ save_char = value.buf[value.length]; value.buf[value.length] = '\0'; s = strchr(value.buf, '#'); if (s == NULL) { value.buf[value.length] = save_char; return SREQ_BAD_HDR_FORMAT; } /* move to value */ s++; n = value.length - (size_t)(s - value.buf); if (matchstr(s, n, "%s", action_name) != PARSE_OK) { value.buf[value.length] = save_char; return SREQ_BAD_HDR_FORMAT; } /* action name or variable ? */ if (memptr_cmp(action_name, "QueryStateVariable") == 0) { /* query variable */ action_name->buf = NULL; action_name->length = 0; } /* restore */ value.buf[value.length] = save_char; return 0; }
/************************************************************************ * Function : config_description_doc * * Parameters : * INOUT IXML_Document *doc ;IMXL description document to be * configured * IN const char* ip_str ; string containing the IP port number * OUT char** root_path_str ; buffer to hold the root path * of the configured description document * INOUT IXML_Document *doc : Description document * IN const char* ip_str : ipaddress string * OUT char** root_path_str : root path string * * Description : Configure the description document. Add the standard * format and then add information from the root device and any * child nodes. * * Return : int ; * UPNP_E_SUCCESS - On Success * UPNP_E_OUTOF_MEMORY - Default Error * UPNP_E_INVALID_DESC - Invalid child node * UPNP_E_INVALID_URL - Invalid node information * * Note : ************************************************************************/ static int config_description_doc( INOUT IXML_Document * doc, IN const char *ip_str, OUT char **root_path_str ) { xboolean addNew = FALSE; IXML_NodeList *baseList; IXML_Element *element = NULL; IXML_Element *newElement = NULL; IXML_Node *textNode = NULL; IXML_Node *rootNode = NULL; IXML_Node *urlbase_node = NULL; char *urlBaseStr = "URLBase"; const DOMString domStr = NULL; uri_type uri; int err_code; int len; membuffer url_str; membuffer root_path; membuffer_init( &url_str ); membuffer_init( &root_path ); err_code = UPNP_E_OUTOF_MEMORY; // default error baseList = ixmlDocument_getElementsByTagName( doc, urlBaseStr ); if( baseList == NULL ) { // urlbase not found -- create new one addNew = TRUE; element = ixmlDocument_createElement( doc, urlBaseStr ); if( element == NULL ) { goto error_handler; } if( membuffer_append_str( &url_str, "http://" ) != 0 || membuffer_append_str( &url_str, ip_str ) != 0 || membuffer_append_str( &url_str, "/" ) != 0 || membuffer_append_str( &root_path, "/" ) != 0 ) { goto error_handler; } rootNode = ixmlNode_getFirstChild( ( IXML_Node * ) doc ); if( rootNode == NULL ) { err_code = UPNP_E_INVALID_DESC; goto error_handler; } err_code = ixmlNode_appendChild( rootNode, ( IXML_Node * ) element ); if( err_code != IXML_SUCCESS ) { goto error_handler; } textNode = ixmlDocument_createTextNode( doc, ( char * )url_str.buf ); if( textNode == NULL ) { goto error_handler; } err_code = ixmlNode_appendChild( ( IXML_Node * ) element, textNode ); if( err_code != IXML_SUCCESS ) { goto error_handler; } } else { // urlbase found urlbase_node = ixmlNodeList_item( baseList, 0 ); assert( urlbase_node != NULL ); textNode = ixmlNode_getFirstChild( urlbase_node ); if( textNode == NULL ) { err_code = UPNP_E_INVALID_DESC; goto error_handler; } domStr = ixmlNode_getNodeValue( textNode ); if( domStr == NULL ) { err_code = UPNP_E_INVALID_URL; goto error_handler; } len = parse_uri( domStr, strlen( domStr ), &uri ); if( len < 0 || uri.type != ABSOLUTE ) { err_code = UPNP_E_INVALID_URL; goto error_handler; } if( membuffer_assign( &url_str, uri.scheme.buff, uri.scheme.size ) != 0 || membuffer_append_str( &url_str, "://" ) != 0 || membuffer_append_str( &url_str, ip_str ) != 0 ) { goto error_handler; } // add leading '/' if missing from relative path if( ( uri.pathquery.size > 0 && uri.pathquery.buff[0] != '/' ) || ( uri.pathquery.size == 0 ) ) { if( membuffer_append_str( &url_str, "/" ) != 0 || membuffer_append_str( &root_path, "/" ) != 0 ) { goto error_handler; } } if( membuffer_append( &url_str, uri.pathquery.buff, uri.pathquery.size ) != 0 || membuffer_append( &root_path, uri.pathquery.buff, uri.pathquery.size ) != 0 ) { goto error_handler; } // add trailing '/' if missing if( url_str.buf[url_str.length - 1] != '/' ) { if( membuffer_append( &url_str, "/", 1 ) != 0 ) { goto error_handler; } } err_code = ixmlNode_setNodeValue( textNode, url_str.buf ); if( err_code != IXML_SUCCESS ) { goto error_handler; } } *root_path_str = membuffer_detach( &root_path ); // return path err_code = UPNP_E_SUCCESS; error_handler: if( err_code != UPNP_E_SUCCESS ) { ixmlElement_free( newElement ); } ixmlNodeList_free( baseList ); membuffer_destroy( &root_path ); membuffer_destroy( &url_str ); return err_code; }
/**************************************************************************** * Function : SoapSendActionEx * * Parameters : * IN char* action_url : device contrl URL * IN char *service_type : device service type IN IXML_Document *Header: Soap header * IN IXML_Document *action_node : SOAP action node ( SOAP body) * OUT IXML_Document **response_node : SOAP response node * * Description : This function is called by UPnP API to send the SOAP * action request and waits till it gets the response from the device * pass the response to the API layer. This action is similar to the * the SoapSendAction with only difference that it allows users to * pass the SOAP header along the SOAP body ( soap action request) * * Return : int * returns UPNP_E_SUCCESS if successful else returns appropriate error * Note : ****************************************************************************/ int SoapSendActionEx( IN char *action_url, IN char *service_type, IN IXML_Document * header, IN IXML_Document * action_node, OUT IXML_Document ** response_node ) { char *xml_header_str = NULL; char *action_str = NULL; memptr name; membuffer request; membuffer responsename; int err_code; int ret_code; http_parser_t response; uri_type url; int upnp_error_code; char *upnp_error_str; int got_response = FALSE; const char *xml_start = "<s:Envelope " "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"; const char *xml_header_start = "<s:Header>\r\n"; const char *xml_header_end = "</s:Header>\r\n"; const char *xml_body_start = "<s:Body>"; const char *xml_end = "</s:Body>\r\n" "</s:Envelope>\r\n"; size_t xml_start_len; size_t xml_header_start_len; size_t xml_header_str_len; size_t xml_header_end_len; size_t xml_body_start_len; size_t action_str_len; size_t xml_end_len; off_t content_length; *response_node = NULL; /* init */ err_code = UPNP_E_OUTOF_MEMORY; /* default error */ UpnpPrintf( UPNP_INFO, SOAP, __FILE__, __LINE__, "Inside SoapSendActionEx():" ); /* init */ membuffer_init( &request ); membuffer_init( &responsename ); /* header string */ xml_header_str = ixmlPrintNode( ( IXML_Node * ) header ); if( xml_header_str == NULL ) { goto error_handler; } /* print action */ action_str = ixmlPrintNode( ( IXML_Node * ) action_node ); if( action_str == NULL ) { goto error_handler; } /* get action name */ if( get_action_name( action_str, &name ) != 0 ) { err_code = UPNP_E_INVALID_ACTION; goto error_handler; } /* parse url */ if( http_FixStrUrl( action_url, strlen( action_url ), &url ) != 0 ) { err_code = UPNP_E_INVALID_URL; goto error_handler; } UpnpPrintf( UPNP_INFO, SOAP, __FILE__, __LINE__, "path=%.*s, hostport=%.*s\n", (int)url.pathquery.size, url.pathquery.buff, (int)url.hostport.text.size, url.hostport.text.buff ); xml_start_len = strlen( xml_start ); xml_body_start_len = strlen( xml_body_start ); xml_end_len = strlen( xml_end ); action_str_len = strlen( action_str ); xml_header_start_len = strlen( xml_header_start ); xml_header_end_len = strlen( xml_header_end ); xml_header_str_len = strlen( xml_header_str ); /* make request msg */ request.size_inc = 50; content_length = (off_t)(xml_start_len + xml_header_start_len + xml_header_str_len + xml_header_end_len + xml_body_start_len + action_str_len + xml_end_len); if (http_MakeMessage( &request, 1, 1, "q" "N" "s" "sssbsc" "Uc" "b" "b" "b" "b" "b" "b" "b", SOAPMETHOD_POST, &url, content_length, ContentTypeHeader, "SOAPACTION: \"", service_type, "#", name.buf, name.length, "\"", xml_start, xml_start_len, xml_header_start, xml_header_start_len, xml_header_str, xml_header_str_len, xml_header_end, xml_header_end_len, xml_body_start, xml_body_start_len, action_str, action_str_len, xml_end, xml_end_len ) != 0 ) { goto error_handler; } ret_code = soap_request_and_response( &request, &url, &response ); got_response = TRUE; if( ret_code != UPNP_E_SUCCESS ) { err_code = ret_code; goto error_handler; } if( membuffer_append( &responsename, name.buf, name.length ) != 0 || membuffer_append_str( &responsename, "Response" ) != 0 ) { goto error_handler; } /* get action node from the response */ ret_code = get_response_value( &response.msg, SOAP_ACTION_RESP, responsename.buf, &upnp_error_code, ( IXML_Node ** ) response_node, &upnp_error_str ); if( ret_code == SOAP_ACTION_RESP ) { err_code = UPNP_E_SUCCESS; } else if( ret_code == SOAP_ACTION_RESP_ERROR ) { err_code = upnp_error_code; } else { err_code = ret_code; } error_handler: ixmlFreeDOMString( action_str ); ixmlFreeDOMString( xml_header_str ); membuffer_destroy( &request ); membuffer_destroy( &responsename ); if( got_response ) { httpmsg_destroy( &response.msg ); } return err_code; }