TODO: OTP-authenticatd password change tested
[libisds.git] / src / validator.c
blob87fc5ead48b7b02a7e2dc6e6e7b2594662d4d5b1
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 (!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 (!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 (!message) {
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 xmlNodePtr response_body = NULL, isds_node;
164 char *file = NULL;
165 const char *name_space = ISDS_NS;
167 if (!context) return IE_INVALID_CONTEXT;
168 if (!response) return IE_INVAL;
169 if (!raw_response_length && raw_response) return IE_INVAL;
171 /* Effective ISDS URL is build from base URL and suffix.
172 * Other connection types has specific stable URL. */
173 if (context->type == CTX_TYPE_ISDS) {
174 switch (service) {
175 case SERVICE_DM_OPERATIONS: file = "DS/dz"; break;
176 case SERVICE_DM_INFO: file = "DS/dx"; break;
177 case SERVICE_DB_SEARCH: file = "DS/df"; break;
178 case SERVICE_DB_ACCESS: file = "DS/DsManage"; break;
179 case SERVICE_DB_MANIPULATION: file = "DS/DsManage"; break;
180 case SERVICE_ASWS: file = ""; break;
181 default: return (IE_INVAL);
185 /* Also name space differs in some cases */
186 if (CTX_TYPE_TESTING_REQUEST_COLLECTOR == context->type)
187 name_space = ISDS1_NS;
188 else if (SERVICE_ASWS == service)
189 name_space = OISDS_NS;
191 err = _isds_soap(context, file, request, &response_body,
192 raw_response, raw_response_length);
194 if (err) goto leave;
196 if (!response_body) {
197 isds_log_message(context, _("SOAP returned empty body"));
198 err = IE_ISDS;
201 /* Find ISDS element */
202 for (isds_node = response_body; isds_node; isds_node = isds_node->next) {
203 if (isds_node->type == XML_ELEMENT_NODE &&
204 isds_node->ns &&
205 !xmlStrcmp(isds_node->ns->href, BAD_CAST name_space))
206 break;
208 if (!isds_node) {
209 char *name_space_local = _isds_utf82locale(name_space);
210 isds_printf_message(context,
211 _("SOAP response does not contain element from name space %s"),
212 name_space_local);
213 free(name_space_local);
214 err = IE_ISDS;
215 goto leave;
218 /* Destroy other nodes */
219 if (isds_node == response_body)
220 response_body = response_body->next;
221 xmlUnlinkNode(isds_node);
222 xmlFreeNodeList(response_body);
223 response_body = NULL;
225 /* TODO: validate the response */
227 /* Build XML document */
228 *response = xmlNewDoc(BAD_CAST "1.0");
229 if (!*response) {
230 isds_log_message(context, _("Could not build ISDS response document"));
231 err = IE_ERROR;
232 goto leave;
234 xmlDocSetRootElement(*response, isds_node);
236 leave:
237 if (err) {
238 xmlFreeDoc(*response);
239 if (raw_response) zfree(*raw_response);
241 xmlFreeNodeList(response_body);
243 return err;
245 #endif /* HAVE_LIBCURL */
248 /* Walk through list of isds_documents and check for their types and
249 * references.
250 * @context is session context
251 * @documents is list of isds_document to check
252 * @returns IE_SUCCESS if structure is valid, otherwise context' message will
253 * be filled with explanation of found problem. */
254 _hidden isds_error _isds_check_documents_hierarchy(struct isds_ctx *context,
255 const struct isds_list *documents) {
257 const struct isds_list *item;
258 const struct isds_document *document;
259 _Bool main_exists = 0;
261 if (!context) return IE_INVALID_CONTEXT;
262 if (!documents) return IE_INVAL;
264 for (item = documents; item; item = item->next) {
265 document = (const struct isds_document *) item->data;
266 if (!document) continue;
268 /* Only one document can be main */
269 if (document->dmFileMetaType == FILEMETATYPE_MAIN) {
270 if (main_exists) {
271 isds_log_message(context,
272 _("List contains more main documents"));
273 return IE_ERROR;
275 main_exists = 1;
278 /* All document identifiers should be unique */
279 if (document->dmFileGuid) {
280 if (isds_find_document_by_id(documents, document->dmFileGuid) !=
281 document) {
282 isds_printf_message(context, _("List contains more documents "
283 "with the same ID `%s'"), document->dmFileGuid);
284 return IE_ERROR;
288 /* All document references should point to existing document ID */
289 /* ???: Should we forbid self-referencing? */
290 if (document->dmUpFileGuid) {
291 if (!isds_find_document_by_id(documents,
292 document->dmUpFileGuid)) {
293 isds_printf_message(context, _("List contains documents "
294 "referencing to not existing document ID `%s'"),
295 document->dmUpFileGuid);
296 return IE_ERROR;
301 if (!main_exists) {
302 isds_log_message(context, _("List does not contain main document"));
303 return IE_ERROR;
306 return IE_SUCCESS;
310 /* Check for message ID length
311 * @context is session context
312 * @message_id checked message ID
313 * @return IE_SUCCESS or appropriate error code and fill context' message */
314 isds_error validate_message_id_length(struct isds_ctx *context,
315 const xmlChar *message_id) {
316 if (!context) return IE_INVALID_CONTEXT;
317 if (!message_id) return IE_INVAL;
319 const int length = xmlUTF8Strlen(message_id);
321 if (length == -1) {
322 char *message_id_locale = _isds_utf82locale((char*) message_id);
323 isds_printf_message(context,
324 _("Could not check message ID length: %s"),
325 message_id_locale);
326 free(message_id_locale);
327 return IE_ERROR;
330 if (length >= 20) {
331 char *message_id_locale = _isds_utf82locale((char*) message_id);
332 isds_printf_message(context,
333 _("Message ID must not be longer than 20 characters: %s"),
334 message_id_locale);
335 free(message_id_locale);
336 return IE_INVAL;
339 return IE_SUCCESS;
343 #if HAVE_LIBCURL
344 /* Send @request to Czech POINT conversion deposit and return response
345 * as XML document.
346 * @context is Czech POINT session context,
347 * @request is tree with deposit message, can be NULL
348 * @response is automatically allocated response from server as XML Document
349 * In case of error, @response will be deallocated.
350 * */
351 _hidden isds_error _czp_czpdeposit(struct isds_ctx *context,
352 const xmlNodePtr request, xmlDocPtr *response) {
353 isds_error err = IE_SUCCESS;
354 xmlNodePtr response_body = NULL, deposit_node;
356 if (!context) return IE_INVALID_CONTEXT;
357 if (!response) return IE_INVAL;
359 err = _isds_soap(context, NULL, request, &response_body, NULL, NULL);
361 if (err) goto leave;
363 if (!response_body) {
364 isds_log_message(context, _("SOAP returned empty body"));
365 err = IE_ISDS;
368 /* Find deposit element */
369 for (deposit_node = response_body; deposit_node;
370 deposit_node = deposit_node->next) {
371 if (deposit_node->type == XML_ELEMENT_NODE &&
372 deposit_node->ns &&
373 !xmlStrcmp(deposit_node->ns->href, BAD_CAST DEPOSIT_NS))
374 break;
376 if (!deposit_node) {
377 isds_log_message(context,
378 _("SOAP response does not contain "
379 "Czech POINT deposit element"));
380 err = IE_ISDS;
381 goto leave;
384 /* Destroy other nodes */
385 if (deposit_node == response_body)
386 response_body = response_body->next;
387 xmlUnlinkNode(deposit_node);
388 xmlFreeNodeList(response_body);
389 response_body = NULL;
391 /* Build XML document */
392 *response = xmlNewDoc(BAD_CAST "1.0");
393 if (!*response) {
394 isds_log_message(context,
395 _("Could not build Czech POINT deposit response document"));
396 err = IE_ERROR;
397 goto leave;
399 xmlDocSetRootElement(*response, deposit_node);
401 leave:
402 if (err) {
403 xmlFreeDoc(*response);
405 xmlFreeNodeList(response_body);
407 return err;
409 #endif /* HAVE_LIBCURL */