source: branches/prototype-v0/zoo-project/zoo-kernel/server_internal.c @ 822

Last change on this file since 822 was 822, checked in by djay, 3 years ago

Commit the minimal requirements for remote HPC support

  • Property svn:keywords set to Id
File size: 35.5 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    setMapInMaps(m,"lenv","message","Unable to read produced file. Please try again later");
371    return ;
372  }
373  fseek(file, 0, SEEK_END);
374  long count = ftell(file);
375  rewind(file);
376  struct stat file_status; 
377  stat(filename, &file_status);
378  map* tmpMap1=getMap(content,"value");
379  if(tmpMap1==NULL){
380    addToMap(content,"value","");
381    tmpMap1=getMap(content,"value");
382  }
383  free(tmpMap1->value);
384  tmpMap1->value=(char*) malloc((count+1)*sizeof(char)); 
385  fread(tmpMap1->value,1,count,file);
386  tmpMap1->value[count]=0;
387  fclose(file);
388  char rsize[1000];
389  sprintf(rsize,"%ld",count);
390  addToMap(content,"size",rsize);
391}
392
393
394/**
395 * Write a file from value and length
396 *
397 * @param fname the file name
398 * @param val the value
399 * @param length the value length
400 */
401int writeFile(char* fname,char* val,int length){
402  FILE* of=fopen(fname,"wb");
403  if(of==NULL){
404    return -1;
405  }
406  size_t ret=fwrite(val,sizeof(char),length,of);
407  if(ret<length){
408    fprintf(stderr,"Write error occurred!\n");
409    fclose(of);
410    return -1;
411  }
412  fclose(of);
413  return 1;
414}
415
416/**
417 * Dump all values in a maps as files
418 *
419 * @param main_conf the maps containing the settings of the main.cfg file
420 * @param in the maps containing values to dump as files
421 */
422void dumpMapsValuesToFiles(maps** main_conf,maps** in){
423  map* tmpPath=getMapFromMaps(*main_conf,"main","tmpPath");
424  map* tmpSid=getMapFromMaps(*main_conf,"lenv","usid");
425  maps* inputs=*in;
426  int length=0;
427  while(inputs!=NULL){
428    if(getMap(inputs->content,"mimeType")!=NULL &&
429       getMap(inputs->content,"cache_file")==NULL){
430      map* cMap=inputs->content;
431      if(getMap(cMap,"length")!=NULL){
432        map* tmpLength=getMap(cMap,"length");
433        int len=atoi(tmpLength->value);
434        int k=0;
435        for(k=0;k<len;k++){
436          map* cMimeType=getMapArray(cMap,"mimeType",k);
437          map* cValue=getMapArray(cMap,"value",k);
438          map* cSize=getMapArray(cMap,"size",k);
439          char file_ext[32];
440          getFileExtension(cMimeType != NULL ? cMimeType->value : NULL, file_ext, 32);
441          char* val=(char*)malloc((strlen(tmpPath->value)+strlen(inputs->name)+strlen(tmpSid->value)+strlen(file_ext)+16)*sizeof(char));
442          sprintf(val,"%s/Input_%s_%s_%d.%s",tmpPath->value,inputs->name,tmpSid->value,k,file_ext);
443          length=0;
444          if(cSize!=NULL){
445            length=atoi(cSize->value);
446          }
447          writeFile(val,cValue->value,length);
448          setMapArray(cMap,"cache_file",k,val);
449          free(val);
450        }
451      }else{
452        int length=0;
453        map* cMimeType=getMap(cMap,"mimeType");
454        map* cValue=getMap(cMap,"value");
455        map* cSize=getMap(cMap,"size");
456        char file_ext[32];
457        getFileExtension(cMimeType != NULL ? cMimeType->value : NULL, file_ext, 32);
458        char *val=(char*)malloc((strlen(tmpPath->value)+strlen(inputs->name)+strlen(tmpSid->value)+strlen(file_ext)+16)*sizeof(char));
459        sprintf(val,"%s/Input_%s_%s_%d.%s",tmpPath->value,inputs->name,tmpSid->value,0,file_ext);
460        if(cSize!=NULL){
461          length=atoi(cSize->value);
462        }
463        writeFile(val,cValue->value,length);
464        addToMap(cMap,"cache_file",val);
465        free(val);
466      }
467    }
468    inputs=inputs->next;
469  }
470}
471
472
473/**
474 * Base64 encoding of a char*
475 *
476 * @param input the value to encode
477 * @param length the value length
478 * @return the buffer containing the base64 value
479 * @warning make sure to free the returned value
480 */
481char *base64(const char *input, int length)
482{
483  BIO *bmem, *b64;
484  BUF_MEM *bptr;
485
486  b64 = BIO_new(BIO_f_base64());
487  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
488  bmem = BIO_new(BIO_s_mem());
489  b64 = BIO_push(b64, bmem);
490  BIO_write(b64, input, length);
491  BIO_flush(b64);
492  BIO_get_mem_ptr(b64, &bptr);
493
494  char *buff = (char *)malloc((bptr->length+1)*sizeof(char));
495  memcpy(buff, bptr->data, bptr->length);
496  buff[bptr->length] = 0;
497
498  BIO_free_all(b64);
499
500  return buff;
501}
502
503/**
504 * Base64 decoding of a char*
505 *
506 * @param input the value to decode
507 * @param length the value length
508 * @param red the value length
509 * @return the buffer containing the base64 value
510 * @warning make sure to free the returned value
511 */
512char *base64d(const char *input, int length,int* red)
513{
514  BIO *b64, *bmem;
515
516  char *buffer = (char *)malloc(length);
517  if(buffer){
518    memset(buffer, 0, length);
519    b64 = BIO_new(BIO_f_base64());
520    if(b64){
521      bmem = BIO_new_mem_buf((unsigned char*)input,length);
522      bmem = BIO_push(b64, bmem);
523      *red=BIO_read(bmem, buffer, length);
524      buffer[length-1]=0;
525      BIO_free_all(bmem);
526    }
527  }
528  return buffer;
529}
530
531/**
532 * Read Base64 value and split it value by lines of 64 char.
533 *
534 * @param in the map containing the value to split
535 */
536void readBase64(map **in){
537  char *res = NULL;
538  char *curs = (*in)->value;
539  int i = 0;
540  for (i = 0; i <= strlen ((*in)->value) / 64;
541       i++)
542    {
543      if (res == NULL)
544        res =
545          (char *) malloc (65 * sizeof (char));
546      else
547        res =
548          (char *) realloc (res,
549                            (((i + 1) * 65) +
550                             i) * sizeof (char));
551      int csize = i * 65;
552      strncpy (res + csize, curs, 64);
553      if (i == strlen ((*in)->value) / 64)
554        strcat (res, "\n\0");
555      else
556        {
557          strncpy (res + (((i + 1) * 64) + i),
558                   "\n\0", 2);
559          curs += 64;
560        }
561    }
562  free ((*in)->value);
563  (*in)->value = zStrdup (res);
564  free (res);
565}
566
567
568/**
569 * Add the default values defined in the zcfg to a maps.
570 *
571 * @param out the maps containing the inputs or outputs given in the initial
572 *  HTTP request
573 * @param in the description of all inputs or outputs available for a service
574 * @param m the maps containing the settings of the main.cfg file
575 * @param type 0 for inputs and 1 for outputs
576 * @param err the map to store potential missing mandatory input parameters or
577 *  wrong output names depending on the type.
578 * @return "" if no error was detected, the name of last input or output causing
579 *  an error.
580 */
581char* addDefaultValues(maps** out,elements* in,maps* m,int type,map** err){
582  map *res=*err;
583  elements* tmpInputs=in;
584  elements* tmpInputss=NULL;
585  maps* out1=*out;
586  maps* out1s=NULL;
587  char *result=NULL;
588  int nb=0;
589  int inb=0;
590 loopOnInputs:
591  if(type==1){
592    while(out1!=NULL){
593      if(getElements(in,out1->name)==NULL){
594        if(res==NULL){
595          res=createMap("value",out1->name);
596        }else{
597          setMapArray(res,"value",nb,out1->name);
598        }
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 /*toto*/{ 
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(/*tmpMaps->child!=NULL && */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    /*if(tmpInputs->child!=NULL){
844      tmpInputss=tmpInputs->next;
845      tmpInputs=tmpInputs->child;
846      if(tmpMaps!=NULL){
847        out1=tmpMaps->child;
848        out1s=tmpMaps;
849      }
850      }else*/
851      tmpInputs=tmpInputs->next;
852  }
853  if(tmpInputss!=NULL){
854    out1=out1s;
855    tmpInputs=tmpInputss;
856    tmpInputss=NULL;
857    out1s=NULL;
858    goto loopOnInputs;
859  }
860  if(res!=NULL){
861    *err=res;
862    return result;
863  }
864  return "";
865}
866
867/**
868 * Access the last error message returned by the OS when trying to dynamically
869 * load a shared library.
870 *
871 * @return the last error message
872 * @warning The character string returned from getLastErrorMessage resides
873 * in a static buffer. The application should not write to this
874 * buffer or attempt to free() it.
875 */ 
876char* getLastErrorMessage() {                                             
877#ifdef WIN32
878  LPVOID lpMsgBuf;
879  DWORD errCode = GetLastError();
880  static char msg[ERROR_MSG_MAX_LENGTH];
881  size_t i;
882 
883  DWORD length = FormatMessage(
884                               FORMAT_MESSAGE_ALLOCATE_BUFFER | 
885                               FORMAT_MESSAGE_FROM_SYSTEM |
886                               FORMAT_MESSAGE_IGNORE_INSERTS,
887                               NULL,
888                               errCode,
889                               MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
890                               (LPTSTR) &lpMsgBuf,
891                               0, NULL );       
892 
893#ifdef UNICODE         
894  wcstombs_s( &i, msg, ERROR_MSG_MAX_LENGTH,
895              (wchar_t*) lpMsgBuf, _TRUNCATE );
896#else
897  strcpy_s( msg, ERROR_MSG_MAX_LENGTH,
898            (char *) lpMsgBuf );               
899#endif 
900  LocalFree(lpMsgBuf);
901
902  return msg;
903#else
904  return dlerror();
905#endif
906}
907
908#include <dirent.h>
909#ifndef RELY_ON_DB
910/**
911 * Read the Result file (.res).
912 *
913 * @param conf the maps containing the setting of the main.cfg file
914 * @param pid the service identifier (usid key from the [lenv] section)
915 */
916void readFinalRes(maps* conf,char* pid,map* statusInfo){
917  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
918  char* fbkpid =
919    (char *)
920    malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
921  sprintf (fbkpid, "%s/%s.res", r_inputs->value, pid);
922  struct stat file_status;
923  int istat = stat (fbkpid, &file_status);
924  if (istat == 0 && file_status.st_size > 0)
925    {
926      maps *res = (maps *) malloc (MAPS_SIZE);
927      conf_read (fbkpid, res);
928      res->child=NULL;
929      map* status=getMapFromMaps(res,"status","status");
930      addToMap(statusInfo,"Status",status->value);
931      freeMaps(&res);
932      free(res);
933    }
934  else
935    addToMap(statusInfo,"Status","Failed"); 
936  free(fbkpid);
937}
938
939/**
940 * Check if a service is running.
941 *
942 * @param conf the maps containing the setting of the main.cfg file
943 * @param pid the unique service identifier (usid from the lenv section)
944 * @return 1 in case the service is still running, 0 otherwise
945 */
946int isRunning(maps* conf,char* pid){
947  int res=0;
948  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
949  char* fbkpid =
950    (char *)
951    malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
952  sprintf (fbkpid, "%s/%s.pid", r_inputs->value, pid);
953  FILE* f0 = fopen (fbkpid, "r");
954  if(f0!=NULL){
955    fclose(f0);
956    res=1;
957  }
958  free(fbkpid);
959  return res;
960}
961#else
962#include "sqlapi.h"
963#endif
964
965/**
966 * Run GetStatus requests.
967 *
968 * @param conf the maps containing the setting of the main.cfg file
969 * @param pid the service identifier (usid key from the [lenv] section)
970 * @param req the request (GetStatus / GetResult)
971 */
972void runGetStatus(maps* conf,char* pid,char* req){
973  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
974  char *sid=getStatusId(conf,pid);
975  if(sid==NULL){
976    errorException (conf, _("The JobID from the request does not match any of the Jobs running on this server"),
977                    "NoSuchJob", pid);
978  }else{
979    map* statusInfo=createMap("JobID",pid);
980    if(isRunning(conf,pid)>0){
981      if(strncasecmp(req,"GetResult",strlen(req))==0){
982        errorException (conf, _("The result for the requested JobID has not yet been generated. "),
983                        "ResultNotReady", pid);
984        return;
985      }
986      else
987        if(strncasecmp(req,"GetStatus",strlen(req))==0){
988          addToMap(statusInfo,"Status","Running");
989          char* tmpStr=_getStatus(conf,pid);
990          if(tmpStr!=NULL && strncmp(tmpStr,"-1",2)!=0){
991            char *tmpStr1=strdup(tmpStr);
992            char *tmpStr0=strdup(strstr(tmpStr,"|")+1);
993            free(tmpStr);
994            tmpStr1[strlen(tmpStr1)-strlen(tmpStr0)-1]='\0';
995            addToMap(statusInfo,"PercentCompleted",tmpStr1);
996            addToMap(statusInfo,"Message",tmpStr0);
997            free(tmpStr0);
998            free(tmpStr1);
999          }
1000        }
1001    }
1002    else{
1003      if(strncasecmp(req,"GetResult",strlen(req))==0){
1004        char* result=_getStatusFile(conf,pid);
1005        if(result!=NULL){
1006          char *encoding=getEncoding(conf);
1007          printf("Content-Type: text/xml; charset=%s\r\nStatus: 200 OK\r\n\r\n",encoding);
1008          printf("%s",result);
1009          fflush(stdout);
1010          freeMap(&statusInfo);
1011          free(statusInfo);
1012          return;
1013        }else{
1014          errorException (conf, _("The result for the requested JobID has not yet been generated. "),
1015                          "ResultNotReady", pid);
1016          freeMap(&statusInfo);
1017          free(statusInfo);
1018          return;
1019        }
1020      }else
1021        if(strncasecmp(req,"GetStatus",strlen(req))==0){
1022          readFinalRes(conf,pid,statusInfo);
1023          char* tmpStr=_getStatus(conf,pid);
1024          if(tmpStr!=NULL && strncmp(tmpStr,"-1",2)!=0){
1025            char *tmpStr1=strdup(tmpStr);
1026            char *tmpStr0=strdup(strstr(tmpStr,"|")+1);
1027            free(tmpStr);
1028            tmpStr1[strlen(tmpStr1)-strlen(tmpStr0)-1]='\0';
1029            addToMap(statusInfo,"PercentCompleted",tmpStr1);
1030            addToMap(statusInfo,"Message",tmpStr0);
1031            free(tmpStr0);
1032            free(tmpStr1);
1033          }
1034        }
1035    }
1036    printStatusInfo(conf,statusInfo,req);
1037    freeMap(&statusInfo);
1038    free(statusInfo);
1039  }
1040  return;
1041}
1042
1043/**
1044 * Run Dismiss requests.
1045 *
1046 * @param conf the maps containing the setting of the main.cfg file
1047 * @param pid the service identifier (usid key from the [lenv] section)
1048 */
1049void runDismiss(maps* conf,char* pid){
1050  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
1051  char *sid=getStatusId(conf,pid);
1052  if(sid==NULL){
1053    errorException (conf, _("The JobID from the request does not match any of the Jobs running on this server"),
1054                    "NoSuchJob", pid);
1055  }else{
1056    // We should send the Dismiss request to the target host if it differs
1057    char* fbkpid =
1058      (char *)
1059      malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
1060    sprintf (fbkpid, "%s/%s.pid", r_inputs->value, pid);
1061    FILE* f0 = fopen (fbkpid, "r");
1062    if(f0!=NULL){
1063      long flen;
1064      char *fcontent;
1065      fseek (f0, 0, SEEK_END);
1066      flen = ftell (f0);
1067      fseek (f0, 0, SEEK_SET);
1068      fcontent = (char *) malloc ((flen + 1) * sizeof (char));
1069      fread(fcontent,flen,1,f0);
1070      fcontent[flen]=0;
1071      fclose(f0);
1072#ifndef WIN32
1073      kill(atoi(fcontent),SIGKILL);
1074#else
1075      HANDLE myZooProcess=OpenProcess(PROCESS_ALL_ACCESS,false,atoi(fcontent));
1076      TerminateProcess(myZooProcess,1);
1077      CloseHandle(myZooProcess);
1078#endif
1079      free(fcontent);
1080    }
1081    free(fbkpid);
1082    struct dirent *dp;
1083    DIR *dirp = opendir(r_inputs->value);
1084    char fileName[1024];
1085    int hasFile=-1;
1086    if(dirp!=NULL){
1087      while ((dp = readdir(dirp)) != NULL){
1088#ifdef DEBUG
1089        fprintf(stderr,"File : %s searched : %s\n",dp->d_name,tmp);
1090#endif
1091        if(strstr(dp->d_name,pid)!=0){
1092          sprintf(fileName,"%s/%s",r_inputs->value,dp->d_name);
1093          if(unlink(fileName)!=0){
1094            errorException (conf, 
1095                            _("The job cannot be removed, a file cannot be removed"),
1096                            "NoApplicableCode", NULL);
1097            return;
1098          }
1099        }
1100      }
1101    }
1102#ifdef RELY_ON_DB
1103    removeService(conf,pid);
1104#endif
1105    map* statusInfo=createMap("JobID",pid);
1106    addToMap(statusInfo,"Status","Dismissed");
1107    printStatusInfo(conf,statusInfo,"Dismiss");
1108    free(statusInfo);
1109  }
1110  return;
1111}
1112
1113extern int getServiceFromFile (maps *, const char *, service **);
1114
1115/**
1116 * Parse the service file using getServiceFromFile or use getServiceFromYAML
1117 * if YAML support was activated.
1118 *
1119 * @param conf the conf maps containing the main.cfg settings
1120 * @param file the file name to parse
1121 * @param service the service to update witht the file content
1122 * @param name the service name
1123 * @return true if the file can be parsed or false
1124 * @see getServiceFromFile, getServiceFromYAML
1125 */
1126int readServiceFile (maps * conf, char *file, service ** service, char *name){
1127  int t = getServiceFromFile (conf, file, service);
1128#ifdef YAML
1129  if (t < 0){
1130    t = getServiceFromYAML (conf, file, service, name);
1131  }
1132#endif
1133  return t;
1134}
1135
1136/**
1137 * Create the profile registry.
1138 *
1139 * The profile registry is optional (created only if the registry key is
1140 * available in the [main] section of the main.cfg file) and can be used to
1141 * store the profiles hierarchy. The registry is a directory which should
1142 * contain the following sub-directories:
1143 *  * concept: direcotry containing .html files describing concept
1144 *  * generic: directory containing .zcfg files for wps:GenericProcess
1145 *  * implementation: directory containing .zcfg files for wps:Process
1146 *
1147 * @param m the conf maps containing the main.cfg settings
1148 * @param r the registry to update
1149 * @param reg_dir the resgitry
1150 * @return 0 if the resgitry is null or was correctly updated, -1 on failure
1151 */
1152int createRegistry (maps* m,registry ** r, char *reg_dir)
1153{
1154  char registryKeys[3][15]={
1155    "concept",
1156    "generic",
1157    "implementation"
1158  };
1159  int scount = 0,i=0;
1160  if (reg_dir == NULL)
1161    return 0;
1162  for(i=0;i<3;i++){
1163    char * tmpName =
1164      (char *) malloc ((strlen (reg_dir) + strlen (registryKeys[i]) + 2) *
1165                       sizeof (char));
1166    sprintf (tmpName, "%s/%s", reg_dir, registryKeys[i]);
1167   
1168    DIR *dirp1 = opendir (tmpName);
1169    if(dirp1==NULL){
1170      setMapInMaps(m,"lenv","message",_("Unable to open the registry directory."));
1171      setMapInMaps(m,"lenv","type","InternalError");
1172      return -1;
1173    }
1174    struct dirent *dp1;
1175    while ((dp1 = readdir (dirp1)) != NULL){
1176      char* extn = strstr(dp1->d_name, ".zcfg");
1177      if(dp1->d_name[0] != '.' && extn != NULL && strlen(extn) == 5)
1178        {
1179          int t;
1180          char *tmps1=
1181            (char *) malloc ((strlen (tmpName) + strlen (dp1->d_name) + 2) *
1182                             sizeof (char));
1183          sprintf (tmps1, "%s/%s", tmpName, dp1->d_name);
1184          char *tmpsn = zStrdup (dp1->d_name);
1185          tmpsn[strlen (tmpsn) - 5] = 0;
1186          service* s1 = (service *) malloc (SERVICE_SIZE);
1187          if (s1 == NULL)
1188            {
1189              setMapInMaps(m,"lenv","message",_("Unable to allocate memory."));
1190              setMapInMaps(m,"lenv","type","InternalError");
1191              return -2;
1192            }
1193          t = readServiceFile (m, tmps1, &s1, tmpsn);
1194          free (tmpsn);
1195          if (t < 0)
1196            {
1197              map *tmp00 = getMapFromMaps (m, "lenv", "message");
1198              char tmp01[1024];
1199              if (tmp00 != NULL)
1200                sprintf (tmp01, _("Unable to parse the ZCFG file: %s (%s)"),
1201                         dp1->d_name, tmp00->value);
1202              else
1203                sprintf (tmp01, _("Unable to parse the ZCFG file: %s."),
1204                         dp1->d_name);
1205              setMapInMaps(m,"lenv","message",tmp01);
1206              setMapInMaps(m,"lenv","type","InternalError");
1207              return -1;
1208            }
1209          if(strncasecmp(registryKeys[i],"implementation",14)==0){
1210            inheritance(*r,&s1);
1211          }
1212          addServiceToRegistry(r,registryKeys[i],s1);
1213          freeService (&s1);
1214          free (s1);
1215          scount++;
1216        }
1217    }
1218    (void) closedir (dirp1);
1219  }
1220  return 0;
1221}
1222
1223#ifdef WIN32
1224/**
1225 * Create a KVP request for executing background task.
1226 * TODO: use the XML request in case of input POST request.
1227 *
1228 * @param m the maps containing the parameters from the main.cfg file
1229 * @param length the total length of the KVP parameters
1230 * @param type
1231 */
1232char* getMapsAsKVP(maps* m,int length,int type){
1233  char *dataInputsKVP=(char*) malloc(length*sizeof(char));
1234  char *dataInputsKVPi=NULL;
1235  maps* curs=m;
1236  int i=0;
1237  while(curs!=NULL){
1238    map *inRequest=getMap(curs->content,"inRequest");
1239    map *hasLength=getMap(curs->content,"length");
1240    if((inRequest!=NULL && strncasecmp(inRequest->value,"true",4)==0) ||
1241       inRequest==NULL){
1242      if(i==0)
1243        if(type==0){
1244          sprintf(dataInputsKVP,"%s=",curs->name);
1245          if(hasLength!=NULL){
1246            dataInputsKVPi=(char*)malloc((strlen(curs->name)+2)*sizeof(char));
1247            sprintf(dataInputsKVPi,"%s=",curs->name);
1248          }
1249        }
1250        else
1251          sprintf(dataInputsKVP,"%s",curs->name);
1252      else{
1253        char *temp=zStrdup(dataInputsKVP);
1254        if(type==0)
1255          sprintf(dataInputsKVP,"%s;%s=",temp,curs->name);
1256        else
1257          sprintf(dataInputsKVP,"%s;%s",temp,curs->name);
1258      }
1259      map* icurs=curs->content;
1260      if(type==0){
1261        char *temp=zStrdup(dataInputsKVP);
1262        if(getMap(curs->content,"xlink:href")!=NULL)
1263          sprintf(dataInputsKVP,"%sReference",temp);
1264        else{
1265          if(hasLength!=NULL){
1266            int j;
1267            for(j=0;j<atoi(hasLength->value);j++){
1268              map* tmp0=getMapArray(curs->content,"value",j);
1269              if(j==0)
1270                free(temp);
1271              temp=zStrdup(dataInputsKVP);
1272              if(j==0)
1273                sprintf(dataInputsKVP,"%s%s",temp,tmp0->value);
1274              else
1275                sprintf(dataInputsKVP,"%s;%s%s",temp,dataInputsKVPi,tmp0->value);
1276            }
1277          }
1278          else
1279            sprintf(dataInputsKVP,"%s%s",temp,icurs->value);
1280        }
1281        free(temp);
1282      }
1283      while(icurs!=NULL){
1284        if(strncasecmp(icurs->name,"value",5)!=0 &&
1285           strncasecmp(icurs->name,"mimeType_",9)!=0 &&
1286           strncasecmp(icurs->name,"dataType_",9)!=0 &&
1287           strncasecmp(icurs->name,"size",4)!=0 &&
1288           strncasecmp(icurs->name,"length",4)!=0 &&
1289           strncasecmp(icurs->name,"isArray",7)!=0 &&
1290           strcasecmp(icurs->name,"Reference")!=0 &&
1291           strcasecmp(icurs->name,"minOccurs")!=0 &&
1292           strcasecmp(icurs->name,"maxOccurs")!=0 &&
1293           strncasecmp(icurs->name,"fmimeType",9)!=0 &&
1294           strcasecmp(icurs->name,"inRequest")!=0){
1295          char *itemp=zStrdup(dataInputsKVP);
1296          if(strcasecmp(icurs->name,"xlink:href")!=0)
1297            sprintf(dataInputsKVP,"%s@%s=%s",itemp,icurs->name,icurs->value);
1298          else
1299            sprintf(dataInputsKVP,"%s@%s=%s",itemp,icurs->name,url_encode(icurs->value));
1300          free(itemp);
1301        }
1302        icurs=icurs->next;
1303      }
1304    }
1305    curs=curs->next;
1306    i++;
1307  }
1308  return dataInputsKVP;
1309}
1310#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