TODO: Implement gAddressExt group
[libisds.git] / src / validator.c
blob4a3db6e133504ca19ae0bc0d3502e72a3f9d351d
1 #include "isds_priv.h"
2 #include "utils.h"
3 #include "validator.h"
4 #if HAVE_LIBCURL
5 #include "soap.h"
6 #endif
9 #if HAVE_LIBCURL
10 /* Get ISDS status info from ISDS @response XML document.
11 * Be ware that different request families return differently encoded status
12 * (e.g. dmStatus, dbStatus)
13 * @context is ISDS context
14 * @service is ISDS web service identifier
15 * @response is ISDS response document
16 * @code is automatically allocated status code of the response
17 * @message is automatically allocated status message. Returned NULL means no
18 * message was delivered by server. Use NULL if you don't care.
19 * @refnumber is automatically reallocated request serial number assigned by
20 * ISDS. Returned *NULL means no number was delivered by server.
21 * Use NULL if you don't care. */
22 _hidden isds_error isds_response_status(struct isds_ctx *context,
23 const isds_service service, xmlDocPtr response,
24 xmlChar **code, xmlChar **message, xmlChar **refnumber) {
25 isds_error err = IE_SUCCESS;
26 xmlChar *status_code_expr = NULL, *status_message_expr = NULL;
27 xmlXPathContextPtr xpath_ctx = NULL;
28 xmlXPathObjectPtr result = NULL;
30 if (!response || !code) {
31 err = IE_INVAL;
32 goto leave;
35 switch (service) {
36 case SERVICE_DM_OPERATIONS:
37 case SERVICE_DM_INFO:
38 status_code_expr = BAD_CAST
39 "/*/isds:dmStatus/isds:dmStatusCode/text()";
40 status_message_expr = BAD_CAST
41 "/*/isds:dmStatus/isds:dmStatusMessage/text()";
42 break;
43 case SERVICE_DB_SEARCH:
44 case SERVICE_DB_ACCESS:
45 case SERVICE_DB_MANIPULATION:
46 status_code_expr = BAD_CAST
47 "/*/isds:dbStatus/isds:dbStatusCode/text()";
48 status_message_expr = BAD_CAST
49 "/*/isds:dbStatus/isds:dbStatusMessage/text()";
50 break;
51 case SERVICE_ASWS:
52 status_code_expr = BAD_CAST
53 "/*/oisds:dbStatus/oisds:dbStatusCode/text()";
54 status_message_expr = BAD_CAST
55 "/*/oisds:dbStatus/oisds:dbStatusMessage/text()";
56 break;
57 default:
58 err = IE_NOTSUP;
59 goto leave;
62 xpath_ctx = xmlXPathNewContext(response);
63 if (!xpath_ctx) {
64 err = IE_ERROR;
65 goto leave;
67 if (_isds_register_namespaces(xpath_ctx,
68 (context->type == CTX_TYPE_TESTING_REQUEST_COLLECTOR) ?
69 MESSAGE_NS_1 : MESSAGE_NS_UNSIGNED)) {
70 err = IE_ERROR;
71 goto leave;
74 /* Get status code */
75 result = xmlXPathEvalExpression(status_code_expr, xpath_ctx);
76 if (!result) {
77 err = IE_ERROR;
78 goto leave;
80 if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
81 isds_log_message(context,
82 (context->type == CTX_TYPE_TESTING_REQUEST_COLLECTOR) ?
83 _("ISDS1 response is missing StatusCode element") :
84 _("ISDS response is missing StatusCode element"));
85 err = IE_ISDS;
86 goto leave;
88 *code = xmlXPathCastNodeSetToString(result->nodesetval);
89 if (NULL == *code) {
90 err = IE_ERROR;
91 goto leave;
94 if (message) {
95 /* Get status message */
96 xmlXPathFreeObject(result);
97 result = xmlXPathEvalExpression(status_message_expr, xpath_ctx);
98 if (!result) {
99 err = IE_ERROR;
100 goto leave;
102 if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
103 /* E.g. CreateMessageResponse with dmStatusCode 9005 has empty
104 * message */
105 *message = NULL;
106 } else {
107 *message = xmlXPathCastNodeSetToString(result->nodesetval);
108 if (NULL == *message) {
109 err = IE_ERROR;
110 goto leave;
115 if (refnumber) {
116 /* Get reference number of client request */
117 zfree(*refnumber);
118 xmlXPathFreeObject(result);
119 result = xmlXPathEvalExpression(
120 (SERVICE_ASWS == service) ?
121 BAD_CAST "/*/oisds:dbStatus/oisds:dbStatusRefNumber/text()":
122 BAD_CAST "/*/isds:dbStatus/isds:dbStatusRefNumber/text()",
123 xpath_ctx);
124 if (!result) {
125 err = IE_ERROR;
126 goto leave;
128 if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
129 *refnumber = NULL;
130 } else {
131 *refnumber = xmlXPathCastNodeSetToString(result->nodesetval);
132 if (NULL == *refnumber) {
133 err = IE_ERROR;
134 goto leave;
138 leave:
139 xmlXPathFreeObject(result);
140 xmlXPathFreeContext(xpath_ctx);
141 return err;
145 /* Send @request to ISDS and return ISDS @response as XML document.
146 * Be ware the @response can be invalid (in sense of XML Schema).
147 * (And it is because current ISDS server does not follow its own
148 * specification. Please apology my government, its herd of incompetent
149 * creatures.)
150 * @context is ISDS session context,
151 * @service identifies ISDS web service
152 * @request is tree with ISDS message, can be NULL
153 * @response is automatically allocated response from server as XML Document
154 * @raw_response is automatically allocated bit stream with response body. Use
155 * NULL if you don't care
156 * @raw_response_length is size of @raw_response in bytes
157 * In case of error, @response and @raw_response will be deallocated.
158 * */
159 _hidden isds_error _isds(struct isds_ctx *context, const isds_service service,
160 const xmlNodePtr request, xmlDocPtr *response,
161 void **raw_response, size_t *raw_response_length) {
162 isds_error err = IE_SUCCESS;
163 xmlDocPtr response_document = NULL;
164 xmlNodePtr response_body, isds_node;
165 char *file = NULL;
166 const char *name_space = ISDS_NS;
168 if (!context) return IE_INVALID_CONTEXT;
169 if (!response) return IE_INVAL;
170 if (!raw_response_length && raw_response) return IE_INVAL;
172 /* Effective ISDS URL is build from base URL and suffix.
173 * Other connection types has specific stable URL. */
174 if (context->type == CTX_TYPE_ISDS) {
175 switch (service) {
176 case SERVICE_DM_OPERATIONS: file = "DS/dz"; break;
177 case SERVICE_DM_INFO: file = "DS/dx"; break;
178 case SERVICE_DB_SEARCH: file = "DS/df"; break;
179 case SERVICE_DB_ACCESS: file = "DS/DsManage"; break;
180 case SERVICE_DB_MANIPULATION: file = "DS/DsManage"; break;
181 case SERVICE_ASWS: file = ""; break;
182 default: return (IE_INVAL);
186 /* Also name space differs in some cases */
187 if (CTX_TYPE_TESTING_REQUEST_COLLECTOR == context->type)
188 name_space = ISDS1_NS;
189 else if (SERVICE_ASWS == service)
190 name_space = OISDS_NS;
192 err = _isds_soap(context, file, request, &response_document, &response_body,
193 raw_response, raw_response_length);
195 if (err) goto leave;
197 if (!response_body) {
198 isds_log_message(context, _("SOAP returned empty body"));
199 err = IE_ISDS;
202 /* Find ISDS element */
203 for (isds_node = response_body; isds_node; isds_node = isds_node->next) {
204 if (isds_node->type == XML_ELEMENT_NODE &&
205 isds_node->ns &&
206 !xmlStrcmp(isds_node->ns->href, BAD_CAST name_space))
207 break;
209 if (!isds_node) {
210 char *name_space_local = _isds_utf82locale(name_space);
211 isds_printf_message(context,
212 _("SOAP response does not contain element from name space %s"),
213 name_space_local);
214 free(name_space_local);
215 err = IE_ISDS;
216 goto leave;
219 /* TODO: validate the response */
221 /* Build XML document */
222 *response = xmlNewDoc(BAD_CAST "1.0");
223 if (!*response) {
224 isds_log_message(context, _("Could not build ISDS response document"));
225 err = IE_ERROR;
226 goto leave;
228 xmlDocSetRootElement(*response, isds_node);
230 leave:
231 if (err) {
232 xmlFreeDoc(*response);
233 if (raw_response) zfree(*raw_response);
235 xmlFreeDoc(response_document);
237 return err;
239 #endif /* HAVE_LIBCURL */
242 /* Walk through list of isds_documents and check for their types and
243 * references.
244 * @context is session context
245 * @documents is list of isds_document to check
246 * @returns IE_SUCCESS if structure is valid, otherwise context' message will
247 * be filled with explanation of found problem. */
248 _hidden isds_error _isds_check_documents_hierarchy(struct isds_ctx *context,
249 const struct isds_list *documents) {
251 const struct isds_list *item;
252 const struct isds_document *document;
253 _Bool main_exists = 0;
255 if (!context) return IE_INVALID_CONTEXT;
256 if (!documents) return IE_INVAL;
258 for (item = documents; item; item = item->next) {
259 document = (const struct isds_document *) item->data;
260 if (!document) continue;
262 /* Only one document can be main */
263 if (document->dmFileMetaType == FILEMETATYPE_MAIN) {
264 if (main_exists) {
265 isds_log_message(context,
266 _("List contains more main documents"));
267 return IE_ERROR;
269 main_exists = 1;
272 /* All document identifiers should be unique */
273 if (document->dmFileGuid) {
274 if (isds_find_document_by_id(documents, document->dmFileGuid) !=
275 document) {
276 isds_printf_message(context, _("List contains more documents "
277 "with the same ID `%s'"), document->dmFileGuid);
278 return IE_ERROR;
282 /* All document references should point to existing document ID */
283 /* ???: Should we forbid self-referencing? */
284 if (document->dmUpFileGuid) {
285 if (!isds_find_document_by_id(documents,
286 document->dmUpFileGuid)) {
287 isds_printf_message(context, _("List contains documents "
288 "referencing to not existing document ID `%s'"),
289 document->dmUpFileGuid);
290 return IE_ERROR;
295 if (!main_exists) {
296 isds_log_message(context, _("List does not contain main document"));
297 return IE_ERROR;
300 return IE_SUCCESS;
304 /* Check for message ID length
305 * @context is session context
306 * @message_id checked message ID
307 * @return IE_SUCCESS or appropriate error code and fill context' message */
308 isds_error validate_message_id_length(struct isds_ctx *context,
309 const xmlChar *message_id) {
310 if (!context) return IE_INVALID_CONTEXT;
311 if (!message_id) return IE_INVAL;
313 const int length = xmlUTF8Strlen(message_id);
315 if (length == -1) {
316 char *message_id_locale = _isds_utf82locale((char*) message_id);
317 isds_printf_message(context,
318 _("Could not check message ID length: %s"),
319 message_id_locale);
320 free(message_id_locale);
321 return IE_ERROR;
324 if (length >= 20) {
325 char *message_id_locale = _isds_utf82locale((char*) message_id);
326 isds_printf_message(context,
327 _("Message ID must not be longer than 20 characters: %s"),
328 message_id_locale);
329 free(message_id_locale);
330 return IE_INVAL;
333 return IE_SUCCESS;
337 #if HAVE_LIBCURL
338 /* Send @request to Czech POINT conversion deposit and return response
339 * as XML document.
340 * @context is Czech POINT session context,
341 * @request is tree with deposit message, can be NULL
342 * @response is automatically allocated response from server as XML Document
343 * In case of error, @response will be deallocated.
344 * */
345 _hidden isds_error _czp_czpdeposit(struct isds_ctx *context,
346 const xmlNodePtr request, xmlDocPtr *response) {
347 isds_error err = IE_SUCCESS;
348 xmlDocPtr response_document = NULL;
349 xmlNodePtr response_body = NULL, deposit_node;
351 if (!context) return IE_INVALID_CONTEXT;
352 if (!response) return IE_INVAL;
354 err = _isds_soap(context, NULL, request,
355 &response_document, &response_body, NULL, NULL);
357 if (err) goto leave;
359 if (!response_body) {
360 isds_log_message(context, _("SOAP returned empty body"));
361 err = IE_ISDS;
364 /* Find deposit element */
365 for (deposit_node = response_body; deposit_node;
366 deposit_node = deposit_node->next) {
367 if (deposit_node->type == XML_ELEMENT_NODE &&
368 deposit_node->ns &&
369 !xmlStrcmp(deposit_node->ns->href, BAD_CAST DEPOSIT_NS))
370 break;
372 if (!deposit_node) {
373 isds_log_message(context,
374 _("SOAP response does not contain "
375 "Czech POINT deposit element"));
376 err = IE_ISDS;
377 goto leave;
380 /* Build XML document */
381 *response = xmlNewDoc(BAD_CAST "1.0");
382 if (!*response) {
383 isds_log_message(context,
384 _("Could not build Czech POINT deposit response document"));
385 err = IE_ERROR;
386 goto leave;
388 xmlDocSetRootElement(*response, deposit_node);
390 leave:
391 if (err) {
392 xmlFreeDoc(*response);
394 xmlFreeDoc(response_document);
396 return err;
398 #endif /* HAVE_LIBCURL */