Initial revision
[TestXSLT.git] / libsablot / src / engine / uri.cpp
1 /* 
2  * The contents of this file are subject to the Mozilla Public
3  * License Version 1.1 (the "License"); you may not use this file
4  * except in compliance with the License. You may obtain a copy of
5  * the License at http://www.mozilla.org/MPL/
6  * 
7  * Software distributed under the License is distributed on an "AS
8  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9  * implied. See the License for the specific language governing
10  * rights and limitations under the License.
11  * 
12  * The Original Code is the Sablotron XSLT Processor.
13  * 
14  * The Initial Developer of the Original Code is Ginger Alliance Ltd.
15  * Portions created by Ginger Alliance are Copyright (C) 2000-2002
16  * Ginger Alliance Ltd. All Rights Reserved.
17  * 
18  * Contributor(s):
19  * 
20  * Alternatively, the contents of this file may be used under the
21  * terms of the GNU General Public License Version 2 or later (the
22  * "GPL"), in which case the provisions of the GPL are applicable 
23  * instead of those above.  If you wish to allow use of your 
24  * version of this file only under the terms of the GPL and not to
25  * allow others to use your version of this file under the MPL,
26  * indicate your decision by deleting the provisions above and
27  * replace them with the notice and other provisions required by
28  * the GPL.  If you do not delete the provisions above, a recipient
29  * may use your version of this file under either the MPL or the
30  * GPL.
31  */
32
33 /*****************************************************************
34     uri.cpp
35 *****************************************************************/
36
37 #include "uri.h"
38 #include <string.h>
39 #include "proc.h"
40
41 // GP: clean
42
43 /*****************************************************************
44
45     global functions
46
47 *****************************************************************/
48
49 #define RF(CONDITION) {if (!(CONDITION)) return;}
50
51 // definition of names for various URI-reference parts
52 #define U_SCHEME    0
53 #define U_AUTH      1
54 #define U_PATH      2
55 #define U_QUERY     3
56 #define U_FRAG      4
57
58 // definition of slahes in path names
59 #define slashes "/\\"
60 #define isSlash(c) (c == '/' || c == '\\')
61
62 /*****************************************************************
63 splitBy
64
65   splits a given string into two parts divided by the first occurence
66   of a delimiter from a given set. If no delimiter is found, returns FALSE
67   and leaves 'string' as is; otherwise shifts 'string' to the character
68   following the delimiter.
69 ARGS:
70   string        the asciiz string to be split
71   delims        the asciiz set of delimiters (all of them ASCII chars)
72   part1         first of the two parts
73 RETURN:
74   string        shifted to the other part (past the delimiter)
75   .             the delimiter found (or 0)
76 *****************************************************************/
77
78 char splitBy(const char *&string, const char *delims, Str &part1)
79 {
80     char c;
81     int firstLen = strcspn(string, delims);
82     part1.nset(string, firstLen);
83     if (!!(c = string[firstLen]))
84         string += firstLen + 1;
85     return c;
86 }
87
88 typedef Str FiveStr[5];
89
90 void splitURI(const char *uri, FiveStr &parts)
91 {
92     const char *rest;
93     char c;
94     for (int i = 0; i < 5; i++) 
95         parts[i].empty();
96     RF( uri && *uri );
97     // extract the scheme part of the URI
98     if (!splitBy(rest = uri, ":", parts[U_SCHEME]))
99         parts[U_SCHEME].empty();
100     // if "//" follows, extract the authority part
101     c = 'A';    // marks the absence of auth
102     if (isSlash(*rest) && isSlash(rest[1]))
103         RF( c = splitBy(rest += 2, slashes"?#", parts[U_AUTH]) );
104     if (isSlash(c) || c == 'A')
105         // extract the path
106         RF( c = splitBy(rest -= (isSlash(c)), "?#", parts[U_PATH]) );
107     if (c == '?')
108         // extract the query
109         RF( c = splitBy(rest, "#", parts[U_QUERY]) );
110     // copy the fragment
111     parts[U_FRAG] = (char *) rest;
112 };
113
114 void joinURI(DStr &joined, FiveStr &parts, Bool schemeToo)
115 {
116     joined.empty();
117     if (schemeToo && !parts[U_SCHEME].isEmpty())
118         joined = parts[U_SCHEME] + ":";
119     if (!parts[U_AUTH].isEmpty())
120         joined += Str("//") + parts[U_AUTH];            // add authority
121     joined += parts[U_PATH];                // add path
122     if (!parts[U_QUERY].isEmpty())          // add query
123         joined += Str("?") + parts[U_QUERY];
124     if (!parts[U_FRAG].isEmpty())           // add fragment
125         joined += Str("#") + parts[U_FRAG];
126 }
127
128 /*****************************************************************
129 schemeToURI_()
130
131   converts the scheme given as Str to one of the URI_... constants.
132   If the scheme is neither "file" or "arg" then URI_EXTENSION is
133   simply returned.
134 *****************************************************************/
135
136 URIScheme schemeToURI_(Sit S, Str& scheme)
137 {
138     if (scheme.eqNoCase("file") && !S.hasFlag(SAB_FILES_TO_HANDLER))
139         return URI_FILE;
140     else
141     {
142         if (scheme.eqNoCase("arg"))
143             return URI_ARG;
144         else 
145             return URI_EXTENSION;
146     }
147 }
148
149 /*****************************************************************
150 cutLast()
151
152   truncates a path after 'howmany'-th slash from the right (1-based).
153   If there are fewer slashes, sets path to empty string and returns
154   FALSE, otherwise returns TRUE.
155 ARGS
156   path      the path to be truncated
157   howmany   # of slashes that disappear in truncation, MINUS 1
158 RETURNS
159   .         TRUE iff that many slashes were found
160   path      the truncated path
161 *****************************************************************/
162
163 Bool cutLast(Str& path, int howmany)
164 {
165     Str temp = path;
166     char *p = (char*) temp;
167     int slashCount = 0,
168         i;
169     for (i = temp.length() - 1; i >= 0; i--)
170     {
171         if (isSlash(p[i]))
172             slashCount++;
173         if (slashCount == howmany)
174             break;
175     };
176     if (i >= 0)
177         path.nset(p, i+1);
178     else
179         path.empty();
180     return (Bool)(i >= 0);
181 };
182
183 /*****************************************************************
184 joinPaths()
185
186   merges a relative path with a base path
187 ARGS
188   relPath       the relative path. The result is returned here.
189   basePath      the base path (always absolute)
190 RETURNS
191   relPath       the newly constructed absolute path
192 *****************************************************************/
193
194 Bool segP(Str &s, int oneOrTwo)
195 {
196     return (Bool) !strcmp((char *) s, (oneOrTwo == 1 ? "." : ".."));
197 }
198
199 void joinPaths(Str& relPath, const Str& basePath)
200 {
201     Str segment;
202     DStr absPath;
203     // append the relPath to all-but-the-last-segment-of-basePath
204
205     Bool endSlash = cutLast(absPath = basePath, 1),
206         lastSeg;
207     DStr result = absPath + (endSlash? "" : "/") + relPath;
208        
209     // throw out all '.' from the path
210     const char *p = (const char*) result;
211     absPath.empty();
212     while(splitBy(p, slashes, segment))
213     {
214         if (!segP(segment, 1))
215             absPath += segment + "/";
216     }
217     if (!segP(segment, 1))
218         absPath += segment;
219
220     // throw out all "something/.." from the path
221     p = (char*) absPath;
222     int depth = 0;
223     result.empty();
224     do
225     {
226         lastSeg = (Bool) !splitBy(p, slashes, segment);
227         if (!segP(segment, 2))
228         {
229             result += segment + (lastSeg ? "" : "/");
230             depth++;
231         }
232         else
233         {
234             if (depth > 1)
235             {
236                 cutLast(result, 2);
237                 depth--;
238             }
239             else
240                 result += segment + (lastSeg ? "" : "/");
241         };
242     }
243     while(!lastSeg);
244     relPath = result;
245 }
246
247
248 URIScheme makeAbsoluteURI2(Sit S, const char* uri,
249                            const char* base, Str& absolute, Str& scheme)
250 {
251     FiveStr 
252         u_parts, 
253         b_parts;
254     Bool 
255         u_defined[5],
256         u_any = FALSE;
257
258     // first, break up the URIs into their 5 components
259     splitURI(uri, u_parts);
260     splitURI(base, b_parts);
261
262     // set u_defined[i] to TRUE if the i-th uri component is nonvoid
263     for (int i = 0; i < 5; i++)
264         u_any = (Bool) ((u_defined[i] = (Bool) !u_parts[i].isEmpty()) || u_any);
265
266     if (!u_any) // all components empty: the reference is to the current document
267     {
268         splitURI(base,u_parts);
269         u_parts[U_QUERY].empty();       // query and fragment are NOT inherited from base
270         u_parts[U_FRAG].empty();
271     }
272     else    // not all components are empty
273     {
274         if (!u_defined[U_SCHEME])                       // undefined scheme
275         {
276             u_parts[U_SCHEME] = b_parts[U_SCHEME];      // inherit scheme from base
277             if (!u_defined[U_AUTH])                     // undefined authority
278             {
279                 u_parts[U_AUTH] = b_parts[U_AUTH];      // inherit authority from base
280                 if (!isSlash(u_parts[U_PATH][0]))       // path is relative
281                     joinPaths(u_parts[U_PATH], b_parts[U_PATH]);    // append path to base path
282                 // query and fragment stay as they are in 'uri'
283             }
284         }
285         else    // scheme defined, check for paths not starting with '/'
286         {
287             if (!u_defined[U_AUTH] && !isSlash(u_parts[U_PATH][0]))
288                 u_parts[U_PATH] = Str("/") + u_parts[U_PATH];
289         }
290     }
291     DStr joined = absolute;
292     joinURI(joined, u_parts, FALSE);         // join all components into a URI for return (no scheme)
293     absolute = joined;
294     return schemeToURI_(S, scheme = u_parts[U_SCHEME]);
295 }
296
297
298 //    URIScheme makeAbsoluteURI(uri, base, absolute)
299 //
300 //    Merges a (possibly relative) URI reference with a base URI, setting
301 //    'absolute' to the result. 
302 //
303 URIScheme makeAbsoluteURI(Sit S, const char* uri,
304                           const char* base, Str& absolute)
305 {
306     Str scheme;
307     URIScheme temp;
308     temp = makeAbsoluteURI2(S, uri, base, absolute, scheme);
309     absolute = (scheme + ":") + absolute;
310     return temp;
311 }
312
313
314 URIScheme uri2SchemePath(Sit S, const char *absolute, Str& scheme, Str& rest)
315 {
316     Bool found = (Bool) !!splitBy(absolute, ":", scheme);
317     assert(found);
318     rest = (char*) absolute;
319 /*
320  *    if (isSlash(*absolute) && isSlash(absolute[1]))
321  *       rest = (char*) absolute + 2;
322  *   else
323  *       rest = (char*) absolute;
324  */
325     return schemeToURI_(S, scheme);
326 }
327
328
329 /*****************************************************************
330 DataLine
331
332   is a class that holds the machinery needed to retrieve data from
333   a given URI. There are two internally supported URI schemes:
334   file  (the plain "file://...")
335   arg   (for access to named memory blocks passed to Sablotron)
336
337   Other schemes are passed to the extending scheme handler (if
338   one has been registered). This way, requests such as http:...
339   can be processed.
340
341   The life cycle of a DataLine:
342     Upon construction, no URI is attached yet.
343     Call open() to associate a URI.
344     Repeatedly call save() or get() to retrieve data.
345     Call close() to close the resource.
346     Call the destructor.
347
348   The 'write' data line with the scheme of 'arg' will need to be
349   accessible to the user even after the Processor object is destroyed;
350   it is then freed by 'SablotFreeBuffer'.
351 *****************************************************************/
352
353 /*****************************************************************
354 DataLine::DataLine()
355
356   This constructor just sets everything to zeroes and such.
357 *****************************************************************/
358
359 DataLine::DataLine()
360 {
361     mode = DLMODE_NONE;       
362     scheme = URI_NONE;      
363     f = NULL;               
364     buffer = NULL;          
365     outBuf = NULL;
366     bufCurr = 0;
367     fileIsStd = FALSE;
368     utf16Encoded = FALSE;
369     handler = NULL;
370     handlerUD = NULL;
371     handle = 0;
372     gotWholeDocument = FALSE;
373 }
374
375 /*****************************************************************
376 DataLine::~DataLine()
377
378   The destructor asserts that the data line had been closed.
379 *****************************************************************/
380
381 DataLine::~DataLine()
382 {
383     // removing the asserts (can be killed anytime due to error)
384     // assert(mode == DLMODE_CLOSED || mode == DLMODE_NONE);
385     // assert(!f);
386     // if there is an outBuf, delete it now
387     if (outBuf)
388         delete outBuf;
389 }
390
391 /*****************************************************************
392 DataLine::open()
393
394   Opens the data line for a given URI and access mode. Actual
395   data transfer is only done on subsequent get() or save() calls.
396   open() tries to call the extending scheme handler if it cannot
397   handle a request itself.
398
399 ARGS
400 _uri        the URI identifier for the resource, including the
401             scheme (e.g. "file:///x.xml")
402 _baseUri    the base URI used in case the reference in _uri is
403             relative
404 _mode       the access mode (DLMODE_READ, DLMODE_WRITE)
405 *****************************************************************/
406
407 #define specErr1(S, code, arg) \
408 {if (ignoreErr) {Warn1(S,code,arg); return NOT_OK;} else Err1(S,code,arg);}
409
410 eFlag DataLine::open(Sit S, const char *_uri, DLAccessMode _mode, 
411                      StrStrList* argList_, Bool ignoreErr /* = FALSE */)
412 {
413     assert(mode == DLMODE_NONE);  // the buffer must not be open yet
414     // combine _uri and _baseUri into one
415     Str strScheme, strPath;
416     scheme = uri2SchemePath(S, _uri, strScheme, strPath);
417     char *name = (char*) strPath;
418
419     // mode set in the end
420     fullUri = (char*)_uri;
421
422     switch(scheme)
423     {
424     case URI_FILE:
425         {
426             if (name[0] == '/' && name[1] == '/')
427                 name += 2;          // skipping the "//" in front
428             // try to open the file
429 #ifdef _MSC_VER
430             if (!(f = stdopen(name,_mode == DLMODE_WRITE ? "wb" : "rt")))
431 #else
432             if (!(f = stdopen(name,_mode == DLMODE_WRITE ? "w" : "r")))
433 #endif
434                 specErr1(S, E_FILE_OPEN, name);
435             // set fileIsStd if filename is "stdin", "stdout" or "stderr"
436             fileIsStd = isstd(name);
437         }; break;
438     case URI_ARG:
439         {
440             // if opening for read access, get the pointer to the argument contents
441             // plus some extra information
442             if (_mode == DLMODE_READ)
443             {
444               Str *value = NULL;
445               if (argList_)
446                 value = argList_ -> find(name);
447               if (!value)
448                 specErr1(S, E1_ARG_NOT_FOUND, name);
449               buffer = (char*)*value;
450             }
451             // if opening for write access, just allocate a new dynamic block
452             else
453                 outBuf = new DynBlock;
454         }; break;
455     default:
456         {
457             // try the extending scheme handler
458             // ask the handler address from the Processor
459           Processor *proc = S.getProcessor();
460           if (proc)
461             handler = proc->getSchemeHandler(&handlerUD);
462           else
463             handler = NULL;
464           // if there is no handler, report unsupported scheme
465           if (!handler)
466             specErr1(S, E1_UNSUPPORTED_SCHEME, strScheme);
467           // try the fast way
468           int count = 0;
469           buffer = NULL;
470           if (_mode == DLMODE_READ && handler -> getAll)
471             handler -> getAll(handlerUD, proc,
472                               strScheme, name, &buffer, &count);
473           if (buffer && (count != -1))
474             {
475               gotWholeDocument = TRUE;
476               bufCurr = 0;
477             }
478           else
479             {
480               // call the handler's open() function, obtaining a handle
481               switch(handler -> open(handlerUD, proc, 
482                                      strScheme, name, &handle))
483                 {
484                 case SH_ERR_UNSUPPORTED_SCHEME:     // scheme not supported
485                   specErr1(S, E1_UNSUPPORTED_SCHEME, strScheme);
486                 case SH_ERR_NOT_OK:
487                   specErr1(S, E1_URI_OPEN, strScheme + ":" + strPath);
488                 };
489             }
490         };
491     };
492     // open successfully completed. Set the new mode.
493     mode = _mode;
494     return OK;
495 }
496
497 /*****************************************************************
498 DataLine::close()
499
500   closes the resource attached to this data line.
501 *****************************************************************/
502 eFlag DataLine::close(Sit S)
503 {
504     assert(mode != DLMODE_NONE);
505     switch(scheme)
506     {
507     case URI_FILE:
508         {
509             assert(f);
510             if (!fileIsStd)
511             {
512               if (fclose(f))
513                 Err1(S, E1_URI_CLOSE, fullUri);
514             };
515             f = NULL;
516         }; break;
517     case URI_ARG:
518         break;
519     case URI_EXTENSION:
520         {
521             if (gotWholeDocument)
522             {
523                 NZ(handler) -> freeMemory(handlerUD, S.getProcessor(), buffer);
524             }
525             else
526             {
527                 if(NZ(handler) -> close(handlerUD, S.getProcessor(), handle))
528                     Err1(S, E1_URI_CLOSE, fullUri);
529             }
530         }; break;
531     };
532     mode = DLMODE_CLOSED;
533     return OK;
534 }
535
536 /*****************************************************************
537 save()
538
539   saves an UTF-8 string pointed to by data to the data line.
540   This is the place to perform any recoding, escaping and other operations
541   that require char-by-char scanning of the string.
542 *****************************************************************/
543
544 int my_wcslen(const char *p)
545 {
546     int len;
547     for (len = 2;  *(short int*)p; p += 2, len += 2);
548     return len;
549 }
550
551 eFlag DataLine::save(Sit S, const char *data, int length)
552 {
553     assert(mode == DLMODE_WRITE); // assume the file open for writing
554     // int length = utf16Encoded ? my_wcslen(data) : strlen(data);
555     switch (scheme)             // choose the output procedure 
556     {
557     case URI_FILE:              // file: scheme
558         {
559             assert(f);          // the file must be open
560             // fputs(data, f);
561             fwrite(data, 1, length, f);
562         }; break;
563     case URI_ARG:               // arg: scheme
564         {
565             assert(outBuf);     // the output buffer must exist
566             outBuf -> nadd(data, length); 
567         }; break;
568     case URI_EXTENSION:         // external handler
569         {
570             int actual = length;
571             if( NZ(handler) -> put(handlerUD, S.getProcessor(), handle, data, &actual) )
572                 Err1(S, E1_URI_WRITE, fullUri);
573         };
574     }
575     return OK;
576 }
577
578 /*................................................................
579 pointsAtEnd()
580
581   DESCRIPTION
582 a macro that returns nonzero if the given char* points at a 
583 string terminator
584
585   ARGS
586 p       the pointer
587 is16    TRUE iff the string is UTF-16
588 ................................................................*/
589
590 #define pointsAtEnd(p, is16) ((is16) ? (!*(unsigned short*)(p)) : (!*(p)))
591
592 /*****************************************************************
593     get()
594
595 - retrieves at most 'maxcount' bytes into buffer 'dest'.
596 - input should be NUL-terminated
597 - if a terminating 0 is reached, copying stops
598 *****************************************************************/
599
600 int DataLine::get(Sit S, char *dest,int maxcount)
601 {
602     int result = 0;
603     assert(mode == DLMODE_READ);  // assume the file open for reading
604     switch(scheme)
605     {
606     case URI_FILE:
607         {
608             assert(f);          // the file must be open
609             result = fread(dest,1,maxcount,f);
610             // return the number of bytes read
611         }; break;
612     case URI_ARG:
613         {
614             assert(buffer);     // the buffer must exist
615             // do a 'strncpy' that shifts dest and bufCurr;
616             // i counts the number of bytes transferred
617                         char * copyChar = dest;
618             int i;
619             for (i = 0; 
620                 (!pointsAtEnd(buffer + bufCurr, utf16Encoded)) && (i < maxcount); 
621                 i++)
622                 {
623                     *(copyChar++) = buffer[bufCurr++];
624                 };
625                 result = i;
626         }; break;
627     case URI_EXTENSION:         // external handler
628         {
629             if (gotWholeDocument)
630             {
631                 // ugly hack: copied the following from above
632                 assert(buffer);     // the buffer must exist
633                                 char * copyChar = dest;
634                 int i;
635                 for (i = 0; 
636                 (!pointsAtEnd(buffer + bufCurr, utf16Encoded)) && (i < maxcount); 
637                 i++)
638                 {
639                     *(copyChar++) = buffer[bufCurr++];
640                 };
641                 result = i;
642             }
643             else
644             {
645                 int actual = maxcount;
646                 if( NZ(handler) -> get(handlerUD, S.getProcessor(), handle, dest, &actual) )
647                 {
648                     S.message( MT_ERROR, E1_URI_READ, fullUri, "" );
649                     return -1;
650                 }
651                 result = actual;
652             }
653         }; break;
654     }
655         // need to NUL terminate in order to prevent C string
656         // functions running off the end of the buffer
657
658         // assignment assumes that the passed in dest is allocated
659         // one bigger than maxcount
660         dest[result] = '\0';
661     return result;              // return the number of bytes read
662 }
663
664 /*****************************************************************
665 getOutBuffer()
666
667   returns the pointer to the output buffer which may be used after
668   all processing is finished (remains allocated along with the
669   whole DataLine)
670 *****************************************************************/
671
672 DynBlock* DataLine::getOutBuffer()
673 {
674     // check that the output buffer exists and that we're open for write
675     assert(mode == DLMODE_WRITE && scheme == URI_ARG); 
676     return NZ(outBuf); // -> getPointer();
677 }
678
679 eFlag DataLine::setURIAndClose(Sit S, const char *_uri)
680 {
681     assert( mode == DLMODE_NONE );
682     mode = DLMODE_CLOSED;
683     scheme = URI_ARG;
684     fullUri = _uri;
685     return OK;
686 }
687
688 void DataLine::report(Sit S, MsgType type, MsgCode code, const Str& arg1, const Str& arg2)
689 {
690     S.message(type, code, arg1, arg2);
691 }
692