cURL / Mailing Lists / curl-library / Single Mail

curl-library

RE: Known problems with simple formadd and curl_formfree?

From: Max L. Eidswick <max_at_eidswick.com>
Date: Sat, 19 Jan 2008 16:24:26 -0700

From: curl-library-bounces_at_cool.haxx.se [mailto:curl-library-bounces_at_cool.haxx.se] On Behalf Of Daniel Stenberg
Sent: Monday, January 14, 2008 4:14 AM
To: libcurl development
Subject: RE: Known problems with simple formadd and curl_formfree?

On Sun, 13 Jan 2008, Max L. Eidswick wrote:

> One piece of additional info ... the memory violation is eliminated if I
> comment out the formfree call. Of course this results in memory leakage,
> but the error seems to be related to repeated form creation with formadd's
> and then a periodic formfree. I am sure this is heavily tested common code
> in the libcurl library, so it is probably misusage by my code, simple as it
> is.

Yes it is widely used, tested since very long time and not modified in ages.
This is not a known problem. I've never seen anyone have problems with this
before!

Please provide us a complete source code for an application that repeats your
problem and we can have a look!

-- 
OK, one of our developers is sure this is not in his source :-).  We have put together a small test case which follows.  It is
probably a very simple misinterpretation of the docs.  This source runs fine with the formfree call commented out and crashes in the
second or third iteration if not.
Please let me know if there is additional information needed or if you see something obvious.
Thanks,
Max
----- simple test case follows -----
// ---------------------------------------------------------------------
// test module to identify possible error use of libcurl formadd and 
// formfree put together from BackupBox Version 6 code and libcurl 
// examples.
// 
// This is a win32 test using 7.17.1 
//
// SEARCH FOR "TEST POINT:" and you will find the formfree call which
// always causes this module to crash on the second or third iteration.
// Comment out the call to formfree and it works but has the expected 
// memory leak from the form never being freed.  Uncomment it and it 
// crashes.  I am reasonable sure this is a problem with this source
// module and not the libcurl library since this code should be very
// stable.  Thanks for looking into this.  Max
// ---------------------------------------------------------------------
// standard c and windows includes
// ---------------------------------------------------------------------
#include	<conio.h>
#include	<winsock2.h>
#include	<windows.h>
#include	<windowsx.h>		// GET_X_PARAM
#include	<stdio.h>
#include	<time.h>
#include	<shlobj.h>			// used for SHGetFolderPath
#include	<Lmcons.h>			// used by GetUserName
#include	<Sddl.h>
#include	<AccCtrl.h>
#include	<Aclapi.h>
#include	<prsht.h>			// property page test **********
// ---------------------------------------------------------------------
// cURL inclusions (winsock2.h is before windows.h)
// ---------------------------------------------------------------------
#define	_WIN32_WINNT		0x0500		// for icon notification size
#define	_WIN32_IE			0x0501		// needed for balloon tooltip
#define IsKeyDown(key)	((GetAsyncKeyState(key) & 0x8000) == 0x8000)
// deal with VS2005 BS ...
#define	_CRT_SECURE_NO_DEPRECATE	1	// get rid of VS2005 warnings
#define _CRT_SECURE_NO_WARNINGS		1	// more vs2005 bs
#pragma warning( disable : 4996 )
// curl stuff, remove later if unneeded
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <ws2tcpip.h>
#ifndef INET_ADDRSTRLEN
#define socklen_t int
#endif
// ---------------------------------------------------------------------
// libcurl includes in ..\RedPawComms\libcURL\curl-7.17.1\include
#include <curl/curl.h>
// ---------------------------------------------------------------------
// test module storage allocations and structures
struct MemoryStruct // used by writeBuffer
{
	char *memory ; // memory address
	size_t size ; // size of memory area at memory address
	size_t lastSize ; // size of last call to write memory proc
} ;
struct	MemoryStruct writeBuffer = { '\0', 0, 0 } ;
struct	curl_httppost *formPostPing	= NULL ; // for libcurl curl_formadd
struct	curl_httppost *lastPtr	= NULL ; // for libcurl curl_formadd
// ---------------------------------------------------------------------
UCHAR	ucCURLErrorBuffer[CURL_ERROR_SIZE] ; // used by libcurl errors
ULONG	ulStartClick = 0 ; // millisecond counter before easy perform
ULONG	ulStopClick = 0 ; // millisecond counter after easy perform
ULONG	ulWriteProcMemoryPointer = 0 ; // global from write callback
ULONG	ulWriteProcMemorySizeBytes = 0 ; // global from write callback
UCHAR	szTmpBuf[2048] ; // plenty of space for string output
BOOL	bDone = FALSE ; // exit flag, when done
// ---------------------------------------------------------------------
CURL	*curlhPing ; // libcurl ping handle
CURLcode res ; // result code from libcurl
// =====================================================================
// libcurl write function callback for easy process, write to memory
//		*buf points to incoming data buffer
//		size * nmemb = total number of bytes in this call 
//		void *data is our writeBuffer buffer defined by setopt
// =====================================================================
size_t CurlWriteDataToMemory (
			void *buf, // data pointer
			size_t size, // content size in bytes of each member
			size_t nmemb, // number of members
			void *data // pointer passed in CURLOPT_WRITEDATA
										) // WriteDataToMemory prototype
{ // begin of WriteDataToMemory ========================================
	// -----------------------------------------------------------------
	// this proce is called as many times as needed to complete a web
	// transfer.  It will assemble each call's buffer contents
	// into a memory address pointed to by *data = writeBuffer
	size_t realSize = size * nmemb ; // incoming data count
	// -----------------------------------------------------------------
	// point our mem structure to bytes at address in data
	// -----------------------------------------------------------------
	struct MemoryStruct *mem = (struct MemoryStruct *)data ;
	// -----------------------------------------------------------------
	// allocate or reallocate current buffer to larger buffer if needed
	// -----------------------------------------------------------------
	if ( mem->memory ) // if there is an address in the data area, so...
	{
		// -------------------------------------------------------------
		// allocate new memory for the incoming data, adding to the 
		// existing buffer -- add one byte for a zero termination later
		// -------------------------------------------------------------
		mem->memory = 
			(char *)realloc ( mem->memory, mem->size + realSize + 1 ) ;
	} // end of if mem->memory
	else // there is no address in the data area yet, so ...
	{
		// -------------------------------------------------------------
		// we do not yet have a memory area to copy the incoming bytes 
		// to, so get a buffer and store its pointer and size ...
		// -------------------------------------------------------------
		mem->memory = (char *)malloc ( mem->size + realSize + 1 ) ;
	} // end of else on if mem->memory
	// -----------------------------------------------------------------
	if ( mem->memory ) // don't do this if something went wrong getting
	{				   // the memory above
		// -------------------------------------------------------------
		// copy the incoming bytes to the buffer, update the memory info
		// structure and return -- this will be called until the libcurl
		// easy process call is done
		// -------------------------------------------------------------
		// copy incoming bytes to next offset in mem->memory buffer from
		// buf buffer for realSize bytes ...
		memcpy ( &(mem->memory[mem->size]), buf, realSize ) ;
		mem->lastSize = realSize ; // save the size of the last transfer
		mem->size += realSize ; // show new buffer count in memory
		mem->memory[mem->size] = 0 ; // zero terminate the string
	}
	else // an error occured malloc or realloc memory
	{
		MessageBox ( NULL, "FATAL Error in malloc/realloc - exiting.",
					"FATAL Error in malloc/realloc, exiting.", MB_OK ) ;
		ExitProcess ( 100 ) ; // ABEND ABEND ABEND ABEND ABEND ABEND
	} // end of else clause on memory allocation code
	// -----------------------------------------------------------------
	ulWriteProcMemoryPointer = (ULONG) mem->memory ;
	ulWriteProcMemorySizeBytes = mem->size ;
	// -----------------------------------------------------------------
	// end of RPV7CurlWriteDataToMemory proc
	// -----------------------------------------------------------------
	return size * nmemb ; // tell libcurl we processed them all
}  // end of CurlWriteDataToMemory proc ================================
// =====================================================================
//			start of main test win32 procedure
//
// Testing:  libcurl call to formadd then formfree
// 
// =====================================================================
int main ( int argc, char *argv[] ) // test for comms dvlmt
{
	// ----- local  data definitions for main proc ---------------------
	// setup libcurl global info for win32, global cleanup on exit
	if ( curl_global_init ( CURL_GLOBAL_ALL | CURL_GLOBAL_WIN32 ) )
	{	
		// report and ABEND!
		MessageBox ( NULL, "libcurl global init error", 
			"libcurl global init error", MB_OK ) ;		
	} // end of call to curl global init failed
	
	// -----------------------------------------------------------------
	// test section infinite loop, exit with lshift-lcontrol-E for exit
	// -----------------------------------------------------------------
	while ( !bDone ) // infinite loop for our test 
	{
		// for this test, easy init and easy cleanup each iteration,
		// later the easy init will be put outside ping loop, but let's 
		// find out why the formfree is failing first ...
		curlhPing = curl_easy_init (  ) ; // get curl handle 
		if ( !curlhPing ) // zero means no handle, failed
		{
			// failed to get easy handle, so global cleanup and ABEND
			curl_global_cleanup ( ) ; // clean up environment
			MessageBox ( NULL, "libcurl easy init failure",
				"libcurl easy init failure", MB_OK ) ;
			ExitProcess ( 101 ) ; // ABEND ABEND ABEND ABEND ABEND ABEND
		} // end of if !curl call to curl init failed
		// -------------------------------------------------------------
		// set up a call back to process the website returned data in 
		// bytes terminated by a null and storage for the write function
		// -------------------------------------------------------------
		curl_easy_setopt	( 
					curlhPing, // ping curl handle
					CURLOPT_WRITEFUNCTION, // setup write callback
					CurlWriteDataToMemory // our write callback
							) ;
		// as the write function picks up bytes have it copied to our 
		// write buffer with a null termination byte (see 
		// WriteDataToMemory proc for details)
		curl_easy_setopt	(
					curlhPing,
					CURLOPT_WRITEDATA,
					&writeBuffer
							) ;
		// ------------------------------------------------------------
		// if there are any errors, make sure we know what libcurl says
		// is going on
		// ------------------------------------------------------------
		curl_easy_setopt	(
					curlhPing,
					CURLOPT_ERRORBUFFER,
					ucCURLErrorBuffer
							) ;
		// -------------------------------------------------------------
		// curl fails on 400+ error codes from the server - it's unclear
		// if this does anything useful for our ping example, but it is
		// in the planned production design, so it is here too ...
		// -------------------------------------------------------------
		curl_easy_setopt ( curlhPing, CURLOPT_FAILONERROR, TRUE ) ;	
		// -------------------------------------------------------------
		// setup our test URL every time for this test 
		// -------------------------------------------------------------
		curl_easy_setopt	(	// setup URL to touch
					curlhPing, 
					CURLOPT_URL,
					"http://redpawsys.com/RPV7CommServices/RPV7Ping.php"
							) ;
		// -------------------------------------------------------------
		// here is the main test area -- use formadd to build a multi-
		// part HTTP POST - in this case, only add a couple of fields to
		// test with.  The production design has 6-8 form fields/values
		// -------------------------------------------------------------
		// CURLFORM_COPYNAME - from libcurl pdf docs on formadd ...
		// followed by a string which provides the name of this part. 
		// libcurl copies the string so your application doesn't need to
		// keep it around after this function call. If the name isn't 
		// NUL-terminated, or if you'd like it to contain zero bytes,
		// you must set its length with CURLFORM_NAMELENGTH.
		//
		// The copied data will be freed by curl_formfree(3).
		// -------------------------------------------------------------
		curl_formadd (	// add field/value for test
					&formPostPing,	// the form to add to
					&lastPtr, // managed by formadd
					CURLFORM_COPYNAME, "clguid", // client guid for test
					// set the value of "filename" to src
					CURLFORM_COPYCONTENTS, // client guid value
					"{1234-12345678-1234-123456789012}", // for testing
					CURLFORM_END 
					  ) ; // per libcurl spec
		curl_formadd (	// add field/value for test
					&formPostPing,
					&lastPtr, 
					CURLFORM_COPYNAME, "grpguid", // group guid for test
					CURLFORM_COPYCONTENTS, 
					"{1111-12345678-1234-123456789012}",
					CURLFORM_END 
					  ) ;
		curl_formadd (	// add field/value for test
					&formPostPing,	// the form to add the src filename to
					&lastPtr, 
					// set the request string to filename
					CURLFORM_COPYNAME, "distro",	
					// set the value of "filename" to src
					CURLFORM_COPYCONTENTS, "AlphaRelease",
					CURLFORM_END ) ;
		// -------------------------------------------------------------
		// Tells libcurl you want a multipart/formdata HTTP POST to be
		// made and you instruct what data to pass on to the server. 
		// Pass a pointer to a linked list of curl_httppost structs as 
		// parameter. . The easiest way to create such a list, is to 
		// use curl_formadd(3) as documented. The data in this list must
		// remain intact until you close this curl handle again with 
		// curl_easy_cleanup(3).
		//
		// NOTE REGARDING TEST ISSUE:  This loop will do the easy 
		// cleanup each iteration, however there is about a 4k per
		// iteration memory leak we are guessing is the form that is 
		// not returned to the system memory pool.  If we 
		// -------------------------------------------------------------
		curl_easy_setopt ( curlhPing, CURLOPT_HTTPPOST, formPostPing ) ;
		// -------------------------------------------------------------
		// check the msecs before and after the call to easy perform
		ulStartClick = GetTickCount ( ) ;
		res = curl_easy_perform ( curlhPing ) ; // <<<<< ----- PING !
		ulStopClick = GetTickCount ( ) ;
		if ( res ) // libcurl call failed for some reason, so let's see
		{
			sprintf ( szTmpBuf, "cURL error: %d, %s", 
									curlhPing, ucCURLErrorBuffer ) ;
			MessageBox ( NULL, szTmpBuf, "cURL Error!", MB_OK ) ;
			printf ( "\n ----- %s ----- \n", szTmpBuf ) ;
		} // end of if libcurl perform call failed
		else // the libcurl call was successful, so let's just see the 
		{	 // click count and some of the PHP script response ...
			printf ( "curl ping time: %d msecs, received %d bytes:"
				"%.30s\n", 
				ulStopClick - ulStartClick, writeBuffer.size,
				writeBuffer.memory ) ;
		
		} // end of else libcurl perform call was successful
		// -------------------------------------------------------------
		// cleanup memory allocation by write callback
		if ( ulWriteProcMemoryPointer ) 
				free ( (char *) ulWriteProcMemoryPointer ) ;
		writeBuffer.lastSize = 0 ;
		writeBuffer.memory = 0 ;
		writeBuffer.size = 0 ;
		// -------------------------------------------------------------
		// curl_formfree() is used to clean up data previously 
		// built/appended with curl_formadd(3). This must be called when
		// the data has been used, which typically means after the 
		// curl_easy_perform(3) has been called.
		// -------------------------------------------------------------
		// TEST POINT:  COMMENT OUT THE NEXT STATEMENT AND ALL WORKS 
		// FINE (EXCEPT FOR LOSING 4K MEMORY EACH ITERATION), UNCOMMENT
		// IT AND IT WILL CRASH ...
		// *************************************************************
		// curl_formfree ( formPostPing ) ;	// free the form
		// *************************************************************
		curl_easy_cleanup ( curlhPing ) ; // end of easy session 
		// -------------------------------------------------------------
		if ( _kbhit () )
			if ( _getch ( ) == 27 ) bDone = TRUE ; // exit
	}
	// free up global resources - 
	// You should call curl_global_cleanup(3) once for each call you 
	// make to curl_global_init(3), after you are done using libcurl.
	//
	// NOTE: this is not thread safe so must be called in main upon exit
	curl_global_cleanup ( ) ; // clean up environment
	
	// -----------------------------------------------------------------
	printf ( "\n\n ----- End of test of libcurl ----- "
				"Press any key to exit." ) ;
	res = _getch ( ) ;
	// -----------------------------------------------------------------
	// end of this test program 
	// -----------------------------------------------------------------
	ExitProcess ( 0 ) ;
	return ( TRUE ) ;
} // ~~~~~ end of main procedure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Received on 2008-01-20