/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 2004, 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 http://curl.haxx.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.
 *
 ***************************************************************************/

#include "setup.h"

#include <curl/curl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#ifdef USE_LIBIDN
#include <idna.h>
#endif

#include "strerror.h"

#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>

#ifdef HAVE_NO_STRERROR_R_DECL
#ifdef HAVE_POSIX_STRERROR_R
/* seen on AIX 5100-02 gcc 2.9 */
extern int strerror_r( int errnum, char *strerrbuf, size_t buflen );
#else
extern char *strerror_r( int errnum, char *buf, size_t buflen );
#endif
#endif

const char *
curl_easy_strerror( CURLcode error ) {
	switch ( error ) {
	case CURLE_OK:
		return "no error";

	case CURLE_UNSUPPORTED_PROTOCOL:
		return "unsupported protocol";

	case CURLE_FAILED_INIT:
		return "failed init";

	case CURLE_URL_MALFORMAT:
		return "URL using bad/illegal format or missing URL";

	case CURLE_COULDNT_RESOLVE_PROXY:
		return "couldnt resolve proxy name";

	case CURLE_COULDNT_RESOLVE_HOST:
		return "couldnt resolve host name";

	case CURLE_COULDNT_CONNECT:
		return "couldn't connect to server";

	case CURLE_FTP_WEIRD_SERVER_REPLY:
		return "FTP: weird server reply";

	case CURLE_FTP_ACCESS_DENIED:
		return "FTP: access denied";

	case CURLE_FTP_USER_PASSWORD_INCORRECT:
		return "FTP: user and/or password incorrect";

	case CURLE_FTP_WEIRD_PASS_REPLY:
		return "FTP: unknown PASS reply";

	case CURLE_FTP_WEIRD_USER_REPLY:
		return "FTP: unknown USER reply";

	case CURLE_FTP_WEIRD_PASV_REPLY:
		return "FTP: unknown PASV reply";

	case CURLE_FTP_WEIRD_227_FORMAT:
		return "FTP: unknown 227 response format";

	case CURLE_FTP_CANT_GET_HOST:
		return "FTP: can't figure out the host in the PASV response";

	case CURLE_FTP_CANT_RECONNECT:
		return "FTP: can't connect to server the response code is unknown";

	case CURLE_FTP_COULDNT_SET_BINARY:
		return "FTP: couldn't set binary mode";

	case CURLE_PARTIAL_FILE:
		return "Transferred a partial file";

	case CURLE_FTP_COULDNT_RETR_FILE:
		return "FTP: couldn't retrieve (RETR failed) the specified file";

	case CURLE_FTP_WRITE_ERROR:
		return "FTP: the post-transfer acknowledge response was not OK";

	case CURLE_FTP_QUOTE_ERROR:
		return "FTP: a quote command returned error";

	case CURLE_HTTP_RETURNED_ERROR:
		return "HTTP response code said error";

	case CURLE_WRITE_ERROR:
		return "failed writing received data to disk/application";

	case CURLE_FTP_COULDNT_STOR_FILE:
		return "failed FTP upload (the STOR command)";

	case CURLE_READ_ERROR:
		return "failed to open/read local data from file/application";

	case CURLE_OUT_OF_MEMORY:
		return "out of memory";

	case CURLE_OPERATION_TIMEOUTED:
		return "a timeout was reached";

	case CURLE_FTP_COULDNT_SET_ASCII:
		return "FTP could not set ASCII mode (TYPE A)";

	case CURLE_FTP_PORT_FAILED:
		return "FTP command PORT failed";

	case CURLE_FTP_COULDNT_USE_REST:
		return "FTP command REST failed";

	case CURLE_FTP_COULDNT_GET_SIZE:
		return "FTP command SIZE failed";

	case CURLE_HTTP_RANGE_ERROR:
		return "a range was requested but the server did not deliver it";

	case CURLE_HTTP_POST_ERROR:
		return "internal problem setting up the POST";

	case CURLE_SSL_CONNECT_ERROR:
		return "SSL connect error";

	case CURLE_FTP_BAD_DOWNLOAD_RESUME:
		return "couldn't resume FTP download";

	case CURLE_FILE_COULDNT_READ_FILE:
		return "couldn't read a file:// file";

	case CURLE_LDAP_CANNOT_BIND:
		return "LDAP: cannot bind";

	case CURLE_LDAP_SEARCH_FAILED:
		return "LDAP: search failed";

	case CURLE_LIBRARY_NOT_FOUND:
		return "a required shared library was not found";

	case CURLE_FUNCTION_NOT_FOUND:
		return "a required function in the shared library was not found";

	case CURLE_ABORTED_BY_CALLBACK:
		return "the operation was aborted by an application callback";

	case CURLE_BAD_FUNCTION_ARGUMENT:
		return "a libcurl function was given a bad argument";

	case CURLE_INTERFACE_FAILED:
		return "failed binding local connection end";

	case CURLE_TOO_MANY_REDIRECTS:
		return "number of redirects hit maximum amount";

	case CURLE_UNKNOWN_TELNET_OPTION:
		return "User specified an unknown option";

	case CURLE_TELNET_OPTION_SYNTAX:
		return "Malformed telnet option";

	case CURLE_SSL_PEER_CERTIFICATE:
		return "SSL peer certificate was not ok";

	case CURLE_GOT_NOTHING:
		return "server returned nothing (no headers, no data)";

	case CURLE_SSL_ENGINE_NOTFOUND:
		return "SSL crypto engine not found";

	case CURLE_SSL_ENGINE_SETFAILED:
		return "can not set SSL crypto engine as default";

	case CURLE_SEND_ERROR:
		return "failed sending data to the peer";

	case CURLE_RECV_ERROR:
		return "failure when receiving data from the peer";

	case CURLE_SHARE_IN_USE:
		return "share is already in use";

	case CURLE_SSL_CERTPROBLEM:
		return "problem with the local SSL certificate";

	case CURLE_SSL_CIPHER:
		return "couldn't use specified SSL cipher";

	case CURLE_SSL_CACERT:
		return "problem with the SSL CA cert (path? access rights?)";

	case CURLE_BAD_CONTENT_ENCODING:
		return "Unrecognized HTTP Content-Encoding";

	case CURLE_LDAP_INVALID_URL:
		return "Invalid LDAP URL";

	case CURLE_FILESIZE_EXCEEDED:
		return "Maximum file size exceeded";

	case CURLE_FTP_SSL_FAILED:
		return "Requested FTP SSL level failed";

	case CURLE_URL_MALFORMAT_USER: /* not used by current libcurl */
	case CURLE_MALFORMAT_USER:   /* not used by current libcurl */
	case CURLE_BAD_CALLING_ORDER: /* not used by current libcurl */
	case CURLE_BAD_PASSWORD_ENTERED: /* not used by current libcurl */
	case CURLE_OBSOLETE:         /* not used by current libcurl */
	case CURL_LAST:
		break;
	}
	/*
	 * By using a switch, gcc -Wall will complain about enum values
	 * which do not appear, helping keep this function up-to-date.
	 * By using gcc -Wall -Werror, you can't forget.
	 *
	 * A table would not have the same benefit.  Most compilers will
	 * generate code very similar to a table in any case, so there
	 * is little performance gain from a table.  And something is broken
	 * for the user's application, anyways, so does it matter how fast
	 * it _doesn't_ work?
	 *
	 * The line number for the error will be near this comment, which
	 * is why it is here, and not at the start of the switch.
	 */
	return "unknown error";
}

