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

Last change on this file since 806 was 805, checked in by djay, 8 years ago

Return default iotype if no value has been provided. Fix potential segfault in case tmpIoType is null.

  • Property svn:keywords set to Id
File size: 35.2 KB
RevLine 
[642]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"
[654]26#include "service_internal.h"
[642]27#include "response_print.h"
28#include "mimetypes.h"
29#ifndef WIN32
30#include <dlfcn.h>
[680]31#include <uuid/uuid.h>
32#else
33#include <rpc.h>
34#define ERROR_MSG_MAX_LENGTH 1024
[642]35#endif
[657]36#include <signal.h>
[642]37
[789]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
[653]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 */
[642]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  }
[653]92  return -1;
[642]93}
94
95/**
[652]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
[781]100 *  resource once used.
[652]101 */
102char *get_uuid(){
103  char *res=(char*)malloc(37*sizeof(char));
[680]104#ifdef WIN32
105  UUID uuid;
106  UuidCreate(&uuid);
107  RPC_CSTR rest = NULL;
108  UuidToString(&uuid,&rest);
109#else
[652]110  uuid_t uuid;
111  uuid_generate_time(uuid);
[653]112  char rest[128];
[652]113  uuid_unparse(uuid,rest);
[680]114#endif
115  sprintf(res,"%s",rest);
[682]116#ifdef WIN32
117  RpcStringFree(&rest);
118#endif
[652]119  return res;
120}
121
122/**
[642]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){
[756]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){
[785]295      tmp1=zStrdup(tmp);
[756]296      free(tmp);
297    }else{
298      if(tmp0!=NULL){
[785]299        tmp1=zStrdup(tmp0);
[756]300        free(tmp0);
301      }
302    }
303  }
[642]304  char *pToken,*saveptr;
[756]305  pToken=strtok_r(tmp1,",",&saveptr);
[642]306  int res=-1;
[756]307  while(pToken!=NULL){
308    if(strcasecmp(str,pToken)==0){
309      res=1;
310      break;
[642]311    }
[756]312    pToken=strtok_r(NULL,",",&saveptr);
[642]313  }
[756]314  if(tmp1!=NULL)
315    free(tmp1);
[642]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){
[781]409    fprintf(stderr,"Write error occurred!\n");
[642]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");
[652]425  map* tmpSid=getMapFromMaps(*main_conf,"lenv","usid");
[642]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;
[790]585  elements* tmpInputss=NULL;
[642]586  maps* out1=*out;
[790]587  maps* out1s=NULL;
[642]588  char *result=NULL;
589  int nb=0;
[790]590  int inb=0;
591 loopOnInputs:
[642]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      }
[790]603      inb++;
[642]604      out1=out1->next;
605    }
606    if(res!=NULL){
[790]607      fflush(stderr);
[642]608      *err=res;
609      return result;
610    }
[790]611    if(out1==NULL && inb>=1)
612      out1=*out;
[642]613  }
614  while(tmpInputs!=NULL){
615    maps *tmpMaps=getMaps(out1,tmpInputs->name);
616    if(tmpMaps==NULL){
[790]617      maps* tmpMaps2=createMaps(tmpInputs->name);
[642]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      }
[790]656     
[642]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            tmpm=tmpm->next;
667          }
668        }
669        addToMap(tmpMaps2->content,"inRequest","false");
670        if(type==0){
671          map *tmpMap=getMap(tmpMaps2->content,"value");
672          if(tmpMap==NULL)
673            addToMap(tmpMaps2->content,"value","NULL");
674        }
675        if(out1==NULL){
676          *out=dupMaps(&tmpMaps2);
677          out1=*out;
678        }
679        else
680          addMapsToMaps(&out1,tmpMaps2);
681        freeMap(&tmpMaps2->content);
682        free(tmpMaps2->content);
683        tmpMaps2->content=NULL;
684        freeMaps(&tmpMaps2);
685        free(tmpMaps2);
686        tmpMaps2=NULL;
687      }
688    }
689    else{
[790]690      iotype* tmpIoType=NULL;
691      if(tmpMaps->content!=NULL){
692        tmpIoType=getIoTypeFromElement(tmpInputs,tmpInputs->name,
693                                       tmpMaps->content);
694        if(type==0) {
695          /**
696           * In case of an Input maps, then add the minOccurs and maxOccurs to the
697           * content map.
698           */
699          map* tmpMap1=getMap(tmpInputs->content,"minOccurs");
700          if(tmpMap1!=NULL){
701            if(tmpMaps->content==NULL)
702              tmpMaps->content=createMap("minOccurs",tmpMap1->value);
703            else
704              addToMap(tmpMaps->content,"minOccurs",tmpMap1->value);
705          }
706          map* tmpMaxO=getMap(tmpInputs->content,"maxOccurs");
707          if(tmpMaxO!=NULL){
708            if(tmpMaps->content==NULL)
709              tmpMaps->content=createMap("maxOccurs",tmpMaxO->value);
710            else
711              addToMap(tmpMaps->content,"maxOccurs",tmpMaxO->value);
712          }
713          map* tmpMaxMB=getMap(tmpInputs->content,"maximumMegabytes");
714          if(tmpMaxMB!=NULL){
715            if(tmpMaps->content==NULL)
716              tmpMaps->content=createMap("maximumMegabytes",tmpMaxMB->value);
717            else
718              addToMap(tmpMaps->content,"maximumMegabytes",tmpMaxMB->value);
719          }
720          /**
721           * Parsing BoundingBoxData, fill the following map and then add it to
722           * the content map of the Input maps:
723           * lowerCorner, upperCorner, srs and dimensions
724           * cf. parseBoundingBox
725           */
726          if(tmpInputs->format!=NULL && strcasecmp(tmpInputs->format,"BoundingBoxData")==0){
727            maps* tmpI=getMaps(*out,tmpInputs->name);
728            if(tmpI!=NULL){
729              map* tmpV=getMap(tmpI->content,"value");
730              if(tmpV!=NULL){
731                char *tmpVS=strdup(tmpV->value);
732                map* tmp=parseBoundingBox(tmpVS);
733                free(tmpVS);
734                map* tmpC=tmp;
735                while(tmpC!=NULL){
736                  addToMap(tmpMaps->content,tmpC->name,tmpC->value);
737                  tmpC=tmpC->next;
738                }
739                freeMap(&tmp);
740                free(tmp);
[642]741              }
742            }
743          }
744        }
[790]745      }else{
746        if(tmpInputs!=NULL){
747          tmpIoType=tmpInputs->defaults;
748        }
[642]749      }
750
751      if(tmpIoType!=NULL){
752        map* tmpContent=tmpIoType->content;
753        map* cval=NULL;
754        int hasPassed=-1;
755        while(tmpContent!=NULL){
756          if((cval=getMap(tmpMaps->content,tmpContent->name))==NULL){
757#ifdef DEBUG
758            fprintf(stderr,"addDefaultValues %s => %s\n",tmpContent->name,tmpContent->value);
759#endif
760            if(tmpMaps->content==NULL)
761              tmpMaps->content=createMap(tmpContent->name,tmpContent->value);
762            else
763              addToMap(tmpMaps->content,tmpContent->name,tmpContent->value);
764           
765            if(hasPassed<0 && type==0 && getMap(tmpMaps->content,"isArray")!=NULL){
766              map* length=getMap(tmpMaps->content,"length");
767              int i;
768              char *tcn=strdup(tmpContent->name);
769              for(i=1;i<atoi(length->value);i++){
770#ifdef DEBUG
771                dumpMap(tmpMaps->content);
772                fprintf(stderr,"addDefaultValues %s_%d => %s\n",tcn,i,tmpContent->value);
773#endif
774                int len=strlen((char*) tcn);
775                char *tmp1=(char *)malloc((len+10)*sizeof(char));
776                sprintf(tmp1,"%s_%d",tcn,i);
777#ifdef DEBUG
778                fprintf(stderr,"addDefaultValues %s => %s\n",tmp1,tmpContent->value);
779#endif
780                addToMap(tmpMaps->content,tmp1,tmpContent->value);
781                free(tmp1);
782                hasPassed=1;
783              }
784              free(tcn);
785            }
786          }
787          tmpContent=tmpContent->next;
788        }
789#ifdef USE_MS
790        /**
791         * check for useMapServer presence
792         */
[805]793        if(tmpIoType!=NULL){
794          map* tmpCheck=getMap(tmpIoType->content,"useMapServer");
795          if(tmpCheck!=NULL){
796            // Get the default value
797            tmpIoType=getIoTypeFromElement(tmpInputs,tmpInputs->name,NULL);
798            tmpCheck=getMap(tmpMaps->content,"mimeType");
799            addToMap(tmpMaps->content,"requestedMimeType",tmpCheck->value);
800            map* cursor=tmpIoType->content;
801            while(cursor!=NULL){
802              addToMap(tmpMaps->content,cursor->name,cursor->value);
803              cursor=cursor->next;
804            }
[642]805         
[805]806            cursor=tmpInputs->content;
807            while(cursor!=NULL){
808              if(strcasecmp(cursor->name,"Title")==0 ||
809                 strcasecmp(cursor->name,"Abstract")==0)
810                addToMap(tmpMaps->content,cursor->name,cursor->value);
811              cursor=cursor->next;
812            }
[642]813          }
814        }
815#endif
816      }
817      if(tmpMaps->content==NULL)
818        tmpMaps->content=createMap("inRequest","true");
819      else
820        addToMap(tmpMaps->content,"inRequest","true");
[790]821      elements* tmpElements=getElements(in,tmpMaps->name);
[798]822      if(tmpMaps->child!=NULL && tmpElements!=NULL && tmpElements->child!=NULL){
[790]823        char *res=addDefaultValues(&tmpMaps->child,tmpElements->child,m,type,err);
824        if(strlen(res)>0){
825          fprintf(stderr,"%s %d\n",__FILE__,__LINE__);
826          return res;
827        }
828      }
[642]829    }
[790]830    if(tmpInputs->child!=NULL){
831      tmpInputss=tmpInputs->next;
832      tmpInputs=tmpInputs->child;
833      if(tmpMaps!=NULL){
834        out1=tmpMaps->child;
835        out1s=tmpMaps;
836      }
837    }else
838      tmpInputs=tmpInputs->next;
[642]839  }
[790]840  if(tmpInputss!=NULL){
841    out1=out1s;
842    tmpInputs=tmpInputss;
843    tmpInputss=NULL;
844    out1s=NULL;
845    goto loopOnInputs;
846  }
[642]847  if(res!=NULL){
848    *err=res;
849    return result;
850  }
851  return "";
852}
853
854/**
855 * Access the last error message returned by the OS when trying to dynamically
856 * load a shared library.
857 *
858 * @return the last error message
859 * @warning The character string returned from getLastErrorMessage resides
860 * in a static buffer. The application should not write to this
861 * buffer or attempt to free() it.
862 */ 
863char* getLastErrorMessage() {                                             
864#ifdef WIN32
865  LPVOID lpMsgBuf;
866  DWORD errCode = GetLastError();
867  static char msg[ERROR_MSG_MAX_LENGTH];
868  size_t i;
869 
870  DWORD length = FormatMessage(
871                               FORMAT_MESSAGE_ALLOCATE_BUFFER | 
872                               FORMAT_MESSAGE_FROM_SYSTEM |
873                               FORMAT_MESSAGE_IGNORE_INSERTS,
874                               NULL,
875                               errCode,
876                               MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
877                               (LPTSTR) &lpMsgBuf,
878                               0, NULL );       
879 
880#ifdef UNICODE         
881  wcstombs_s( &i, msg, ERROR_MSG_MAX_LENGTH,
882              (wchar_t*) lpMsgBuf, _TRUNCATE );
883#else
884  strcpy_s( msg, ERROR_MSG_MAX_LENGTH,
885            (char *) lpMsgBuf );               
886#endif 
887  LocalFree(lpMsgBuf);
[680]888
[642]889  return msg;
890#else
891  return dlerror();
892#endif
893}
894
[654]895#include <dirent.h>
896#ifndef RELY_ON_DB
897/**
898 * Read the Result file (.res).
899 *
900 * @param conf the maps containing the setting of the main.cfg file
901 * @param pid the service identifier (usid key from the [lenv] section)
902 */
903void readFinalRes(maps* conf,char* pid,map* statusInfo){
904  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
905  char* fbkpid =
906    (char *)
907    malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
908  sprintf (fbkpid, "%s/%s.res", r_inputs->value, pid);
909  struct stat file_status;
910  int istat = stat (fbkpid, &file_status);
911  if (istat == 0 && file_status.st_size > 0)
912    {
913      maps *res = (maps *) malloc (MAPS_SIZE);
914      conf_read (fbkpid, res);
[790]915      res->child=NULL;
[654]916      map* status=getMapFromMaps(res,"status","status");
917      addToMap(statusInfo,"Status",status->value);
918      freeMaps(&res);
919      free(res);
920    }
921  else
922    addToMap(statusInfo,"Status","Failed"); 
923  free(fbkpid);
924}
[642]925
[654]926/**
927 * Check if a service is running.
928 *
929 * @param conf the maps containing the setting of the main.cfg file
930 * @param pid the unique service identifier (usid from the lenv section)
931 * @return 1 in case the service is still running, 0 otherwise
932 */
933int isRunning(maps* conf,char* pid){
934  int res=0;
935  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
936  char* fbkpid =
937    (char *)
938    malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
939  sprintf (fbkpid, "%s/%s.pid", r_inputs->value, pid);
940  FILE* f0 = fopen (fbkpid, "r");
941  if(f0!=NULL){
942    fclose(f0);
943    res=1;
944  }
945  free(fbkpid);
946  return res;
947}
948#else
949#include "sqlapi.h"
950#endif
[642]951
[654]952/**
953 * Run GetStatus requests.
954 *
955 * @param conf the maps containing the setting of the main.cfg file
956 * @param pid the service identifier (usid key from the [lenv] section)
957 * @param req the request (GetStatus / GetResult)
958 */
959void runGetStatus(maps* conf,char* pid,char* req){
960  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
961  char *sid=getStatusId(conf,pid);
962  if(sid==NULL){
963    errorException (conf, _("The JobID from the request does not match any of the Jobs running on this server"),
964                    "NoSuchJob", pid);
965  }else{
966    map* statusInfo=createMap("JobID",pid);
967    if(isRunning(conf,pid)>0){
968      if(strncasecmp(req,"GetResult",strlen(req))==0){
969        errorException (conf, _("The result for the requested JobID has not yet been generated. "),
970                        "ResultNotReady", pid);
971        return;
972      }
973      else
974        if(strncasecmp(req,"GetStatus",strlen(req))==0){
975          addToMap(statusInfo,"Status","Running");
976          char* tmpStr=_getStatus(conf,pid);
977          if(tmpStr!=NULL && strncmp(tmpStr,"-1",2)!=0){
978            char *tmpStr1=strdup(tmpStr);
979            char *tmpStr0=strdup(strstr(tmpStr,"|")+1);
980            free(tmpStr);
981            tmpStr1[strlen(tmpStr1)-strlen(tmpStr0)-1]='\0';
982            addToMap(statusInfo,"PercentCompleted",tmpStr1);
983            addToMap(statusInfo,"Message",tmpStr0);
984            free(tmpStr0);
985            free(tmpStr1);
986          }
987        }
988    }
989    else{
990      if(strncasecmp(req,"GetResult",strlen(req))==0){
991        char* result=_getStatusFile(conf,pid);
992        if(result!=NULL){
993          char *encoding=getEncoding(conf);
[682]994          printf("Content-Type: text/xml; charset=%s\r\nStatus: 200 OK\r\n\r\n",encoding);
995          printf("%s",result);
[654]996          fflush(stdout);
997          freeMap(&statusInfo);
998          free(statusInfo);
999          return;
1000        }else{
1001          errorException (conf, _("The result for the requested JobID has not yet been generated. "),
1002                          "ResultNotReady", pid);
1003          freeMap(&statusInfo);
1004          free(statusInfo);
1005          return;
1006        }
1007      }else
1008        if(strncasecmp(req,"GetStatus",strlen(req))==0){
1009          readFinalRes(conf,pid,statusInfo);
1010          char* tmpStr=_getStatus(conf,pid);
1011          if(tmpStr!=NULL && strncmp(tmpStr,"-1",2)!=0){
1012            char *tmpStr1=strdup(tmpStr);
1013            char *tmpStr0=strdup(strstr(tmpStr,"|")+1);
1014            free(tmpStr);
1015            tmpStr1[strlen(tmpStr1)-strlen(tmpStr0)-1]='\0';
1016            addToMap(statusInfo,"PercentCompleted",tmpStr1);
1017            addToMap(statusInfo,"Message",tmpStr0);
1018            free(tmpStr0);
1019            free(tmpStr1);
1020          }
1021        }
1022    }
1023    printStatusInfo(conf,statusInfo,req);
1024    freeMap(&statusInfo);
1025    free(statusInfo);
1026  }
1027  return;
1028}
1029
1030/**
1031 * Run Dismiss requests.
1032 *
1033 * @param conf the maps containing the setting of the main.cfg file
1034 * @param pid the service identifier (usid key from the [lenv] section)
1035 */
1036void runDismiss(maps* conf,char* pid){
1037  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
1038  char *sid=getStatusId(conf,pid);
1039  if(sid==NULL){
1040    errorException (conf, _("The JobID from the request does not match any of the Jobs running on this server"),
1041                    "NoSuchJob", pid);
1042  }else{
1043    // We should send the Dismiss request to the target host if it differs
1044    char* fbkpid =
1045      (char *)
1046      malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
1047    sprintf (fbkpid, "%s/%s.pid", r_inputs->value, pid);
1048    FILE* f0 = fopen (fbkpid, "r");
1049    if(f0!=NULL){
1050      long flen;
1051      char *fcontent;
1052      fseek (f0, 0, SEEK_END);
1053      flen = ftell (f0);
1054      fseek (f0, 0, SEEK_SET);
1055      fcontent = (char *) malloc ((flen + 1) * sizeof (char));
1056      fread(fcontent,flen,1,f0);
1057      fcontent[flen]=0;
1058      fclose(f0);
[680]1059#ifndef WIN32
[654]1060      kill(atoi(fcontent),SIGKILL);
[680]1061#else
1062      HANDLE myZooProcess=OpenProcess(PROCESS_ALL_ACCESS,false,atoi(fcontent));
1063      TerminateProcess(myZooProcess,1);
1064      CloseHandle(myZooProcess);
1065#endif
[654]1066      free(fcontent);
1067    }
1068    free(fbkpid);
1069    struct dirent *dp;
1070    DIR *dirp = opendir(r_inputs->value);
1071    char fileName[1024];
1072    int hasFile=-1;
1073    if(dirp!=NULL){
1074      while ((dp = readdir(dirp)) != NULL){
1075#ifdef DEBUG
1076        fprintf(stderr,"File : %s searched : %s\n",dp->d_name,tmp);
1077#endif
1078        if(strstr(dp->d_name,pid)!=0){
1079          sprintf(fileName,"%s/%s",r_inputs->value,dp->d_name);
1080          if(unlink(fileName)!=0){
[676]1081            errorException (conf, 
1082                            _("The job cannot be removed, a file cannot be removed"),
[654]1083                            "NoApplicableCode", NULL);
1084            return;
1085          }
1086        }
1087      }
1088    }
1089#ifdef RELY_ON_DB
1090    removeService(conf,pid);
1091#endif
1092    map* statusInfo=createMap("JobID",pid);
1093    addToMap(statusInfo,"Status","Dismissed");
1094    printStatusInfo(conf,statusInfo,"Dismiss");
1095    free(statusInfo);
1096  }
1097  return;
1098}
[676]1099
1100extern int getServiceFromFile (maps *, const char *, service **);
1101
1102/**
1103 * Parse the service file using getServiceFromFile or use getServiceFromYAML
1104 * if YAML support was activated.
1105 *
1106 * @param conf the conf maps containing the main.cfg settings
1107 * @param file the file name to parse
1108 * @param service the service to update witht the file content
1109 * @param name the service name
1110 * @return true if the file can be parsed or false
1111 * @see getServiceFromFile, getServiceFromYAML
1112 */
1113int readServiceFile (maps * conf, char *file, service ** service, char *name){
1114  int t = getServiceFromFile (conf, file, service);
1115#ifdef YAML
1116  if (t < 0){
1117    t = getServiceFromYAML (conf, file, service, name);
1118  }
1119#endif
1120  return t;
1121}
1122
1123/**
1124 * Create the profile registry.
1125 *
1126 * The profile registry is optional (created only if the registry key is
1127 * available in the [main] section of the main.cfg file) and can be used to
1128 * store the profiles hierarchy. The registry is a directory which should
1129 * contain the following sub-directories:
1130 *  * concept: direcotry containing .html files describing concept
1131 *  * generic: directory containing .zcfg files for wps:GenericProcess
1132 *  * implementation: directory containing .zcfg files for wps:Process
1133 *
1134 * @param m the conf maps containing the main.cfg settings
1135 * @param r the registry to update
1136 * @param reg_dir the resgitry
1137 * @return 0 if the resgitry is null or was correctly updated, -1 on failure
1138 */
1139int createRegistry (maps* m,registry ** r, char *reg_dir)
1140{
1141  char registryKeys[3][15]={
1142    "concept",
1143    "generic",
1144    "implementation"
1145  };
1146  int scount = 0,i=0;
1147  if (reg_dir == NULL)
1148    return 0;
1149  for(i=0;i<3;i++){
1150    char * tmpName =
1151      (char *) malloc ((strlen (reg_dir) + strlen (registryKeys[i]) + 2) *
1152                       sizeof (char));
1153    sprintf (tmpName, "%s/%s", reg_dir, registryKeys[i]);
1154   
1155    DIR *dirp1 = opendir (tmpName);
[720]1156    if(dirp1==NULL){
1157      setMapInMaps(m,"lenv","message",_("Unable to open the registry directory."));
1158      setMapInMaps(m,"lenv","type","InternalError");
1159      return -1;
1160    }
[676]1161    struct dirent *dp1;
1162    while ((dp1 = readdir (dirp1)) != NULL){
1163      char* extn = strstr(dp1->d_name, ".zcfg");
1164      if(dp1->d_name[0] != '.' && extn != NULL && strlen(extn) == 5)
1165        {
1166          int t;
1167          char *tmps1=
1168            (char *) malloc ((strlen (tmpName) + strlen (dp1->d_name) + 2) *
1169                             sizeof (char));
1170          sprintf (tmps1, "%s/%s", tmpName, dp1->d_name);
1171          char *tmpsn = zStrdup (dp1->d_name);
1172          tmpsn[strlen (tmpsn) - 5] = 0;
1173          service* s1 = (service *) malloc (SERVICE_SIZE);
1174          if (s1 == NULL)
1175            {
1176              setMapInMaps(m,"lenv","message",_("Unable to allocate memory."));
1177              setMapInMaps(m,"lenv","type","InternalError");
1178              return -2;
1179            }
1180          t = readServiceFile (m, tmps1, &s1, tmpsn);
1181          free (tmpsn);
1182          if (t < 0)
1183            {
1184              map *tmp00 = getMapFromMaps (m, "lenv", "message");
1185              char tmp01[1024];
1186              if (tmp00 != NULL)
1187                sprintf (tmp01, _("Unable to parse the ZCFG file: %s (%s)"),
1188                         dp1->d_name, tmp00->value);
1189              else
1190                sprintf (tmp01, _("Unable to parse the ZCFG file: %s."),
1191                         dp1->d_name);
1192              setMapInMaps(m,"lenv","message",tmp01);
1193              setMapInMaps(m,"lenv","type","InternalError");
1194              return -1;
1195            }
1196          if(strncasecmp(registryKeys[i],"implementation",14)==0){
1197            inheritance(*r,&s1);
1198          }
1199          addServiceToRegistry(r,registryKeys[i],s1);
1200          freeService (&s1);
1201          free (s1);
1202          scount++;
1203        }
1204    }
1205    (void) closedir (dirp1);
1206  }
1207  return 0;
1208}
[680]1209
1210#ifdef WIN32
1211/**
1212 * Create a KVP request for executing background task.
1213 * TODO: use the XML request in case of input POST request.
1214 *
1215 * @param m the maps containing the parameters from the main.cfg file
1216 * @param length the total length of the KVP parameters
1217 * @param type
1218 */
1219char* getMapsAsKVP(maps* m,int length,int type){
1220  char *dataInputsKVP=(char*) malloc(length*sizeof(char));
1221  char *dataInputsKVPi=NULL;
1222  maps* curs=m;
1223  int i=0;
1224  while(curs!=NULL){
1225    map *inRequest=getMap(curs->content,"inRequest");
1226    map *hasLength=getMap(curs->content,"length");
1227    if((inRequest!=NULL && strncasecmp(inRequest->value,"true",4)==0) ||
1228       inRequest==NULL){
1229      if(i==0)
1230        if(type==0){
1231          sprintf(dataInputsKVP,"%s=",curs->name);
1232          if(hasLength!=NULL){
1233            dataInputsKVPi=(char*)malloc((strlen(curs->name)+2)*sizeof(char));
1234            sprintf(dataInputsKVPi,"%s=",curs->name);
1235          }
1236        }
1237        else
1238          sprintf(dataInputsKVP,"%s",curs->name);
1239      else{
1240        char *temp=zStrdup(dataInputsKVP);
1241        if(type==0)
1242          sprintf(dataInputsKVP,"%s;%s=",temp,curs->name);
1243        else
1244          sprintf(dataInputsKVP,"%s;%s",temp,curs->name);
1245      }
1246      map* icurs=curs->content;
1247      if(type==0){
1248        char *temp=zStrdup(dataInputsKVP);
1249        if(getMap(curs->content,"xlink:href")!=NULL)
1250          sprintf(dataInputsKVP,"%sReference",temp);
1251        else{
1252          if(hasLength!=NULL){
1253            int j;
1254            for(j=0;j<atoi(hasLength->value);j++){
1255              map* tmp0=getMapArray(curs->content,"value",j);
1256              if(j==0)
1257                free(temp);
1258              temp=zStrdup(dataInputsKVP);
1259              if(j==0)
1260                sprintf(dataInputsKVP,"%s%s",temp,tmp0->value);
1261              else
1262                sprintf(dataInputsKVP,"%s;%s%s",temp,dataInputsKVPi,tmp0->value);
1263            }
1264          }
1265          else
1266            sprintf(dataInputsKVP,"%s%s",temp,icurs->value);
1267        }
1268        free(temp);
1269      }
1270      while(icurs!=NULL){
1271        if(strncasecmp(icurs->name,"value",5)!=0 &&
1272           strncasecmp(icurs->name,"mimeType_",9)!=0 &&
1273           strncasecmp(icurs->name,"dataType_",9)!=0 &&
1274           strncasecmp(icurs->name,"size",4)!=0 &&
1275           strncasecmp(icurs->name,"length",4)!=0 &&
1276           strncasecmp(icurs->name,"isArray",7)!=0 &&
1277           strcasecmp(icurs->name,"Reference")!=0 &&
1278           strcasecmp(icurs->name,"minOccurs")!=0 &&
1279           strcasecmp(icurs->name,"maxOccurs")!=0 &&
1280           strncasecmp(icurs->name,"fmimeType",9)!=0 &&
1281           strcasecmp(icurs->name,"inRequest")!=0){
1282          char *itemp=zStrdup(dataInputsKVP);
1283          if(strcasecmp(icurs->name,"xlink:href")!=0)
1284            sprintf(dataInputsKVP,"%s@%s=%s",itemp,icurs->name,icurs->value);
1285          else
1286            sprintf(dataInputsKVP,"%s@%s=%s",itemp,icurs->name,url_encode(icurs->value));
1287          free(itemp);
1288        }
1289        icurs=icurs->next;
1290      }
1291    }
1292    curs=curs->next;
1293    i++;
1294  }
1295  return dataInputsKVP;
1296}
1297#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