+ ctx->stream = init_arg;
+ if (!ctx->stream || SUCCESS != zend_list_addref(ctx->stream->rsrc_id)) {
+ efree(ctx);
+ return FAILURE;
+ }
+ php_stream_set_option(ctx->stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_FULL, &buffer_size);
+ zend_hash_init(&ctx->header, 0, NULL, ZVAL_PTR_DTOR, 0);
+ php_http_version_init(&ctx->version, 1, 1 TSRMLS_CC);
+ ctx->status_code = 200;
+ ctx->chunked = 1;
+ ctx->request = get_request(r->options TSRMLS_CC);
+
+ /* there are some limitations regarding TE:chunked, see https://tools.ietf.org/html/rfc7230#section-3.3.1 */
+ if (ctx->request && ctx->request->http.version.major == 1 && ctx->request->http.version.minor == 0) {
+ ctx->version.minor = 0;
+ }
+
+ r->ctx = ctx;
+
+ return SUCCESS;
+}
+static void php_http_env_response_stream_dtor(php_http_env_response_t *r)
+{
+ php_http_env_response_stream_ctx_t *ctx = r->ctx;
+ TSRMLS_FETCH_FROM_CTX(r->ts);
+
+ if (ctx->chunked_filter) {
+ ctx->chunked_filter = php_stream_filter_remove(ctx->chunked_filter, 1 TSRMLS_CC);
+ }
+ zend_hash_destroy(&ctx->header);
+ zend_list_delete(ctx->stream->rsrc_id);
+ efree(ctx);
+ r->ctx = NULL;
+}
+static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header, php_http_buffer_t *buf TSRMLS_DC)
+{
+ HashPosition pos;
+ zval **val;
+
+ FOREACH_HASH_VAL(pos, header, val) {
+ if (Z_TYPE_PP(val) == IS_ARRAY) {
+ php_http_env_response_stream_header(ctx, Z_ARRVAL_PP(val), buf TSRMLS_CC);
+ } else {
+ zval *tmp = php_http_ztyp(IS_STRING, *val);
+
+ if (ctx->chunked) {
+ /* disable chunked transfer encoding if we've got an explicit content-length */
+ if (!strncasecmp(Z_STRVAL_P(tmp), "Content-Length:", lenof("Content-Length:"))) {
+ ctx->chunked = 0;
+ }
+ }
+ php_http_buffer_append(buf, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp));
+ php_http_buffer_appends(buf, PHP_HTTP_CRLF);
+ zval_ptr_dtor(&tmp);
+ }
+ }
+}
+static ZEND_RESULT_CODE php_http_env_response_stream_start(php_http_env_response_stream_ctx_t *ctx TSRMLS_DC)
+{
+ php_http_buffer_t header_buf;
+
+ if (ctx->started || ctx->finished) {
+ return FAILURE;
+ }
+
+ php_http_buffer_init(&header_buf);
+ php_http_buffer_appendf(&header_buf, "HTTP/%u.%u %ld %s" PHP_HTTP_CRLF, ctx->version.major, ctx->version.minor, ctx->status_code, php_http_env_get_response_status_for_code(ctx->status_code));
+
+ /* there are some limitations regarding TE:chunked, see https://tools.ietf.org/html/rfc7230#section-3.3.1 */
+ if (ctx->version.major == 1 && ctx->version.minor == 0) {
+ ctx->chunked = 0;
+ } else if (ctx->status_code == 204 || ctx->status_code/100 == 1) {
+ ctx->chunked = 0;
+ } else if (ctx->request && ctx->status_code/100 == 2 && !strcasecmp(ctx->request->http.info.request.method, "CONNECT")) {
+ ctx->chunked = 0;
+ }
+
+ php_http_env_response_stream_header(ctx, &ctx->header, &header_buf TSRMLS_CC);
+
+ /* enable chunked transfer encoding */
+ if (ctx->chunked) {
+ php_http_buffer_appends(&header_buf, "Transfer-Encoding: chunked" PHP_HTTP_CRLF);
+ }
+ php_http_buffer_appends(&header_buf, PHP_HTTP_CRLF);
+
+ if (header_buf.used == php_stream_write(ctx->stream, header_buf.data, header_buf.used)) {
+ ctx->started = 1;
+ }
+ php_http_buffer_dtor(&header_buf);
+ php_stream_flush(ctx->stream);
+
+ if (ctx->chunked) {
+ ctx->chunked_filter = php_stream_filter_create("http.chunked_encode", NULL, 0 TSRMLS_CC);
+ php_stream_filter_append(&ctx->stream->writefilters, ctx->chunked_filter);
+ }
+
+ return ctx->started ? SUCCESS : FAILURE;
+}
+static long php_http_env_response_stream_get_status(php_http_env_response_t *r)
+{
+ php_http_env_response_stream_ctx_t *ctx = r->ctx;
+
+ return ctx->status_code;
+}
+static ZEND_RESULT_CODE php_http_env_response_stream_set_status(php_http_env_response_t *r, long http_code)
+{
+ php_http_env_response_stream_ctx_t *stream_ctx = r->ctx;
+
+ if (stream_ctx->started || stream_ctx->finished) {
+ return FAILURE;
+ }
+
+ stream_ctx->status_code = http_code;
+
+ return SUCCESS;
+}
+static ZEND_RESULT_CODE php_http_env_response_stream_set_protocol_version(php_http_env_response_t *r, php_http_version_t *v)
+{
+ php_http_env_response_stream_ctx_t *stream_ctx = r->ctx;
+
+ if (stream_ctx->started || stream_ctx->finished) {
+ return FAILURE;
+ }
+
+ memcpy(&stream_ctx->version, v, sizeof(stream_ctx->version));
+
+ return SUCCESS;
+}
+static ZEND_RESULT_CODE php_http_env_response_stream_set_header_ex(php_http_env_response_t *r, zend_bool replace, const char *fmt, va_list argv)
+{
+ php_http_env_response_stream_ctx_t *stream_ctx = r->ctx;
+ char *header_end, *header_str = NULL;
+ size_t header_len = 0;
+ zval *zheader, **zheader_ptr;
+
+ if (stream_ctx->started || stream_ctx->finished) {
+ return FAILURE;
+ }
+
+ header_len = vspprintf(&header_str, 0, fmt, argv);
+
+ if (!(header_end = strchr(header_str, ':'))) {
+ efree(header_str);
+ return FAILURE;
+ }
+
+ *header_end = '\0';
+
+ if (!replace && (SUCCESS == zend_hash_find(&stream_ctx->header, header_str, header_end - header_str + 1, (void *) &zheader_ptr))) {
+ convert_to_array(*zheader_ptr);
+ *header_end = ':';
+ return add_next_index_stringl(*zheader_ptr, header_str, header_len, 0);
+ } else {
+ MAKE_STD_ZVAL(zheader);
+ ZVAL_STRINGL(zheader, header_str, header_len, 0);
+
+ if (SUCCESS != zend_hash_update(&stream_ctx->header, header_str, header_end - header_str + 1, (void *) &zheader, sizeof(zval *), NULL)) {
+ zval_ptr_dtor(&zheader);
+ return FAILURE;
+ }
+
+ *header_end = ':';
+ return SUCCESS;
+ }
+}
+static ZEND_RESULT_CODE php_http_env_response_stream_set_header(php_http_env_response_t *r, const char *fmt, ...)
+{
+ ZEND_RESULT_CODE ret;
+ va_list argv;
+
+ va_start(argv, fmt);
+ ret = php_http_env_response_stream_set_header_ex(r, 1, fmt, argv);
+ va_end(argv);
+
+ return ret;
+}
+static ZEND_RESULT_CODE php_http_env_response_stream_add_header(php_http_env_response_t *r, const char *fmt, ...)
+{
+ ZEND_RESULT_CODE ret;
+ va_list argv;
+
+ va_start(argv, fmt);
+ ret = php_http_env_response_stream_set_header_ex(r, 0, fmt, argv);
+ va_end(argv);
+
+ return ret;
+}
+static ZEND_RESULT_CODE php_http_env_response_stream_del_header(php_http_env_response_t *r, const char *header_str, size_t header_len)
+{
+ php_http_env_response_stream_ctx_t *stream_ctx = r->ctx;
+
+ if (stream_ctx->started || stream_ctx->finished) {
+ return FAILURE;
+ }
+
+ zend_hash_del(&stream_ctx->header, header_str, header_len + 1);
+ return SUCCESS;
+}
+static ZEND_RESULT_CODE php_http_env_response_stream_write(php_http_env_response_t *r, const char *data_str, size_t data_len)
+{
+ php_http_env_response_stream_ctx_t *stream_ctx = r->ctx;
+ TSRMLS_FETCH_FROM_CTX(r->ts);
+
+ if (stream_ctx->finished) {
+ return FAILURE;
+ }
+ if (!stream_ctx->started) {
+ if (SUCCESS != php_http_env_response_stream_start(stream_ctx TSRMLS_CC)) {
+ return FAILURE;
+ }
+ }