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