diff --git a/ChangeLog b/ChangeLog index 8676a3f..42c82aa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -353,6 +353,18 @@ Version 1.8.0 * Rewrote Peter Hartley's patch to include a new extra header field in FileInfo. +******************************************************************************* +Version 1.6.20 +******************************************************************************* + +2014-01-03 Peng + + rewrite soap_device.c + + 1) separate HTTP handling from SOAP handling + 2) remove repeated validity check, each check is performed exactly once + 3) fix HTTP status code per UPnP spec, SOAP spec and RFC 2774 + ******************************************************************************* Version 1.6.19 ******************************************************************************* diff --git a/upnp/src/genlib/net/http/statcodes.c b/upnp/src/genlib/net/http/statcodes.c index 2599d4a..c02fbf4 100644 --- a/upnp/src/genlib/net/http/statcodes.c +++ b/upnp/src/genlib/net/http/statcodes.c @@ -88,14 +88,19 @@ static const char *Http4xxStr = "Unsupported Media Type\0" "Requested Range Not Satisfiable\0" "Expectation Failed\0"; -#define NUM_5XX_CODES 6 +#define NUM_5XX_CODES 11 static const char *Http5xxCodes[NUM_5XX_CODES]; static const char *Http5xxStr = "Internal Server Error\0" "Not Implemented\0" "Bad Gateway\0" "Service Unavailable\0" - "Gateway Timeout\0" "HTTP Version Not Supported\0"; + "Gateway Timeout\0" + "HTTP Version Not Supported\0" + "Variant Also Negotiates\0" + "Insufficient Storage\0" + "Loop Detected\0" + "\0" "Not Extended\0"; static int gInitialized = FALSE; diff --git a/upnp/src/inc/statcodes.h b/upnp/src/inc/statcodes.h index 9ce22db..91af94a 100644 --- a/upnp/src/inc/statcodes.h +++ b/upnp/src/inc/statcodes.h @@ -79,6 +79,10 @@ #define HTTP_SERVICE_UNAVAILABLE 503 #define HTTP_GATEWAY_TIMEOUT 504 #define HTTP_HTTP_VERSION_NOT_SUPPORTED 505 +#define HTTP_VARIANT_ALSO_NEGOTIATES 506 +#define HTTP_INSUFFICIENT_STORAGE 507 +#define HTTP_LOOP_DETECTED 508 +#define HTTP_NOT_EXTENDED 510 /* HTTP lib error codes */ diff --git a/upnp/src/soap/soap_device.c b/upnp/src/soap/soap_device.c index f45d068..f133596 100644 --- a/upnp/src/soap/soap_device.c +++ b/upnp/src/soap/soap_device.c @@ -49,6 +49,9 @@ #include "unixutil.h" #include "upnpapi.h" +#include +#include + #ifdef WIN32 #define snprintf _snprintf #endif @@ -58,12 +61,15 @@ #define SREQ_HDR_NOT_FOUND -1 #define SREQ_BAD_HDR_FORMAT -2 +#define SREQ_NOT_EXTENDED -3 #define SOAP_INVALID_ACTION 401 #define SOAP_INVALID_ARGS 402 #define SOAP_OUT_OF_SYNC 403 #define SOAP_INVALID_VAR 404 #define SOAP_ACTION_FAILED 501 +#define SOAP_MEMORY_OUT 603 + static const char *SOAP_BODY = "Body"; static const char *SOAP_URN = "http:/""/schemas.xmlsoap.org/soap/envelope/"; @@ -73,81 +79,17 @@ static const char *Soap_Invalid_Action = "Invalid Action"; /*static const char* Soap_Invalid_Args = "Invalid Args"; */ static const char *Soap_Action_Failed = "Action Failed"; static const char *Soap_Invalid_Var = "Invalid Var"; +static const char *Soap_Memory_out = "Out of Memory"; -/*! - * \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; +typedef struct soap_devserv_t { + char dev_udn[NAME_SIZE]; + char service_type[NAME_SIZE]; + char service_id[NAME_SIZE]; + memptr action_name; + Upnp_FunPtr callback; + void *cookie; +}soap_devserv_t; - /* 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; -} /*! * \brief Sends SOAP error response. @@ -271,288 +213,6 @@ static UPNP_INLINE void send_var_query_response( membuffer_destroy(&response); } -/*! - * \brief Separates the action node from the root DOM node. - * - * \return 0 if successful, or -1 if fails. - */ -static UPNP_INLINE int get_action_node( - /*! [in] The root DOM node. */ - IXML_Document *TempDoc, - /*! [in] IXML_Node name to be searched. */ - char *NodeName, - /*! [out] Response/Output node. */ - IXML_Document **RespNode) -{ - IXML_Node *EnvpNode = NULL; - IXML_Node *BodyNode = NULL; - IXML_Node *ActNode = NULL; - DOMString ActNodeName = NULL; - const DOMString nodeName; - int ret_code = -1; /* error, by default */ - IXML_NodeList *nl = NULL; - - UpnpPrintf(UPNP_INFO, SOAP, __FILE__, __LINE__, - "get_action_node(): node name =%s\n ", NodeName); - *RespNode = NULL; - /* Got the Envelope node here */ - EnvpNode = ixmlNode_getFirstChild((IXML_Node *) TempDoc); - if (!EnvpNode) - goto error_handler; - nl = ixmlElement_getElementsByTagNameNS((IXML_Element *)EnvpNode, - "*", "Body"); - if (!nl) - goto error_handler; - BodyNode = ixmlNodeList_item(nl, 0); - if (!BodyNode) - goto error_handler; - /* Got action node here */ - ActNode = ixmlNode_getFirstChild(BodyNode); - if (!ActNode) - goto error_handler; - /* Test whether this is the action node */ - nodeName = ixmlNode_getNodeName(ActNode); - if (!nodeName) - goto error_handler; - if (!strstr(nodeName, NodeName)) - goto error_handler; - else { - ActNodeName = ixmlPrintNode(ActNode); - if (!ActNodeName) - goto error_handler; - ret_code = ixmlParseBufferEx(ActNodeName, RespNode); - if (ret_code != IXML_SUCCESS) { - ret_code = -1; - goto error_handler; - } - } - /* success */ - ret_code = 0; - -error_handler: - ixmlFreeDOMString(ActNodeName); - if (nl) - ixmlNodeList_free(nl); - return ret_code; -} - -/*! - * \brief Checks the soap body xml came in the SOAP request. - * - * \return UPNP_E_SUCCESS if successful else returns appropriate error. - */ -static int check_soap_body( - /* [in] soap body xml document. */ - IN IXML_Document *doc, - /* [in] URN. */ - IN const char *urn, - /* [in] Name of the requested action. */ - IN const char *actionName) -{ - IXML_NodeList *nl = NULL; - IXML_Node *bodyNode = NULL; - IXML_Node *actionNode = NULL; - const DOMString ns = NULL; - const DOMString name = NULL; - int ret_code = UPNP_E_INVALID_ACTION; - - nl = ixmlDocument_getElementsByTagNameNS(doc, SOAP_URN, SOAP_BODY); - if (nl) { - bodyNode = ixmlNodeList_item(nl, 0); - if (bodyNode) { - actionNode = ixmlNode_getFirstChild(bodyNode); - if (actionNode) { - ns = ixmlNode_getNamespaceURI(actionNode); - name = ixmlNode_getLocalName(actionNode); - /* Don't check version number, to accept a - * request comming on a v1 service when - * publishing a v2 service */ - if (name && - ns && - !strcmp(actionName, name) && - !strncmp(urn, ns, strlen (urn) - 2)) - ret_code = UPNP_E_SUCCESS; - } - } - ixmlNodeList_free(nl); - } - return ret_code; -} - -/*! - * \brief Checks the HTTP header of the SOAP request coming from the - * control point. - * - * \return UPNP_E_SUCCESS if successful else returns appropriate error. - */ -static int check_soap_action_header( - /*! [in] HTTP request. */ - http_message_t * request, - /*! [in] URN. */ - const char *urn, - /*! [out] Name of the SOAP action. */ - char **actionName) -{ - memptr header_name; - http_header_t *soap_action_header = NULL; - char *ns_compare = NULL; - size_t tempSize = 0; - int ret_code = UPNP_E_SUCCESS; - char *temp_header_value = NULL; - char *temp = NULL; - char *temp2 = NULL; - - /* check soap action header */ - soap_action_header = httpmsg_find_hdr(request, HDR_SOAPACTION, - &header_name); - if (!soap_action_header) { - ret_code = UPNP_E_INVALID_ACTION; - return ret_code; - } - if (soap_action_header->value.length <= 0) { - ret_code = UPNP_E_INVALID_ACTION; - return ret_code; - } - temp_header_value = malloc(soap_action_header->value.length + 1); - if (!temp_header_value) { - ret_code = UPNP_E_OUTOF_MEMORY; - return ret_code; - } - strncpy(temp_header_value, soap_action_header->value.buf, - soap_action_header->value.length); - temp_header_value[soap_action_header->value.length] = 0; - temp = strchr(temp_header_value, '#'); - if (!temp) { - free(temp_header_value); - ret_code = UPNP_E_INVALID_ACTION; - return ret_code; - } - - (*temp) = 0; /* temp make string */ - - /* check to see if it is Query State Variable or - * Service Action */ - tempSize = strlen(urn) + 2; - ns_compare = malloc(tempSize); - if (!ns_compare) { - ret_code = UPNP_E_OUTOF_MEMORY; - free(temp_header_value); - return ret_code; - } - snprintf(ns_compare, tempSize, "\"%s", urn); - /* Don't check version number, to accept a request comming on a v1 - * service when publishing a v2 service */ - if (strncmp(temp_header_value, ns_compare, strlen(ns_compare) - 2)) - ret_code = UPNP_E_INVALID_ACTION; - else { - ret_code = UPNP_E_SUCCESS; - temp++; - temp2 = strchr(temp, '\"'); - /* remove ending " if present */ - if (temp2) - (*temp2) = 0; - if (*temp) - (*actionName) = strdup(temp); - if (!*actionName) - ret_code = UPNP_E_OUTOF_MEMORY; - } - - free(temp_header_value); - free(ns_compare); - return ret_code; -} - -/*! - * \brief Retrives all the information needed to process the incoming SOAP - * request. It finds the device and service info and also the callback - * function to hand-over the request to the device application. - * - * \return UPNP_E_SUCCESS if successful else returns appropriate error. - */ -static int get_device_info( - /*! [in] HTTP request. */ - http_message_t *request, - /*! [in] flag for a querry. */ - int isQuery, - /*! [in] Action request document. */ - IXML_Document *actionDoc, - /*! [in] . */ - int AddressFamily, - /*! [out] Device UDN string. */ - OUT UpnpString *device_udn, - /*! [out] Service ID string. */ - UpnpString *service_id, - /*! [out] callback function of the device application. */ - Upnp_FunPtr *callback, - /*! [out] cookie stored by device application. */ - void **cookie) -{ - struct Handle_Info *device_info; - int device_hnd; - service_info *serv_info; - char save_char; - /* error by default */ - int ret_code = -1; - const char *control_url; - char *actionName = NULL; - - /* null-terminate pathquery of url */ - control_url = request->uri.pathquery.buff; - save_char = control_url[request->uri.pathquery.size]; - ((char *)control_url)[request->uri.pathquery.size] = '\0'; - - HandleLock(); - - if (GetDeviceHandleInfo(AddressFamily, &device_hnd, - &device_info) != HND_DEVICE) - goto error_handler; - serv_info = FindServiceControlURLPath( - &device_info->ServiceTable, control_url); - if (!serv_info) - goto error_handler; - if (isQuery) { - ret_code = check_soap_action_header(request, - QUERY_STATE_VAR_URN, &actionName); - if (ret_code != UPNP_E_SUCCESS && - ret_code != UPNP_E_OUTOF_MEMORY) { - ret_code = UPNP_E_INVALID_ACTION; - goto error_handler; - } - /* check soap body */ - ret_code = check_soap_body(actionDoc, QUERY_STATE_VAR_URN, - actionName); - free(actionName); - if (ret_code != UPNP_E_SUCCESS) - goto error_handler; - } else { - ret_code = check_soap_action_header(request, - serv_info->serviceType, &actionName); - if (ret_code != UPNP_E_SUCCESS && - ret_code != UPNP_E_OUTOF_MEMORY) { - ret_code = UPNP_E_INVALID_SERVICE; - goto error_handler; - } - /* check soap body */ - ret_code = check_soap_body(actionDoc, serv_info->serviceType, - actionName); - free(actionName); - if (ret_code != UPNP_E_SUCCESS) { - ret_code = UPNP_E_INVALID_SERVICE; - goto error_handler; - } - } - UpnpString_set_String(device_udn, serv_info->UDN); - UpnpString_set_String(service_id, serv_info->serviceId); - *callback = device_info->Callback; - *cookie = device_info->Cookie; - ret_code = 0; - - error_handler: - /* restore */ - ((char *)control_url)[request->uri.pathquery.size] = save_char; - HandleUnlock(); - return ret_code; -} /*! * \brief Sends the SOAP action response. @@ -623,94 +283,46 @@ error_handler: } } -/*! - * \brief Finds the name of the state variable asked in the SOAP request. - * - * \return 0 if successful else returns -1. - */ -static UPNP_INLINE int get_var_name( - /*! [in] Document containing variable request. */ - IXML_Document *TempDoc, - /*! [out] Name of the state varible. */ - char *VarName) -{ - IXML_Node *EnvpNode = NULL; - IXML_Node *BodyNode = NULL; - IXML_Node *StNode = NULL; - IXML_Node *VarNameNode = NULL; - IXML_Node *VarNode = NULL; - const DOMString StNodeName = NULL; - const DOMString Temp = NULL; - int ret_val = -1; - - /* Got the Envelop node here */ - EnvpNode = ixmlNode_getFirstChild((IXML_Node *) TempDoc); - if (EnvpNode == NULL) - goto error_handler; - /* Got Body here */ - BodyNode = ixmlNode_getFirstChild(EnvpNode); - if (BodyNode == NULL) - goto error_handler; - /* Got action node here */ - StNode = ixmlNode_getFirstChild(BodyNode); - if (StNode == NULL) - goto error_handler; - /* Test whether this is the action node */ - StNodeName = ixmlNode_getNodeName(StNode); - if (StNodeName == NULL || - strstr(StNodeName, "QueryStateVariable") == NULL) - goto error_handler; - VarNameNode = ixmlNode_getFirstChild(StNode); - if (VarNameNode == NULL) - goto error_handler; - VarNode = ixmlNode_getFirstChild(VarNameNode); - Temp = ixmlNode_getNodeValue(VarNode); - linecopy(VarName, Temp); - UpnpPrintf(UPNP_INFO, SOAP, __FILE__, __LINE__, - "Received query for variable name %s\n", VarName); - - /* success */ - ret_val = 0; - -error_handler: - return ret_val; -} /*! * \brief Handles the SOAP requests to querry the state variables. * This functionality has been deprecated in the UPnP V1.0 architecture. */ static UPNP_INLINE void handle_query_variable( - IN SOCKINFO *info, - IN http_message_t *request, - IN IXML_Document *xml_doc) + /*! [in] Socket info. */ + SOCKINFO *info, + /*! [in] HTTP Request. */ + http_message_t *request, + /*! [in] SOAP device/service information. */ + soap_devserv_t *soap_info, + /*! [in] Node containing variable name. */ + IXML_Node *req_node) { UpnpStateVarRequest *variable = UpnpStateVarRequest_new(); - Upnp_FunPtr soap_event_callback; - void *cookie; - char var_name[LINE_SIZE]; const char *err_str; - int err_code = 0; + int err_code; + const DOMString var_name; /* set default error */ err_code = SOAP_INVALID_VAR; err_str = Soap_Invalid_Var; - if (variable == NULL || get_var_name(xml_doc, var_name) != 0) - goto error_handler; - /* get info for event */ - err_code = get_device_info(request, 1, xml_doc, - info->foreign_sockaddr.ss_family, - (UpnpString *)UpnpStateVarRequest_get_DevUDN(variable), - (UpnpString *)UpnpStateVarRequest_get_ServiceID(variable), - &soap_event_callback, &cookie); - if (err_code != UPNP_E_SUCCESS) + if (variable == NULL) { + err_code = SOAP_MEMORY_OUT; + err_str = Soap_Memory_out; goto error_handler; + } + UpnpStateVarRequest_set_ErrCode(variable, UPNP_E_SUCCESS); + UpnpStateVarRequest_strcpy_DevUDN(variable, soap_info->dev_udn); + UpnpStateVarRequest_strcpy_ServiceID(variable, soap_info->service_id); + var_name = ixmlNode_getNodeValue(req_node); UpnpStateVarRequest_strcpy_StateVarName(variable, var_name); UpnpStateVarRequest_set_CtrlPtIPAddr(variable, &info->foreign_sockaddr); + /* send event */ - soap_event_callback(UPNP_CONTROL_GET_VAR_REQUEST, variable, cookie); + soap_info->callback(UPNP_CONTROL_GET_VAR_REQUEST, variable, + soap_info->cookie); UpnpPrintf(UPNP_INFO, SOAP, __FILE__, __LINE__, "Return from callback for var request\n"); /* validate, and handle result */ @@ -725,8 +337,9 @@ static UPNP_INLINE void handle_query_variable( } /* send response */ send_var_query_response(info, UpnpStateVarRequest_get_CurrentVal(variable), request); + err_code = 0; - /* error handling and cleanup */ + /* error handling and cleanup */ error_handler: UpnpStateVarRequest_delete(variable); if (err_code != 0) @@ -734,59 +347,59 @@ error_handler: } /*! - * \brief Handles the SOAP action request. It checks the integrity of the SOAP - * action request and gives the call back to the device application. + * \brief Handles the SOAP action request. */ static void handle_invoke_action( - /*! [in] Socket info. */ - IN SOCKINFO *info, - /*! [in] HTTP Request. */ - IN http_message_t *request, - /*! [in] Name of the SOAP Action. */ - IN memptr action_name, - /*! [in] Document containing the SOAP action request. */ - IN IXML_Document *xml_doc) + /*! [in] Socket info. */ + SOCKINFO *info, + /*! [in] HTTP Request. */ + http_message_t *request, + /*! [in] SOAP device/service information. */ + soap_devserv_t *soap_info, + /*! [in] Node containing the SOAP action request. */ + IXML_Node *req_node) { char save_char; UpnpActionRequest *action = UpnpActionRequest_new(); - UpnpString *devUDN = UpnpString_new(); - UpnpString *serviceID = UpnpString_new(); IXML_Document *actionRequestDoc = NULL; IXML_Document *actionResultDoc = NULL; - Upnp_FunPtr soap_event_callback; - void *cookie = NULL; int err_code; const char *err_str; + memptr action_name; + DOMString act_node = NULL; /* null-terminate */ + action_name = soap_info->action_name; save_char = action_name.buf[action_name.length]; action_name.buf[action_name.length] = '\0'; - /* set default error */ - err_code = SOAP_INVALID_ACTION; - err_str = Soap_Invalid_Action; /* get action node */ - if (get_action_node(xml_doc, action_name.buf, &actionRequestDoc) == -1) + act_node = ixmlPrintNode(req_node); + if (!act_node) { + err_code = SOAP_MEMORY_OUT; + err_str = Soap_Memory_out; goto error_handler; - /* get device info for action event */ - err_code = get_device_info(request, - 0, - xml_doc, - info->foreign_sockaddr.ss_family, - devUDN, - serviceID, - &soap_event_callback, &cookie); - - if (err_code != UPNP_E_SUCCESS) + } + err_code = ixmlParseBufferEx(act_node, &actionRequestDoc); + if (err_code != IXML_SUCCESS) { + if (IXML_INSUFFICIENT_MEMORY == err_code) { + err_code = SOAP_MEMORY_OUT; + err_str = Soap_Memory_out; + } else { + err_code = SOAP_INVALID_ACTION; + err_str = Soap_Invalid_Action; + } goto error_handler; + } UpnpActionRequest_set_ErrCode(action, UPNP_E_SUCCESS); - UpnpActionRequest_strcpy_ErrStr(action, ""); UpnpActionRequest_strcpy_ActionName(action, action_name.buf); - UpnpActionRequest_set_DevUDN(action, devUDN); - UpnpActionRequest_set_ServiceID(action, serviceID); + UpnpActionRequest_strcpy_DevUDN(action, soap_info->dev_udn); + UpnpActionRequest_strcpy_ServiceID(action, soap_info->service_id); UpnpActionRequest_set_ActionRequest(action, actionRequestDoc); + UpnpActionRequest_set_ActionResult(action, NULL); UpnpActionRequest_set_CtrlPtIPAddr(action, &info->foreign_sockaddr); + UpnpPrintf(UPNP_INFO, SOAP, __FILE__, __LINE__, "Calling Callback\n"); - soap_event_callback(UPNP_CONTROL_ACTION_REQUEST, action, cookie); + soap_info->callback(UPNP_CONTROL_ACTION_REQUEST, action, soap_info->cookie); err_code = UpnpActionRequest_get_ErrCode(action); if (err_code != UPNP_E_SUCCESS) { err_str = UpnpActionRequest_get_ErrStr_cstr(action); @@ -809,19 +422,281 @@ static void handle_invoke_action( /* error handling and cleanup */ error_handler: + ixmlDocument_free(actionResultDoc); + ixmlDocument_free(actionRequestDoc); + ixmlFreeDOMString(act_node); /* restore */ action_name.buf[action_name.length] = save_char; if (err_code != 0) send_error_response(info, err_code, err_str, request); - UpnpString_delete(serviceID); - UpnpString_delete(devUDN); UpnpActionRequest_delete(action); } +/*! + * \brief Retrieve SOAP device/service information associated + * with request-URI, which includes the callback function to hand-over + * the request to the device application. + * + * \return 0 if OK, -1 on error. + */ +static int get_dev_service( + /*! [in] HTTP request. */ + http_message_t *request, + /*! [in] Address family: AF_INET or AF_INET6. */ + int AddressFamily, + /*! [out] SOAP device/service information. */ + soap_devserv_t *soap_info) +{ + struct Handle_Info *device_info; + int device_hnd; + service_info *serv_info; + char save_char; + /* error by default */ + int ret_code = -1; + const char *control_url; + + /* null-terminate pathquery of url */ + control_url = request->uri.pathquery.buff; + save_char = control_url[request->uri.pathquery.size]; + ((char *)control_url)[request->uri.pathquery.size] = '\0'; + + HandleReadLock(); + + if (GetDeviceHandleInfo(AddressFamily, &device_hnd, + &device_info) != HND_DEVICE) + goto error_handler; + serv_info = FindServiceControlURLPath( + &device_info->ServiceTable, control_url); + if (!serv_info) + goto error_handler; + + namecopy(soap_info->dev_udn, serv_info->UDN); + namecopy(soap_info->service_type, serv_info->serviceType); + namecopy(soap_info->service_id, serv_info->serviceId); + soap_info->callback = device_info->Callback; + soap_info->cookie = device_info->Cookie; + ret_code = 0; + + error_handler: + /* restore */ + ((char *)control_url)[request->uri.pathquery.size] = save_char; + HandleUnlock(); + return ret_code; +} + +/*! + * \brief Get the SOAPACTION header value for M-POST request. + * + * \return UPNP_E_SUCCESS if OK, error number on failure. + */ +static int get_mpost_acton_hdrval( + /*! [in] HTTP request. */ + http_message_t *request, + /*! [out] Buffer to get the header value */ + memptr *val) +{ + http_header_t *hdr; + memptr ns_value, dummy_quote, value; + membuffer soap_action_name; + + assert(HTTPMETHOD_MPOST == request->method); + hdr = httpmsg_find_hdr(request, HDR_MAN, &value); + if (NULL == hdr) + return SREQ_NOT_EXTENDED; + if (matchstr(value.buf, value.length, "%q%i ; ns = %s", + &dummy_quote, &ns_value) != PARSE_OK) + return SREQ_NOT_EXTENDED; + /* 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 (NULL == hdr) + return SREQ_HDR_NOT_FOUND; + val->buf = hdr->value.buf; + val->length = hdr->value.length; + return UPNP_E_SUCCESS; +} + +/*! + * \brief Check the header validity, and get the action name + * and the version of the service that the CP wants to use. + * + * \return UPNP_E_SUCCESS if OK, error number on failure. + */ +static int check_soapaction_hdr( + /*! [in] HTTP request. */ + http_message_t *request, + /*! [in, out] SOAP device/service information. */ + soap_devserv_t *soap_info) +{ + memptr value; + char save_char; + char *hash_pos = NULL; + char *col_pos1, *col_pos2, *serv_type; + int ret_code; + + /* find SOAPACTION header */ + if (SOAPMETHOD_POST == request->method) { + if (!httpmsg_find_hdr(request, HDR_SOAPACTION, &value)) + return SREQ_HDR_NOT_FOUND; + } else { + ret_code = get_mpost_acton_hdrval(request, &value); + if (ret_code != UPNP_E_SUCCESS) { + return ret_code; + } + } + + /* error by default */ + ret_code = SREQ_BAD_HDR_FORMAT; + /* get action name*/ + save_char = value.buf[value.length]; + value.buf[value.length] = '\0'; + hash_pos = strchr(value.buf, '#'); + if (NULL == hash_pos) { + goto error_handler; + } + *hash_pos = '\0'; + if (matchstr(hash_pos+1, + value.length - (size_t)(hash_pos+1 - value.buf), + "%s", &soap_info->action_name) != PARSE_OK) { + goto error_handler; + } + + /* check service type */ + if (value.buf[0] != '\"') { + goto error_handler; + } + serv_type = &value.buf[1]; + col_pos1 = strrchr(serv_type, ':'); + if (NULL == col_pos1) { + goto error_handler; + } + col_pos2 = strrchr(soap_info->service_type, ':'); + /* XXX: this should be checked when service list is generated */ + assert(col_pos2 != NULL); + if (col_pos2-soap_info->service_type == col_pos1-serv_type && + strncmp(soap_info->service_type, serv_type, col_pos1-serv_type) == 0) { + /* for action invocation, update the version information */ + namecopy(soap_info->service_type, serv_type); + } else if (strcmp(serv_type, QUERY_STATE_VAR_URN) == 0 && + memptr_cmp(&soap_info->action_name, "QueryStateVariable") == 0) { + /* query variable */ + soap_info->action_name.buf = NULL; + soap_info->action_name.length = 0; + } else { + goto error_handler; + } + ret_code = UPNP_E_SUCCESS; + +error_handler: + if (hash_pos != NULL) { + *hash_pos = '#'; + } + value.buf[value.length] = save_char; + return ret_code; +} + + +/*! + * \brief Check validity of the SOAP request per UPnP specification. + * + * \return 0 if OK, -1 on failure. + */ +static int check_soap_request( + /*! [in] SOAP device/service information. */ + soap_devserv_t *soap_info, + /*! [in] Document containing the SOAP action request. */ + IN IXML_Document *xml_doc, + /*! [out] Node containing the SOAP action request/variable name. */ + IXML_Node **req_node) +{ + IXML_Node *envp_node = NULL; + IXML_Node *body_node = NULL; + IXML_Node *action_node = NULL; + const DOMString local_name = NULL; + const DOMString ns_uri = NULL; + int ret_val = -1; + + /* Got the Envelop node here */ + envp_node = ixmlNode_getFirstChild((IXML_Node *) xml_doc); + if (NULL == envp_node) { + goto error_handler; + } + ns_uri = ixmlNode_getNamespaceURI(envp_node); + if (NULL == ns_uri || strcmp(ns_uri, SOAP_URN) != 0) { + goto error_handler; + } + /* Got Body here */ + body_node = ixmlNode_getFirstChild(envp_node); + if (NULL == body_node) { + goto error_handler; + } + local_name = ixmlNode_getLocalName(body_node); + if (NULL == local_name || strcmp(local_name, SOAP_BODY) != 0) { + goto error_handler; + } + /* Got action node here */ + action_node = ixmlNode_getFirstChild(body_node); + if (NULL == action_node) { + goto error_handler; + } + /* check local name and namespace of action node */ + ns_uri = ixmlNode_getNamespaceURI(action_node); + if (NULL == ns_uri) { + goto error_handler; + } + local_name = ixmlNode_getLocalName(action_node); + if (NULL == local_name) { + goto error_handler; + } + if (NULL == soap_info->action_name.buf) { + IXML_Node *varname_node = NULL; + IXML_Node *nametxt_node = NULL; + if (strcmp(ns_uri, QUERY_STATE_VAR_URN) != 0 || + strcmp(local_name, "QueryStateVariable") != 0) { + goto error_handler; + } + varname_node = ixmlNode_getFirstChild(action_node); + if(NULL == varname_node) { + goto error_handler; + } + local_name = ixmlNode_getLocalName(varname_node); + if (strcmp(local_name, "varName") != 0) { + goto error_handler; + } + nametxt_node = ixmlNode_getFirstChild(varname_node); + if (NULL == nametxt_node || + ixmlNode_getNodeType(nametxt_node) != eTEXT_NODE) { + goto error_handler; + } + *req_node = nametxt_node; + } else { + /* check service type against SOAPACTION header */ + if (strcmp(soap_info->service_type, ns_uri) != 0 || + memptr_cmp(&soap_info->action_name, local_name) != 0) { + goto error_handler; + } + *req_node = action_node; + } + /* success */ + ret_val = 0; + +error_handler: + return ret_val; +} + + /*! * \brief This is a callback called by minisever after receiving the request - * from the control point. This function will start processing the request. - * It calls handle_invoke_action to handle the SOAP action. + * from the control point. After HTTP processing, it calls handle_soap_request + * to start SOAP processing. */ void soap_device_callback( /*! [in] Parsed request received by the device. */ @@ -832,43 +707,74 @@ void soap_device_callback( SOCKINFO *info) { int err_code; - const char *err_str; - memptr action_name; IXML_Document *xml_doc = NULL; + soap_devserv_t *soap_info = NULL; + IXML_Node *req_node = NULL; - /* set default error */ - err_code = SOAP_INVALID_ACTION; - err_str = Soap_Invalid_Action; - + /* get device/service identified by the request-URI */ + soap_info = malloc(sizeof(soap_devserv_t)); + if (NULL == soap_info) { + err_code = HTTP_INTERNAL_SERVER_ERROR; + goto error_handler; + } + if (get_dev_service(request, + info->foreign_sockaddr.ss_family, soap_info) < 0) { + err_code = HTTP_NOT_FOUND; + goto error_handler; + } /* validate: content-type == text/xml */ - if (!has_xml_content_type(request)) + if (!has_xml_content_type(request)) { + err_code = HTTP_UNSUPPORTED_MEDIA_TYPE; goto error_handler; - /* type of request */ - if (get_request_type(request, &action_name) != 0) + } + /* check SOAPACTION HTTP header */ + err_code = check_soapaction_hdr(request, soap_info); + if (err_code != UPNP_E_SUCCESS) { + switch (err_code) { + case SREQ_NOT_EXTENDED: + err_code = HTTP_NOT_EXTENDED; + break; + case UPNP_E_OUTOF_MEMORY: + err_code = HTTP_INTERNAL_SERVER_ERROR; + break; + default: + err_code = HTTP_BAD_REQUEST; + break; + } goto error_handler; + } /* parse XML */ err_code = ixmlParseBufferEx(request->entity.buf, &xml_doc); if (err_code != IXML_SUCCESS) { - if (err_code == IXML_INSUFFICIENT_MEMORY) - err_code = UPNP_E_OUTOF_MEMORY; + if (IXML_INSUFFICIENT_MEMORY == err_code) + err_code = HTTP_INTERNAL_SERVER_ERROR; else - err_code = SOAP_ACTION_FAILED; - err_str = "XML error"; + err_code = HTTP_BAD_REQUEST; goto error_handler; } - if (action_name.length == 0) + /* check SOAP body */ + if (check_soap_request(soap_info, xml_doc, &req_node) < 0) + { + err_code = HTTP_BAD_REQUEST; + goto error_handler; + } + /* process SOAP request */ + if (NULL == soap_info->action_name.buf) /* query var */ - handle_query_variable(info, request, xml_doc); + handle_query_variable(info, request, soap_info, req_node); else /* invoke action */ - handle_invoke_action(info, request, action_name, xml_doc); - /* no error */ - err_code = 0; + handle_invoke_action(info, request, soap_info, req_node); - error_handler: + err_code = HTTP_OK; + +error_handler: ixmlDocument_free(xml_doc); - if (err_code != 0) - send_error_response(info, err_code, err_str, request); + free(soap_info); + if (err_code != HTTP_OK) { + http_SendStatusResponse(info, err_code, request->major_version, + request->minor_version); + } return; parser = parser; }