|  | /*************************************************************************** | 
|  | *                                  _   _ ____  _ | 
|  | *  Project                     ___| | | |  _ \| | | 
|  | *                             / __| | | | |_) | | | 
|  | *                            | (__| |_| |  _ <| |___ | 
|  | *                             \___|\___/|_| \_\_____| | 
|  | * | 
|  | * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. | 
|  | * | 
|  | * This software is licensed as described in the file COPYING, which | 
|  | * you should have received as part of this distribution. The terms | 
|  | * are also available at https://curl.se/docs/copyright.html. | 
|  | * | 
|  | * You may opt to use, copy, modify, merge, publish, distribute and/or sell | 
|  | * copies of the Software, and permit persons to whom the Software is | 
|  | * furnished to do so, under the terms of the COPYING file. | 
|  | * | 
|  | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | 
|  | * KIND, either express or implied. | 
|  | * | 
|  | * SPDX-License-Identifier: curl | 
|  | * | 
|  | ***************************************************************************/ | 
|  | #include "server_setup.h" | 
|  |  | 
|  | #include "getpart.h" | 
|  |  | 
|  | #define ENABLE_CURLX_PRINTF | 
|  | /* make the curlx header define all printf() functions to use the curlx_* | 
|  | versions instead */ | 
|  | #include "curlx.h" /* from the private lib dir */ | 
|  |  | 
|  | /* just to please curl_base64.h we create a fake struct */ | 
|  | struct Curl_easy { | 
|  | int fake; | 
|  | }; | 
|  |  | 
|  | #include "curl_base64.h" | 
|  | #include "curl_memory.h" | 
|  |  | 
|  | /* include memdebug.h last */ | 
|  | #include "memdebug.h" | 
|  |  | 
|  | #define EAT_SPACE(p) while(*(p) && ISSPACE(*(p))) (p)++ | 
|  |  | 
|  | #define EAT_WORD(p)  while(*(p) && !ISSPACE(*(p)) && ('>' != *(p))) (p)++ | 
|  |  | 
|  | #ifdef DEBUG_GETPART | 
|  | #define show(x) printf x | 
|  | #else | 
|  | #define show(x) Curl_nop_stmt | 
|  | #endif | 
|  |  | 
|  | #if defined(_MSC_VER) && defined(_DLL) | 
|  | #  pragma warning(disable:4232) /* MSVC extension, dllimport identity */ | 
|  | #endif | 
|  |  | 
|  | curl_malloc_callback Curl_cmalloc = (curl_malloc_callback)malloc; | 
|  | curl_free_callback Curl_cfree = (curl_free_callback)free; | 
|  | curl_realloc_callback Curl_crealloc = (curl_realloc_callback)realloc; | 
|  | curl_strdup_callback Curl_cstrdup = (curl_strdup_callback)strdup; | 
|  | curl_calloc_callback Curl_ccalloc = (curl_calloc_callback)calloc; | 
|  | #if defined(WIN32) && defined(UNICODE) | 
|  | curl_wcsdup_callback Curl_cwcsdup = (curl_wcsdup_callback)_wcsdup; | 
|  | #endif | 
|  |  | 
|  | #if defined(_MSC_VER) && defined(_DLL) | 
|  | #  pragma warning(default:4232) /* MSVC extension, dllimport identity */ | 
|  | #endif | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Curl_convert_clone() returns a malloced copy of the source string (if | 
|  | * returning CURLE_OK), with the data converted to network format. This | 
|  | * function is used by base64 code in libcurl built to support data | 
|  | * conversion. This is a DUMMY VERSION that returns data unmodified - for | 
|  | * use by the test server only. | 
|  | */ | 
|  | CURLcode Curl_convert_clone(struct Curl_easy *data, | 
|  | const char *indata, | 
|  | size_t insize, | 
|  | char **outbuf); | 
|  | CURLcode Curl_convert_clone(struct Curl_easy *data, | 
|  | const char *indata, | 
|  | size_t insize, | 
|  | char **outbuf) | 
|  | { | 
|  | char *convbuf; | 
|  | (void)data; | 
|  |  | 
|  | convbuf = malloc(insize); | 
|  | if(!convbuf) | 
|  | return CURLE_OUT_OF_MEMORY; | 
|  |  | 
|  | memcpy(convbuf, indata, insize); | 
|  | *outbuf = convbuf; | 
|  | return CURLE_OK; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * line_length() | 
|  | * | 
|  | * Counts the number of characters in a line including a new line. | 
|  | * Unlike strlen() it does not stop at nul bytes. | 
|  | * | 
|  | */ | 
|  | static size_t line_length(const char *buffer, int bytestocheck) | 
|  | { | 
|  | size_t length = 1; | 
|  |  | 
|  | while(*buffer != '\n' && --bytestocheck) { | 
|  | length++; | 
|  | buffer++; | 
|  | } | 
|  | if(*buffer != '\n') { | 
|  | /* | 
|  | * We didn't find a new line so the last byte must be a | 
|  | * '\0' character inserted by fgets() which we should not | 
|  | * count. | 
|  | */ | 
|  | length--; | 
|  | } | 
|  |  | 
|  | return length; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * readline() | 
|  | * | 
|  | * Reads a complete line from a file into a dynamically allocated buffer. | 
|  | * | 
|  | * Calling function may call this multiple times with same 'buffer' | 
|  | * and 'bufsize' pointers to avoid multiple buffer allocations. Buffer | 
|  | * will be reallocated and 'bufsize' increased until whole line fits in | 
|  | * buffer before returning it. | 
|  | * | 
|  | * Calling function is responsible to free allocated buffer. | 
|  | * | 
|  | * This function may return: | 
|  | *   GPE_OUT_OF_MEMORY | 
|  | *   GPE_END_OF_FILE | 
|  | *   GPE_OK | 
|  | */ | 
|  |  | 
|  | static int readline(char **buffer, size_t *bufsize, size_t *length, | 
|  | FILE *stream) | 
|  | { | 
|  | size_t offset = 0; | 
|  | char *newptr; | 
|  |  | 
|  | if(!*buffer) { | 
|  | *buffer = malloc(128); | 
|  | if(!*buffer) | 
|  | return GPE_OUT_OF_MEMORY; | 
|  | *bufsize = 128; | 
|  | } | 
|  |  | 
|  | for(;;) { | 
|  | int bytestoread = curlx_uztosi(*bufsize - offset); | 
|  |  | 
|  | if(!fgets(*buffer + offset, bytestoread, stream)) | 
|  | return (offset != 0) ? GPE_OK : GPE_END_OF_FILE; | 
|  |  | 
|  | *length = offset + line_length(*buffer + offset, bytestoread); | 
|  | if(*(*buffer + *length - 1) == '\n') | 
|  | break; | 
|  | offset = *length; | 
|  | if(*length < *bufsize - 1) | 
|  | continue; | 
|  |  | 
|  | newptr = realloc(*buffer, *bufsize * 2); | 
|  | if(!newptr) | 
|  | return GPE_OUT_OF_MEMORY; | 
|  | *buffer = newptr; | 
|  | *bufsize *= 2; | 
|  | } | 
|  |  | 
|  | return GPE_OK; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * appenddata() | 
|  | * | 
|  | * This appends data from a given source buffer to the end of the used part of | 
|  | * a destination buffer. Arguments relative to the destination buffer are, the | 
|  | * address of a pointer to the destination buffer 'dst_buf', the length of data | 
|  | * in destination buffer excluding potential null string termination 'dst_len', | 
|  | * the allocated size of destination buffer 'dst_alloc'. All three destination | 
|  | * buffer arguments may be modified by this function. Arguments relative to the | 
|  | * source buffer are, a pointer to the source buffer 'src_buf' and indication | 
|  | * whether the source buffer is base64 encoded or not 'src_b64'. | 
|  | * | 
|  | * If the source buffer is indicated to be base64 encoded, this appends the | 
|  | * decoded data, binary or whatever, to the destination. The source buffer | 
|  | * may not hold binary data, only a null terminated string is valid content. | 
|  | * | 
|  | * Destination buffer will be enlarged and relocated as needed. | 
|  | * | 
|  | * Calling function is responsible to provide preallocated destination | 
|  | * buffer and also to deallocate it when no longer needed. | 
|  | * | 
|  | * This function may return: | 
|  | *   GPE_OUT_OF_MEMORY | 
|  | *   GPE_OK | 
|  | */ | 
|  |  | 
|  | static int appenddata(char  **dst_buf,   /* dest buffer */ | 
|  | size_t *dst_len,   /* dest buffer data length */ | 
|  | size_t *dst_alloc, /* dest buffer allocated size */ | 
|  | char   *src_buf,   /* source buffer */ | 
|  | size_t  src_len,   /* source buffer length */ | 
|  | int     src_b64)   /* != 0 if source is base64 encoded */ | 
|  | { | 
|  | size_t need_alloc = 0; | 
|  |  | 
|  | if(!src_len) | 
|  | return GPE_OK; | 
|  |  | 
|  | need_alloc = src_len + *dst_len + 1; | 
|  |  | 
|  | if(src_b64) { | 
|  | if(src_buf[src_len - 1] == '\r') | 
|  | src_len--; | 
|  |  | 
|  | if(src_buf[src_len - 1] == '\n') | 
|  | src_len--; | 
|  | } | 
|  |  | 
|  | /* enlarge destination buffer if required */ | 
|  | if(need_alloc > *dst_alloc) { | 
|  | size_t newsize = need_alloc * 2; | 
|  | char *newptr = realloc(*dst_buf, newsize); | 
|  | if(!newptr) { | 
|  | return GPE_OUT_OF_MEMORY; | 
|  | } | 
|  | *dst_alloc = newsize; | 
|  | *dst_buf = newptr; | 
|  | } | 
|  |  | 
|  | /* memcpy to support binary blobs */ | 
|  | memcpy(*dst_buf + *dst_len, src_buf, src_len); | 
|  | *dst_len += src_len; | 
|  | *(*dst_buf + *dst_len) = '\0'; | 
|  |  | 
|  | return GPE_OK; | 
|  | } | 
|  |  | 
|  | static int decodedata(char  **buf,   /* dest buffer */ | 
|  | size_t *len)   /* dest buffer data length */ | 
|  | { | 
|  | CURLcode error = CURLE_OK; | 
|  | unsigned char *buf64 = NULL; | 
|  | size_t src_len = 0; | 
|  |  | 
|  | if(!*len) | 
|  | return GPE_OK; | 
|  |  | 
|  | /* base64 decode the given buffer */ | 
|  | error = Curl_base64_decode(*buf, &buf64, &src_len); | 
|  | if(error) | 
|  | return GPE_OUT_OF_MEMORY; | 
|  |  | 
|  | if(!src_len) { | 
|  | /* | 
|  | ** currently there is no way to tell apart an OOM condition in | 
|  | ** Curl_base64_decode() from zero length decoded data. For now, | 
|  | ** let's just assume it is an OOM condition, currently we have | 
|  | ** no input for this function that decodes to zero length data. | 
|  | */ | 
|  | free(buf64); | 
|  |  | 
|  | return GPE_OUT_OF_MEMORY; | 
|  | } | 
|  |  | 
|  | /* memcpy to support binary blobs */ | 
|  | memcpy(*buf, buf64, src_len); | 
|  | *len = src_len; | 
|  | *(*buf + src_len) = '\0'; | 
|  |  | 
|  | free(buf64); | 
|  |  | 
|  | return GPE_OK; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * getpart() | 
|  | * | 
|  | * This returns whole contents of specified XML-like section and subsection | 
|  | * from the given file. This is mostly used to retrieve a specific part from | 
|  | * a test definition file for consumption by test suite servers. | 
|  | * | 
|  | * Data is returned in a dynamically allocated buffer, a pointer to this data | 
|  | * and the size of the data is stored at the addresses that caller specifies. | 
|  | * | 
|  | * If the returned data is a string the returned size will be the length of | 
|  | * the string excluding null termination. Otherwise it will just be the size | 
|  | * of the returned binary data. | 
|  | * | 
|  | * Calling function is responsible to free returned buffer. | 
|  | * | 
|  | * This function may return: | 
|  | *   GPE_NO_BUFFER_SPACE | 
|  | *   GPE_OUT_OF_MEMORY | 
|  | *   GPE_OK | 
|  | */ | 
|  |  | 
|  | int getpart(char **outbuf, size_t *outlen, | 
|  | const char *main, const char *sub, FILE *stream) | 
|  | { | 
|  | # define MAX_TAG_LEN 200 | 
|  | char couter[MAX_TAG_LEN + 1]; /* current outermost section */ | 
|  | char cmain[MAX_TAG_LEN + 1];  /* current main section */ | 
|  | char csub[MAX_TAG_LEN + 1];   /* current sub section */ | 
|  | char ptag[MAX_TAG_LEN + 1];   /* potential tag */ | 
|  | char patt[MAX_TAG_LEN + 1];   /* potential attributes */ | 
|  | char *buffer = NULL; | 
|  | char *ptr; | 
|  | char *end; | 
|  | union { | 
|  | ssize_t sig; | 
|  | size_t uns; | 
|  | } len; | 
|  | size_t bufsize = 0; | 
|  | size_t outalloc = 256; | 
|  | size_t datalen; | 
|  | int in_wanted_part = 0; | 
|  | int base64 = 0; | 
|  | int nonewline = 0; | 
|  | int error; | 
|  |  | 
|  | enum { | 
|  | STATE_OUTSIDE = 0, | 
|  | STATE_OUTER   = 1, | 
|  | STATE_INMAIN  = 2, | 
|  | STATE_INSUB   = 3, | 
|  | STATE_ILLEGAL = 4 | 
|  | } state = STATE_OUTSIDE; | 
|  |  | 
|  | *outlen = 0; | 
|  | *outbuf = malloc(outalloc); | 
|  | if(!*outbuf) | 
|  | return GPE_OUT_OF_MEMORY; | 
|  | *(*outbuf) = '\0'; | 
|  |  | 
|  | couter[0] = cmain[0] = csub[0] = ptag[0] = patt[0] = '\0'; | 
|  |  | 
|  | while((error = readline(&buffer, &bufsize, &datalen, stream)) == GPE_OK) { | 
|  |  | 
|  | ptr = buffer; | 
|  | EAT_SPACE(ptr); | 
|  |  | 
|  | if('<' != *ptr) { | 
|  | if(in_wanted_part) { | 
|  | show(("=> %s", buffer)); | 
|  | error = appenddata(outbuf, outlen, &outalloc, buffer, datalen, | 
|  | base64); | 
|  | if(error) | 
|  | break; | 
|  | } | 
|  | continue; | 
|  | } | 
|  |  | 
|  | ptr++; | 
|  |  | 
|  | if('/' == *ptr) { | 
|  | /* | 
|  | ** closing section tag | 
|  | */ | 
|  |  | 
|  | ptr++; | 
|  | end = ptr; | 
|  | EAT_WORD(end); | 
|  | len.sig = end - ptr; | 
|  | if(len.sig > MAX_TAG_LEN) { | 
|  | error = GPE_NO_BUFFER_SPACE; | 
|  | break; | 
|  | } | 
|  | memcpy(ptag, ptr, len.uns); | 
|  | ptag[len.uns] = '\0'; | 
|  |  | 
|  | if((STATE_INSUB == state) && !strcmp(csub, ptag)) { | 
|  | /* end of current sub section */ | 
|  | state = STATE_INMAIN; | 
|  | csub[0] = '\0'; | 
|  | if(in_wanted_part) { | 
|  | /* end of wanted part */ | 
|  | in_wanted_part = 0; | 
|  |  | 
|  | /* Do we need to base64 decode the data? */ | 
|  | if(base64) { | 
|  | error = decodedata(outbuf, outlen); | 
|  | if(error) | 
|  | return error; | 
|  | } | 
|  | if(nonewline) | 
|  | (*outlen)--; | 
|  | break; | 
|  | } | 
|  | } | 
|  | else if((STATE_INMAIN == state) && !strcmp(cmain, ptag)) { | 
|  | /* end of current main section */ | 
|  | state = STATE_OUTER; | 
|  | cmain[0] = '\0'; | 
|  | if(in_wanted_part) { | 
|  | /* end of wanted part */ | 
|  | in_wanted_part = 0; | 
|  |  | 
|  | /* Do we need to base64 decode the data? */ | 
|  | if(base64) { | 
|  | error = decodedata(outbuf, outlen); | 
|  | if(error) | 
|  | return error; | 
|  | } | 
|  | if(nonewline) | 
|  | (*outlen)--; | 
|  | break; | 
|  | } | 
|  | } | 
|  | else if((STATE_OUTER == state) && !strcmp(couter, ptag)) { | 
|  | /* end of outermost file section */ | 
|  | state = STATE_OUTSIDE; | 
|  | couter[0] = '\0'; | 
|  | if(in_wanted_part) { | 
|  | /* end of wanted part */ | 
|  | in_wanted_part = 0; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | } | 
|  | else if(!in_wanted_part) { | 
|  | /* | 
|  | ** opening section tag | 
|  | */ | 
|  |  | 
|  | /* get potential tag */ | 
|  | end = ptr; | 
|  | EAT_WORD(end); | 
|  | len.sig = end - ptr; | 
|  | if(len.sig > MAX_TAG_LEN) { | 
|  | error = GPE_NO_BUFFER_SPACE; | 
|  | break; | 
|  | } | 
|  | memcpy(ptag, ptr, len.uns); | 
|  | ptag[len.uns] = '\0'; | 
|  |  | 
|  | /* ignore comments, doctypes and xml declarations */ | 
|  | if(('!' == ptag[0]) || ('?' == ptag[0])) { | 
|  | show(("* ignoring (%s)", buffer)); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* get all potential attributes */ | 
|  | ptr = end; | 
|  | EAT_SPACE(ptr); | 
|  | end = ptr; | 
|  | while(*end && ('>' != *end)) | 
|  | end++; | 
|  | len.sig = end - ptr; | 
|  | if(len.sig > MAX_TAG_LEN) { | 
|  | error = GPE_NO_BUFFER_SPACE; | 
|  | break; | 
|  | } | 
|  | memcpy(patt, ptr, len.uns); | 
|  | patt[len.uns] = '\0'; | 
|  |  | 
|  | if(STATE_OUTSIDE == state) { | 
|  | /* outermost element (<testcase>) */ | 
|  | strcpy(couter, ptag); | 
|  | state = STATE_OUTER; | 
|  | continue; | 
|  | } | 
|  | else if(STATE_OUTER == state) { | 
|  | /* start of a main section */ | 
|  | strcpy(cmain, ptag); | 
|  | state = STATE_INMAIN; | 
|  | continue; | 
|  | } | 
|  | else if(STATE_INMAIN == state) { | 
|  | /* start of a sub section */ | 
|  | strcpy(csub, ptag); | 
|  | state = STATE_INSUB; | 
|  | if(!strcmp(cmain, main) && !strcmp(csub, sub)) { | 
|  | /* start of wanted part */ | 
|  | in_wanted_part = 1; | 
|  | if(strstr(patt, "base64=")) | 
|  | /* bit rough test, but "mostly" functional, */ | 
|  | /* treat wanted part data as base64 encoded */ | 
|  | base64 = 1; | 
|  | if(strstr(patt, "nonewline=")) { | 
|  | show(("* setting nonewline\n")); | 
|  | nonewline = 1; | 
|  | } | 
|  | } | 
|  | continue; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | if(in_wanted_part) { | 
|  | show(("=> %s", buffer)); | 
|  | error = appenddata(outbuf, outlen, &outalloc, buffer, datalen, base64); | 
|  | if(error) | 
|  | break; | 
|  | } | 
|  |  | 
|  | } /* while */ | 
|  |  | 
|  | free(buffer); | 
|  |  | 
|  | if(error != GPE_OK) { | 
|  | if(error == GPE_END_OF_FILE) | 
|  | error = GPE_OK; | 
|  | else { | 
|  | free(*outbuf); | 
|  | *outbuf = NULL; | 
|  | *outlen = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | return error; | 
|  | } |