const char *
curl_multi_strerror( CURLMcode error ) {
	switch ( error ) {
	case CURLM_CALL_MULTI_PERFORM:
		return "please call curl_multi_perform() soon";

	case CURLM_OK:
		return "no error";

	case CURLM_BAD_HANDLE:
		return "invalid multi handle";

	case CURLM_BAD_EASY_HANDLE:
		return "invalid easy handle";

	case CURLM_OUT_OF_MEMORY:
		return "out of memory";

	case CURLM_INTERNAL_ERROR:
		return "internal error";

	case CURLM_LAST:
		break;
	}

	return "unknown error";
}

const char *
curl_share_strerror( CURLSHcode error ) {
	switch ( error ) {
	case CURLSHE_OK:
		return "no error";

	case CURLSHE_BAD_OPTION:
		return "unknown share option";

	case CURLSHE_IN_USE:
		return "share currently in use";

	case CURLSHE_INVALID:
		return "invalid share handle";

	case CURLSHE_NOMEM:
		return "out of memory";

	case CURLSHE_LAST:
		break;
	}

	return "CURLSH unknown";
}

#if defined( WIN32 ) && !defined( __CYGWIN__ )

/* This function handles most / all (?) Winsock errors cURL is able to produce.
 */
static const char *
get_winsock_error( int err, char *buf, size_t len ) {
	char *p;

	switch ( err ) {
	case WSAEINTR:
		p = "Call interrupted.";
		break;
	case WSAEBADF:
		p = "Bad file";
		break;
	case WSAEACCES:
		p = "Bad access";
		break;
	case WSAEFAULT:
		p = "Bad argument";
		break;
	case WSAEINVAL:
		p = "Invalid arguments";
		break;
	case WSAEMFILE:
		p = "Out of file descriptors";
		break;
	case WSAEWOULDBLOCK:
		p = "Call would block";
		break;
	case WSAEINPROGRESS:
	case WSAEALREADY:
		p = "Blocking call in progress";
		break;
	case WSAENOTSOCK:
		p = "Descriptor is not a socket.";
		break;
	case WSAEDESTADDRREQ:
		p = "Need destination address";
		break;
	case WSAEMSGSIZE:
		p = "Bad message size";
		break;
	case WSAEPROTOTYPE:
		p = "Bad protocol";
		break;
	case WSAENOPROTOOPT:
		p = "Protocol option is unsupported";
		break;
	case WSAEPROTONOSUPPORT:
		p = "Protocol is unsupported";
		break;
	case WSAESOCKTNOSUPPORT:
		p = "Socket is unsupported";
		break;
	case WSAEOPNOTSUPP:
		p = "Operation not supported";
		break;
	case WSAEAFNOSUPPORT:
		p = "Address family not supported";
		break;
	case WSAEPFNOSUPPORT:
		p = "Protocol family not supported";
		break;
	case WSAEADDRINUSE:
		p = "Address already in use";
		break;
	case WSAEADDRNOTAVAIL:
		p = "Address not available";
		break;
	case WSAENETDOWN:
		p = "Network down";
		break;
	case WSAENETUNREACH:
		p = "Network unreachable";
		break;
	case WSAENETRESET:
		p = "Network has been reset";
		break;
	case WSAECONNABORTED:
		p = "Connection was aborted";
		break;
	case WSAECONNRESET:
		p = "Connection was reset";
		break;
	case WSAENOBUFS:
		p = "No buffer space";
		break;
	case WSAEISCONN:
		p = "Socket is already connected";
		break;
	case WSAENOTCONN:
		p = "Socket is not connected";
		break;
	case WSAESHUTDOWN:
		p = "Socket has been shut down";
		break;
	case WSAETOOMANYREFS:
		p = "Too many references";
		break;
	case WSAETIMEDOUT:
		p = "Timed out";
		break;
	case WSAECONNREFUSED:
		p = "Connection refused";
		break;
	case WSAELOOP:
		p = "Loop??";
		break;
	case WSAENAMETOOLONG:
		p = "Name too long";
		break;
	case WSAEHOSTDOWN:
		p = "Host down";
		break;
	case WSAEHOSTUNREACH:
		p = "Host unreachable";
		break;
	case WSAENOTEMPTY:
		p = "Not empty";
		break;
	case WSAEPROCLIM:
		p = "Process limit reached";
		break;
	case WSAEUSERS:
		p = "Too many users";
		break;
	case WSAEDQUOT:
		p = "Bad quota";
		break;
	case WSAESTALE:
		p = "Something is stale";
		break;
	case WSAEREMOTE:
		p = "Remote error";
		break;
	case WSAEDISCON:
		p = "Disconnected";
		break;

		/* Extended Winsock errors */
	case WSASYSNOTREADY:
		p = "Winsock library is not ready";
		break;
	case WSANOTINITIALISED:
		p = "Winsock library not initalised";
		break;
	case WSAVERNOTSUPPORTED:
		p = "Winsock version not supported.";
		break;

		/* getXbyY() errors (already handled in herrmsg):
		 * Authoritative Answer: Host not found */
	case WSAHOST_NOT_FOUND:
		p = "Host not found";
		break;

		/* Non-Authoritative: Host not found, or SERVERFAIL */
	case WSATRY_AGAIN:
		p = "Host not found, try again";
		break;

		/* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
	case WSANO_RECOVERY:
		p = "Unrecoverable error in call to nameserver";
		break;

		/* Valid name, no data record of requested type */
	case WSANO_DATA:
		p = "No data record of requested type";
		break;

	default:
		return NULL;
	}
	strncpy( buf, p, len );
	buf [len - 1] = '\0';
	return buf;
}
#endif   /* WIN32 && !__CYGWIN__ */

