source: trunk/zoo-project/zoo-kernel/server_internal.c @ 889

Last change on this file since 889 was 889, checked in by knut, 2 years ago

Added some new logging functionality (function logMessage(), macros zooLog, zooLogMsg). Added utility functions setErrorMessage(), hasvalue(), and nonempty() in service.c. Added enum WPSException and arrays WPSExceptionText and WPSExceptionCode (see also function setErrorMessage). New conditional definition of type bool in service.c (to fix issue with bool). Null pointer check in function addToMap. Added missing return values and explicit type casts in some functions. Removed Windows-specific code in function dumpBackFinalFile (zoo_service_loader.c) that may cause error for async raw data output in formats other than XML.

  • Property svn:keywords set to Id
File size: 35.3 KB
Line 
1/*
2 * Author : Gérald Fenoy
3 *
4 *  Copyright 2008-2015 GeoLabs SARL. All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24
25#include "server_internal.h"
26#include "service_internal.h"
27#include "response_print.h"
28#include "mimetypes.h"
29#ifndef WIN32
30#include <dlfcn.h>
31#include <uuid/uuid.h>
32#else
33#include <rpc.h>
34#define ERROR_MSG_MAX_LENGTH 1024
35#endif
36#include <signal.h>
37
38// #include <stdlib.h>
39/*
40 * Compare two file path strings to see if they refer to the same file.
41 *
42 * @param path1 the first file path
43 * @param path2 the second file path
44 *
45 * @return 0 if the files are identical
46 */
47#define PATHBUFSIZE 4096
48int zoo_path_compare(char* path1, char* path2) {
49
50  if (path1 == NULL || path2 == NULL) {
51    return -1;
52  }
53
54  char realpath1[PATHBUFSIZE];
55  char realpath2[PATHBUFSIZE];
56
57#ifdef WIN32
58  int res1 = GetFullPathName(path1, PATHBUFSIZE, realpath1, NULL);
59  int res2 = GetFullPathName(path2, PATHBUFSIZE, realpath2, NULL);
60
61  if (res1 == 0 || res2 == 0) {
62    return -1;
63  }
64  else {
65    return strncasecmp(realpath1, realpath2, PATHBUFSIZE);
66  }
67#else
68  char* ptr1 = realpath(path1, realpath1);
69  char* ptr2 = realpath(path2, realpath2);
70
71  if (ptr1 == NULL || ptr2 == NULL) {
72    return -1;
73  }
74  else {
75    return strncmp(realpath1, realpath2, PATHBUFSIZE);
76  }
77#endif
78}
79
80/**
81 * Detect WPS version used (1.0.0 or 2.0.0).
82 *
83 * @param version number as char* (1.0.0 or 2.0.0)
84 * @return 0 in case of version 1.0.0, 1 for 2.0.0, -1 in other case
85 */
86int getVersionId(const char* version){
87  int schemaId=0;
88  for(;schemaId<2;schemaId++){
89    if(strncasecmp(version,schemas[schemaId][0],5)==0)
90      return schemaId;
91  }
92  return -1;
93}
94
95/**
96 * Generate a UUID.
97 * ref: https://www.ietf.org/rfc/rfc4122.txt / 4.2
98 *
99 * @return a new char* containing the UUID, make sure to free the returned
100 *  resource once used.
101 */
102char *get_uuid(){
103  char *res=(char*)malloc(37*sizeof(char));
104#ifdef WIN32
105  UUID uuid;
106  UuidCreate(&uuid);
107  RPC_CSTR rest = NULL;
108  UuidToString(&uuid,&rest);
109#else
110  uuid_t uuid;
111  uuid_generate_time(uuid);
112  char rest[128];
113  uuid_unparse(uuid,rest);
114#endif
115  sprintf(res,"%s",rest);
116#ifdef WIN32
117  RpcStringFree(&rest);
118#endif
119  return res;
120}
121
122/**
123 * Extract the service identifier from the full service identifier
124 * ie:
125 *  - Full service name: OTB.BandMath
126 *  - Service name: BandMath
127 *
128 * @param conf the maps containing the settings of the main.cfg file
129 * @param conf_dir the full path to the ZOO-Kernel directory
130 * @param identifier the full service name (potentialy including a prefix, ie:
131 *  Prefix.MyService)
132 * @param buffer the resulting service identifier (without any prefix)
133 */
134void parseIdentifier(maps* conf,char* conf_dir,char *identifier,char* buffer){
135  setMapInMaps(conf,"lenv","oIdentifier",identifier);
136  char *lid=zStrdup(identifier);
137  char *saveptr1;
138  char *tmps1=strtok_r(lid,".",&saveptr1);
139  int level=0;
140  char key[25];
141  char levels[18];
142  while(tmps1!=NULL){
143    char *test=zStrdup(tmps1);
144    char* tmps2=(char*)malloc((strlen(test)+2)*sizeof(char));
145    sprintf(key,"sprefix_%d",level);
146    sprintf(tmps2,"%s.",test);
147    sprintf(levels,"%d",level);
148    setMapInMaps(conf,"lenv","level",levels);
149    setMapInMaps(conf,"lenv",key,tmps2);
150    free(tmps2);
151    free(test);
152    level++;
153    tmps1=strtok_r(NULL,".",&saveptr1);
154  }
155  int i=0;
156  sprintf(buffer,"%s",conf_dir);
157  for(i=0;i<level;i++){
158    char *tmp0=zStrdup(buffer);
159    sprintf(key,"sprefix_%d",i);
160    map* tmp00=getMapFromMaps(conf,"lenv",key);
161    if(tmp00!=NULL)
162      sprintf(buffer,"%s/%s",tmp0,tmp00->value);
163    free(tmp0);
164    buffer[strlen(buffer)-1]=0;
165    if(i+1<level){ 
166      #ifdef IGNORE_METAPATH
167        map* tmpMap = createMap("metapath", "");
168      #else 
169        map* tmpMap=getMapFromMaps(conf,"lenv","metapath");
170      #endif     
171      if(tmpMap==NULL || strlen(tmpMap->value)==0){
172        char *tmp01=zStrdup(tmp00->value);
173        tmp01[strlen(tmp01)-1]=0;
174        setMapInMaps(conf,"lenv","metapath",tmp01);
175        free(tmp01);
176        tmp01=NULL;
177      }
178      else{
179        if(tmp00!=NULL && tmpMap!=NULL){
180          char *tmp00s=zStrdup(tmp00->value);
181          tmp00s[strlen(tmp00s)-1]=0;
182          char *value=(char*)malloc((strlen(tmp00s)+strlen(tmpMap->value)+2)*sizeof(char));
183          sprintf(value,"%s/%s",tmpMap->value,tmp00s);
184          setMapInMaps(conf,"lenv","metapath",value);
185          free(value);
186          free(tmp00s);
187          value=NULL;
188        }
189      }
190    }else{
191      char *tmp01=zStrdup(tmp00->value);
192      tmp01[strlen(tmp01)-1]=0;
193      setMapInMaps(conf,"lenv","Identifier",tmp01);
194      free(tmp01);
195    }
196  }
197  char *tmp0=zStrdup(buffer);
198  sprintf(buffer,"%s.zcfg",tmp0);
199  free(tmp0);
200  free(lid);
201}
202
203/**
204 * Converts a hex character to its integer value
205 *
206 * @param ch the char to convert
207 * @return the converted char
208 */
209char from_hex(char ch) {
210  return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
211}
212
213/**
214 * Converts an integer value to its hec character
215 *
216 * @param code the char to convert
217 * @return the converted char
218 */
219char to_hex(char code) {
220  static char hex[] = "0123456789abcdef";
221  return hex[code & 15];
222}
223
224/**
225 * URLEncode an url
226 *
227 * @param str the url to encode
228 * @return a url-encoded version of str
229 * @warning be sure to free() the returned string after use
230 */
231char *url_encode(char *str) {
232  char *pstr = str, *buf = (char*) malloc(strlen(str) * 3 + 1), *pbuf = buf;
233  while (*pstr) {
234    if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') 
235      *pbuf++ = *pstr;
236    else if (*pstr == ' ') 
237      *pbuf++ = '+';
238    else 
239      *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
240    pstr++;
241  }
242  *pbuf = '\0';
243  return buf;
244}
245
246/**
247 * Decode an URLEncoded url
248 *
249 * @param str the URLEncoded url to decode
250 * @return a url-decoded version of str
251 * @warning be sure to free() the returned string after use
252 */
253char *url_decode(char *str) {
254  char *pstr = str, *buf = (char*) malloc(strlen(str) + 1), *pbuf = buf;
255  while (*pstr) {
256    if (*pstr == '%') {
257      if (pstr[1] && pstr[2]) {
258        *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
259        pstr += 2;
260      }
261    } else if (*pstr == '+') { 
262      *pbuf++ = ' ';
263    } else {
264      *pbuf++ = *pstr;
265    }
266    pstr++;
267  }
268  *pbuf = '\0';
269  return buf;
270}
271
272/**
273 * Verify if a given language is listed in the lang list defined in the [main]
274 * section of the main.cfg file.
275 *
276 * @param conf the map containing the settings from the main.cfg file
277 * @param str the specific language
278 * @return 1 if the specific language is listed, -1 in other case.
279 */
280int isValidLang(maps* conf,const char *str){
281  map *tmpMap=getMapFromMaps(conf,"main","language");
282  char *tmp0=NULL,*tmp=NULL,*tmp1=NULL;
283  if(tmpMap!=NULL)
284    tmp0=zStrdup(tmpMap->value);
285  tmpMap=getMapFromMaps(conf,"main","lang");
286  if(tmpMap!=NULL)
287    tmp=zStrdup(tmpMap->value);
288  if(tmp0!=NULL && tmp!=NULL){
289    tmp1=(char*)malloc((strlen(tmp0)+strlen(tmp)+2)*sizeof(char));
290    sprintf(tmp1,"%s,%s",tmp0,tmp);
291    free(tmp0);
292    free(tmp);
293  }else{
294    if(tmp!=NULL){
295      tmp1=zStrdup(tmp);
296      free(tmp);
297    }else{
298      if(tmp0!=NULL){
299        tmp1=zStrdup(tmp0);
300        free(tmp0);
301      }
302    }
303  }
304  char *pToken,*saveptr;
305  pToken=strtok_r(tmp1,",",&saveptr);
306  int res=-1;
307  while(pToken!=NULL){
308    if(strcasecmp(str,pToken)==0){
309      res=1;
310      break;
311    }
312    pToken=strtok_r(NULL,",",&saveptr);
313  }
314  if(tmp1!=NULL)
315    free(tmp1);
316  return res;
317}
318
319
320/**
321 * Access the value of the encoding key in a maps
322 *
323 * @param m the maps to search for the encoding key
324 * @return the value of the encoding key in a maps if encoding key exists,
325 *  "UTF-8" in other case.
326 */
327char* getEncoding(maps* m){
328  if(m!=NULL){
329    map* tmp=getMap(m->content,"encoding");
330    if(tmp!=NULL){
331      return tmp->value;
332    }
333    else
334      return (char*)"UTF-8";
335  }
336  else
337    return (char*)"UTF-8"; 
338}
339
340/**
341 * Access the value of the version key in a maps
342 *
343 * @param m the maps to search for the version key
344 * @return the value of the version key in a maps if encoding key exists,
345 *  "1.0.0" in other case.
346 */
347char* getVersion(maps* m){
348  if(m!=NULL){
349    map* tmp=getMap(m->content,"version");
350    if(tmp!=NULL){
351      return tmp->value;
352    }
353    else
354      return (char*)"1.0.0";
355  }
356  else
357    return (char*)"1.0.0";
358}
359
360/**
361 * Read a file generated by a service.
362 *
363 * @param m the conf maps
364 * @param content the output item
365 * @param filename the file to read
366 */
367void readGeneratedFile(maps* m,map* content,char* filename){
368  FILE * file=fopen(filename,"rb");
369  if(file==NULL){
370    fprintf(stderr,"Failed to open file %s for reading purpose.\n",filename);
371    setMapInMaps(m,"lenv","message","Unable to read produced file. Please try again later");
372    return ;
373  }
374  fseek(file, 0, SEEK_END);
375  long count = ftell(file);
376  rewind(file);
377  struct stat file_status; 
378  stat(filename, &file_status);
379  map* tmpMap1=getMap(content,"value");
380  if(tmpMap1==NULL){
381    addToMap(content,"value","");
382    tmpMap1=getMap(content,"value");
383  }
384  free(tmpMap1->value);
385  tmpMap1->value=(char*) malloc((count+1)*sizeof(char)); 
386  fread(tmpMap1->value,1,count,file);
387  tmpMap1->value[count]=0;
388  fclose(file);
389  char rsize[1000];
390  sprintf(rsize,"%ld",count);
391  addToMap(content,"size",rsize);
392}
393
394
395/**
396 * Write a file from value and length
397 *
398 * @param fname the file name
399 * @param val the value
400 * @param length the value length
401 */
402int writeFile(char* fname,char* val,int length){
403  FILE* of=fopen(fname,"wb");
404  if(of==NULL){
405    return -1;
406  }
407  size_t ret=fwrite(val,sizeof(char),length,of);
408  if(ret<length){
409    fprintf(stderr,"Write error occurred!\n");
410    fclose(of);
411    return -1;
412  }
413  fclose(of);
414  return 1;
415}
416
417/**
418 * Dump all values in a maps as files
419 *
420 * @param main_conf the maps containing the settings of the main.cfg file
421 * @param in the maps containing values to dump as files
422 */
423void dumpMapsValuesToFiles(maps** main_conf,maps** in){
424  map* tmpPath=getMapFromMaps(*main_conf,"main","tmpPath");
425  map* tmpSid=getMapFromMaps(*main_conf,"lenv","usid");
426  maps* inputs=*in;
427  int length=0;
428  while(inputs!=NULL){
429    if(getMap(inputs->content,"mimeType")!=NULL &&
430       getMap(inputs->content,"cache_file")==NULL){
431      map* cMap=inputs->content;
432      if(getMap(cMap,"length")!=NULL){
433        map* tmpLength=getMap(cMap,"length");
434        int len=atoi(tmpLength->value);
435        int k=0;
436        for(k=0;k<len;k++){
437          map* cMimeType=getMapArray(cMap,"mimeType",k);
438          map* cValue=getMapArray(cMap,"value",k);
439          map* cSize=getMapArray(cMap,"size",k);
440          char file_ext[32];
441          getFileExtension(cMimeType != NULL ? cMimeType->value : NULL, file_ext, 32);
442          char* val=(char*)malloc((strlen(tmpPath->value)+strlen(inputs->name)+strlen(tmpSid->value)+strlen(file_ext)+16)*sizeof(char));
443          sprintf(val,"%s/Input_%s_%s_%d.%s",tmpPath->value,inputs->name,tmpSid->value,k,file_ext);
444          length=0;
445          if(cSize!=NULL){
446            length=atoi(cSize->value);
447          }
448          writeFile(val,cValue->value,length);
449          setMapArray(cMap,"cache_file",k,val);
450          free(val);
451        }
452      }else{
453        int length=0;
454        map* cMimeType=getMap(cMap,"mimeType");
455        map* cValue=getMap(cMap,"value");
456        map* cSize=getMap(cMap,"size");
457        char file_ext[32];
458        getFileExtension(cMimeType != NULL ? cMimeType->value : NULL, file_ext, 32);
459        char *val=(char*)malloc((strlen(tmpPath->value)+strlen(inputs->name)+strlen(tmpSid->value)+strlen(file_ext)+16)*sizeof(char));
460        sprintf(val,"%s/Input_%s_%s_%d.%s",tmpPath->value,inputs->name,tmpSid->value,0,file_ext);
461        if(cSize!=NULL){
462          length=atoi(cSize->value);
463        }
464        writeFile(val,cValue->value,length);
465        addToMap(cMap,"cache_file",val);
466        free(val);
467      }
468    }
469    inputs=inputs->next;
470  }
471}
472
473
474/**
475 * Base64 encoding of a char*
476 *
477 * @param input the value to encode
478 * @param length the value length
479 * @return the buffer containing the base64 value
480 * @warning make sure to free the returned value
481 */
482char *base64(const char *input, int length)
483{
484  BIO *bmem, *b64;
485  BUF_MEM *bptr;
486
487  b64 = BIO_new(BIO_f_base64());
488  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
489  bmem = BIO_new(BIO_s_mem());
490  b64 = BIO_push(b64, bmem);
491  BIO_write(b64, input, length);
492  BIO_flush(b64);
493  BIO_get_mem_ptr(b64, &bptr);
494
495  char *buff = (char *)malloc((bptr->length+1)*sizeof(char));
496  memcpy(buff, bptr->data, bptr->length);
497  buff[bptr->length] = 0;
498
499  BIO_free_all(b64);
500
501  return buff;
502}
503
504/**
505 * Base64 decoding of a char*
506 *
507 * @param input the value to decode
508 * @param length the value length
509 * @param red the value length
510 * @return the buffer containing the base64 value
511 * @warning make sure to free the returned value
512 */
513char *base64d(const char *input, int length,int* red)
514{
515  BIO *b64, *bmem;
516
517  char *buffer = (char *)malloc(length);
518  if(buffer){
519    memset(buffer, 0, length);
520    b64 = BIO_new(BIO_f_base64());
521    if(b64){
522      bmem = BIO_new_mem_buf((unsigned char*)input,length);
523      bmem = BIO_push(b64, bmem);
524      *red=BIO_read(bmem, buffer, length);
525      buffer[length-1]=0;
526      BIO_free_all(bmem);
527    }
528  }
529  return buffer;
530}
531
532/**
533 * Read Base64 value and split it value by lines of 64 char.
534 *
535 * @param in the map containing the value to split
536 */
537void readBase64(map **in){
538  char *res = NULL;
539  char *curs = (*in)->value;
540  int i = 0;
541  for (i = 0; i <= strlen ((*in)->value) / 64;
542       i++)
543    {
544      if (res == NULL)
545        res =
546          (char *) malloc (65 * sizeof (char));
547      else
548        res =
549          (char *) realloc (res,
550                            (((i + 1) * 65) +
551                             i) * sizeof (char));
552      int csize = i * 65;
553      strncpy (res + csize, curs, 64);
554      if (i == strlen ((*in)->value) / 64)
555        strcat (res, "\n\0");
556      else
557        {
558          strncpy (res + (((i + 1) * 64) + i),
559                   "\n\0", 2);
560          curs += 64;
561        }
562    }
563  free ((*in)->value);
564  (*in)->value = zStrdup (res);
565  free (res);
566}
567
568
569/**
570 * Add the default values defined in the zcfg to a maps.
571 *
572 * @param out the maps containing the inputs or outputs given in the initial
573 *  HTTP request
574 * @param in the description of all inputs or outputs available for a service
575 * @param m the maps containing the settings of the main.cfg file
576 * @param type 0 for inputs and 1 for outputs
577 * @param err the map to store potential missing mandatory input parameters or
578 *  wrong output names depending on the type.
579 * @return "" if no error was detected, the name of last input or output causing
580 *  an error.
581 */
582char* addDefaultValues(maps** out,elements* in,maps* m,int type,map** err){
583  map *res=*err;
584  elements* tmpInputs=in;
585  elements* tmpInputss=NULL;
586  maps* out1=*out;
587  maps* out1s=NULL;
588  char *result=NULL;
589  int nb=0;
590  int inb=0;
591 loopOnInputs:
592  if(type==1){
593    while(out1!=NULL){
594      if(getElements(in,out1->name)==NULL){
595        if(res==NULL){
596          res=createMap("value",out1->name);
597        }else{
598          setMapArray(res,"value",nb,out1->name);
599        }
600        nb++;
601        result=out1->name;
602      }
603      inb++;
604      out1=out1->next;
605    }
606    if(res!=NULL){
607      fflush(stderr);
608      *err=res;
609      return result;
610    }
611    if(out1==NULL && inb>=1)
612      out1=*out;
613  }
614  while(tmpInputs!=NULL){
615    maps *tmpMaps=getMaps(out1,tmpInputs->name);
616    if(tmpMaps==NULL){
617      maps* tmpMaps2=createMaps(tmpInputs->name);
618      if(type==0){
619        map* tmpMapMinO=getMap(tmpInputs->content,"minOccurs");
620        if(tmpMapMinO!=NULL){
621          if(atoi(tmpMapMinO->value)>=1){
622            freeMaps(&tmpMaps2);
623            free(tmpMaps2);
624            if(res==NULL){
625              res=createMap("value",tmpInputs->name);
626            }else{
627              setMapArray(res,"value",nb,tmpInputs->name);
628            }
629            nb++;
630            result=tmpInputs->name;
631          }
632          else{
633            if(tmpMaps2->content==NULL)
634              tmpMaps2->content=createMap("minOccurs",tmpMapMinO->value);
635            else
636              addToMap(tmpMaps2->content,"minOccurs",tmpMapMinO->value);
637          }
638        }
639        if(res==NULL){
640          map* tmpMaxO=getMap(tmpInputs->content,"maxOccurs");
641          if(tmpMaxO!=NULL){
642            if(tmpMaps2->content==NULL)
643              tmpMaps2->content=createMap("maxOccurs",tmpMaxO->value);
644            else
645              addToMap(tmpMaps2->content,"maxOccurs",tmpMaxO->value);
646          }
647          map* tmpMaxMB=getMap(tmpInputs->content,"maximumMegabytes");
648          if(tmpMaxMB!=NULL){
649            if(tmpMaps2->content==NULL)
650              tmpMaps2->content=createMap("maximumMegabytes",tmpMaxMB->value);
651            else
652              addToMap(tmpMaps2->content,"maximumMegabytes",tmpMaxMB->value);
653          }
654        }
655      }
656     
657      if(res==NULL){
658        iotype* tmpIoType=tmpInputs->defaults;
659        if(tmpIoType!=NULL){
660          map* tmpm=tmpIoType->content;
661          while(tmpm!=NULL){
662            if(tmpMaps2->content==NULL)
663              tmpMaps2->content=createMap(tmpm->name,tmpm->value);
664            else{
665              addToMap(tmpMaps2->content,tmpm->name,tmpm->value);
666            }
667            tmpm=tmpm->next;
668          }
669        }
670        if(tmpMaps2->content==NULL){
671          tmpMaps2->content=createMap("inRequest","false");
672          dumpMaps(tmpMaps2);
673        }
674        else
675          addToMap(tmpMaps2->content,"inRequest","false");
676        if(type==0){
677          map *tmpMap=getMap(tmpMaps2->content,"value");
678          if(tmpMap==NULL)
679            addToMap(tmpMaps2->content,"value","NULL");
680        }
681        elements* tmpElements=getElements(in,tmpMaps2->name);
682        if(tmpElements!=NULL && tmpElements->child!=NULL){
683          char *res=addDefaultValues(&tmpMaps2->child,tmpElements->child,m,type,err);
684          if(strlen(res)>0){
685            return res;
686          }
687        }
688
689        if(out1==NULL){
690          *out=dupMaps(&tmpMaps2);
691          out1=*out;
692        }
693        else
694          addMapsToMaps(&out1,tmpMaps2);
695        freeMap(&tmpMaps2->content);
696        free(tmpMaps2->content);
697        tmpMaps2->content=NULL;
698        freeMaps(&tmpMaps2);
699        free(tmpMaps2);
700        tmpMaps2=NULL;
701      }
702    }
703    else { 
704      iotype* tmpIoType=NULL;
705      if(tmpMaps->content!=NULL){
706        tmpIoType=getIoTypeFromElement(tmpInputs,tmpInputs->name,
707                                       tmpMaps->content);
708        if(type==0) {
709          /**
710           * In case of an Input maps, then add the minOccurs and maxOccurs to the
711           * content map.
712           */
713          map* tmpMap1=getMap(tmpInputs->content,"minOccurs");
714          if(tmpMap1!=NULL){
715            if(tmpMaps->content==NULL)
716              tmpMaps->content=createMap("minOccurs",tmpMap1->value);
717            else
718              addToMap(tmpMaps->content,"minOccurs",tmpMap1->value);
719          }
720          map* tmpMaxO=getMap(tmpInputs->content,"maxOccurs");
721          if(tmpMaxO!=NULL){
722            if(tmpMaps->content==NULL)
723              tmpMaps->content=createMap("maxOccurs",tmpMaxO->value);
724            else
725              addToMap(tmpMaps->content,"maxOccurs",tmpMaxO->value);
726          }
727          map* tmpMaxMB=getMap(tmpInputs->content,"maximumMegabytes");
728          if(tmpMaxMB!=NULL){
729            if(tmpMaps->content==NULL)
730              tmpMaps->content=createMap("maximumMegabytes",tmpMaxMB->value);
731            else
732              addToMap(tmpMaps->content,"maximumMegabytes",tmpMaxMB->value);
733          }
734          /**
735           * Parsing BoundingBoxData, fill the following map and then add it to
736           * the content map of the Input maps:
737           * lowerCorner, upperCorner, srs and dimensions
738           * cf. parseBoundingBox
739           */
740          if(tmpInputs->format!=NULL && strcasecmp(tmpInputs->format,"BoundingBoxData")==0){
741            maps* tmpI=getMaps(*out,tmpInputs->name);
742            if(tmpI!=NULL){
743              map* tmpV=getMap(tmpI->content,"value");
744              if(tmpV!=NULL){
745                char *tmpVS=strdup(tmpV->value);
746                map* tmp=parseBoundingBox(tmpVS);
747                free(tmpVS);
748                map* tmpC=tmp;
749                while(tmpC!=NULL){
750                  addToMap(tmpMaps->content,tmpC->name,tmpC->value);
751                  tmpC=tmpC->next;
752                }
753                freeMap(&tmp);
754                free(tmp);
755              }
756            }
757          }
758        }
759      }else{
760        if(tmpInputs!=NULL){
761          tmpIoType=tmpInputs->defaults;
762        }
763      }
764
765      if(tmpIoType!=NULL){
766        map* tmpContent=tmpIoType->content;
767        map* cval=NULL;
768        int hasPassed=-1;
769        while(tmpContent!=NULL){
770          if((cval=getMap(tmpMaps->content,tmpContent->name))==NULL){
771#ifdef DEBUG
772            fprintf(stderr,"addDefaultValues %s => %s\n",tmpContent->name,tmpContent->value);
773#endif
774            if(tmpMaps->content==NULL)
775              tmpMaps->content=createMap(tmpContent->name,tmpContent->value);
776            else
777              addToMap(tmpMaps->content,tmpContent->name,tmpContent->value);
778           
779            if(hasPassed<0 && type==0 && getMap(tmpMaps->content,"isArray")!=NULL){
780              map* length=getMap(tmpMaps->content,"length");
781              int i;
782              char *tcn=strdup(tmpContent->name);
783              for(i=1;i<atoi(length->value);i++){
784#ifdef DEBUG
785                dumpMap(tmpMaps->content);
786                fprintf(stderr,"addDefaultValues %s_%d => %s\n",tcn,i,tmpContent->value);
787#endif
788                int len=strlen((char*) tcn);
789                char *tmp1=(char *)malloc((len+10)*sizeof(char));
790                sprintf(tmp1,"%s_%d",tcn,i);
791#ifdef DEBUG
792                fprintf(stderr,"addDefaultValues %s => %s\n",tmp1,tmpContent->value);
793#endif
794                addToMap(tmpMaps->content,tmp1,tmpContent->value);
795                free(tmp1);
796                hasPassed=1;
797              }
798              free(tcn);
799            }
800          }
801          tmpContent=tmpContent->next;
802        }
803#ifdef USE_MS
804        /**
805         * check for useMapServer presence
806         */
807        if(tmpIoType!=NULL){
808          map* tmpCheck=getMap(tmpIoType->content,"useMapServer");
809          if(tmpCheck!=NULL){
810            // Get the default value
811            tmpIoType=getIoTypeFromElement(tmpInputs,tmpInputs->name,NULL);
812            tmpCheck=getMap(tmpMaps->content,"mimeType");
813            addToMap(tmpMaps->content,"requestedMimeType",tmpCheck->value);
814            map* cursor=tmpIoType->content;
815            while(cursor!=NULL){
816              addToMap(tmpMaps->content,cursor->name,cursor->value);
817              cursor=cursor->next;
818            }
819         
820            cursor=tmpInputs->content;
821            while(cursor!=NULL){
822              if(strcasecmp(cursor->name,"Title")==0 ||
823                 strcasecmp(cursor->name,"Abstract")==0)
824                addToMap(tmpMaps->content,cursor->name,cursor->value);
825              cursor=cursor->next;
826            }
827          }
828        }
829#endif
830      }
831      if(tmpMaps->content==NULL)
832        tmpMaps->content=createMap("inRequest","true");
833      else
834        addToMap(tmpMaps->content,"inRequest","true");
835      elements* tmpElements=getElements(in,tmpMaps->name);
836      if(tmpElements!=NULL && tmpElements->child!=NULL){
837        char *res=addDefaultValues(&tmpMaps->child,tmpElements->child,m,type,err);
838        if(strlen(res)>0){
839          return res;
840        }
841      }
842    }
843    tmpInputs=tmpInputs->next;
844  }
845  if(tmpInputss!=NULL){
846    out1=out1s;
847    tmpInputs=tmpInputss;
848    tmpInputss=NULL;
849    out1s=NULL;
850    goto loopOnInputs;
851  }
852  if(res!=NULL){
853    *err=res;
854    return result;
855  }
856  return "";
857}
858
859/**
860 * Access the last error message returned by the OS when trying to dynamically
861 * load a shared library.
862 *
863 * @return the last error message
864 * @warning The character string returned from getLastErrorMessage resides
865 * in a static buffer. The application should not write to this
866 * buffer or attempt to free() it.
867 */ 
868char* getLastErrorMessage() {                                             
869#ifdef WIN32
870  LPVOID lpMsgBuf;
871  DWORD errCode = GetLastError();
872  static char msg[ERROR_MSG_MAX_LENGTH];
873  size_t i;
874 
875  DWORD length = FormatMessage(
876                               FORMAT_MESSAGE_ALLOCATE_BUFFER | 
877                               FORMAT_MESSAGE_FROM_SYSTEM |
878                               FORMAT_MESSAGE_IGNORE_INSERTS,
879                               NULL,
880                               errCode,
881                               MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
882                               (LPTSTR) &lpMsgBuf,
883                               0, NULL );       
884 
885#ifdef UNICODE         
886  wcstombs_s( &i, msg, ERROR_MSG_MAX_LENGTH,
887              (wchar_t*) lpMsgBuf, _TRUNCATE );
888#else
889  strcpy_s( msg, ERROR_MSG_MAX_LENGTH,
890            (char *) lpMsgBuf );               
891#endif 
892  LocalFree(lpMsgBuf);
893
894  return msg;
895#else
896  return dlerror();
897#endif
898}
899
900#include <dirent.h>
901#ifndef RELY_ON_DB
902/**
903 * Read the Result file (.res).
904 *
905 * @param conf the maps containing the setting of the main.cfg file
906 * @param pid the service identifier (usid key from the [lenv] section)
907 */
908void readFinalRes(maps* conf,char* pid,map* statusInfo){
909  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
910  char* fbkpid =
911    (char *)
912    malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
913  sprintf (fbkpid, "%s/%s.res", r_inputs->value, pid);
914  struct stat file_status;
915  int istat = stat (fbkpid, &file_status);
916  if (istat == 0 && file_status.st_size > 0)
917    {
918      maps *res = (maps *) malloc (MAPS_SIZE);
919      conf_read (fbkpid, res);
920      res->child=NULL;
921      map* status=getMapFromMaps(res,"status","status");
922      addToMap(statusInfo,"Status",status->value);
923      freeMaps(&res);
924      free(res);
925    }
926  else
927    addToMap(statusInfo,"Status","Failed"); 
928  free(fbkpid);
929}
930
931/**
932 * Check if a service is running.
933 *
934 * @param conf the maps containing the setting of the main.cfg file
935 * @param pid the unique service identifier (usid from the lenv section)
936 * @return 1 in case the service is still running, 0 otherwise
937 */
938int isRunning(maps* conf,char* pid){
939  int res=0;
940  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
941  char* fbkpid =
942    (char *)
943    malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
944  sprintf (fbkpid, "%s/%s.pid", r_inputs->value, pid); 
945  FILE* f0 = fopen (fbkpid, "r");
946  if(f0!=NULL){
947    fclose(f0);
948    res=1;
949  }
950  free(fbkpid);
951  return res;
952}
953#else
954#include "sqlapi.h"
955#endif
956
957/**
958 * Run GetStatus requests.
959 *
960 * @param conf the maps containing the setting of the main.cfg file
961 * @param pid the service identifier (usid key from the [lenv] section)
962 * @param req the request (GetStatus / GetResult)
963 */
964void runGetStatus(maps* conf,char* pid,char* req){
965  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
966  char *sid=getStatusId(conf,pid);
967  if(sid==NULL){
968    errorException (conf, _("The JobID from the request does not match any of the Jobs running on this server"),
969                    "NoSuchJob", pid);
970  }else{
971    map* statusInfo=createMap("JobID",pid);
972    if(isRunning(conf,pid)>0){         
973      if(strncasecmp(req,"GetResult",strlen(req))==0){
974        errorException (conf, _("The result for the requested JobID has not yet been generated. "),
975                        "ResultNotReady", pid);
976        return;
977      }
978      else
979        if(strncasecmp(req,"GetStatus",strlen(req))==0){
980          addToMap(statusInfo,"Status","Running");
981          char* tmpStr=_getStatus(conf,pid);
982          if(tmpStr!=NULL && strncmp(tmpStr,"-1",2)!=0){
983            char *tmpStr1=strdup(tmpStr);
984            char *tmpStr0=strdup(strstr(tmpStr,"|")+1);
985            free(tmpStr);
986            tmpStr1[strlen(tmpStr1)-strlen(tmpStr0)-1]='\0';
987            addToMap(statusInfo,"PercentCompleted",tmpStr1);
988            addToMap(statusInfo,"Message",tmpStr0);
989            free(tmpStr0);
990            free(tmpStr1);
991          }
992        }
993    }
994    else{
995      if(strncasecmp(req,"GetResult",strlen(req))==0){
996        char* result=_getStatusFile(conf,pid);
997        if(result!=NULL){
998          char *encoding=getEncoding(conf);
999          printf("Content-Type: text/xml; charset=%s\r\nStatus: 200 OK\r\n\r\n",encoding);
1000          printf("%s",result);
1001          fflush(stdout);
1002          freeMap(&statusInfo);
1003          free(statusInfo);
1004          return;
1005        }else{
1006          errorException (conf, _("The result for the requested JobID has not yet been generated. "),
1007                          "ResultNotReady", pid);
1008          freeMap(&statusInfo);
1009          free(statusInfo);
1010          return;
1011        }
1012      }else
1013        if(strncasecmp(req,"GetStatus",strlen(req))==0){
1014          readFinalRes(conf,pid,statusInfo);
1015          char* tmpStr=_getStatus(conf,pid);
1016          if(tmpStr!=NULL && strncmp(tmpStr,"-1",2)!=0){
1017            char *tmpStr1=strdup(tmpStr);
1018            char *tmpStr0=strdup(strstr(tmpStr,"|")+1);
1019            free(tmpStr);
1020            tmpStr1[strlen(tmpStr1)-strlen(tmpStr0)-1]='\0';
1021            addToMap(statusInfo,"PercentCompleted",tmpStr1);
1022            addToMap(statusInfo,"Message",tmpStr0);
1023            free(tmpStr0);
1024            free(tmpStr1);
1025          }
1026        }
1027    }
1028    printStatusInfo(conf,statusInfo,req);
1029    freeMap(&statusInfo);
1030    free(statusInfo);
1031  }
1032  return;
1033}
1034
1035/**
1036 * Run Dismiss requests.
1037 *
1038 * @param conf the maps containing the setting of the main.cfg file
1039 * @param pid the service identifier (usid key from the [lenv] section)
1040 */
1041void runDismiss(maps* conf,char* pid){
1042  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
1043  char *sid=getStatusId(conf,pid);
1044  if(sid==NULL){
1045    errorException (conf, _("The JobID from the request does not match any of the Jobs running on this server"),
1046                    "NoSuchJob", pid);
1047  }else{
1048    // We should send the Dismiss request to the target host if it differs
1049    char* fbkpid =
1050      (char *)
1051      malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
1052    sprintf (fbkpid, "%s/%s.pid", r_inputs->value, pid);
1053    FILE* f0 = fopen (fbkpid, "r");
1054    if(f0!=NULL){
1055      long flen;
1056      char *fcontent;
1057      fseek (f0, 0, SEEK_END);
1058      flen = ftell (f0);
1059      fseek (f0, 0, SEEK_SET);
1060      fcontent = (char *) malloc ((flen + 1) * sizeof (char));
1061      fread(fcontent,flen,1,f0);
1062      fcontent[flen]=0;
1063      fclose(f0);
1064#ifndef WIN32
1065      kill(atoi(fcontent),SIGKILL);
1066#else
1067      HANDLE myZooProcess=OpenProcess(PROCESS_ALL_ACCESS,false,atoi(fcontent));
1068      TerminateProcess(myZooProcess,1);
1069      CloseHandle(myZooProcess);
1070#endif
1071      free(fcontent);
1072    }
1073    free(fbkpid);
1074    struct dirent *dp;
1075    DIR *dirp = opendir(r_inputs->value);
1076    char fileName[1024];
1077    int hasFile=-1;
1078    if(dirp!=NULL){
1079      while ((dp = readdir(dirp)) != NULL){
1080#ifdef DEBUG
1081        fprintf(stderr,"File : %s searched : %s\n",dp->d_name,tmp);
1082#endif
1083        if(strstr(dp->d_name,pid)!=0){
1084          sprintf(fileName,"%s/%s",r_inputs->value,dp->d_name);
1085          if(unlink(fileName)!=0){
1086            errorException (conf, 
1087                            _("The job cannot be removed, a file cannot be removed"),
1088                            "NoApplicableCode", NULL);
1089            return;
1090          }
1091        }
1092      }
1093    }
1094#ifdef RELY_ON_DB
1095    removeService(conf,pid);
1096#endif
1097    map* statusInfo=createMap("JobID",pid);
1098    addToMap(statusInfo,"Status","Dismissed");
1099    printStatusInfo(conf,statusInfo,"Dismiss");
1100    free(statusInfo);
1101  }
1102  return;
1103}
1104
1105extern int getServiceFromFile (maps *, const char *, service **);
1106
1107/**
1108 * Parse the service file using getServiceFromFile or use getServiceFromYAML
1109 * if YAML support was activated.
1110 *
1111 * @param conf the conf maps containing the main.cfg settings
1112 * @param file the file name to parse
1113 * @param service the service to update witht the file content
1114 * @param name the service name
1115 * @return true if the file can be parsed or false
1116 * @see getServiceFromFile, getServiceFromYAML
1117 */
1118int readServiceFile (maps * conf, char *file, service ** service, char *name){
1119  int t = getServiceFromFile (conf, file, service);
1120#ifdef YAML
1121  if (t < 0){
1122    t = getServiceFromYAML (conf, file, service, name);
1123  }
1124#endif
1125  return t;
1126}
1127
1128/**
1129 * Create the profile registry.
1130 *
1131 * The profile registry is optional (created only if the registry key is
1132 * available in the [main] section of the main.cfg file) and can be used to
1133 * store the profiles hierarchy. The registry is a directory which should
1134 * contain the following sub-directories:
1135 *  * concept: direcotry containing .html files describing concept
1136 *  * generic: directory containing .zcfg files for wps:GenericProcess
1137 *  * implementation: directory containing .zcfg files for wps:Process
1138 *
1139 * @param m the conf maps containing the main.cfg settings
1140 * @param r the registry to update
1141 * @param reg_dir the resgitry
1142 * @return 0 if the resgitry is null or was correctly updated, -1 on failure
1143 */
1144int createRegistry (maps* m,registry ** r, char *reg_dir)
1145{
1146  char registryKeys[3][15]={
1147    "concept",
1148    "generic",
1149    "implementation"
1150  };
1151  int scount = 0,i=0;
1152  if (reg_dir == NULL)
1153    return 0;
1154  for(i=0;i<3;i++){
1155    char * tmpName =
1156      (char *) malloc ((strlen (reg_dir) + strlen (registryKeys[i]) + 2) *
1157                       sizeof (char));
1158    sprintf (tmpName, "%s/%s", reg_dir, registryKeys[i]);
1159   
1160    DIR *dirp1 = opendir (tmpName);
1161    if(dirp1==NULL){
1162      setMapInMaps(m,"lenv","message",_("Unable to open the registry directory."));
1163      setMapInMaps(m,"lenv","type","InternalError");
1164      return -1;
1165    }
1166    struct dirent *dp1;
1167    while ((dp1 = readdir (dirp1)) != NULL){
1168      char* extn = strstr(dp1->d_name, ".zcfg");
1169      if(dp1->d_name[0] != '.' && extn != NULL && strlen(extn) == 5)
1170        {
1171          int t;
1172          char *tmps1=
1173            (char *) malloc ((strlen (tmpName) + strlen (dp1->d_name) + 2) *
1174                             sizeof (char));
1175          sprintf (tmps1, "%s/%s", tmpName, dp1->d_name);
1176          char *tmpsn = zStrdup (dp1->d_name);
1177          tmpsn[strlen (tmpsn) - 5] = 0;
1178          service* s1 = (service *) malloc (SERVICE_SIZE);
1179          if (s1 == NULL)
1180            {
1181              setMapInMaps(m,"lenv","message",_("Unable to allocate memory."));
1182              setMapInMaps(m,"lenv","type","InternalError");
1183              return -2;
1184            }
1185          t = readServiceFile (m, tmps1, &s1, tmpsn);
1186          free (tmpsn);
1187          if (t < 0)
1188            {
1189              map *tmp00 = getMapFromMaps (m, "lenv", "message");
1190              char tmp01[1024];
1191              if (tmp00 != NULL)
1192                sprintf (tmp01, _("Unable to parse the ZCFG file: %s (%s)"),
1193                         dp1->d_name, tmp00->value);
1194              else
1195                sprintf (tmp01, _("Unable to parse the ZCFG file: %s."),
1196                         dp1->d_name);
1197              setMapInMaps(m,"lenv","message",tmp01);
1198              setMapInMaps(m,"lenv","type","InternalError");
1199              return -1;
1200            }
1201          if(strncasecmp(registryKeys[i],"implementation",14)==0){
1202            inheritance(*r,&s1);
1203          }
1204          addServiceToRegistry(r,registryKeys[i],s1);
1205          freeService (&s1);
1206          free (s1);
1207          scount++;
1208        }
1209    }
1210    (void) closedir (dirp1);
1211  }
1212  return 0;
1213}
1214
1215#ifdef WIN32
1216/**
1217 * Create a KVP request for executing background task.
1218 * TODO: use the XML request in case of input POST request.
1219 *
1220 * @param m the maps containing the parameters from the main.cfg file
1221 * @param length the total length of the KVP parameters
1222 * @param type
1223 */
1224char* getMapsAsKVP(maps* m,int length,int type){
1225  char *dataInputsKVP=(char*) malloc(length*sizeof(char));
1226  char *dataInputsKVPi=NULL;
1227  maps* curs=m;
1228  int i=0;
1229  while(curs!=NULL){
1230    map *inRequest=getMap(curs->content,"inRequest");
1231    map *hasLength=getMap(curs->content,"length");
1232    if((inRequest!=NULL && strncasecmp(inRequest->value,"true",4)==0) ||
1233       inRequest==NULL){
1234      if(i==0)
1235        if(type==0){
1236          sprintf(dataInputsKVP,"%s=",curs->name);
1237          if(hasLength!=NULL){
1238            dataInputsKVPi=(char*)malloc((strlen(curs->name)+2)*sizeof(char));
1239            sprintf(dataInputsKVPi,"%s=",curs->name);
1240          }
1241        }
1242        else
1243          sprintf(dataInputsKVP,"%s",curs->name);
1244      else{
1245        char *temp=zStrdup(dataInputsKVP);
1246        if(type==0)
1247          sprintf(dataInputsKVP,"%s;%s=",temp,curs->name);
1248        else
1249          sprintf(dataInputsKVP,"%s;%s",temp,curs->name);
1250      }
1251      map* icurs=curs->content;
1252      if(type==0){
1253        char *temp=zStrdup(dataInputsKVP);
1254        if(getMap(curs->content,"xlink:href")!=NULL)
1255          sprintf(dataInputsKVP,"%sReference",temp);
1256        else{
1257          if(hasLength!=NULL){
1258            int j;
1259            for(j=0;j<atoi(hasLength->value);j++){
1260              map* tmp0=getMapArray(curs->content,"value",j);
1261              if(j==0)
1262                free(temp);
1263              temp=zStrdup(dataInputsKVP);
1264              if(j==0)
1265                sprintf(dataInputsKVP,"%s%s",temp,tmp0->value);
1266              else
1267                sprintf(dataInputsKVP,"%s;%s%s",temp,dataInputsKVPi,tmp0->value);
1268            }
1269          }
1270          else
1271            sprintf(dataInputsKVP,"%s%s",temp,icurs->value);
1272        }
1273        free(temp);
1274      }
1275      while(icurs!=NULL){
1276        if(strncasecmp(icurs->name,"value",5)!=0 &&
1277           strncasecmp(icurs->name,"mimeType_",9)!=0 &&
1278           strncasecmp(icurs->name,"dataType_",9)!=0 &&
1279           strncasecmp(icurs->name,"size",4)!=0 &&
1280           strncasecmp(icurs->name,"length",4)!=0 &&
1281           strncasecmp(icurs->name,"isArray",7)!=0 &&
1282           strcasecmp(icurs->name,"Reference")!=0 &&
1283           strcasecmp(icurs->name,"minOccurs")!=0 &&
1284           strcasecmp(icurs->name,"maxOccurs")!=0 &&
1285           strncasecmp(icurs->name,"fmimeType",9)!=0 &&
1286           strcasecmp(icurs->name,"inRequest")!=0){
1287          char *itemp=zStrdup(dataInputsKVP);
1288          if(strcasecmp(icurs->name,"xlink:href")!=0)
1289            sprintf(dataInputsKVP,"%s@%s=%s",itemp,icurs->name,icurs->value);
1290          else
1291            sprintf(dataInputsKVP,"%s@%s=%s",itemp,icurs->name,url_encode(icurs->value));
1292          free(itemp);
1293        }
1294        icurs=icurs->next;
1295      }
1296    }
1297    curs=curs->next;
1298    i++;
1299  }
1300  return dataInputsKVP;
1301}
1302#endif
Note: See TracBrowser for help on using the repository browser.

Search

ZOO Sponsors

http://www.zoo-project.org/trac/chrome/site/img/geolabs-logo.pnghttp://www.zoo-project.org/trac/chrome/site/img/neogeo-logo.png http://www.zoo-project.org/trac/chrome/site/img/apptech-logo.png http://www.zoo-project.org/trac/chrome/site/img/3liz-logo.png http://www.zoo-project.org/trac/chrome/site/img/gateway-logo.png

Become a sponsor !

Knowledge partners

http://www.zoo-project.org/trac/chrome/site/img/ocu-logo.png http://www.zoo-project.org/trac/chrome/site/img/gucas-logo.png http://www.zoo-project.org/trac/chrome/site/img/polimi-logo.png http://www.zoo-project.org/trac/chrome/site/img/fem-logo.png http://www.zoo-project.org/trac/chrome/site/img/supsi-logo.png http://www.zoo-project.org/trac/chrome/site/img/cumtb-logo.png

Become a knowledge partner

Related links

http://zoo-project.org/img/ogclogo.png http://zoo-project.org/img/osgeologo.png