+ php_error_docref(NULL, E_WARNING, "Failed to send response body");
+ return FAILURE;
+ }
+
+ if (SUCCESS != r->ops->finish(r)) {
+ php_error_docref(NULL, E_WARNING, "Failed to finish response");
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+static long php_http_env_response_sapi_get_status(php_http_env_response_t *r)
+{
+ return php_http_env_get_response_code();
+}
+static ZEND_RESULT_CODE php_http_env_response_sapi_set_status(php_http_env_response_t *r, long http_code)
+{
+ return php_http_env_set_response_code(http_code);
+}
+static ZEND_RESULT_CODE php_http_env_response_sapi_set_protocol_version(php_http_env_response_t *r, php_http_version_t *v)
+{
+
+ return php_http_env_set_response_protocol_version(v);
+}
+static ZEND_RESULT_CODE php_http_env_response_sapi_set_header(php_http_env_response_t *r, const char *fmt, ...)
+{
+ ZEND_RESULT_CODE ret;
+ va_list args;
+
+ va_start(args, fmt);
+ ret = php_http_env_set_response_header_va(0, 1, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+static ZEND_RESULT_CODE php_http_env_response_sapi_add_header(php_http_env_response_t *r, const char *fmt, ...)
+{
+ ZEND_RESULT_CODE ret;
+ va_list args;
+
+ va_start(args, fmt);
+ ret = php_http_env_set_response_header_va(0, 0, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+static ZEND_RESULT_CODE php_http_env_response_sapi_del_header(php_http_env_response_t *r, const char *header_str, size_t header_len)
+{
+ return php_http_env_set_response_header_value(0, header_str, header_len, NULL, 1);
+}
+static ZEND_RESULT_CODE php_http_env_response_sapi_write(php_http_env_response_t *r, const char *data_str, size_t data_len)
+{
+ if (0 < PHPWRITE(data_str, data_len)) {
+ return SUCCESS;
+ }
+ return FAILURE;
+}
+static ZEND_RESULT_CODE php_http_env_response_sapi_flush(php_http_env_response_t *r)
+{
+ if (php_output_get_level()) {
+ php_output_flush_all();
+ }
+ if (!(php_output_get_status() & PHP_OUTPUT_IMPLICITFLUSH)) {
+ sapi_flush();
+ }
+
+ return SUCCESS;
+}
+static ZEND_RESULT_CODE php_http_env_response_sapi_finish(php_http_env_response_t *r)
+{
+ return SUCCESS;
+}
+
+static php_http_env_response_ops_t php_http_env_response_sapi_ops = {
+ NULL,
+ NULL,
+ php_http_env_response_sapi_get_status,
+ php_http_env_response_sapi_set_status,
+ php_http_env_response_sapi_set_protocol_version,
+ php_http_env_response_sapi_set_header,
+ php_http_env_response_sapi_add_header,
+ php_http_env_response_sapi_del_header,
+ php_http_env_response_sapi_write,
+ php_http_env_response_sapi_flush,
+ php_http_env_response_sapi_finish
+};
+
+php_http_env_response_ops_t *php_http_env_response_get_sapi_ops(void)
+{
+ return &php_http_env_response_sapi_ops;
+}
+
+typedef struct php_http_env_response_stream_ctx {
+ HashTable header;
+ php_http_version_t version;
+ long status_code;
+
+ php_stream *stream;
+
+ unsigned started:1;
+ unsigned finished:1;
+} php_http_env_response_stream_ctx_t;
+
+static ZEND_RESULT_CODE php_http_env_response_stream_init(php_http_env_response_t *r, void *init_arg)
+{
+ php_http_env_response_stream_ctx_t *ctx;
+
+ ctx = ecalloc(1, sizeof(*ctx));
+
+ ctx->stream = init_arg;
+ ++GC_REFCOUNT(ctx->stream->res);
+ ZEND_INIT_SYMTABLE(&ctx->header);
+ php_http_version_init(&ctx->version, 1, 1);
+ ctx->status_code = 200;
+
+ 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;
+
+ zend_hash_destroy(&ctx->header);
+ zend_list_delete(ctx->stream->res);
+ efree(ctx);
+ r->ctx = NULL;
+}
+static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header)
+{
+ zval *val;
+
+ ZEND_HASH_FOREACH_VAL(header, val)
+ {
+ if (Z_TYPE_P(val) == IS_ARRAY) {
+ php_http_env_response_stream_header(ctx, Z_ARRVAL_P(val));
+ } else {
+ zend_string *zs = zval_get_string(val);
+
+ php_stream_write(ctx->stream, zs->val, zs->len);
+ php_stream_write_string(ctx->stream, PHP_HTTP_CRLF);
+ zend_string_release(zs);
+ }
+ }
+ ZEND_HASH_FOREACH_END();
+}
+static ZEND_RESULT_CODE php_http_env_response_stream_start(php_http_env_response_stream_ctx_t *ctx)
+{
+ if (ctx->started || ctx->finished) {