/*
 * Our thread-safe and smart strerror() replacement.
 *
 * The 'err' argument passed in to this function MUST be a true errno number
 * as reported on this system. We do no range checking on the number before
 * we pass it to the "number-to-message" convertion function and there might
 * be systems that don't do proper range checking in there themselves.
 *
 * We don't do range checking (on systems other than Windows) since there is
 * no good reliable and portable way to do it.
 */
const char *Curl_strerror( struct connectdata *conn, int err ) {
	char *buf, *p;
	size_t max;

	curlassert( conn );
	curlassert( err >= 0 );

	buf = conn->syserr_buf;
	max = sizeof( conn->syserr_buf ) - 1;
	*buf = '\0';

#if defined( WIN32 ) && !defined( __CYGWIN__ )
	/* 'sys_nerr' is the maximum errno number, it is not widely portable */
	if ( err >= 0 && err < sys_nerr ) {
		strncpy( buf, strerror( err ), max );
	} else {
		if ( !get_winsock_error( err, buf, max ) &&
			 !FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, err,
							 LANG_NEUTRAL, buf, max, NULL ) ) {
			snprintf( buf, max, "Unknown error %d (%#x)", err, err );
		}
	}
#else /* not native Windows coming up */

	/* These should be atomic and hopefully thread-safe */
