Commit message: Update header handling to RFC 7230
CVE: CVE-2015-8935
CWE: CWE-79
Vulnerability description: The sapi_header_op function in main/SAPI.c in PHP before 5.4.38, 5.5.x before 5.5.22, and 5.6.x before 5.6.6 supports deprecated line folding without considering browser compatibility, which allows remote attackers to conduct cross-site scripting (XSS) attacks against Internet Explorer by leveraging (1) %0A%20 or (2) %0D%0A%20 mishandling in the header function.

Project: php-src
File: SAPI.c

static void sapi_globals_ctor(sapi_globals_struct *sapi_globals TSRMLS_DC)
{
	memset(sapi_globals, 0, sizeof(*sapi_globals));
	zend_hash_init_ex(&sapi_globals->known_post_content_types, 5, NULL, NULL, 1, 0);
	php_setup_sapi_content_types(TSRMLS_C);
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_get_target_uid(uid_t *obj TSRMLS_DC)
{
	if (sapi_module.get_target_uid) {
		return sapi_module.get_target_uid(obj TSRMLS_CC);
	} else {
		return FAILURE;
	}
}

Project: php-src
File: SAPI.c

static void sapi_update_response_code(int ncode TSRMLS_DC)
{
	/* if the status code did not change, we do not want
	   to change the status line, and no need to change the code */
	if (SG(sapi_headers).http_response_code == ncode) {
		return;
	}

	if (SG(sapi_headers).http_status_line) {
		efree(SG(sapi_headers).http_status_line);
		SG(sapi_headers).http_status_line = NULL;
	}
	SG(sapi_headers).http_response_code = ncode;
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_register_input_filter(unsigned int (*input_filter)(int arg, char *var, char **val, unsigned int val_len, unsigned int *new_val_len TSRMLS_DC), unsigned int (*input_filter_init)(TSRMLS_D) TSRMLS_DC)
{
	if (SG(sapi_started) && EG(in_execution)) {
		return FAILURE;
	}
	sapi_module.input_filter = input_filter;
	sapi_module.input_filter_init = input_filter_init;
	return SUCCESS;
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_register_treat_data(void (*treat_data)(int arg, char *str, zval *destArray TSRMLS_DC) TSRMLS_DC)
{
	if (SG(sapi_started) && EG(in_execution)) {
		return FAILURE;
	}
	sapi_module.treat_data = treat_data;
	return SUCCESS;
}

Project: php-src
File: SAPI.c

SAPI_API void sapi_unregister_post_entry(sapi_post_entry *post_entry TSRMLS_DC)
{
	if (SG(sapi_started) && EG(in_execution)) {
		return;
	}
	zend_hash_del(&SG(known_post_content_types), post_entry->content_type,
			post_entry->content_type_len+1);
}

Project: php-src
File: SAPI.c

SAPI_API void sapi_shutdown(void)
{
#ifdef ZTS
	ts_free_id(sapi_globals_id);
#else
	sapi_globals_dtor(&sapi_globals);
#endif

	reentrancy_shutdown();

	virtual_cwd_shutdown();

#ifdef PHP_WIN32
	tsrm_win32_shutdown();
#endif
}

Project: php-src
File: SAPI.c

static void sapi_read_post_data(TSRMLS_D)
{
	sapi_post_entry *post_entry;
	uint content_type_length = strlen(SG(request_info).content_type);
	char *content_type = estrndup(SG(request_info).content_type, content_type_length);
	char *p;
	char oldchar=0;
	void (*post_reader_func)(TSRMLS_D) = NULL;


	/* dedicated implementation for increased performance:
	 * - Make the content type lowercase
	 * - Trim descriptive data, stay with the content-type only
	 */
	for (p=content_type; p<content_type+content_type_length; p++) {
		switch (*p) {
			case ';':
			case ',':
			case ' ':
				content_type_length = p-content_type;
				oldchar = *p;
				*p = 0;
				break;
			default:
				*p = tolower(*p);
				break;
		}
	}

	/* now try to find an appropriate POST content handler */
	if (zend_hash_find(&SG(known_post_content_types), content_type,
			content_type_length+1, (void **) &post_entry) == SUCCESS) {
		/* found one, register it for use */
		SG(request_info).post_entry = post_entry;
		post_reader_func = post_entry->post_reader;
	} else {
		/* fallback */
		SG(request_info).post_entry = NULL;
		if (!sapi_module.default_post_reader) {
			/* no default reader ? */
			SG(request_info).content_type_dup = NULL;
			sapi_module.sapi_error(E_WARNING, "Unsupported content type:  '%s'", content_type);
			return;
		}
	}
	if (oldchar) {
		*(p-1) = oldchar;
	}

	SG(request_info).content_type_dup = content_type;

	if(post_reader_func) {
		post_reader_func(TSRMLS_C);
	}

	if(sapi_module.default_post_reader) {
		sapi_module.default_post_reader(TSRMLS_C);
	}
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_get_target_gid(gid_t *obj TSRMLS_DC)
{
	if (sapi_module.get_target_gid) {
		return sapi_module.get_target_gid(obj TSRMLS_CC);
	} else {
		return FAILURE;
	}
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_send_headers(TSRMLS_D)
{
	int retval;
	int ret = FAILURE;

	if (SG(headers_sent) || SG(request_info).no_headers || SG(callback_run)) {
		return SUCCESS;
	}

	/* Success-oriented.  We set headers_sent to 1 here to avoid an infinite loop
	 * in case of an error situation.
	 */
	if (SG(sapi_headers).send_default_content_type && sapi_module.send_headers) {
		sapi_header_struct default_header;
	    uint len;

		SG(sapi_headers).mimetype = get_default_content_type(0, &len TSRMLS_CC);
		default_header.header_len = sizeof("Content-type: ") - 1 + len;
		default_header.header = emalloc(default_header.header_len + 1);
		memcpy(default_header.header, "Content-type: ", sizeof("Content-type: ") - 1);
		memcpy(default_header.header + sizeof("Content-type: ") - 1, SG(sapi_headers).mimetype, len + 1);
		sapi_header_add_op(SAPI_HEADER_ADD, &default_header TSRMLS_CC);
		SG(sapi_headers).send_default_content_type = 0;
	}

	if (SG(callback_func) && !SG(callback_run)) {
		SG(callback_run) = 1;
		sapi_run_header_callback(TSRMLS_C);
	}

	SG(headers_sent) = 1;

	if (sapi_module.send_headers) {
		retval = sapi_module.send_headers(&SG(sapi_headers) TSRMLS_CC);
	} else {
		retval = SAPI_HEADER_DO_SEND;
	}

	switch (retval) {
		case SAPI_HEADER_SENT_SUCCESSFULLY:
			ret = SUCCESS;
			break;
		case SAPI_HEADER_DO_SEND: {
				sapi_header_struct http_status_line;
				char buf[255];

				if (SG(sapi_headers).http_status_line) {
					http_status_line.header = SG(sapi_headers).http_status_line;
					http_status_line.header_len = strlen(SG(sapi_headers).http_status_line);
				} else {
					http_status_line.header = buf;
					http_status_line.header_len = slprintf(buf, sizeof(buf), "HTTP/1.0 %d X", SG(sapi_headers).http_response_code);
				}
				sapi_module.send_header(&http_status_line, SG(server_context) TSRMLS_CC);
			}
			zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t) sapi_module.send_header, SG(server_context) TSRMLS_CC);
			if(SG(sapi_headers).send_default_content_type) {
				sapi_header_struct default_header;

				sapi_get_default_content_type_header(&default_header TSRMLS_CC);
				sapi_module.send_header(&default_header, SG(server_context) TSRMLS_CC);
				sapi_free_header(&default_header);
			}
			sapi_module.send_header(NULL, SG(server_context) TSRMLS_CC);
			ret = SUCCESS;
			break;
		case SAPI_HEADER_SEND_FAILED:
			SG(headers_sent) = 0;
			ret = FAILURE;
			break;
	}

	sapi_send_headers_free(TSRMLS_C);

	return ret;
}

Project: php-src
File: SAPI.c

SAPI_API char *sapi_get_default_content_type(TSRMLS_D)
{
	uint len;

	return get_default_content_type(0, &len TSRMLS_CC);
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_flush(TSRMLS_D)
{
	if (sapi_module.flush) {
		sapi_module.flush(SG(server_context));
		return SUCCESS;
	} else {
		return FAILURE;
	}
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_register_default_post_reader(void (*default_post_reader)(TSRMLS_D) TSRMLS_DC)
{
	if (SG(sapi_started) && EG(in_execution)) {
		return FAILURE;
	}
	sapi_module.default_post_reader = default_post_reader;
	return SUCCESS;
}

Project: php-src
File: SAPI.c

static void sapi_globals_dtor(sapi_globals_struct *sapi_globals TSRMLS_DC)
{
	zend_hash_destroy(&sapi_globals->known_post_content_types);
}

Project: php-src
File: SAPI.c

static inline char *get_default_content_type(uint prefix_len, uint *len TSRMLS_DC)
{
	char *mimetype, *charset, *content_type;
	uint mimetype_len, charset_len;

	if (SG(default_mimetype)) {
		mimetype = SG(default_mimetype);
		mimetype_len = strlen(SG(default_mimetype));
	} else {
		mimetype = SAPI_DEFAULT_MIMETYPE;
		mimetype_len = sizeof(SAPI_DEFAULT_MIMETYPE) - 1;
	}
	if (SG(default_charset)) {
		charset = SG(default_charset);
		charset_len = strlen(SG(default_charset));
	} else {
		charset = SAPI_DEFAULT_CHARSET;
		charset_len = sizeof(SAPI_DEFAULT_CHARSET) - 1;
	}

	if (*charset && strncasecmp(mimetype, "text/", 5) == 0) {
		char *p;

		*len = prefix_len + mimetype_len + sizeof("; charset=") - 1 + charset_len;
		content_type = (char*)emalloc(*len + 1);
		p = content_type + prefix_len;
		memcpy(p, mimetype, mimetype_len);
		p += mimetype_len;
		memcpy(p, "; charset=", sizeof("; charset=") - 1);
		p += sizeof("; charset=") - 1;
		memcpy(p, charset, charset_len + 1);
	} else {
		*len = prefix_len + mimetype_len;
		content_type = (char*)emalloc(*len + 1);
		memcpy(content_type + prefix_len, mimetype, mimetype_len + 1);
	}
	return content_type;
}

Project: php-src
File: SAPI.c

static void sapi_remove_header(zend_llist *l, char *name, uint len) {
	sapi_header_struct *header;
	zend_llist_element *next;
	zend_llist_element *current=l->head;

	while (current) {
		header = (sapi_header_struct *)(current->data);
		next = current->next;
		if (header->header_len > len && header->header[len] == ':'
				&& !strncasecmp(header->header, name, len)) {
			if (current->prev) {
				current->prev->next = next;
			} else {
				l->head = next;
			}
			if (next) {
				next->prev = current->prev;
			} else {
				l->tail = current->prev;
			}
			sapi_free_header(header);
			efree(current);
			--l->count;
		}
		current = next;
	}
}

Project: php-src
File: SAPI.c

SAPI_API struct stat *sapi_get_stat(TSRMLS_D)
{
	if (sapi_module.get_stat) {
		return sapi_module.get_stat(TSRMLS_C);
	} else {
		if (!SG(request_info).path_translated || (VCWD_STAT(SG(request_info).path_translated, &SG(global_stat)) == -1)) {
			return NULL;
		}
		return &SG(global_stat);
	}
}

Project: php-src
File: SAPI.c

static void sapi_header_add_op(sapi_header_op_enum op, sapi_header_struct *sapi_header TSRMLS_DC)
{
	if (!sapi_module.header_handler ||
		(SAPI_HEADER_ADD & sapi_module.header_handler(sapi_header, op, &SG(sapi_headers) TSRMLS_CC))) {
		if (op == SAPI_HEADER_REPLACE) {
			char *colon_offset = strchr(sapi_header->header, ':');

			if (colon_offset) {
				char sav = *colon_offset;

				*colon_offset = 0;
		        sapi_remove_header(&SG(sapi_headers).headers, sapi_header->header, strlen(sapi_header->header));
				*colon_offset = sav;
			}
		}
		zend_llist_add_element(&SG(sapi_headers).headers, (void *) sapi_header);
	} else {
		sapi_free_header(sapi_header);
	}
}

Project: php-src
File: SAPI.c

SAPI_API size_t sapi_apply_default_charset(char **mimetype, size_t len TSRMLS_DC)
{
	char *charset, *newtype;
	size_t newlen;
	charset = SG(default_charset) ? SG(default_charset) : SAPI_DEFAULT_CHARSET;

	if (*mimetype != NULL) {
		if (*charset && strncmp(*mimetype, "text/", 5) == 0 && strstr(*mimetype, "charset=") == NULL) {
			newlen = len + (sizeof(";charset=")-1) + strlen(charset);
			newtype = emalloc(newlen + 1);
	 		PHP_STRLCPY(newtype, *mimetype, newlen + 1, len);
			strlcat(newtype, ";charset=", newlen + 1);
			strlcat(newtype, charset, newlen + 1);
			efree(*mimetype);
			*mimetype = newtype;
			return newlen;
		}
	}
	return 0;
}

Project: php-src
File: SAPI.c

SAPI_API void sapi_free_header(sapi_header_struct *sapi_header)
{
	efree(sapi_header->header);
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_register_post_entries(sapi_post_entry *post_entries TSRMLS_DC)
{
	sapi_post_entry *p=post_entries;

	while (p->content_type) {
		if (sapi_register_post_entry(p TSRMLS_CC) == FAILURE) {
			return FAILURE;
		}
		p++;
	}
	return SUCCESS;
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_get_fd(int *fd TSRMLS_DC)
{
	if (sapi_module.get_fd) {
		return sapi_module.get_fd(fd TSRMLS_CC);
	} else {
		return FAILURE;
	}
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_force_http_10(TSRMLS_D)
{
	if (sapi_module.force_http_10) {
		return sapi_module.force_http_10(TSRMLS_C);
	} else {
		return FAILURE;
	}
}

Project: php-src
File: SAPI.c

static void sapi_send_headers_free(TSRMLS_D)
{
	if (SG(sapi_headers).http_status_line) {
		efree(SG(sapi_headers).http_status_line);
		SG(sapi_headers).http_status_line = NULL;
	}
}

Project: php-src
File: SAPI.c

SAPI_API int sapi_register_post_entry(sapi_post_entry *post_entry TSRMLS_DC)
{
	if (SG(sapi_started) && EG(in_execution)) {
		return FAILURE;
	}
	return zend_hash_add(&SG(known_post_content_types),
			post_entry->content_type, post_entry->content_type_len+1,
			(void *) post_entry, sizeof(sapi_post_entry), NULL);
}

Project: php-src
File: SAPI.c

SAPI_API void sapi_terminate_process(TSRMLS_D) {
	if (sapi_module.terminate_process) {
		sapi_module.terminate_process(TSRMLS_C);
	}
}

Project: php-src
File: SAPI.c

SAPI_API void sapi_get_default_content_type_header(sapi_header_struct *default_header TSRMLS_DC)
{
    uint len;

	default_header->header = get_default_content_type(sizeof("Content-type: ")-1, &len TSRMLS_CC);
	default_header->header_len = len;
	memcpy(default_header->header, "Content-type: ", sizeof("Content-type: ") - 1);
}

Project: php-src
File: SAPI.c

SAPI_API void sapi_startup(sapi_module_struct *sf)
{
#ifdef ZEND_SIGNALS
	zend_signal_startup();
#endif

	sf->ini_entries = NULL;
	sapi_module = *sf;

#ifdef ZTS
	ts_allocate_id(&sapi_globals_id, sizeof(sapi_globals_struct), (ts_allocate_ctor) sapi_globals_ctor, (ts_allocate_dtor) sapi_globals_dtor);
# ifdef PHP_WIN32
	_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
# endif
#else
	sapi_globals_ctor(&sapi_globals);
#endif

	virtual_cwd_startup(); /* Could use shutdown to free the main cwd but it would just slow it down for CGI */

#ifdef PHP_WIN32
	tsrm_win32_startup();
#endif

	reentrancy_startup();
}

Project: php-src
File: SAPI.c

SAPI_API void sapi_initialize_empty_request(TSRMLS_D)
{
	SG(server_context) = NULL;
	SG(request_info).request_method = NULL;
	SG(request_info).auth_digest = SG(request_info).auth_user = SG(request_info).auth_password = NULL;
	SG(request_info).content_type_dup = NULL;
}

Project: php-src
File: SAPI.c

SAPI_API double sapi_get_request_time(TSRMLS_D)
{
	if(SG(global_request_time)) return SG(global_request_time);

	if (sapi_module.get_request_time && SG(server_context)) {
		SG(global_request_time) = sapi_module.get_request_time(TSRMLS_C);
	} else {
		struct timeval tp = {0};
		if (!gettimeofday(&tp, NULL)) {
			SG(global_request_time) = (double)(tp.tv_sec + tp.tv_usec / 1000000.00);
		} else {
			SG(global_request_time) = (double)time(0);
		}
	}
	return SG(global_request_time);
}