wsdapi: Add support for writing XML attributes.
[wine.git] / dlls / wsdapi / soap.c
blob2bfb16042c292360b4ce5257ddabff820a078c84
1 /*
2 * Web Services on Devices
4 * Copyright 2017-2018 Owen Rudge for CodeWeavers
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include <stdarg.h>
22 #include <limits.h>
24 #define COBJMACROS
26 #include "wsdapi_internal.h"
27 #include "wine/debug.h"
28 #include "wine/heap.h"
29 #include "webservices.h"
31 WINE_DEFAULT_DEBUG_CHANNEL(wsdapi);
33 #define APP_MAX_DELAY 500
35 static const WCHAR discoveryTo[] = {
36 'u','r','n',':',
37 's','c','h','e','m','a','s','-','x','m','l','s','o','a','p','-','o','r','g',':',
38 'w','s',':','2','0','0','5',':','0','4',':',
39 'd','i','s','c','o','v','e','r','y', 0 };
41 static const WCHAR actionHello[] = {
42 'h','t','t','p',':','/','/',
43 's','c','h','e','m','a','s','.','x','m','l','s','o','a','p','.','o','r','g','/',
44 'w','s','/','2','0','0','5','/','0','4','/',
45 'd','i','s','c','o','v','e','r','y','/',
46 'H','e','l','l','o', 0 };
48 static const WCHAR addressingNsUri[] = {
49 'h','t','t','p',':','/','/',
50 's','c','h','e','m','a','s','.','x','m','l','s','o','a','p','.','o','r','g','/',
51 'w','s','/','2','0','0','4','/','0','8','/','a','d','d','r','e','s','s','i','n','g', 0 };
53 static const WCHAR discoveryNsUri[] = {
54 'h','t','t','p',':','/','/',
55 's','c','h','e','m','a','s','.','x','m','l','s','o','a','p','.','o','r','g','/',
56 'w','s','/','2','0','0','5','/','0','4','/','d','i','s','c','o','v','e','r','y', 0 };
58 static const WCHAR envelopeNsUri[] = {
59 'h','t','t','p',':','/','/',
60 'w','w','w','.','w','3','.','o','r','g','/',
61 '2','0','0','3','/','0','5','/','s','o','a','p','-','e','n','v','e','l','o','p','e', 0 };
63 static const WCHAR addressingPrefix[] = { 'w','s','a', 0 };
64 static const WCHAR discoveryPrefix[] = { 'w','s','d', 0 };
65 static const WCHAR envelopePrefix[] = { 's','o','a','p', 0 };
66 static const WCHAR headerString[] = { 'H','e','a','d','e','r', 0 };
67 static const WCHAR actionString[] = { 'A','c','t','i','o','n', 0 };
68 static const WCHAR messageIdString[] = { 'M','e','s','s','a','g','e','I','D', 0 };
69 static const WCHAR toString[] = { 'T','o', 0 };
70 static const WCHAR relatesToString[] = { 'R','e','l','a','t','e','s','T','o', 0 };
71 static const WCHAR appSequenceString[] = { 'A','p','p','S','e','q','u','e','n','c','e', 0 };
72 static const WCHAR emptyString[] = { 0 };
74 struct discovered_namespace
76 struct list entry;
77 LPCWSTR prefix;
78 LPCWSTR uri;
81 static char *wide_to_utf8(LPCWSTR wide_string, int *length)
83 char *new_string = NULL;
85 if (wide_string == NULL)
86 return NULL;
88 *length = WideCharToMultiByte(CP_UTF8, 0, wide_string, -1, NULL, 0, NULL, NULL);
90 if (*length < 0)
91 return NULL;
93 new_string = heap_alloc(*length);
94 WideCharToMultiByte(CP_UTF8, 0, wide_string, -1, new_string, *length, NULL, NULL);
96 return new_string;
99 static WS_XML_STRING *populate_xml_string(LPCWSTR str)
101 WS_XML_STRING *xml = heap_alloc_zero(sizeof(WS_XML_STRING));
102 int utf8Length;
104 if (xml == NULL)
105 return NULL;
107 xml->bytes = (BYTE *)wide_to_utf8(str, &utf8Length);
109 if (xml->bytes == NULL)
111 heap_free(xml);
112 return NULL;
115 xml->dictionary = NULL;
116 xml->id = 0;
117 xml->length = (xml->bytes == NULL) ? 0 : (utf8Length - 1);
119 return xml;
122 static inline void free_xml_string(WS_XML_STRING *value)
124 if (value == NULL)
125 return;
127 if (value->bytes != NULL)
128 heap_free(value->bytes);
130 heap_free(value);
133 static HRESULT write_xml_attribute(WSDXML_ATTRIBUTE *attribute, WS_XML_WRITER *writer)
135 WS_XML_STRING *local_name = NULL, *element_ns = NULL, *ns_prefix = NULL;
136 WS_XML_UTF16_TEXT utf16_text;
137 HRESULT ret = E_OUTOFMEMORY;
138 int text_len;
140 if (attribute == NULL)
141 return S_OK;
143 /* Start the attribute */
144 local_name = populate_xml_string(attribute->Name->LocalName);
145 if (local_name == NULL) goto cleanup;
147 if (attribute->Name->Space == NULL)
149 element_ns = populate_xml_string(emptyString);
150 if (element_ns == NULL) goto cleanup;
152 ns_prefix = NULL;
154 else
156 element_ns = populate_xml_string(attribute->Name->Space->Uri);
157 if (element_ns == NULL) goto cleanup;
159 ns_prefix = populate_xml_string(attribute->Name->Space->PreferredPrefix);
160 if (ns_prefix == NULL) goto cleanup;
163 ret = WsWriteStartAttribute(writer, ns_prefix, local_name, element_ns, FALSE, NULL);
164 if (FAILED(ret)) goto cleanup;
166 text_len = lstrlenW(attribute->Value);
168 utf16_text.text.textType = WS_XML_TEXT_TYPE_UTF16;
169 utf16_text.bytes = (BYTE *)attribute->Value;
170 utf16_text.byteCount = min(WSD_MAX_TEXT_LENGTH, text_len) * sizeof(WCHAR);
172 ret = WsWriteText(writer, (WS_XML_TEXT *)&utf16_text, NULL);
173 if (FAILED(ret)) goto cleanup;
175 ret = WsWriteEndAttribute(writer, NULL);
176 if (FAILED(ret)) goto cleanup;
178 cleanup:
179 free_xml_string(local_name);
180 free_xml_string(element_ns);
181 free_xml_string(ns_prefix);
183 return ret;
186 static HRESULT write_xml_element(WSDXML_ELEMENT *element, WS_XML_WRITER *writer)
188 WS_XML_STRING *local_name = NULL, *element_ns = NULL, *ns_prefix = NULL;
189 WSDXML_ATTRIBUTE *current_attribute;
190 WS_XML_UTF16_TEXT utf16_text;
191 WSDXML_NODE *current_child;
192 WSDXML_TEXT *node_as_text;
193 int text_len;
194 HRESULT ret = E_OUTOFMEMORY;
196 if (element == NULL)
197 return S_OK;
199 /* Start the element */
200 local_name = populate_xml_string(element->Name->LocalName);
201 if (local_name == NULL) goto cleanup;
203 element_ns = populate_xml_string(element->Name->Space->Uri);
204 if (element_ns == NULL) goto cleanup;
206 ns_prefix = populate_xml_string(element->Name->Space->PreferredPrefix);
207 if (ns_prefix == NULL) goto cleanup;
209 ret = WsWriteStartElement(writer, ns_prefix, local_name, element_ns, NULL);
210 if (FAILED(ret)) goto cleanup;
212 /* Write attributes */
213 current_attribute = element->FirstAttribute;
215 while (current_attribute != NULL)
217 ret = write_xml_attribute(current_attribute, writer);
218 if (FAILED(ret)) goto cleanup;
219 current_attribute = current_attribute->Next;
222 /* Write child elements */
223 current_child = element->FirstChild;
225 while (current_child != NULL)
227 if (current_child->Type == ElementType)
229 ret = write_xml_element((WSDXML_ELEMENT *)current_child, writer);
230 if (FAILED(ret)) goto cleanup;
232 else if (current_child->Type == TextType)
234 node_as_text = (WSDXML_TEXT *)current_child;
235 text_len = lstrlenW(node_as_text->Text);
237 utf16_text.text.textType = WS_XML_TEXT_TYPE_UTF16;
238 utf16_text.byteCount = min(WSD_MAX_TEXT_LENGTH, text_len) * sizeof(WCHAR);
239 utf16_text.bytes = (BYTE *)node_as_text->Text;
241 ret = WsWriteText(writer, (WS_XML_TEXT *)&utf16_text, NULL);
242 if (FAILED(ret)) goto cleanup;
245 current_child = current_child->Next;
248 /* End the element */
249 ret = WsWriteEndElement(writer, NULL);
251 cleanup:
252 free_xml_string(local_name);
253 free_xml_string(element_ns);
254 free_xml_string(ns_prefix);
256 return ret;
259 static WSDXML_ELEMENT *add_child_element(IWSDXMLContext *xml_context, WSDXML_ELEMENT *parent, LPCWSTR ns_uri,
260 LPCWSTR name, LPCWSTR text)
262 WSDXML_ELEMENT *element_obj;
263 WSDXML_NAME *name_obj;
265 if (FAILED(IWSDXMLContext_AddNameToNamespace(xml_context, ns_uri, name, &name_obj)))
266 return NULL;
268 if (FAILED(WSDXMLBuildAnyForSingleElement(name_obj, text, &element_obj)))
270 WSDFreeLinkedMemory(name_obj);
271 return NULL;
274 WSDFreeLinkedMemory(name_obj);
276 /* Add the element as a child - this will link the element's memory allocation to the parent's */
277 if (FAILED(WSDXMLAddChild(parent, element_obj)))
279 WSDFreeLinkedMemory(element_obj);
280 return NULL;
283 return element_obj;
286 static BOOL create_guid(LPWSTR buffer)
288 const WCHAR formatString[] = { 'u','r','n',':','u','u','i','d',':','%','s', 0 };
290 WCHAR* uuidString = NULL;
291 UUID uuid;
293 if (UuidCreate(&uuid) != RPC_S_OK)
294 return FALSE;
296 UuidToStringW(&uuid, (RPC_WSTR*)&uuidString);
298 if (uuidString == NULL)
299 return FALSE;
301 wsprintfW(buffer, formatString, uuidString);
302 RpcStringFreeW((RPC_WSTR*)&uuidString);
304 return TRUE;
307 static void populate_soap_header(WSD_SOAP_HEADER *header, LPCWSTR to, LPCWSTR action, LPCWSTR message_id,
308 WSD_APP_SEQUENCE *sequence, const WSDXML_ELEMENT *any_headers)
310 ZeroMemory(header, sizeof(WSD_SOAP_HEADER));
312 header->To = to;
313 header->Action = action;
314 header->MessageID = message_id;
315 header->AppSequence = sequence;
316 header->AnyHeaders = (WSDXML_ELEMENT *)any_headers;
318 /* TODO: Implement RelatesTo, ReplyTo, From, FaultTo */
321 static BOOL add_discovered_namespace(struct list *namespaces, WSDXML_NAMESPACE *discovered_ns)
323 struct discovered_namespace *ns;
325 LIST_FOR_EACH_ENTRY(ns, namespaces, struct discovered_namespace, entry)
327 if (lstrcmpW(ns->uri, discovered_ns->Uri) == 0)
328 return TRUE; /* Already added */
331 ns = WSDAllocateLinkedMemory(namespaces, sizeof(struct discovered_namespace));
333 if (ns == NULL)
334 return FALSE;
336 ns->prefix = duplicate_string(ns, discovered_ns->PreferredPrefix);
337 ns->uri = duplicate_string(ns, discovered_ns->Uri);
339 if ((ns->prefix == NULL) || (ns->uri == NULL))
340 return FALSE;
342 list_add_tail(namespaces, &ns->entry);
343 return TRUE;
346 static WSDXML_ELEMENT *create_soap_header_xml_elements(IWSDXMLContext *xml_context, WSD_SOAP_HEADER *header)
348 WSDXML_ELEMENT *header_element = NULL, *app_sequence_element = NULL;
349 WSDXML_NAME *header_name = NULL;
351 /* <s:Header> */
352 if (FAILED(IWSDXMLContext_AddNameToNamespace(xml_context, envelopeNsUri, headerString, &header_name))) goto cleanup;
353 if (FAILED(WSDXMLBuildAnyForSingleElement(header_name, NULL, &header_element))) goto cleanup;
354 WSDFreeLinkedMemory(header_name);
356 /* <a:Action> */
357 if (add_child_element(xml_context, header_element, addressingNsUri, actionString, header->Action) == NULL)
358 goto cleanup;
360 /* <a:MessageId> */
361 if (add_child_element(xml_context, header_element, addressingNsUri, messageIdString, header->MessageID) == NULL)
362 goto cleanup;
364 /* <a:To> */
365 if (add_child_element(xml_context, header_element, addressingNsUri, toString, header->To) == NULL)
366 goto cleanup;
368 /* <a:RelatesTo> */
369 if (header->RelatesTo.MessageID != NULL)
371 if (add_child_element(xml_context, header_element, addressingNsUri, relatesToString,
372 header->RelatesTo.MessageID) == NULL) goto cleanup;
375 /* <d:AppSequence> */
376 app_sequence_element = add_child_element(xml_context, header_element, discoveryNsUri, appSequenceString, emptyString);
377 if (app_sequence_element == NULL) goto cleanup;
379 /* TODO: InstanceId attribute */
381 /* TODO: MessageNumber attribute */
383 /* TODO: SequenceID attribute */
385 /* </d:AppSequence> */
387 /* TODO: Write any headers */
389 /* </s:Header> */
391 return header_element;
393 cleanup:
394 if (header_name != NULL) WSDFreeLinkedMemory(header_name);
395 WSDXMLCleanupElement(header_element);
397 return NULL;
400 static HRESULT create_soap_envelope(IWSDXMLContext *xml_context, WSD_SOAP_HEADER *header, WSDXML_ELEMENT *body_element,
401 WS_HEAP **heap, char **output_xml, ULONG *xml_length, struct list *discovered_namespaces)
403 WS_XML_STRING *actual_envelope_prefix = NULL, *envelope_uri_xmlstr = NULL, *tmp_prefix = NULL, *tmp_uri = NULL;
404 WSDXML_NAMESPACE *addressing_ns = NULL, *discovery_ns = NULL, *envelope_ns = NULL;
405 WSDXML_ELEMENT *header_element = NULL;
406 struct discovered_namespace *ns;
407 WS_XML_BUFFER *buffer = NULL;
408 WS_XML_WRITER *writer = NULL;
409 WS_XML_STRING envelope;
410 HRESULT ret = E_OUTOFMEMORY;
411 static BYTE envelopeString[] = "Envelope";
413 /* Create the necessary XML prefixes */
414 if (FAILED(IWSDXMLContext_AddNamespace(xml_context, addressingNsUri, addressingPrefix, &addressing_ns))) goto cleanup;
415 if (!add_discovered_namespace(discovered_namespaces, addressing_ns)) goto cleanup;
417 if (FAILED(IWSDXMLContext_AddNamespace(xml_context, discoveryNsUri, discoveryPrefix, &discovery_ns))) goto cleanup;
418 if (!add_discovered_namespace(discovered_namespaces, discovery_ns)) goto cleanup;
420 if (FAILED(IWSDXMLContext_AddNamespace(xml_context, envelopeNsUri, envelopePrefix, &envelope_ns))) goto cleanup;
421 if (!add_discovered_namespace(discovered_namespaces, envelope_ns)) goto cleanup;
423 envelope.bytes = envelopeString;
424 envelope.length = sizeof(envelopeString) - 1;
425 envelope.dictionary = NULL;
426 envelope.id = 0;
428 actual_envelope_prefix = populate_xml_string(envelope_ns->PreferredPrefix);
429 envelope_uri_xmlstr = populate_xml_string(envelope_ns->Uri);
431 if ((actual_envelope_prefix == NULL) || (envelope_uri_xmlstr == NULL)) goto cleanup;
433 /* Now try to create the appropriate WebServices buffers, etc */
434 ret = WsCreateHeap(16384, 4096, NULL, 0, heap, NULL);
435 if (FAILED(ret)) goto cleanup;
437 ret = WsCreateXmlBuffer(*heap, NULL, 0, &buffer, NULL);
438 if (FAILED(ret)) goto cleanup;
440 ret = WsCreateWriter(NULL, 0, &writer, NULL);
441 if (FAILED(ret)) goto cleanup;
443 ret = WsSetOutputToBuffer(writer, buffer, NULL, 0, NULL);
444 if (FAILED(ret)) goto cleanup;
446 /* Create the header XML elements */
447 header_element = create_soap_header_xml_elements(xml_context, header);
448 if (header_element == NULL) goto cleanup;
450 /* <s:Envelope> */
451 ret = WsWriteStartElement(writer, actual_envelope_prefix, &envelope, envelope_uri_xmlstr, NULL);
452 if (FAILED(ret)) goto cleanup;
454 LIST_FOR_EACH_ENTRY(ns, discovered_namespaces, struct discovered_namespace, entry)
456 tmp_prefix = populate_xml_string(ns->prefix);
457 tmp_uri = populate_xml_string(ns->uri);
459 if ((tmp_prefix == NULL) || (tmp_uri == NULL)) goto cleanup;
461 ret = WsWriteXmlnsAttribute(writer, tmp_prefix, tmp_uri, FALSE, NULL);
462 if (FAILED(ret)) goto cleanup;
464 free_xml_string(tmp_prefix);
465 free_xml_string(tmp_uri);
468 tmp_prefix = NULL;
469 tmp_uri = NULL;
471 /* Write the header */
472 ret = write_xml_element(header_element, writer);
473 if (FAILED(ret)) goto cleanup;
475 ret = WsWriteEndElement(writer, NULL);
476 if (FAILED(ret)) goto cleanup;
478 /* </s:Envelope> */
480 /* Generate the bytes of the document */
481 ret = WsWriteXmlBufferToBytes(writer, buffer, NULL, NULL, 0, *heap, (void**)output_xml, xml_length, NULL);
482 if (FAILED(ret)) goto cleanup;
484 cleanup:
485 WSDFreeLinkedMemory(addressing_ns);
486 WSDFreeLinkedMemory(discovery_ns);
487 WSDFreeLinkedMemory(envelope_ns);
489 WSDXMLCleanupElement(header_element);
491 free_xml_string(actual_envelope_prefix);
492 free_xml_string(envelope_uri_xmlstr);
494 if (writer != NULL)
495 WsFreeWriter(writer);
497 /* Don't free the heap unless the operation has failed */
498 if ((FAILED(ret)) && (*heap != NULL))
500 WsFreeHeap(*heap);
501 *heap = NULL;
504 return ret;
507 static HRESULT write_and_send_message(IWSDiscoveryPublisherImpl *impl, WSD_SOAP_HEADER *header, WSDXML_ELEMENT *body_element,
508 struct list *discovered_namespaces, IWSDUdpAddress *remote_address, int max_initial_delay)
510 static const char xml_header[] = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
511 ULONG xml_length = 0, xml_header_len = sizeof(xml_header) - 1;
512 WS_HEAP *heap = NULL;
513 char *xml = NULL;
514 char *full_xml;
515 HRESULT ret;
517 ret = create_soap_envelope(impl->xmlContext, header, NULL, &heap, &xml, &xml_length, discovered_namespaces);
518 if (ret != S_OK) return ret;
520 /* Prefix the XML header */
521 full_xml = heap_alloc(xml_length + xml_header_len + 1);
523 if (full_xml == NULL)
525 WsFreeHeap(heap);
526 return E_OUTOFMEMORY;
529 memcpy(full_xml, xml_header, xml_header_len);
530 memcpy(full_xml + xml_header_len, xml, xml_length);
531 full_xml[xml_length + xml_header_len] = 0;
533 if (remote_address == NULL)
535 /* Send the message via UDP multicast */
536 ret = send_udp_multicast(impl, full_xml, xml_length + xml_header_len + 1, max_initial_delay) ? S_OK : E_FAIL;
538 else
540 /* TODO: Send the message via UDP unicast */
541 FIXME("TODO: Send the message via UDP unicast\n");
544 heap_free(full_xml);
545 WsFreeHeap(heap);
547 return ret;
550 HRESULT send_hello_message(IWSDiscoveryPublisherImpl *impl, LPCWSTR id, ULONGLONG metadata_ver, ULONGLONG instance_id,
551 ULONGLONG msg_num, LPCWSTR session_id, const WSD_NAME_LIST *types_list, const WSD_URI_LIST *scopes_list,
552 const WSD_URI_LIST *xaddrs_list, const WSDXML_ELEMENT *hdr_any, const WSDXML_ELEMENT *ref_param_any,
553 const WSDXML_ELEMENT *endpoint_ref_any, const WSDXML_ELEMENT *any)
555 struct list *discoveredNamespaces = NULL;
556 WSD_SOAP_HEADER soapHeader;
557 WSD_APP_SEQUENCE sequence;
558 WCHAR message_id[64];
559 HRESULT ret = E_OUTOFMEMORY;
561 sequence.InstanceId = instance_id;
562 sequence.MessageNumber = msg_num;
563 sequence.SequenceId = session_id;
565 if (!create_guid(message_id)) goto cleanup;
567 discoveredNamespaces = WSDAllocateLinkedMemory(NULL, sizeof(struct list));
568 if (!discoveredNamespaces) goto cleanup;
570 list_init(discoveredNamespaces);
572 populate_soap_header(&soapHeader, discoveryTo, actionHello, message_id, &sequence, hdr_any);
574 /* TODO: Populate message body */
576 /* Write and send the message */
577 ret = write_and_send_message(impl, &soapHeader, NULL, discoveredNamespaces, NULL, APP_MAX_DELAY);
579 cleanup:
580 WSDFreeLinkedMemory(discoveredNamespaces);
582 return ret;