hlink: Site data should only be set if the hlink has an HlinkSite.
[wine] / dlls / msxml3 / schema.c
1 /*
2  * Schema cache implementation
3  *
4  * Copyright 2007 Huw Davies
5  * Copyright 2010 Adam Martinson for CodeWeavers
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #define COBJMACROS
23
24 #include "config.h"
25
26 #include <stdarg.h>
27 #include <assert.h>
28 #include "windef.h"
29 #include "winbase.h"
30 #include "winuser.h"
31 #include "ole2.h"
32 #include "msxml6.h"
33
34 #include "wine/debug.h"
35
36 #include "msxml_private.h"
37
38 WINE_DEFAULT_DEBUG_CHANNEL(msxml);
39
40 /* We use a chained hashtable, which can hold any number of schemas
41  * TODO: XDR schema support
42  * TODO: grow/shrink hashtable depending on load factor
43  * TODO: implement read-only where appropriate
44  */
45
46 /* This is just the number of buckets, should be prime */
47 #define DEFAULT_HASHTABLE_SIZE 31
48
49 #ifdef HAVE_LIBXML2
50
51 #include <libxml/tree.h>
52 #include <libxml/xmlschemas.h>
53 #include <libxml/schemasInternals.h>
54 #include <libxml/hash.h>
55
56 static const xmlChar XSD_schema[] = "schema";
57 static const xmlChar XSD_nsURI[] = "http://www.w3.org/2001/XMLSchema";
58 static const xmlChar XDR_schema[] = "Schema";
59 static const xmlChar XDR_nsURI[] = "urn:schemas-microsoft-com:xml-data";
60
61 /* Supported Types:
62  * msxml3 - XDR only
63  * msxml4 - XDR & XSD
64  * msxml5 - XDR & XSD
65  * mxsml6 - XSD only
66  */
67 typedef enum _SCHEMA_TYPE {
68     SCHEMA_TYPE_INVALID,
69     SCHEMA_TYPE_XDR,
70     SCHEMA_TYPE_XSD
71 } SCHEMA_TYPE;
72
73 typedef struct _schema_cache
74 {
75     const struct IXMLDOMSchemaCollection2Vtbl* lpVtbl;
76     xmlHashTablePtr cache;
77     LONG ref;
78 } schema_cache;
79
80 typedef struct _cache_entry
81 {
82     SCHEMA_TYPE type;
83     xmlSchemaPtr schema;
84     xmlDocPtr doc;
85     LONG ref;
86 } cache_entry;
87
88 typedef struct _cache_index_data
89 {
90     LONG index;
91     BSTR* out;
92 } cache_index_data;
93
94 static LONG cache_entry_add_ref(cache_entry* entry)
95 {
96     LONG ref = InterlockedIncrement(&entry->ref);
97     TRACE("%p new ref %d\n", entry, ref);
98     return ref;
99 }
100
101 static LONG cache_entry_release(cache_entry* entry)
102 {
103     LONG ref = InterlockedDecrement(&entry->ref);
104     TRACE("%p new ref %d\n", entry, ref);
105
106     if (ref == 0)
107     {
108         if (entry->type == SCHEMA_TYPE_XSD)
109         {
110             xmldoc_release(entry->doc);
111             entry->schema->doc = NULL;
112             xmlSchemaFree(entry->schema);
113             heap_free(entry);
114         }
115         else /* SCHEMA_TYPE_XDR */
116         {
117             xmldoc_release(entry->doc);
118             heap_free(entry);
119         }
120     }
121     return ref;
122 }
123
124 static inline schema_cache* impl_from_IXMLDOMSchemaCollection2(IXMLDOMSchemaCollection2* iface)
125 {
126     return (schema_cache*)((char*)iface - FIELD_OFFSET(schema_cache, lpVtbl));
127 }
128
129 static inline SCHEMA_TYPE schema_type_from_xmlDocPtr(xmlDocPtr schema)
130 {
131     xmlNodePtr root;
132     if (schema)
133         root = xmlDocGetRootElement(schema);
134     if (root && root->ns)
135     {
136
137         if (xmlStrEqual(root->name, XDR_schema) &&
138             xmlStrEqual(root->ns->href, XDR_nsURI))
139         {
140             return SCHEMA_TYPE_XDR;
141         }
142         else if (xmlStrEqual(root->name, XSD_schema) &&
143                  xmlStrEqual(root->ns->href, XSD_nsURI))
144         {
145             return SCHEMA_TYPE_XSD;
146         }
147     }
148     return SCHEMA_TYPE_INVALID;
149 }
150
151 static cache_entry* cache_entry_from_url(char const* url)
152 {
153     cache_entry* entry = heap_alloc(sizeof(cache_entry));
154     xmlSchemaParserCtxtPtr spctx = xmlSchemaNewParserCtxt(url);
155     entry->type = SCHEMA_TYPE_XSD;
156     entry->ref = 0;
157     if (spctx)
158     {
159         if((entry->schema = xmlSchemaParse(spctx)))
160         {
161             xmldoc_init(entry->schema->doc, &CLSID_DOMDocument40);
162             entry->doc = entry->schema->doc;
163             xmldoc_add_ref(entry->doc);
164         }
165         else
166         {
167             heap_free(entry);
168             entry = NULL;
169         }
170         xmlSchemaFreeParserCtxt(spctx);
171     }
172     else
173     {
174         FIXME("schema for nsURI %s not found\n", wine_dbgstr_a(url));
175         heap_free(entry);
176         entry = NULL;
177     }
178     return entry;
179 }
180
181 static cache_entry* cache_entry_from_xsd_doc(xmlDocPtr doc)
182 {
183     cache_entry* entry = heap_alloc(sizeof(cache_entry));
184     xmlSchemaParserCtxtPtr spctx;
185     xmlDocPtr new_doc = xmlCopyDoc(doc, 1);
186
187     entry->type = SCHEMA_TYPE_XSD;
188     entry->ref = 0;
189     spctx = xmlSchemaNewDocParserCtxt(new_doc);
190
191     if ((entry->schema = xmlSchemaParse(spctx)))
192     {
193         xmldoc_init(entry->schema->doc, &CLSID_DOMDocument40);
194         entry->doc = entry->schema->doc;
195         xmldoc_add_ref(entry->doc);
196     }
197     else
198     {
199         FIXME("failed to parse doc\n");
200         xmlFreeDoc(new_doc);
201         heap_free(entry);
202         entry = NULL;
203     }
204     xmlSchemaFreeParserCtxt(spctx);
205     return entry;
206 }
207
208 static cache_entry* cache_entry_from_xdr_doc(xmlDocPtr doc)
209 {
210     cache_entry* entry = heap_alloc(sizeof(cache_entry));
211     xmlDocPtr new_doc = xmlCopyDoc(doc, 1);
212
213     FIXME("XDR schema support not implemented\n");
214     entry->type = SCHEMA_TYPE_XDR;
215     entry->ref = 0;
216     entry->schema = NULL;
217     entry->doc = new_doc;
218     xmldoc_init(entry->doc, &CLSID_DOMDocument30);
219     xmldoc_add_ref(entry->doc);
220
221     return entry;
222 }
223
224 static HRESULT WINAPI schema_cache_QueryInterface(IXMLDOMSchemaCollection2* iface,
225                                                   REFIID riid, void** ppvObject)
226 {
227     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
228
229     TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppvObject);
230
231     if ( IsEqualIID(riid, &IID_IUnknown) ||
232          IsEqualIID(riid, &IID_IDispatch) ||
233          IsEqualIID(riid, &IID_IXMLDOMSchemaCollection) ||
234          IsEqualIID(riid, &IID_IXMLDOMSchemaCollection2) )
235     {
236         *ppvObject = iface;
237     }
238     else
239     {
240         FIXME("interface %s not implemented\n", debugstr_guid(riid));
241         return E_NOINTERFACE;
242     }
243
244     IXMLDOMSchemaCollection2_AddRef(iface);
245
246     return S_OK;
247 }
248
249 static ULONG WINAPI schema_cache_AddRef(IXMLDOMSchemaCollection2* iface)
250 {
251     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
252     LONG ref = InterlockedIncrement(&This->ref);
253     TRACE("%p new ref %d\n", This, ref);
254     return ref;
255 }
256
257 static void cache_free(void* data, xmlChar* name /* ignored */)
258 {
259     cache_entry_release((cache_entry*)data);
260 }
261
262 static ULONG WINAPI schema_cache_Release(IXMLDOMSchemaCollection2* iface)
263 {
264     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
265     LONG ref = InterlockedDecrement(&This->ref);
266     TRACE("%p new ref %d\n", This, ref);
267
268     if (ref == 0)
269     {
270         xmlHashFree(This->cache, cache_free);
271         heap_free(This);
272     }
273
274     return ref;
275 }
276
277 static HRESULT WINAPI schema_cache_GetTypeInfoCount(IXMLDOMSchemaCollection2* iface,
278                                                     UINT* pctinfo)
279 {
280     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
281
282     TRACE("(%p)->(%p)\n", This, pctinfo);
283
284     *pctinfo = 1;
285
286     return S_OK;
287 }
288
289 static HRESULT WINAPI schema_cache_GetTypeInfo(IXMLDOMSchemaCollection2* iface,
290                                                UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo)
291 {
292     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
293     HRESULT hr;
294
295     TRACE("(%p)->(%u %u %p)\n", This, iTInfo, lcid, ppTInfo);
296
297     hr = get_typeinfo(IXMLDOMSchemaCollection_tid, ppTInfo);
298
299     return hr;
300 }
301
302 static HRESULT WINAPI schema_cache_GetIDsOfNames(IXMLDOMSchemaCollection2* iface,
303                                                  REFIID riid, LPOLESTR* rgszNames,
304                                                  UINT cNames, LCID lcid, DISPID* rgDispId)
305 {
306     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
307     ITypeInfo* typeinfo;
308     HRESULT hr;
309
310     TRACE("(%p)->(%s %p %u %u %p)\n", This, debugstr_guid(riid), rgszNames, cNames,
311           lcid, rgDispId);
312
313     if(!rgszNames || cNames == 0 || !rgDispId)
314         return E_INVALIDARG;
315
316     hr = get_typeinfo(IXMLDOMSchemaCollection_tid, &typeinfo);
317     if(SUCCEEDED(hr))
318     {
319         hr = ITypeInfo_GetIDsOfNames(typeinfo, rgszNames, cNames, rgDispId);
320         ITypeInfo_Release(typeinfo);
321     }
322
323     return hr;
324 }
325
326 static HRESULT WINAPI schema_cache_Invoke(IXMLDOMSchemaCollection2* iface,
327                                           DISPID dispIdMember, REFIID riid, LCID lcid,
328                                           WORD wFlags, DISPPARAMS* pDispParams,
329                                           VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
330                                           UINT* puArgErr)
331 {
332     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
333     ITypeInfo* typeinfo;
334     HRESULT hr;
335
336     TRACE("(%p)->(%d %s %d %d %p %p %p %p)\n", This, dispIdMember, debugstr_guid(riid),
337           lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
338
339     hr = get_typeinfo(IXMLDOMSchemaCollection_tid, &typeinfo);
340     if(SUCCEEDED(hr))
341     {
342         hr = ITypeInfo_Invoke(typeinfo, &(This->lpVtbl), dispIdMember, wFlags, pDispParams,
343                 pVarResult, pExcepInfo, puArgErr);
344         ITypeInfo_Release(typeinfo);
345     }
346
347     return hr;
348 }
349
350 static HRESULT WINAPI schema_cache_add(IXMLDOMSchemaCollection2* iface, BSTR uri, VARIANT var)
351 {
352     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
353     xmlChar* name = xmlChar_from_wchar(uri);
354     TRACE("(%p)->(%s, var(vt %x))\n", This, debugstr_w(uri), V_VT(&var));
355
356     switch (V_VT(&var))
357     {
358         case VT_NULL:
359             {
360                 xmlHashRemoveEntry(This->cache, name, cache_free);
361             }
362             break;
363
364         case VT_BSTR:
365             {
366                 xmlChar* url = xmlChar_from_wchar(V_BSTR(&var));
367                 cache_entry* entry = cache_entry_from_url((char const*)url);
368                 heap_free(url);
369
370                 if (entry)
371                 {
372                     cache_entry_add_ref(entry);
373                 }
374                 else
375                 {
376                     heap_free(name);
377                     return E_FAIL;
378                 }
379
380                 xmlHashRemoveEntry(This->cache, name, cache_free);
381                 xmlHashAddEntry(This->cache, name, entry);
382             }
383             break;
384
385         case VT_DISPATCH:
386             {
387                 xmlDocPtr doc = NULL;
388                 cache_entry* entry;
389                 SCHEMA_TYPE type;
390                 IXMLDOMNode* domnode = NULL;
391                 IDispatch_QueryInterface(V_DISPATCH(&var), &IID_IXMLDOMNode, (void**)&domnode);
392
393                 if (domnode)
394                     doc = xmlNodePtr_from_domnode(domnode, XML_DOCUMENT_NODE)->doc;
395
396                 if (!doc)
397                 {
398                     IXMLDOMNode_Release(domnode);
399                     heap_free(name);
400                     return E_INVALIDARG;
401                 }
402                 type = schema_type_from_xmlDocPtr(doc);
403
404                 if (type == SCHEMA_TYPE_XSD)
405                 {
406                     entry = cache_entry_from_xsd_doc(doc);
407                 }
408                 else if (type == SCHEMA_TYPE_XDR)
409                 {
410                     entry = cache_entry_from_xdr_doc(doc);
411                 }
412                 else
413                 {
414                     WARN("invalid schema!\n");
415                     entry = NULL;
416                 }
417
418                 IXMLDOMNode_Release(domnode);
419
420                 if (entry)
421                 {
422                     cache_entry_add_ref(entry);
423                 }
424                 else
425                 {
426                     heap_free(name);
427                     return E_FAIL;
428                 }
429
430                 xmlHashRemoveEntry(This->cache, name, cache_free);
431                 xmlHashAddEntry(This->cache, name, entry);
432             }
433             break;
434
435         default:
436             {
437                 heap_free(name);
438                 return E_INVALIDARG;
439             }
440     }
441     heap_free(name);
442     return S_OK;
443 }
444
445 static HRESULT WINAPI schema_cache_get(IXMLDOMSchemaCollection2* iface, BSTR uri,
446                                        IXMLDOMNode** node)
447 {
448     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
449     xmlChar* name;
450     cache_entry* entry;
451     TRACE("(%p)->(%s, %p)\n", This, wine_dbgstr_w(uri), node);
452
453     if (!node)
454         return E_POINTER;
455
456     name = xmlChar_from_wchar(uri);
457     entry = (cache_entry*) xmlHashLookup(This->cache, name);
458     heap_free(name);
459
460     /* TODO: this should be read-only */
461     if (entry)
462         return DOMDocument_create_from_xmldoc(entry->doc, (IXMLDOMDocument3**)node);
463
464     *node = NULL;
465     return S_OK;
466 }
467
468 static HRESULT WINAPI schema_cache_remove(IXMLDOMSchemaCollection2* iface, BSTR uri)
469 {
470     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
471     xmlChar* name = xmlChar_from_wchar(uri);
472     TRACE("(%p)->(%s)\n", This, wine_dbgstr_w(uri));
473
474     xmlHashRemoveEntry(This->cache, name, cache_free);
475     heap_free(name);
476     return S_OK;
477 }
478
479 static HRESULT WINAPI schema_cache_get_length(IXMLDOMSchemaCollection2* iface, LONG* length)
480 {
481     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
482     TRACE("(%p)->(%p)\n", This, length);
483
484     if (!length)
485         return E_POINTER;
486     *length = xmlHashSize(This->cache);
487     return S_OK;
488 }
489
490 static void cache_index(void* data /* ignored */, void* index, xmlChar* name)
491 {
492     cache_index_data* index_data = (cache_index_data*)index;
493
494     if (index_data->index-- == 0)
495         *index_data->out = bstr_from_xmlChar(name);
496 }
497
498 static HRESULT WINAPI schema_cache_get_namespaceURI(IXMLDOMSchemaCollection2* iface,
499                                                     LONG index, BSTR* len)
500 {
501     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
502     cache_index_data data = {index,len};
503     TRACE("(%p)->(%i, %p)\n", This, index, len);
504
505     if (!len)
506         return E_POINTER;
507     *len = NULL;
508
509     if (index >= xmlHashSize(This->cache))
510         return E_FAIL;
511
512     xmlHashScan(This->cache, cache_index, &data);
513     return S_OK;
514 }
515
516 static void cache_copy(void* data, void* dest, xmlChar* name)
517 {
518     schema_cache* This = (schema_cache*) dest;
519     cache_entry* entry = (cache_entry*) data;
520
521     if (xmlHashLookup(This->cache, name) == NULL)
522     {
523         cache_entry_add_ref(entry);
524         xmlHashAddEntry(This->cache, name, entry);
525     }
526 }
527
528 static HRESULT WINAPI schema_cache_addCollection(IXMLDOMSchemaCollection2* iface,
529                                                  IXMLDOMSchemaCollection* otherCollection)
530 {
531     schema_cache* This = impl_from_IXMLDOMSchemaCollection2(iface);
532     schema_cache* That = impl_from_IXMLDOMSchemaCollection2((IXMLDOMSchemaCollection2*)otherCollection);
533     TRACE("(%p)->(%p)\n", This, That);
534
535     if (!otherCollection)
536         return E_POINTER;
537
538     /* TODO: detect errors while copying & return E_FAIL */
539     xmlHashScan(That->cache, cache_copy, This);
540
541     return S_OK;
542 }
543
544 static HRESULT WINAPI schema_cache_get__newEnum(IXMLDOMSchemaCollection2* iface,
545                                                 IUnknown** ppUnk)
546 {
547     FIXME("stub\n");
548     if (ppUnk)
549         *ppUnk = NULL;
550     return E_NOTIMPL;
551 }
552
553 static HRESULT WINAPI schema_cache_validate(IXMLDOMSchemaCollection2* iface)
554 {
555     FIXME("stub\n");
556     return E_NOTIMPL;
557 }
558
559 static HRESULT WINAPI schema_cache_put_validateOnLoad(IXMLDOMSchemaCollection2* iface,
560                                                       VARIANT_BOOL validateOnLoad)
561 {
562     FIXME("stub\n");
563     return E_NOTIMPL;
564 }
565
566 static HRESULT WINAPI schema_cache_get_validateOnLoad(IXMLDOMSchemaCollection2* iface,
567                                                       VARIANT_BOOL* validateOnLoad)
568 {
569     FIXME("stub\n");
570     return E_NOTIMPL;
571 }
572
573 static HRESULT WINAPI schema_cache_getSchema(IXMLDOMSchemaCollection2* iface,
574                                              BSTR namespaceURI, ISchema** schema)
575 {
576     FIXME("stub\n");
577     if (schema)
578         *schema = NULL;
579     return E_NOTIMPL;
580 }
581
582 static HRESULT WINAPI schema_cache_getDeclaration(IXMLDOMSchemaCollection2* iface,
583                                                   IXMLDOMNode* node, ISchemaItem** item)
584 {
585     FIXME("stub\n");
586     if (item)
587         *item = NULL;
588     return E_NOTIMPL;
589 }
590
591 static const struct IXMLDOMSchemaCollection2Vtbl schema_cache_vtbl =
592 {
593     schema_cache_QueryInterface,
594     schema_cache_AddRef,
595     schema_cache_Release,
596     schema_cache_GetTypeInfoCount,
597     schema_cache_GetTypeInfo,
598     schema_cache_GetIDsOfNames,
599     schema_cache_Invoke,
600     schema_cache_add,
601     schema_cache_get,
602     schema_cache_remove,
603     schema_cache_get_length,
604     schema_cache_get_namespaceURI,
605     schema_cache_addCollection,
606     schema_cache_get__newEnum,
607     schema_cache_validate,
608     schema_cache_put_validateOnLoad,
609     schema_cache_get_validateOnLoad,
610     schema_cache_getSchema,
611     schema_cache_getDeclaration
612 };
613
614 HRESULT SchemaCache_create(IUnknown* pUnkOuter, void** ppObj)
615 {
616     schema_cache* This = heap_alloc(sizeof(schema_cache));
617     if (!This)
618         return E_OUTOFMEMORY;
619
620     This->lpVtbl = &schema_cache_vtbl;
621     This->cache = xmlHashCreate(DEFAULT_HASHTABLE_SIZE);
622     This->ref = 1;
623
624     *ppObj = &This->lpVtbl;
625     return S_OK;
626 }
627
628 #else
629
630 HRESULT SchemaCache_create(IUnknown* pUnkOuter, void** ppObj)
631 {
632     MESSAGE("This program tried to use a SchemaCache object, but\n"
633             "libxml2 support was not present at compile time.\n");
634     return E_NOTIMPL;
635 }
636
637 #endif