xref: /curl/tests/server/getpart.c (revision e9a7d4a1)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 #include "server_setup.h"
25 
26 #include "getpart.h"
27 
28 #define ENABLE_CURLX_PRINTF
29 /* make the curlx header define all printf() functions to use the curlx_*
30    versions instead */
31 #include "curlx.h" /* from the private lib dir */
32 
33 /* just to please curl_base64.h we create a fake struct */
34 struct Curl_easy {
35   int fake;
36 };
37 
38 #include "curl_base64.h"
39 #include "curl_memory.h"
40 
41 /* include memdebug.h last */
42 #include "memdebug.h"
43 
44 #define EAT_SPACE(p) while(*(p) && ISSPACE(*(p))) (p)++
45 
46 #define EAT_WORD(p)  while(*(p) && !ISSPACE(*(p)) && ('>' != *(p))) (p)++
47 
48 #ifdef DEBUG_GETPART
49 #define show(x) printf x
50 #else
51 #define show(x) Curl_nop_stmt
52 #endif
53 
54 #if defined(_MSC_VER) && defined(_DLL)
55 #  pragma warning(disable:4232) /* MSVC extension, dllimport identity */
56 #endif
57 
58 curl_malloc_callback Curl_cmalloc = (curl_malloc_callback)malloc;
59 curl_free_callback Curl_cfree = (curl_free_callback)free;
60 curl_realloc_callback Curl_crealloc = (curl_realloc_callback)realloc;
61 curl_strdup_callback Curl_cstrdup = (curl_strdup_callback)strdup;
62 curl_calloc_callback Curl_ccalloc = (curl_calloc_callback)calloc;
63 #if defined(_WIN32) && defined(UNICODE)
64 curl_wcsdup_callback Curl_cwcsdup = (curl_wcsdup_callback)_wcsdup;
65 #endif
66 
67 #if defined(_MSC_VER) && defined(_DLL)
68 #  pragma warning(default:4232) /* MSVC extension, dllimport identity */
69 #endif
70 
71 
72 /*
73  * Curl_convert_clone() returns a malloced copy of the source string (if
74  * returning CURLE_OK), with the data converted to network format. This
75  * function is used by base64 code in libcurl built to support data
76  * conversion. This is a DUMMY VERSION that returns data unmodified - for
77  * use by the test server only.
78  */
79 CURLcode Curl_convert_clone(struct Curl_easy *data,
80                             const char *indata,
81                             size_t insize,
82                             char **outbuf);
Curl_convert_clone(struct Curl_easy * data,const char * indata,size_t insize,char ** outbuf)83 CURLcode Curl_convert_clone(struct Curl_easy *data,
84                             const char *indata,
85                             size_t insize,
86                             char **outbuf)
87 {
88   char *convbuf;
89   (void)data;
90 
91   convbuf = malloc(insize);
92   if(!convbuf)
93     return CURLE_OUT_OF_MEMORY;
94 
95   memcpy(convbuf, indata, insize);
96   *outbuf = convbuf;
97   return CURLE_OK;
98 }
99 
100 /*
101  * line_length()
102  *
103  * Counts the number of characters in a line including a new line.
104  * Unlike strlen() it does not stop at nul bytes.
105  *
106  */
line_length(const char * buffer,int bytestocheck)107 static size_t line_length(const char *buffer, int bytestocheck)
108 {
109   size_t length = 1;
110 
111   while(*buffer != '\n' && --bytestocheck) {
112     length++;
113     buffer++;
114   }
115   if(*buffer != '\n') {
116     /*
117      * We didn't find a new line so the last byte must be a
118      * '\0' character inserted by fgets() which we should not
119      * count.
120      */
121     length--;
122   }
123 
124   return length;
125 }
126 
127 /*
128  * readline()
129  *
130  * Reads a complete line from a file into a dynamically allocated buffer.
131  *
132  * Calling function may call this multiple times with same 'buffer'
133  * and 'bufsize' pointers to avoid multiple buffer allocations. Buffer
134  * will be reallocated and 'bufsize' increased until whole line fits in
135  * buffer before returning it.
136  *
137  * Calling function is responsible to free allocated buffer.
138  *
139  * This function may return:
140  *   GPE_OUT_OF_MEMORY
141  *   GPE_END_OF_FILE
142  *   GPE_OK
143  */
144 
readline(char ** buffer,size_t * bufsize,size_t * length,FILE * stream)145 static int readline(char **buffer, size_t *bufsize, size_t *length,
146                     FILE *stream)
147 {
148   size_t offset = 0;
149   char *newptr;
150 
151   if(!*buffer) {
152     *buffer = calloc(1, 128);
153     if(!*buffer)
154       return GPE_OUT_OF_MEMORY;
155     *bufsize = 128;
156   }
157 
158   for(;;) {
159     int bytestoread = curlx_uztosi(*bufsize - offset);
160 
161     if(!fgets(*buffer + offset, bytestoread, stream))
162       return (offset != 0) ? GPE_OK : GPE_END_OF_FILE;
163 
164     *length = offset + line_length(*buffer + offset, bytestoread);
165     if(*(*buffer + *length - 1) == '\n')
166       break;
167     offset = *length;
168     if(*length < *bufsize - 1)
169       continue;
170 
171     newptr = realloc(*buffer, *bufsize * 2);
172     if(!newptr)
173       return GPE_OUT_OF_MEMORY;
174     memset(&newptr[*bufsize], 0, *bufsize);
175     *buffer = newptr;
176     *bufsize *= 2;
177   }
178 
179   return GPE_OK;
180 }
181 
182 /*
183  * appenddata()
184  *
185  * This appends data from a given source buffer to the end of the used part of
186  * a destination buffer. Arguments relative to the destination buffer are, the
187  * address of a pointer to the destination buffer 'dst_buf', the length of data
188  * in destination buffer excluding potential null string termination 'dst_len',
189  * the allocated size of destination buffer 'dst_alloc'. All three destination
190  * buffer arguments may be modified by this function. Arguments relative to the
191  * source buffer are, a pointer to the source buffer 'src_buf' and indication
192  * whether the source buffer is base64 encoded or not 'src_b64'.
193  *
194  * If the source buffer is indicated to be base64 encoded, this appends the
195  * decoded data, binary or whatever, to the destination. The source buffer
196  * may not hold binary data, only a null terminated string is valid content.
197  *
198  * Destination buffer will be enlarged and relocated as needed.
199  *
200  * Calling function is responsible to provide preallocated destination
201  * buffer and also to deallocate it when no longer needed.
202  *
203  * This function may return:
204  *   GPE_OUT_OF_MEMORY
205  *   GPE_OK
206  */
207 
appenddata(char ** dst_buf,size_t * dst_len,size_t * dst_alloc,char * src_buf,size_t src_len,int src_b64)208 static int appenddata(char  **dst_buf,   /* dest buffer */
209                       size_t *dst_len,   /* dest buffer data length */
210                       size_t *dst_alloc, /* dest buffer allocated size */
211                       char   *src_buf,   /* source buffer */
212                       size_t  src_len,   /* source buffer length */
213                       int     src_b64)   /* != 0 if source is base64 encoded */
214 {
215   size_t need_alloc = 0;
216 
217   if(!src_len)
218     return GPE_OK;
219 
220   need_alloc = src_len + *dst_len + 1;
221 
222   if(src_b64) {
223     if(src_buf[src_len - 1] == '\r')
224       src_len--;
225 
226     if(src_buf[src_len - 1] == '\n')
227       src_len--;
228   }
229 
230   /* enlarge destination buffer if required */
231   if(need_alloc > *dst_alloc) {
232     size_t newsize = need_alloc * 2;
233     char *newptr = realloc(*dst_buf, newsize);
234     if(!newptr) {
235       return GPE_OUT_OF_MEMORY;
236     }
237     *dst_alloc = newsize;
238     *dst_buf = newptr;
239   }
240 
241   /* memcpy to support binary blobs */
242   memcpy(*dst_buf + *dst_len, src_buf, src_len);
243   *dst_len += src_len;
244   *(*dst_buf + *dst_len) = '\0';
245 
246   return GPE_OK;
247 }
248 
decodedata(char ** buf,size_t * len)249 static int decodedata(char  **buf,   /* dest buffer */
250                       size_t *len)   /* dest buffer data length */
251 {
252   CURLcode error = CURLE_OK;
253   unsigned char *buf64 = NULL;
254   size_t src_len = 0;
255 
256   if(!*len)
257     return GPE_OK;
258 
259   /* base64 decode the given buffer */
260   error = Curl_base64_decode(*buf, &buf64, &src_len);
261   if(error)
262     return GPE_OUT_OF_MEMORY;
263 
264   if(!src_len) {
265     /*
266     ** currently there is no way to tell apart an OOM condition in
267     ** Curl_base64_decode() from zero length decoded data. For now,
268     ** let's just assume it is an OOM condition, currently we have
269     ** no input for this function that decodes to zero length data.
270     */
271     free(buf64);
272 
273     return GPE_OUT_OF_MEMORY;
274   }
275 
276   /* memcpy to support binary blobs */
277   memcpy(*buf, buf64, src_len);
278   *len = src_len;
279   *(*buf + src_len) = '\0';
280 
281   free(buf64);
282 
283   return GPE_OK;
284 }
285 
286 /*
287  * getpart()
288  *
289  * This returns whole contents of specified XML-like section and subsection
290  * from the given file. This is mostly used to retrieve a specific part from
291  * a test definition file for consumption by test suite servers.
292  *
293  * Data is returned in a dynamically allocated buffer, a pointer to this data
294  * and the size of the data is stored at the addresses that caller specifies.
295  *
296  * If the returned data is a string the returned size will be the length of
297  * the string excluding null termination. Otherwise it will just be the size
298  * of the returned binary data.
299  *
300  * Calling function is responsible to free returned buffer.
301  *
302  * This function may return:
303  *   GPE_NO_BUFFER_SPACE
304  *   GPE_OUT_OF_MEMORY
305  *   GPE_OK
306  */
307 
getpart(char ** outbuf,size_t * outlen,const char * main,const char * sub,FILE * stream)308 int getpart(char **outbuf, size_t *outlen,
309             const char *main, const char *sub, FILE *stream)
310 {
311 # define MAX_TAG_LEN 200
312   char couter[MAX_TAG_LEN + 1]; /* current outermost section */
313   char cmain[MAX_TAG_LEN + 1];  /* current main section */
314   char csub[MAX_TAG_LEN + 1];   /* current sub section */
315   char ptag[MAX_TAG_LEN + 1];   /* potential tag */
316   char patt[MAX_TAG_LEN + 1];   /* potential attributes */
317   char *buffer = NULL;
318   char *ptr;
319   char *end;
320   union {
321     ssize_t sig;
322      size_t uns;
323   } len;
324   size_t bufsize = 0;
325   size_t outalloc = 256;
326   size_t datalen;
327   int in_wanted_part = 0;
328   int base64 = 0;
329   int nonewline = 0;
330   int error;
331 
332   enum {
333     STATE_OUTSIDE = 0,
334     STATE_OUTER   = 1,
335     STATE_INMAIN  = 2,
336     STATE_INSUB   = 3,
337     STATE_ILLEGAL = 4
338   } state = STATE_OUTSIDE;
339 
340   *outlen = 0;
341   *outbuf = malloc(outalloc);
342   if(!*outbuf)
343     return GPE_OUT_OF_MEMORY;
344   *(*outbuf) = '\0';
345 
346   couter[0] = cmain[0] = csub[0] = ptag[0] = patt[0] = '\0';
347 
348   while((error = readline(&buffer, &bufsize, &datalen, stream)) == GPE_OK) {
349 
350     ptr = buffer;
351     EAT_SPACE(ptr);
352 
353     if('<' != *ptr) {
354       if(in_wanted_part) {
355         show(("=> %s", buffer));
356         error = appenddata(outbuf, outlen, &outalloc, buffer, datalen,
357                            base64);
358         if(error)
359           break;
360       }
361       continue;
362     }
363 
364     ptr++;
365 
366     if('/' == *ptr) {
367       /*
368       ** closing section tag
369       */
370 
371       ptr++;
372       end = ptr;
373       EAT_WORD(end);
374       len.sig = end - ptr;
375       if(len.sig > MAX_TAG_LEN) {
376         error = GPE_NO_BUFFER_SPACE;
377         break;
378       }
379       memcpy(ptag, ptr, len.uns);
380       ptag[len.uns] = '\0';
381 
382       if((STATE_INSUB == state) && !strcmp(csub, ptag)) {
383         /* end of current sub section */
384         state = STATE_INMAIN;
385         csub[0] = '\0';
386         if(in_wanted_part) {
387           /* end of wanted part */
388           in_wanted_part = 0;
389 
390           /* Do we need to base64 decode the data? */
391           if(base64) {
392             error = decodedata(outbuf, outlen);
393             if(error)
394               return error;
395           }
396           if(nonewline)
397             (*outlen)--;
398           break;
399         }
400       }
401       else if((STATE_INMAIN == state) && !strcmp(cmain, ptag)) {
402         /* end of current main section */
403         state = STATE_OUTER;
404         cmain[0] = '\0';
405         if(in_wanted_part) {
406           /* end of wanted part */
407           in_wanted_part = 0;
408 
409           /* Do we need to base64 decode the data? */
410           if(base64) {
411             error = decodedata(outbuf, outlen);
412             if(error)
413               return error;
414           }
415           if(nonewline)
416             (*outlen)--;
417           break;
418         }
419       }
420       else if((STATE_OUTER == state) && !strcmp(couter, ptag)) {
421         /* end of outermost file section */
422         state = STATE_OUTSIDE;
423         couter[0] = '\0';
424         if(in_wanted_part) {
425           /* end of wanted part */
426           in_wanted_part = 0;
427           break;
428         }
429       }
430 
431     }
432     else if(!in_wanted_part) {
433       /*
434       ** opening section tag
435       */
436 
437       /* get potential tag */
438       end = ptr;
439       EAT_WORD(end);
440       len.sig = end - ptr;
441       if(len.sig > MAX_TAG_LEN) {
442         error = GPE_NO_BUFFER_SPACE;
443         break;
444       }
445       memcpy(ptag, ptr, len.uns);
446       ptag[len.uns] = '\0';
447 
448       /* ignore comments, doctypes and xml declarations */
449       if(('!' == ptag[0]) || ('?' == ptag[0])) {
450         show(("* ignoring (%s)", buffer));
451         continue;
452       }
453 
454       /* get all potential attributes */
455       ptr = end;
456       EAT_SPACE(ptr);
457       end = ptr;
458       while(*end && ('>' != *end))
459         end++;
460       len.sig = end - ptr;
461       if(len.sig > MAX_TAG_LEN) {
462         error = GPE_NO_BUFFER_SPACE;
463         break;
464       }
465       memcpy(patt, ptr, len.uns);
466       patt[len.uns] = '\0';
467 
468       if(STATE_OUTSIDE == state) {
469         /* outermost element (<testcase>) */
470         strcpy(couter, ptag);
471         state = STATE_OUTER;
472         continue;
473       }
474       else if(STATE_OUTER == state) {
475         /* start of a main section */
476         strcpy(cmain, ptag);
477         state = STATE_INMAIN;
478         continue;
479       }
480       else if(STATE_INMAIN == state) {
481         /* start of a sub section */
482         strcpy(csub, ptag);
483         state = STATE_INSUB;
484         if(!strcmp(cmain, main) && !strcmp(csub, sub)) {
485           /* start of wanted part */
486           in_wanted_part = 1;
487           if(strstr(patt, "base64="))
488               /* bit rough test, but "mostly" functional, */
489               /* treat wanted part data as base64 encoded */
490               base64 = 1;
491           if(strstr(patt, "nonewline=")) {
492             show(("* setting nonewline\n"));
493             nonewline = 1;
494           }
495         }
496         continue;
497       }
498 
499     }
500 
501     if(in_wanted_part) {
502       show(("=> %s", buffer));
503       error = appenddata(outbuf, outlen, &outalloc, buffer, datalen, base64);
504       if(error)
505         break;
506     }
507 
508   } /* while */
509 
510   free(buffer);
511 
512   if(error != GPE_OK) {
513     if(error == GPE_END_OF_FILE)
514       error = GPE_OK;
515     else {
516       free(*outbuf);
517       *outbuf = NULL;
518       *outlen = 0;
519     }
520   }
521 
522   return error;
523 }
524