#ifdef HAVE_STRERROR_R
	/* There are two different APIs for strerror_r(). The POSIX and the GLIBC
	   versions. */
#ifdef HAVE_POSIX_STRERROR_R
	strerror_r( err, buf, max );
	/* this may set errno to ERANGE if insufficient storage was supplied via
	   'strerrbuf' and 'buflen' to contain the generated message string, or
	   EINVAL if the value of 'errnum' is not a valid error number.*/
#else
	{
		/* HAVE_GLIBC_STRERROR_R */
		char buffer[256];
		char *msg = strerror_r( err, buffer, sizeof( buffer ) );
		/* this version of strerror_r() only *might* use the buffer we pass to
		   the function, but it always returns the error message as a pointer,
		   so we must copy that string unconditionally */
		strncpy( buf, msg, max );
	}
#endif /* end of HAVE_GLIBC_STRERROR_R */
#else /* HAVE_STRERROR_R */
	strncpy( buf, strerror( err ), max );
#endif /* end of HAVE_STRERROR_R */
#endif /* end of ! Windows */

	buf[max] = '\0'; /* make sure the string is zero terminated */

	/* strip trailing '\r\n' or '\n'. */
	if ( ( p = strrchr( buf,'\n' ) ) != NULL && ( p - buf ) >= 2 ) {
		*p = '\0';
	}
	if ( ( p = strrchr( buf,'\r' ) ) != NULL && ( p - buf ) >= 1 ) {
		*p = '\0';
	}
	return buf;
}

#ifdef USE_LIBIDN
/*
 * Return error-string for libidn status as returned from idna_to_ascii_lz().
 */
const char *Curl_idn_strerror( struct connectdata *conn, int err ) {
#ifdef HAVE_IDNA_STRERROR
			  ( void ) conn;
	return idna_strerror( (Idna_rc) err );
#else
	const char *str;
	char *buf;
	size_t max;

	curlassert( conn );

	buf = conn->syserr_buf;
	max = sizeof( conn->syserr_buf ) - 1;

	switch ( (Idna_rc)err ) {
	case IDNA_SUCCESS:
		str = "No error";
		break;
	case IDNA_STRINGPREP_ERROR:
		str = "Error in string preparation";
		break;
	case IDNA_PUNYCODE_ERROR:
		str = "Error in Punycode operation";
		break;
	case IDNA_CONTAINS_NON_LDH:
		str = "Illegal ASCII characters";
		break;
	case IDNA_CONTAINS_MINUS:
		str = "Contains minus";
		break;
	case IDNA_INVALID_LENGTH:
		str = "Invalid output length";
		break;
	case IDNA_NO_ACE_PREFIX:
		str = "No ACE prefix (\"xn--\")";
		break;
	case IDNA_ROUNDTRIP_VERIFY_ERROR:
		str = "Roundtrip verify error";
		break;
	case IDNA_CONTAINS_ACE_PREFIX:
		str = "Already have ACE prefix (\"xn--\")";
		break;
	case IDNA_ICONV_ERROR:
		str = "Locale conversion failed";
		break;
	case IDNA_MALLOC_ERROR:
		str = "Allocation failed";
		break;
	case IDNA_DLOPEN_ERROR:
		str = "dlopen() error";
		break;
	default:
		snprintf( buf, max, "error %d", (int)err );
		str = NULL;
		break;
	}
	if ( str ) {
		strncpy( buf, str, max );
	}
	buf[max] = '\0';
	return ( buf );
#endif
}
#endif  /* USE_LIBIDN */
