From c6acde420c313cd15836806b34fd11319ce605d8 Mon Sep 17 00:00:00 2001 From: Elias Nicolas Date: Sat, 12 Mar 2022 08:55:37 -0300 Subject: [PATCH] PHP 8 (#4) - [ ] Panic Testing EngineDefine - [x] Fixed Readme - [x] Fixed Testing strlen(); (probably) - [x] Added script to install or build from source on debian - [x] Dropped support for PHP < 8 - [x] Changed names for easier use. - [x] Dropped _*.c - [x] Added ability to perform make - [x] Moved to own directory Dockerfile, scripts and everything related to php install - [x] Added Automatic make tags based on the system - [x] Found the reason for failing tests. - [x] Migrate to gitlab. - [x] Set up Pipelines. - [x] Update readme to reflect changes. - [x] Removed printf - [x] added old-stable support, I guess - [x] Removed _engine_receiver, simplified using _zend_object directly, created functions overriding module - [x] Better images, fewer variables Co-authored-by: Elias <524433-sailenicolas@users.noreply.gitlab.com> --- .gitlab-ci.yml | 26 ++ .idea/.name | 1 + .idea/{go-php.iml => gophp.iml} | 6 + .idea/modules.xml | 2 +- .idea/runConfigurations/engine_test_go.xml | 16 + .../go_test_gitlab_com_sailenicolas_gophp.xml | 16 + .idea/runConfigurations/receiver_test_go.xml | 14 + .idea/runConfigurations/value_test_go.xml | 13 + .idea/workspace.xml | 114 +++++ .travis.yml | 15 - Dockerfile | 59 --- Makefile | 51 ++- README.md | 44 +- context.c | 110 +---- context.go | 7 +- context_test.go | 16 +- engine.c | 140 +------ engine.go | 23 +- engine_test.go | 5 +- go.mod | 2 +- include/context.h | 5 +- include/engine.h | 10 +- include/php7/_context.h | 11 - include/php7/_engine.h | 10 - include/php7/_receiver.h | 24 -- include/php7/_value.h | 21 - include/php8/_context.h | 11 - include/php8/_engine.h | 10 - include/php8/_receiver.h | 24 -- include/php8/_value.h | 21 - include/receiver.h | 24 +- include/value.h | 42 +- php-tools/Dockerfile | 46 ++ php-tools/build-php.sh | 59 +++ php-tools/docker-entrypoint.sh | 2 + php-tools/install-php.sh | 33 ++ php7-debian.go | 18 - php7.go | 11 - php7-static.go => php8-static.go | 5 +- php8.go | 10 +- receiver.c | 179 +------- receiver.go | 11 +- receiver_test.go | 2 +- src/context.c | 119 ++++++ src/engine.c | 134 ++++++ src/php7/_context.c | 25 -- src/php7/_engine.c | 7 - src/php7/_receiver.c | 88 ---- src/php7/_value.c | 64 --- src/php8/_context.c | 25 -- src/php8/_engine.c | 7 - src/php8/_receiver.c | 88 ---- src/php8/_value.c | 64 --- src/receiver.c | 242 +++++++++++ src/value.c | 396 ++++++++++++++++++ value.c | 379 +---------------- value.go | 28 +- value_test.go | 2 +- 58 files changed, 1421 insertions(+), 1516 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100644 .idea/.name rename .idea/{go-php.iml => gophp.iml} (70%) create mode 100644 .idea/runConfigurations/engine_test_go.xml create mode 100644 .idea/runConfigurations/go_test_gitlab_com_sailenicolas_gophp.xml create mode 100644 .idea/runConfigurations/receiver_test_go.xml create mode 100644 .idea/runConfigurations/value_test_go.xml create mode 100644 .idea/workspace.xml delete mode 100644 .travis.yml delete mode 100644 Dockerfile delete mode 100644 include/php7/_context.h delete mode 100644 include/php7/_engine.h delete mode 100644 include/php7/_receiver.h delete mode 100644 include/php7/_value.h delete mode 100644 include/php8/_context.h delete mode 100644 include/php8/_engine.h delete mode 100644 include/php8/_receiver.h delete mode 100644 include/php8/_value.h create mode 100644 php-tools/Dockerfile create mode 100644 php-tools/build-php.sh create mode 100644 php-tools/docker-entrypoint.sh create mode 100644 php-tools/install-php.sh delete mode 100644 php7-debian.go delete mode 100644 php7.go rename php7-static.go => php8-static.go (80%) create mode 100644 src/context.c create mode 100644 src/engine.c delete mode 100644 src/php7/_context.c delete mode 100644 src/php7/_engine.c delete mode 100644 src/php7/_receiver.c delete mode 100644 src/php7/_value.c delete mode 100644 src/php8/_context.c delete mode 100644 src/php8/_engine.c delete mode 100644 src/php8/_receiver.c delete mode 100644 src/php8/_value.c create mode 100644 src/receiver.c create mode 100644 src/value.c diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..fa9bc7c --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,26 @@ +# This file is a template, and might need editing before it works on your project. +# Select image from https://hub.docker.com/_/php/ +before_script: + - bash php-tools/docker-entrypoint.sh > /dev/null +testgophp81: + image: sailenicolas/gophp:8.1.3 + only: + - php8 + cache: + key: "$CI_COMMIT_REF_SLUG" + paths: + - vendor/ + script: + - make test +testgophp80: + image: sailenicolas/gophp:8.0.16 + only: + - php8 + cache: + key: "$CI_COMMIT_REF_SLUG" + paths: + - vendor/ + script: + - make test + + diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..d084008 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +gophp \ No newline at end of file diff --git a/.idea/go-php.iml b/.idea/gophp.iml similarity index 70% rename from .idea/go-php.iml rename to .idea/gophp.iml index 2986543..c2d1ce8 100644 --- a/.idea/go-php.iml +++ b/.idea/gophp.iml @@ -3,6 +3,12 @@ diff --git a/.idea/modules.xml b/.idea/modules.xml index a1f4559..9b005b9 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/runConfigurations/engine_test_go.xml b/.idea/runConfigurations/engine_test_go.xml new file mode 100644 index 0000000..3a1896d --- /dev/null +++ b/.idea/runConfigurations/engine_test_go.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/go_test_gitlab_com_sailenicolas_gophp.xml b/.idea/runConfigurations/go_test_gitlab_com_sailenicolas_gophp.xml new file mode 100644 index 0000000..94a74c3 --- /dev/null +++ b/.idea/runConfigurations/go_test_gitlab_com_sailenicolas_gophp.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/receiver_test_go.xml b/.idea/runConfigurations/receiver_test_go.xml new file mode 100644 index 0000000..49204f6 --- /dev/null +++ b/.idea/runConfigurations/receiver_test_go.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/value_test_go.xml b/.idea/runConfigurations/value_test_go.xml new file mode 100644 index 0000000..135ea5d --- /dev/null +++ b/.idea/runConfigurations/value_test_go.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..5c2793a --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 60f6ad6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: php - -php: - - 7.4.28 - - 8.1.3 - -sudo: required -services: - - docker - -script: - - make docker-test PHP_VERSION="$TRAVIS_PHP_VERSION" - -notifications: - email: false diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index abf3915..0000000 --- a/Dockerfile +++ /dev/null @@ -1,59 +0,0 @@ -FROM golang:1.17.7-stretch - -# The full PHP version to target, i.e. "7.1.10". -ARG PHP_VERSION - -# Whether or not to build PHP as a static library. -ARG STATIC=false - -# Environment variables used across the build. -ENV PHP_URL="https://secure.php.net/get/php-${PHP_VERSION}.tar.xz/from/this/mirror" -ENV PHP_BASE_DIR="/tmp/php" -ENV PHP_SRC_DIR="${PHP_BASE_DIR}/src" - -# Build variables. -ENV PHP_LDFLAGS="-Wl,-O1 -Wl,--hash-style=both -pie" -ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O2" -ENV PHP_CPPFLAGS="${PHP_CFLAGS}" - -# Fetch PHP source code. This step does not currently validate keys or checksums, as this process -# will eventually transition to using the base `php` Docker images. -ENV FETCH_DEPS="ca-certificates wget" -RUN set -xe && \ - apt-get update && apt-get install -y --no-install-recommends ${FETCH_DEPS} && \ - mkdir -p ${PHP_BASE_DIR} && cd ${PHP_BASE_DIR} && \ - wget -O php.tar.xz ${PHP_URL} && \ - apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false ${FETCH_DEPS} - -# Build PHP library from source. -ENV BUILD_DEPS="build-essential file libpcre3-dev dpkg-dev libcurl4-openssl-dev libedit-dev libsqlite3-dev libssl1.0-dev libxml2-dev zlib1g-dev libonig-dev" -RUN set -xe && \ - apt-get update && apt-get install -y --no-install-recommends ${BUILD_DEPS}; \ - export CFLAGS="${PHP_CFLAGS}" CPPFLAGS="${PHP_CPPFLAGS}" LDFLAGS="${PHP_LDFLAGS}"; \ - arch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" && multiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ - [ "x$STATIC" = "xfalse" ] \ - && options="--enable-embed" \ - || options="--enable-embed=static --enable-static"; \ - [ ! -d /usr/include/curl ] && ln -sT "/usr/include/$multiarch/curl" /usr/local/include/curl; \ - mkdir -p ${PHP_SRC_DIR} && cd ${PHP_SRC_DIR} && \ - tar -xJf ${PHP_BASE_DIR}/php.tar.xz -C . --strip-components=1 && \ - ./configure \ - --prefix=/usr --build="$arch" \ - --with-libdir="lib/$multiarch" \ - --with-pcre-regex=/usr \ - --disable-cgi --disable-fpm \ - --enable-ftp --enable-mbstring \ - --with-curl --with-libedit --with-openssl --with-zlib \ - $options \ - && \ - make -j "$(nproc)" && \ - apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false ${BUILD_DEPS} - -# Install runtime dependencies for testing, building packages etc, and clean up source. -ENV RUNTIME_DEPS="build-essential git curl libssl1.0 libpcre3-dev libcurl4-openssl-dev libedit-dev libxml2-dev zlib1g-dev" -RUN set -xe && \ - apt-get update && apt-get install -y --no-install-recommends ${RUNTIME_DEPS} && \ - cd ${PHP_SRC_DIR} && make -j "$(nproc)" PHP_SAPI=embed install-sapi install-headers && \ - cd / && rm -Rf ${PHP_BASE_DIR} ${PHP_SRC_DIR} - -ENTRYPOINT ["/bin/sh", "-c"] diff --git a/Makefile b/Makefile index 3055af3..b558a98 100644 --- a/Makefile +++ b/Makefile @@ -1,41 +1,41 @@ # Package options. -NAME := go-php +NAME := gophp DESCRIPTION := PHP bindings for the Go programming language IMPORT_PATH := github.com/sailenicolas/$(NAME) VERSION := $(shell git describe --tags --always --dirty="-dev") # Generic build options. -PHP_VERSION := 8.1.3 -STATIC := false -DOCKER_IMAGE := sailenicolas/$(NAME):$(PHP_VERSION) - +PHP_VERSION := $(firstword $(shell php -i | grep "PHP Version" | sed -e 's/PHP Version =>//g')) +STATIC := false +DOCKER_IMAGE := sailenicolas/$(NAME): +BUILD_IMAGE_LSB := $(shell lsb_release -si) # Go build options. GO := go -TAGS := -tags 'php$(word 1,$(subst ., ,$(PHP_VERSION))) $(if $(findstring true,$(STATIC)),static)' +TAGS := -tags $(BUILD_IMAGE_LSB),$(if $(findstring true, $(STATIC)), static,)php$(word 1,$(subst ., ,$(PHP_VERSION))) # Install options. PREFIX := /usr - # Default Makefile options. -VERBOSE := +VERBOSE := true # Variables to pass down to sub-invocations of 'make'. MAKE_OPTIONS := PHP_VERSION=$(PHP_VERSION) GO=$(GO) PREFIX=$(PREFIX) VERBOSE=$(VERBOSE) STATIC=$(STATIC) ## Build binary distribution for library. -build: .build/env/GOPATH/.ok +build: .build @echo "Building '$(NAME)'..." $Q $(GO) install $(if $(VERBOSE),-v) $(TAGS) $(IMPORT_PATH) ## Run test for all local packages or specified PACKAGE. -test: .build/env/GOPATH/.ok +test: .build @echo "Running tests for '$(NAME)'..." - $Q $(GO) test -race $(if $(VERBOSE),-v) $(TAGS) $(if $(PACKAGE),$(PACKAGE),$(PACKAGES)) + @echo "Running tests for '$(VERSIONID)'..." + $Q $(GO) test $(if $(VERBOSE),-v) $(TAGS) $(if $(PACKAGE),$(PACKAGE),$(PACKAGES)) @echo "Running 'vet' for '$(NAME)'..." $Q $(GO) vet $(if $(VERBOSE),-v) $(TAGS) $(if $(PACKAGE),$(PACKAGE),$(PACKAGES)) ## Create test coverage report for all local packages or specified PACKAGE. -cover: .build/env/GOPATH/.ok +cover: .build @echo "Creating code coverage report for '$(NAME)'..." $Q rm -Rf .build/tmp && mkdir -p .build/tmp $Q for pkg in $(if $(PACKAGE),$(PACKAGE),$(PACKAGES)); do \ @@ -52,7 +52,7 @@ cover: .build/env/GOPATH/.ok ## Remove temporary files and packages required for build. clean: @echo "Cleaning '$(NAME)'..." - $Q $(GO) clean + $Q $(GO) clean -cache $Q rm -Rf .build ## Show usage information for this Makefile. @@ -73,29 +73,34 @@ help: # Pull or build Docker image for PHP version specified. docker-image: - $Q docker image pull $(DOCKER_IMAGE) || \ - docker build --build-arg=PHP_VERSION=$(PHP_VERSION) --build-arg=STATIC=$(STATIC) \ - -t $(DOCKER_IMAGE) -f Dockerfile . \ + $Q docker image pull $(DOCKER_IMAGE)8.1.3 || \ + docker build --build-arg=PHP_VERSION="8.1.3" --build-arg=STATIC=$(STATIC) \ + --build-arg=PHP_VERSION_INSTALL=php8.1 \ + -t $(DOCKER_IMAGE)8.1.3 -f "php-tools/Dockerfile" . + $Q docker image pull $(DOCKER_IMAGE)8.0.16 || \ + docker build --build-arg=PHP_VERSION="8.0.16" --build-arg=STATIC=$(STATIC) \ + --build-arg=PHP_VERSION_INSTALL=php8.0 \ + -t $(DOCKER_IMAGE)8.0.16 -f "php-tools/Dockerfile" . # Run Make target in Docker container. For instance, to run 'test', call as 'docker-test'. docker-%: docker-image $Q docker run --rm -e GOPATH="/tmp/go" \ - -v "$(CURDIR):/tmp/go/src/$(IMPORT_PATH)" $(DOCKER_IMAGE) \ + -v "$(CURDIR):/tmp/go/src/$(IMPORT_PATH)" $(DOCKER_IMAGE)$(PHP_VERSION) \ "$(MAKE) -C /tmp/go/src/$(IMPORT_PATH) $(word 2,$(subst -, ,$@)) $(MAKE_OPTIONS)" -.build/env/GOPATH/.ok: - $Q mkdir -p "$(dir .build/env/GOPATH/src/$(IMPORT_PATH))" && touch $@ - $Q ln -s ../../../../../.. ".build/env/GOPATH/src/$(IMPORT_PATH)" +.build: + $Q mkdir -p "$(dir .build/src/$(IMPORT_PATH))" && touch $@ + $Q ln -s ../../../.. ".build/src/$(IMPORT_PATH)" MAKEFILE := $(lastword $(MAKEFILE_LIST)) Q := $(if $(VERBOSE),,@) PACKAGES = $(shell ( \ - cd $(CURDIR)/.build/env/GOPATH/src/$(IMPORT_PATH) && \ - GOPATH=$(CURDIR)/.build/env/GOPATH go list ./... | grep -v "vendor" \ + cd $(CURDIR)/.build/src/$(IMPORT_PATH) && \ + GOPATH=$(CURDIR)/.build go list ./... | grep -v "vendor" \ )) -export GOPATH := $(CURDIR)/.build/env/GOPATH +export GOPATH := $(CURDIR)/.build BOLD = \033[1m UNDERLINE = \033[4m diff --git a/README.md b/README.md index 5b7f36d..22c000f 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,28 @@ -# PHP bindings for Go +# PHP bindings for Go (PHP 8) + +## This fork does not work, is an attempt to make it compatible with PHP 8. [![API Documentation][godoc-svg]][godoc-url] [![MIT License][license-svg]][license-url] This package implements support for executing PHP scripts, exporting Go variables for use in PHP contexts, attaching Go method receivers as PHP classes and returning PHP variables for use in Go contexts. -Both PHP 5.x and PHP 7.x series are supported. +Only PHP > 8.1 series are supported. ## Building -Building this package requires that you have PHP installed as a library. For most Linux systems, this can usually be found in the `php-embed` package, or variations thereof. +Building this package requires that you have PHP installed as a library. -Once the PHP library is available, the bindings can be compiled with `go build` and are `go get`-able. +For most Linux systems, this can usually be found be installed using `install-php.sh` (debian only) or use the build and compile option with `build-php.sh`. -**Note**: Building against PHP 5.x requires that the `php5` tag is provided, i.e.: +Once the PHP library is available, the bindings can be compiled with `go build` and are `go get`-able. +**Note**: Pull Request are welcome. +**Note**: Building against PHP 8.1.3 requires that the `php8` tag is provided, i.e.: ```bash -go get -tags php5 github.com/deuill/go-php +go get -tags php8 gitlab.com/sailenicolas/gophp ``` -This is due to the fact that PHP 7.x is the default build target. +There is not a default build target. ## Status @@ -30,7 +34,7 @@ It is possible to [attach Go method receivers][NewReceiver] as PHP classes, with ### Caveats -Be aware that, by default, PHP is **not** designed to be used in multithreaded environments (which severely restricts the use of these bindings with Goroutines) if not built with [ZTS support](https://secure.php.net/manual/en/pthreads.requirements.php). However, ZTS support has seen major refactoring between PHP 5 and PHP 7, and as such is currently unsupported by this package. +Be aware that, by default, PHP is **not** designed to be used in multithreaded environments (which severely restricts the use of these bindings with Goroutines) if not built with [ZTS support](https://secure.php.net/manual/en/pthreads.requirements.php). However, PHP 8 ZTS support has seen major refactoring, and as such is currently unsupported by this package. Currently, it is recommended to either sync use of seperate Contexts between Goroutines, or share a single Context among all running Goroutines. @@ -47,6 +51,12 @@ These items will be tackled in order of significance (which may not be the order ## Usage +### Tests +To run tests is simple enough, just `make test` + +### Build a docker image +To run tests is simple enough, just `make docker-image` + ### Basic Executing a script is simple: @@ -55,7 +65,7 @@ Executing a script is simple: package main import ( - php "github.com/deuill/go-php" + php "github.com/sailenicolas/gophp" "os" ) @@ -81,7 +91,7 @@ package main import ( "fmt" - php "github.com/deuill/go-php" + php "github.com/sailenicolas/gophp" ) func main() { @@ -107,13 +117,13 @@ Finally, the value is returned as an `interface{}` using `Value.Interface()` (on All code in this repository is covered by the terms of the MIT License, the full text of which can be found in the LICENSE file. -[godoc-url]: https://godoc.org/github.com/deuill/go-php -[godoc-svg]: https://godoc.org/github.com/deuill/go-php?status.svg +[godoc-url]: https://pkg.go.dev/gitlab.com/sailenicolas/gophp +[godoc-svg]: https://pkg.go.dev/badge/gitlab.com/sailenicolas/gophp -[license-url]: https://github.com/deuill/go-php/blob/master/LICENSE +[license-url]: https://gitlab.com/sailenicolas/gophp/blob/master/LICENSE [license-svg]: https://img.shields.io/badge/license-MIT-blue.svg -[Context.Exec]: https://godoc.org/github.com/deuill/go-php/engine#Context.Exec -[Context.Eval]: https://godoc.org/github.com/deuill/go-php/engine#Context.Eval -[NewValue]: https://godoc.org/github.com/deuill/go-php/engine#NewValue -[NewReceiver]: https://godoc.org/github.com/deuill/go-php/engine#NewReceiver +[Context.Exec]: https://pkg.go.dev/github.com/gitlab/gophp#Context.Exec +[Context.Eval]: https://pkg.go.dev/github.com/gitlab/gophp#Context.Eval +[NewValue]: https://pkg.go.dev/github.com/gitlab/gophp#NewValue +[NewReceiver]: https://pkg.go.dev/github.com/gitlab/gophp#NewReceiver diff --git a/context.c b/context.c index bc8ebc4..c1ea4c4 100644 --- a/context.c +++ b/context.c @@ -1,109 +1 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -#include -#include - -#include
-#include
- -#include "value.h" -#include "context.h" - -engine_context *context_new() { - engine_context *context; - - // Initialize context. - context = malloc((sizeof(engine_context))); - if (context == NULL) { - errno = 1; - return NULL; - } - - SG(server_context) = context; - - // Initialize request lifecycle. - if (php_request_startup() == FAILURE) { - SG(server_context) = NULL; - free(context); - - errno = 1; - return NULL; - } - - errno = 0; - return context; -} - -void context_exec(engine_context *context, char *filename) { - int ret; - - // Attempt to execute script file. - zend_first_try { - zend_file_handle script; - - script.type = ZEND_HANDLE_FILENAME; - script.filename = filename; - script.opened_path = NULL; - script.free_filename = 0; - - ret = php_execute_script(&script); - } zend_catch { - errno = 1; - return; - } zend_end_try(); - - if (ret == FAILURE) { - errno = 1; - return; - } - - errno = 0; - return; -} - -void *context_eval(engine_context *context, char *script) { - zval *str = _value_init(); - _value_set_string(&str, script); - - // Compile script value. - uint32_t compiler_options = CG(compiler_options); - - CG(compiler_options) = ZEND_COMPILE_DEFAULT_FOR_EVAL; - zend_op_array *op = zend_compile_string(str, "gophp-engine"); - CG(compiler_options) = compiler_options; - - zval_dtor(str); - - // Return error if script failed to compile. - if (!op) { - errno = 1; - return NULL; - } - - // Attempt to execute compiled string. - zval tmp; - _context_eval(op, &tmp); - - // Allocate result value and copy temporary execution result in. - zval *result = malloc(sizeof(zval)); - value_copy(result, &tmp); - - errno = 0; - return result; -} - -void context_bind(engine_context *context, char *name, void *value) { - engine_value *v = (engine_value *) value; - _context_bind(name, v->internal); -} - -void context_destroy(engine_context *context) { - php_request_shutdown(NULL); - - SG(server_context) = NULL; - free(context); -} - -#include "_context.c" +#include "src/context.c" diff --git a/context.go b/context.go index 010631f..4ee1038 100644 --- a/context.go +++ b/context.go @@ -2,11 +2,8 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. -package php +package gophp -// #cgo CFLAGS: -I/usr/include/php8 -I/usr/include/php8/main -I/usr/include/php8/TSRM -// #cgo CFLAGS: -I/usr/include/php8/Zend -Iinclude -// // #include // #include
// #include "context.h" @@ -77,7 +74,7 @@ func (c *Context) Eval(script string) (*Value, error) { result, err := C.context_eval(c.context, s) if err != nil { - return nil, fmt.Errorf("Error executing script '%s' in context", script) + return nil, fmt.Errorf("error executing script '%s' in context %s", script, err.Error()) } defer C.free(result) diff --git a/context_test.go b/context_test.go index a2948da..ee46bec 100644 --- a/context_test.go +++ b/context_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. -package php +package gophp import ( "bytes" @@ -62,6 +62,7 @@ func TestContextExec(t *testing.T) { } if err := c.Exec(script.Name()); err != nil { + fmt.Println(script.Name()) t.Errorf("Context.Exec('%s'): Execution failed: %s", tt.name, err) continue } @@ -105,6 +106,7 @@ func TestContextEval(t *testing.T) { for _, tt := range evalTests { val, err := c.Eval(tt.script) if err != nil { + fmt.Println(tt.script) t.Errorf("Context.Eval('%s'): %s", tt.script, err) continue } @@ -173,16 +175,20 @@ var logTests = []struct { }{ { "$a = 10; $a + $b;", - "Undefined variable: b in gophp-engine on line 1", + "PHP Warning: Undefined variable $b in gophp-engine on line 1", }, { - "strlen();", - "strlen() expects exactly 1 parameter, 0 given in gophp-engine on line 1", + "strlen(NULL);", + "PHP Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in gophp-engine on line 1", }, { "trigger_error('Test Error');", "Test Error in gophp-engine on line 1", }, + { + "trigger_error('ERRORIFY');", + "ERRORIFY in gophp-engine on line 1", + }, } func TestContextLog(t *testing.T) { @@ -193,7 +199,7 @@ func TestContextLog(t *testing.T) { for _, tt := range logTests { if _, err := c.Eval(tt.script); err != nil { - t.Errorf("Context.Eval('%s'): %s", tt.script, err) + t.Errorf("Context.Eval('%s'): %s", tt.script, err.Error()) continue } diff --git a/engine.c b/engine.c index 4c87203..0d1478b 100644 --- a/engine.c +++ b/engine.c @@ -2,142 +2,4 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. -#include -#include - -#include
-#include
-#include
-#include
- -#include "context.h" -#include "engine.h" -#include "_cgo_export.h" - -// The php.ini defaults for the Go-PHP engine. -const char engine_ini_defaults[] = { - "expose_php = 0\n" - "default_mimetype =\n" - "html_errors = 0\n" - "register_argc_argv = 1\n" - "implicit_flush = 1\n" - "output_buffering = 0\n" - "max_execution_time = 0\n" - "max_input_time = -1\n\0" -}; - -static int engine_ub_write(const char *str, uint len) { - engine_context *context = SG(server_context); - - int written = engineWriteOut(context, (void *) str, len); - if (written != len) { - php_handle_aborted_connection(); - } - - return len; -} - -static int engine_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers) { - engine_context *context = SG(server_context); - - switch (op) { - case SAPI_HEADER_REPLACE: - case SAPI_HEADER_ADD: - case SAPI_HEADER_DELETE: - engineSetHeader(context, op, (void *) sapi_header->header, sapi_header->header_len); - break; - } - - return 0; -} - -static void engine_send_header(sapi_header_struct *sapi_header, void *server_context) { - // Do nothing. -} - -static char *engine_read_cookies() { - return NULL; -} - -static void engine_register_variables(zval *track_vars_array) { - php_import_environment_variables(track_vars_array); -} - -#if PHP_VERSION_ID < 70100 -static void engine_log_message(char *str) { -#else -static void engine_log_message(char *str, int syslog_type_int) { -#endif - engine_context *context = SG(server_context); - - engineWriteLog(context, (void *) str, strlen(str)); -} - -static sapi_module_struct engine_module = { - "gophp-engine", // Name - "Go PHP Engine Library", // Pretty Name - - NULL, // Startup - php_module_shutdown_wrapper, // Shutdown - - NULL, // Activate - NULL, // Deactivate - - _engine_ub_write, // Unbuffered Write - NULL, // Flush - NULL, // Get UID - NULL, // Getenv - - php_error, // Error Handler - - engine_header_handler, // Header Handler - NULL, // Send Headers Handler - engine_send_header, // Send Header Handler - - NULL, // Read POST Data - engine_read_cookies, // Read Cookies - - engine_register_variables, // Register Server Variables - engine_log_message, // Log Message - NULL, // Get Request Time - NULL, // Child Terminate - - STANDARD_SAPI_MODULE_PROPERTIES -}; - -php_engine *engine_init(void) { - php_engine *engine; - - #ifdef HAVE_SIGNAL_H - #if defined(SIGPIPE) && defined(SIG_IGN) - signal(SIGPIPE, SIG_IGN); - #endif - #endif - - sapi_startup(&engine_module); - - engine_module.ini_entries = malloc(sizeof(engine_ini_defaults)); - memcpy(engine_module.ini_entries, engine_ini_defaults, sizeof(engine_ini_defaults)); - - if (php_module_startup(&engine_module, NULL, 0) == FAILURE) { - sapi_shutdown(); - - errno = 1; - return NULL; - } - - engine = malloc((sizeof(php_engine))); - - errno = 0; - return engine; -} - -void engine_shutdown(php_engine *engine) { - php_module_shutdown(); - sapi_shutdown(); - - free(engine_module.ini_entries); - free(engine); -} - -#include "_engine.c" +#include "src/engine.c" diff --git a/engine.go b/engine.go index 738d40a..071fb2e 100644 --- a/engine.go +++ b/engine.go @@ -4,10 +4,8 @@ // Package engine provides methods allowing for the initialization and teardown // of PHP engine bindings, off which execution contexts can be launched. -package php +package gophp -// #cgo CFLAGS: -I/usr/include/php8 -I/usr/include/php8/main -I/usr/include/php8/TSRM -// #cgo CFLAGS: -I/usr/include/php8/Zend -Iinclude // // #include // #include
@@ -40,7 +38,6 @@ func New() (*Engine, error) { if engine != nil { return nil, fmt.Errorf("Cannot activate multiple engine instances") } - ptr, err := C.engine_init() if err != nil { return nil, fmt.Errorf("PHP engine failed to initialize") @@ -61,7 +58,7 @@ func New() (*Engine, error) { func (e *Engine) NewContext() (*Context, error) { ptr, err := C.context_new() if err != nil { - return nil, fmt.Errorf("Failed to initialize context for PHP engine") + return nil, fmt.Errorf("Failed to initialize context for PHP engine" + err.Error()) } ctx := &Context{ @@ -84,6 +81,7 @@ func (e *Engine) NewContext() (*Context, error) { // The constructor function accepts a slice of arguments, as passed by the PHP // context, and should return a method receiver instance, or nil on error (in // which case, an exception is thrown on the PHP object constructor). + func (e *Engine) Define(name string, fn func(args []interface{}) interface{}) error { if _, exists := e.receivers[name]; exists { return fmt.Errorf("Failed to define duplicate receiver '%s'", name) @@ -92,12 +90,11 @@ func (e *Engine) Define(name string, fn func(args []interface{}) interface{}) er rcvr := &Receiver{ name: name, create: fn, - objects: make(map[*C.struct__engine_receiver]*ReceiverObject), + objects: make(map[*C.struct__zend_object]*ReceiverObject), } - n := C.CString(name) - defer C.free(unsafe.Pointer(n)) + defer C.free(unsafe.Pointer(n)) C.receiver_define(n) e.receivers[name] = rcvr @@ -190,7 +187,7 @@ func engineSetHeader(ctx *C.struct__engine_context, operation C.uint, buffer uns } //export engineReceiverNew -func engineReceiverNew(rcvr *C.struct__engine_receiver, args unsafe.Pointer) C.int { +func engineReceiverNew(rcvr *C.struct__zend_object, args unsafe.Pointer) C.int { n := C.GoString(C._receiver_get_name(rcvr)) if engine == nil || engine.receivers[n] == nil { return 1 @@ -214,7 +211,7 @@ func engineReceiverNew(rcvr *C.struct__engine_receiver, args unsafe.Pointer) C.i } //export engineReceiverGet -func engineReceiverGet(rcvr *C.struct__engine_receiver, name *C.char) unsafe.Pointer { +func engineReceiverGet(rcvr *C.struct__zend_object, name *C.char) unsafe.Pointer { n := C.GoString(C._receiver_get_name(rcvr)) if engine == nil || engine.receivers[n].objects[rcvr] == nil { return nil @@ -229,7 +226,7 @@ func engineReceiverGet(rcvr *C.struct__engine_receiver, name *C.char) unsafe.Poi } //export engineReceiverSet -func engineReceiverSet(rcvr *C.struct__engine_receiver, name *C.char, val unsafe.Pointer) { +func engineReceiverSet(rcvr *C.struct__zend_object, name *C.char, val unsafe.Pointer) { n := C.GoString(C._receiver_get_name(rcvr)) if engine == nil || engine.receivers[n].objects[rcvr] == nil { return @@ -244,7 +241,7 @@ func engineReceiverSet(rcvr *C.struct__engine_receiver, name *C.char, val unsafe } //export engineReceiverExists -func engineReceiverExists(rcvr *C.struct__engine_receiver, name *C.char) C.int { +func engineReceiverExists(rcvr *C.struct__zend_object, name *C.char) C.int { n := C.GoString(C._receiver_get_name(rcvr)) if engine == nil || engine.receivers[n].objects[rcvr] == nil { return 0 @@ -258,7 +255,7 @@ func engineReceiverExists(rcvr *C.struct__engine_receiver, name *C.char) C.int { } //export engineReceiverCall -func engineReceiverCall(rcvr *C.struct__engine_receiver, name *C.char, args unsafe.Pointer) unsafe.Pointer { +func engineReceiverCall(rcvr *C.struct__zend_object, name *C.char, args unsafe.Pointer) unsafe.Pointer { n := C.GoString(C._receiver_get_name(rcvr)) if engine == nil || engine.receivers[n].objects[rcvr] == nil { return nil diff --git a/engine_test.go b/engine_test.go index b934ed8..c865226 100644 --- a/engine_test.go +++ b/engine_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. -package php +package gophp import ( "io/ioutil" @@ -39,7 +39,6 @@ var e *Engine func TestEngineNew(t *testing.T) { var err error - if e, err = New(); err != nil { t.Fatalf("New(): %s", err) } @@ -66,7 +65,7 @@ func TestEngineDefine(t *testing.T) { } if err := e.Define("TestDefine", ctor); err != nil { - t.Errorf("Engine.Define(): %s", err) + t.Errorf("Engine.Define(): %s", err.Error()) } if len(e.receivers) != 1 { diff --git a/go.mod b/go.mod index fabad24..43c3719 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module go-php +module gitlab.com/sailenicolas/gophp go 1.17 diff --git a/include/context.h b/include/context.h index 07bb676..12d6f4f 100644 --- a/include/context.h +++ b/include/context.h @@ -4,7 +4,6 @@ #ifndef __CONTEXT_H__ #define __CONTEXT_H__ - typedef struct _engine_context { } engine_context; @@ -13,7 +12,7 @@ void context_exec(engine_context *context, char *filename); void *context_eval(engine_context *context, char *script); void context_bind(engine_context *context, char *name, void *value); void context_destroy(engine_context *context); - -#include "_context.h" +//static void _context_bind(char *name, zval *value); +//static void _context_eval(zend_op_array *op, zval *ret); #endif diff --git a/include/engine.h b/include/engine.h index ee6f296..d086947 100644 --- a/include/engine.h +++ b/include/engine.h @@ -3,14 +3,22 @@ // the LICENSE file. #ifndef __ENGINE_H__ + #define __ENGINE_H__ typedef struct _php_engine { } php_engine; php_engine *engine_init(void); + void engine_shutdown(php_engine *engine); -#include "_engine.h" +//static size_t engine_ub_write(const char *str, size_t len); + +//static char *engine_read_cookies(); + +//static void engine_register_variables(zval *track_vars_array); + +//static void engine_log_message(const char *str, int syslog_type_int); #endif diff --git a/include/php7/_context.h b/include/php7/_context.h deleted file mode 100644 index f629328..0000000 --- a/include/php7/_context.h +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -#ifndef ___CONTEXT_H___ -#define ___CONTEXT_H___ - -static void _context_bind(char *name, zval *value); -static void _context_eval(zend_op_array *op, zval *ret); - -#endif diff --git a/include/php7/_engine.h b/include/php7/_engine.h deleted file mode 100644 index 272ec26..0000000 --- a/include/php7/_engine.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -#ifndef ___ENGINE_H___ -#define ___ENGINE_H___ - -static size_t _engine_ub_write(const char *str, size_t len); - -#endif diff --git a/include/php7/_receiver.h b/include/php7/_receiver.h deleted file mode 100644 index e9b0f9d..0000000 --- a/include/php7/_receiver.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -#ifndef ___RECEIVER_H___ -#define ___RECEIVER_H___ - -static zval *_receiver_get(zval *object, zval *member, int type, void **cache_slot, zval *retval); -static void _receiver_set(zval *object, zval *member, zval *value, void **cache_slot); -static int _receiver_exists(zval *object, zval *member, int check, void **cache_slot); - -static int _receiver_method_call(zend_string *method, zend_object *object, INTERNAL_FUNCTION_PARAMETERS); -static zend_function *_receiver_method_get(zend_object **object, zend_string *name, const zval *key); -static zend_function *_receiver_constructor_get(zend_object *object); - -static void _receiver_free(zend_object *object); -static zend_object *_receiver_init(zend_class_entry *class_type); -static void _receiver_destroy(char *name); - -static engine_receiver *_receiver_this(zval *object); -static void _receiver_handlers_set(zend_object_handlers *handlers); -char *_receiver_get_name(engine_receiver *rcvr); - -#endif diff --git a/include/php7/_value.h b/include/php7/_value.h deleted file mode 100644 index b58786a..0000000 --- a/include/php7/_value.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -#ifndef ___VALUE_H___ -#define ___VALUE_H___ - -zval *_value_init(); -void _value_destroy(engine_value *val); - -int _value_truth(zval *val); -void _value_set_string(zval **val, char *str); - -static int _value_current_key_get(HashTable *ht, zend_string **str_index, zend_ulong *num_index); -static void _value_current_key_set(HashTable *ht, engine_value *val); - -static void _value_array_next_get(HashTable *ht, engine_value *val); -static void _value_array_index_get(HashTable *ht, unsigned long index, engine_value *val); -static void _value_array_key_get(HashTable *ht, char *key, engine_value *val); - -#endif diff --git a/include/php8/_context.h b/include/php8/_context.h deleted file mode 100644 index f629328..0000000 --- a/include/php8/_context.h +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -#ifndef ___CONTEXT_H___ -#define ___CONTEXT_H___ - -static void _context_bind(char *name, zval *value); -static void _context_eval(zend_op_array *op, zval *ret); - -#endif diff --git a/include/php8/_engine.h b/include/php8/_engine.h deleted file mode 100644 index 272ec26..0000000 --- a/include/php8/_engine.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -#ifndef ___ENGINE_H___ -#define ___ENGINE_H___ - -static size_t _engine_ub_write(const char *str, size_t len); - -#endif diff --git a/include/php8/_receiver.h b/include/php8/_receiver.h deleted file mode 100644 index e9b0f9d..0000000 --- a/include/php8/_receiver.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -#ifndef ___RECEIVER_H___ -#define ___RECEIVER_H___ - -static zval *_receiver_get(zval *object, zval *member, int type, void **cache_slot, zval *retval); -static void _receiver_set(zval *object, zval *member, zval *value, void **cache_slot); -static int _receiver_exists(zval *object, zval *member, int check, void **cache_slot); - -static int _receiver_method_call(zend_string *method, zend_object *object, INTERNAL_FUNCTION_PARAMETERS); -static zend_function *_receiver_method_get(zend_object **object, zend_string *name, const zval *key); -static zend_function *_receiver_constructor_get(zend_object *object); - -static void _receiver_free(zend_object *object); -static zend_object *_receiver_init(zend_class_entry *class_type); -static void _receiver_destroy(char *name); - -static engine_receiver *_receiver_this(zval *object); -static void _receiver_handlers_set(zend_object_handlers *handlers); -char *_receiver_get_name(engine_receiver *rcvr); - -#endif diff --git a/include/php8/_value.h b/include/php8/_value.h deleted file mode 100644 index b58786a..0000000 --- a/include/php8/_value.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -#ifndef ___VALUE_H___ -#define ___VALUE_H___ - -zval *_value_init(); -void _value_destroy(engine_value *val); - -int _value_truth(zval *val); -void _value_set_string(zval **val, char *str); - -static int _value_current_key_get(HashTable *ht, zend_string **str_index, zend_ulong *num_index); -static void _value_current_key_set(HashTable *ht, engine_value *val); - -static void _value_array_next_get(HashTable *ht, engine_value *val); -static void _value_array_index_get(HashTable *ht, unsigned long index, engine_value *val); -static void _value_array_key_get(HashTable *ht, char *key, engine_value *val); - -#endif diff --git a/include/receiver.h b/include/receiver.h index 0069ddf..eaad764 100644 --- a/include/receiver.h +++ b/include/receiver.h @@ -5,13 +5,27 @@ #ifndef __RECEIVER_H__ #define __RECEIVER_H__ -typedef struct _engine_receiver { - zend_object obj; -} engine_receiver; - void receiver_define(char *name); void receiver_destroy(char *name); +/* +static zval * receiver_get(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv); +static zval * receiver_set(zend_object *object, zend_string *member, zval *value, void **cache_slot); +static int receiver_exists(zend_object *object, zend_string *member, int has_set_exists, void **cache_slot); + +static int _receiver_method_call(zend_string *method, zend_object *object, INTERNAL_FUNCTION_PARAMETERS); +static zend_function * _receiver_method_get(zend_object **object, zend_string *name, const zval *key); +static zend_function *_receiver_constructor_get(zend_object *object); + +static void receiver_free(zend_object *object); +static zend_object *receiver_init(zend_class_entry *class_type); +static void _receiver_destroy(char *name); -#include "_receiver.h" +static engine_receiver * receiver_this(zend_object *object); +static void receiver_handlers_set(zend_object_handlers *handlers); +static void handlers_init(); +*/ + +char *_receiver_get_name(zend_object *rcvr); #endif + diff --git a/include/value.h b/include/value.h index f559235..252c410 100644 --- a/include/value.h +++ b/include/value.h @@ -21,36 +21,74 @@ enum { KIND_OBJECT }; +// Creates a new value and initializes type to null. engine_value *value_new(); + +// Creates a complete copy of a zval. +// The destination zval needs to be correctly initialized before use. void value_copy(zval *dst, zval *src); + +// Returns engine value type. Usually compared against KIND_* constants, defined +// in the `value.h` header file. int value_kind(engine_value *val); +// Set type and value to null. void value_set_null(engine_value *val); +// Set type and value to integer. void value_set_long(engine_value *val, long int num); +// Set type and value to floating point. void value_set_double(engine_value *val, double num); +// Set type and value to boolean. void value_set_bool(engine_value *val, bool status); + +//void value_set_string(zval **val, char *str); +// Set type and value to string. void value_set_string(engine_value *val, char *str); + +// Set type and value to array with a preset initial size. void value_set_array(engine_value *val, unsigned int size); + +// Set type and value to object. void value_set_object(engine_value *val); + +// Set type and value from zval. The source zval is copied and is otherwise not +// affected. void value_set_zval(engine_value *val, zval *src); +// Set next index of array or map value. void value_array_next_set(engine_value *arr, engine_value *val); + void value_array_index_set(engine_value *arr, unsigned long idx, engine_value *val); + void value_array_key_set(engine_value *arr, const char *key, engine_value *val); + void value_object_property_set(engine_value *obj, const char *key, engine_value *val); -int value_get_long(engine_value *val); +int value_get_long(engine_value *val) ; + double value_get_double(engine_value *val); + bool value_get_bool(engine_value *val); + char *value_get_string(engine_value *val); unsigned int value_array_size(engine_value *arr); engine_value *value_array_keys(engine_value *arr); void value_array_reset(engine_value *arr); + engine_value *value_array_next_get(engine_value *arr); + engine_value *value_array_index_get(engine_value *arr, unsigned long idx); + engine_value *value_array_key_get(engine_value *arr, char *key); +// Destroy and free engine value. +void value_destroy(engine_value *val); + +int value_truth(zval *val); -#include "_value.h" +//static int value_current_key_get(HashTable *ht, zend_string **str_index, zend_ulong *num_index); + +//static void value_current_key_set(HashTable *ht, engine_value *val); #endif + diff --git a/php-tools/Dockerfile b/php-tools/Dockerfile new file mode 100644 index 0000000..ba1d482 --- /dev/null +++ b/php-tools/Dockerfile @@ -0,0 +1,46 @@ +FROM golang:1.17.8-bullseye + +# The full PHP version to target, i.e. "7.1.10". +ARG PHP_VERSION + +# Whether or not to build PHP as a static library. +ARG STATIC=false + +ARG PHP_VERSION_INSTALL + +# Build variables. +ENV PHP_LDFLAGS="-Wl,-O1 -Wl,--hash-style=both -pie" +ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O2" +ENV PHP_CPPFLAGS="${PHP_CFLAGS}" +ENV PHP_VI="${PHP_VERSION_INSTALL}" +# Fetch PHP source code. This step does not currently validate keys or checksums, as this process +# will eventually transition to using the base `php` Docker images. +ENV FETCH_DEPS="apt-utils software-properties-common apt-transport-https lsb-release ca-certificates curl" +RUN set -xe && \ + { \ + echo 'Package: *php*'; \ + echo 'Pin: release a=stable-security'; \ + echo 'Pin-Priority: -1'; \ + } > /etc/apt/preferences.d/no-debian-php;\ + apt-get update && \ + apt-get install -y --no-install-recommends ${FETCH_DEPS} && \ + apt-get update + +ENV BUILD_DEPS="dpkg-dev ${PHP_VI}-dev ${PHP_VI}-common ${PHP_VI}-embed ${PHP_VI}-cli ${PHP_VI}-opcache lib${PHP_VI}-embed ${PHP_VI}-readline ${PHP_VI}-opcache ${PHP_VI}-xml ${PHP_VI} php-common lib${PHP_VI}-embed-dbgsym" + +RUN curl -vksSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg &&\ + echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list && \ + apt-get update && \ + apt-get upgrade -y && \ + apt-get dist-upgrade -y && \ + export CFLAGS="${PHP_CFLAGS}" CPPFLAGS="${PHP_CPPFLAGS}" LDFLAGS="${PHP_LDFLAGS}"; \ + apt-get install -y --no-install-recommends ${BUILD_DEPS} &&\ + ln -sT "/usr/include/php/$(ls /usr/include/php)" /usr/include/php/phpsrc +COPY php-tools/docker-entrypoint.sh /root/ +RUN export PHPVERSIONID=$(ls /usr/include/php) && \ + export arch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" &&\ + export multiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)" && \ + echo 'export PHPVERSIONID=$(ls /usr/include/php)' >> /etc/profile && \ + chmod +x /root/docker-entrypoint.sh; +CMD ["/bin/sh", "-c"] + diff --git a/php-tools/build-php.sh b/php-tools/build-php.sh new file mode 100644 index 0000000..209945e --- /dev/null +++ b/php-tools/build-php.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# Environment variables used across the build. +export PHP_VERSION="8.1.3" +export PHP_URL="https://secure.php.net/get/php-${PHP_VERSION}.tar.xz/from/this/mirror" +export PHP_BASE_DIR="/tmp/php" +export PHP_SRC_DIR="${PHP_BASE_DIR}/src" +export PHP_BASE_INSTALL="/usr/include/php" +export STATIC=false + +# Build variables. +export PHP_LDFLAGS="-Wl,-O1 -Wl,--hash-style=both -pie" +export PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O2" +export PHP_CPPFLAGS="${PHP_CFLAGS}" + +# Fetch PHP source code. This step does not currently validate keys or checksums, as this process +# will eventually transition to using the base `php` Docker images. +export FETCH_DEPS="ca-certificates wget" +set -xe && \ +apt-get update && apt-get install -y --no-install-recommends ${FETCH_DEPS} && \ +mkdir -p ${PHP_BASE_DIR} && cd ${PHP_BASE_DIR}; +if [[ ! -f ${PHP_BASE_DIR}/php-${PHP_VERSION}.tar.xz ]] +then +wget -O php-${PHP_VERSION}.tar.xz ${PHP_URL}; +fi + +# Build PHP library from source. +export BUILD_DEPS="build-essential file libpcre3-dev dpkg-dev libcurl4-openssl-dev libedit-dev libsqlite3-dev libssl-dev libxml2-dev zlib1g-dev libonig-dev" +set -xe && \ + sudo apt-get update; \ + sudo apt-get install -y --no-install-recommends ${BUILD_DEPS}; \ + export CFLAGS="${PHP_CFLAGS}"; \ + export CPPFLAGS="${PHP_CPPFLAGS}";\ + export LDFLAGS="${PHP_LDFLAGS}"; \ + export arch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" && multiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ + [ "x$STATIC" = "xfalse" ] \ + && options="--enable-embed" \ + || options="--enable-embed=static --enable-static"; \ + [ -d "/usr/local/include/curl" ] && [ ! -L "/usr/local/include/curl" ] && ln -sT "/usr/include/$multiarch/curl" /usr/local/include/curl; + [ -d ${PHP_BASE_INSTALL} ] && sudo rm -rf ${PHP_BASE_INSTALL}; + [ ! -d ${PHP_BASE_INSTALL} ] && sudo mkdir -p ${PHP_BASE_INSTALL}; + [ ! -d ${PHP_SRC_DIR} ] && sudo mkdir -p ${PHP_SRC_DIR}; \ + cd ${PHP_SRC_DIR} && \ + tar -xJf ${PHP_BASE_DIR}/php-${PHP_VERSION}.tar.xz -C ${PHP_BASE_INSTALL} --strip-components=1 && cd ${PHP_BASE_INSTALL} && \ + ./configure \ + --prefix=/usr --build="$arch" \ + --with-libdir="lib/$multiarch" \ + --disable-cgi --disable-fpm \ + --enable-opcache \ + --enable-ftp --enable-mbstring \ + --with-curl --with-libedit --with-openssl --with-zlib \ + $options \ + && \ + make -j "$(nproc)" + +# Install runtime dependencies for testing, building packages etc, and clean up source. +export RUNTIME_DEPS="build-essential git curl libssl1.1 libpcre3-dev libcurl4-openssl-dev libedit-dev libxml2-dev zlib1g-dev" +set -xe && \ + apt-get update && apt-get install -y --no-install-recommends ${RUNTIME_DEPS} && \ + cd ${PHP_BASE_INSTALL} && make -j "$(nproc)" PHP_SAPI=embed install-sapi install-headers \ No newline at end of file diff --git a/php-tools/docker-entrypoint.sh b/php-tools/docker-entrypoint.sh new file mode 100644 index 0000000..311cb8c --- /dev/null +++ b/php-tools/docker-entrypoint.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec "$@" diff --git a/php-tools/install-php.sh b/php-tools/install-php.sh new file mode 100644 index 0000000..def5e42 --- /dev/null +++ b/php-tools/install-php.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Environment variables used across the build. +export PHP_VERSION="8.1.3" +export PHP_IV="$(word 1,$(subst ., ,$(PHP_VERSION))).$(word 2,$(subst ., ,$(PHP_VERSION)))" +# Build variables. +export PHP_LDFLAGS="-Wl,-O1 -Wl,--hash-style=both -pie" +export PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O2" +export PHP_CPPFLAGS="${PHP_CFLAGS}" + +# Fetch PHP source code. This step does not currently validate keys or checksums, as this process +# will eventually transition to using the base `php` Docker images. +export FETCH_DEPS="software-properties-common apt-transport-https lsb-release ca-certificates curl" +set -xe && \ + apt-get update && \ + apt-get install -y --no-install-recommends ${FETCH_DEPS} && \ + apt-get update + +# Build PHP library from source. +export BUILD_DEPS="dpkg-dev ${PHP_VI}-dev ${PHP_VI}-common ${PHP_VI}-embed ${PHP_VI}-cli ${PHP_VI}-opcache lib${PHP_VI}-embed ${PHP_VI}-readline ${PHP_VI}-opcache ${PHP_VI}-xml ${PHP_VI} php-common lib${PHP_VI}-embed-dbgsym" + +curl -vksSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg &&\ + echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list && \ + apt-get update && \ + apt-get upgrade -y && \ + apt-get dist-upgrade -y && \ + export CFLAGS="${PHP_CFLAGS}" CPPFLAGS="${PHP_CPPFLAGS}" LDFLAGS="${PHP_LDFLAGS}"; \ + apt-get install -y --no-install-recommends ${BUILD_DEPS}; + +export PHPVERSIONID=$(ls /usr/include/php) && \ + export arch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" &&\ + export multiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)" && \ + echo 'export PHPVERSIONID=$(ls /usr/include/php)' >> /etc/profile && \ + . /etc/profile; diff --git a/php7-debian.go b/php7-debian.go deleted file mode 100644 index 1e5e508..0000000 --- a/php7-debian.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. -// -// Build tags specific to Debian (and Debian-derived, such as Ubuntu) -// distributions. Debian builds its PHP7 packages with non-standard naming -// conventions for include and library paths, so we need a specific build tag -// for building against those packages. -// -// +build debian,!php5 - -package php - -// #cgo CFLAGS: -I/usr/include/php/20151012 -Iinclude/php7 -Isrc/php7 -// #cgo CFLAGS: -I/usr/include/php/20151012/main -I/usr/include/php/20151012/Zend -// #cgo CFLAGS: -I/usr/include/php/20151012/TSRM -// #cgo LDFLAGS: -lphp7.0 -import "C" diff --git a/php7.go b/php7.go deleted file mode 100644 index fa60d15..0000000 --- a/php7.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. -// -// +build !php5 - -package php - -// #cgo CFLAGS: -Iinclude/php7 -Isrc/php7 -// #cgo LDFLAGS: -lphp7 -import "C" diff --git a/php7-static.go b/php8-static.go similarity index 80% rename from php7-static.go rename to php8-static.go index d454343..a23c4de 100644 --- a/php7-static.go +++ b/php8-static.go @@ -2,9 +2,10 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. // -// +build static +//go:build static && php8 +// +build static,php8 -package php +package gophp // #cgo LDFLAGS: -ldl -lm -lcurl -lpcre -lssl -lcrypto -lresolv -ledit -lz -lxml2 import "C" diff --git a/php8.go b/php8.go index 9ff7aee..f28924d 100644 --- a/php8.go +++ b/php8.go @@ -2,11 +2,13 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. // -//go:build !php5 && !php7 -// +build !php5,!php7 +//go:build php8 +// +build php8 -package php +package gophp -// #cgo CFLAGS: -Iinclude/php8 -Isrc/php8 +// #cgo CFLAGS: -I/usr/include/php/phpsrc -Isrc/ -Iinclude/ +// #cgo CFLAGS: -I/usr/include/php/phpsrc/main -I/usr/include/php/phpsrc/Zend +// #cgo CFLAGS: -I/usr/include/php/phpsrc/TSRM // #cgo LDFLAGS: -lphp8 import "C" diff --git a/receiver.c b/receiver.c index df6be2d..2a858bc 100644 --- a/receiver.c +++ b/receiver.c @@ -1,179 +1,2 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. -#include -#include - -#include
-#include -#include - -#include "value.h" -#include "receiver.h" -#include "_cgo_export.h" - -// Fetch and return field for method receiver. -static engine_value *receiver_get(zval *object, zval *member) { - engine_receiver *this = _receiver_this(object); - return engineReceiverGet(this, Z_STRVAL_P(member)); -} - -// Set field for method receiver. -static void receiver_set(zval *object, zval *member, zval *value) { - engine_receiver *this = _receiver_this(object); - engineReceiverSet(this, Z_STRVAL_P(member), (void *) value); -} - -// Check if field exists for method receiver. -static int receiver_exists(zval *object, zval *member, int check) { - engine_receiver *this = _receiver_this(object); - - if (!engineReceiverExists(this, Z_STRVAL_P(member))) { - // Value does not exist. - return 0; - } else if (check == 2) { - // Value exists. - return 1; - } - - int result = 0; - engine_value *val = engineReceiverGet(this, Z_STRVAL_P(member)); - - if (check == 1) { - // Value exists and is "truthy". - convert_to_boolean(val->internal); - result = _value_truth(val->internal); - } else if (check == 0) { - // Value exists and is not null. - result = (val->kind != KIND_NULL) ? 1 : 0; - } else { - // Check value is invalid. - result = 0; - } - - _value_destroy(val); - return result; -} - -// Call function with arguments passed and return value (if any). -static int receiver_method_call(char *name, INTERNAL_FUNCTION_PARAMETERS) { - zval args; - engine_receiver *this = _receiver_this(getThis()); - - array_init_size(&args, ZEND_NUM_ARGS()); - - if (zend_copy_parameters_array(ZEND_NUM_ARGS(), &args) == FAILURE) { - RETVAL_NULL(); - } else { - engine_value *result = engineReceiverCall(this, name, (void *) &args); - if (result == NULL) { - RETVAL_NULL(); - } else { - value_copy(return_value, result->internal); - _value_destroy(result); - } - } - - zval_dtor(&args); -} - -// Create new method receiver instance and attach to instantiated PHP object. -// Returns an exception if method receiver failed to initialize for any reason. -static void receiver_new(INTERNAL_FUNCTION_PARAMETERS) { - zval args; - engine_receiver *this = _receiver_this(getThis()); - - array_init_size(&args, ZEND_NUM_ARGS()); - - if (zend_copy_parameters_array(ZEND_NUM_ARGS(), &args) == FAILURE) { - zend_throw_exception(NULL, "Could not parse parameters for method receiver", 0); - } else { - // Create receiver instance. Throws an exception if creation fails. - int result = engineReceiverNew(this, (void *) &args); - if (result != 0) { - zend_throw_exception(NULL, "Failed to instantiate method receiver", 0); - } - } - - zval_dtor(&args); -} - -// Fetch and return function definition for method receiver. The method call -// happens in the method handler, as returned by this function. -static zend_internal_function *receiver_method_get(zend_object *object) { - zend_internal_function *func = emalloc(sizeof(zend_internal_function)); - - func->type = ZEND_OVERLOADED_FUNCTION; - func->handler = NULL; - func->arg_info = NULL; - func->num_args = 0; - func->scope = object->ce; - func->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; - - return func; -} - -// Fetch and return constructor function definition for method receiver. The -// construct call happens in the constructor handler, as returned by this -// function. -static zend_internal_function *receiver_constructor_get(zend_object *object) { - static zend_internal_function func; - - func.type = ZEND_INTERNAL_FUNCTION; - func.handler = receiver_new; - func.arg_info = NULL; - func.num_args = 0; - func.scope = object->ce; - func.fn_flags = 0; - func.function_name = object->ce->name; - - return &func; -} - -// Table of handler functions for method receivers. -static zend_object_handlers receiver_handlers = { - ZEND_OBJECTS_STORE_HANDLERS, - - _receiver_get, // read_property - _receiver_set, // write_property - NULL, // read_dimension - NULL, // write_dimension - - NULL, // get_property_ptr_ptr - NULL, // get - NULL, // set - - _receiver_exists, // has_property - NULL, // unset_property - NULL, // has_dimension - NULL, // unset_dimension - - NULL, // get_properties - - _receiver_method_get, // get_method - _receiver_method_call, // call_method - - _receiver_constructor_get // get_constructor -}; - -// Define class with unique name. -void receiver_define(char *name) { - zend_class_entry tmp; - INIT_CLASS_ENTRY_EX(tmp, name, strlen(name), NULL); - - zend_class_entry *this = zend_register_internal_class(&tmp); - - this->create_object = _receiver_init; - this->ce_flags |= ZEND_ACC_FINAL; - - // Set standard handlers for receiver. - _receiver_handlers_set(&receiver_handlers); -} - -void receiver_destroy(char *name) { - name = php_strtolower(name, strlen(name)); - _receiver_destroy(name); -} - -#include "_receiver.c" +#include "src/receiver.c" diff --git a/receiver.go b/receiver.go index d20495c..b80b756 100644 --- a/receiver.go +++ b/receiver.go @@ -2,11 +2,8 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. -package php +package gophp -// #cgo CFLAGS: -I/usr/include/php8 -I/usr/include/php8/main -I/usr/include/php8/TSRM -// #cgo CFLAGS: -I/usr/include/php8/Zend -Iinclude -// // #include // #include
// #include "receiver.h" @@ -22,7 +19,7 @@ import ( type Receiver struct { name string create func(args []interface{}) interface{} - objects map[*C.struct__engine_receiver]*ReceiverObject + objects map[*C.struct__zend_object]*ReceiverObject } // NewObject instantiates a new method receiver object, using the Receiver's @@ -35,7 +32,7 @@ func (r *Receiver) NewObject(args []interface{}) (*ReceiverObject, error) { } if obj.instance == nil { - return nil, fmt.Errorf("Failed to instantiate method receiver") + return nil, fmt.Errorf("failed to instantiate method receiver") } v := reflect.ValueOf(obj.instance) @@ -90,7 +87,7 @@ type ReceiverObject struct { // error if the property does not exist or is not addressable. func (o *ReceiverObject) Get(name string) (*Value, error) { if _, exists := o.values[name]; !exists || !o.values[name].CanInterface() { - return nil, fmt.Errorf("Value '%s' does not exist or is not addressable", name) + return nil, fmt.Errorf("value '%s' does not exist or is not addressable", name) } val, err := NewValue(o.values[name].Interface()) diff --git a/receiver_test.go b/receiver_test.go index 8c662b0..5dda5a8 100644 --- a/receiver_test.go +++ b/receiver_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. -package php +package gophp import ( "bytes" diff --git a/src/context.c b/src/context.c new file mode 100644 index 0000000..1ee633a --- /dev/null +++ b/src/context.c @@ -0,0 +1,119 @@ +// Copyright 2017 Alexander Palaistras. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +#include +#include +#include // correct header +#include
+#include
+ +#include "value.h" +#include "context.h" + +engine_context *context_new() { + engine_context *context; + + // Initialize context. + context = malloc((sizeof(engine_context))); + if (context == NULL) { + errno = 1; + return NULL; + } + + SG(server_context) = context; + + // Initialize request lifecycle. + if (php_request_startup() == FAILURE) { + zend_throw_error(NULL, "Something wrong", 0); + SG(server_context) = NULL; + free(context); + + errno = 1; + return NULL; + } + + errno = 0; + return context; +} + +void context_exec(engine_context *context, char *filename) { + int ret; + + // Attempt to execute script file. + zend_first_try { + zend_file_handle script; + zend_stream_init_filename(&script, filename); + ret = php_execute_script(&script); + if (ret == FAILURE) { + zend_throw_error(NULL, "Failed to execute PHP script", 0); + } + zend_destroy_file_handle(&script); + } zend_catch { + errno = 1; + zend_throw_error(NULL, "Failed to execute PHP script", 0); + return; + } zend_end_try(); + + if (ret == FAILURE) { + errno = 1; + return; + } + + errno = 0; + return; +} + +void *context_eval(engine_context *context, char *script) { + zend_string *str = zend_string_init(script, strlen(script), 0); + + // Compile script value. + uint32_t compiler_options = CG(compiler_options); + + CG(compiler_options) = ZEND_COMPILE_DEFAULT_FOR_EVAL; + zend_op_array *op = zend_compile_string(str, "gophp-engine"); + CG(compiler_options) = compiler_options; + + zend_string_release(str); + // Return error if script failed to compile. + if (!op) { + zend_throw_error(NULL, "Failed to execute PHP script", 0); + errno = 1; + return NULL; + } + // Attempt to execute compiled string. _context_eval(op, &tmp); _context_eval(zend_op_array *op, zval *ret) + EG(no_extensions) = 1; + zval tmp; + zend_try { + ZVAL_NULL(&tmp); + zend_execute(op, &tmp); + } zend_catch { + destroy_op_array(op); + efree_size(op, sizeof(zend_op_array)); + zend_bailout(); + } zend_end_try(); + + destroy_op_array(op); + efree_size(op, sizeof(zend_op_array)); + + EG(no_extensions) = 0; + + // Allocate result value and copy temporary execution result in. + zval *result = malloc(sizeof(zval)); + ZVAL_COPY_VALUE(result, &tmp); + zval_copy_ctor(&tmp); + errno = 0; + return result; +} + +void context_bind(engine_context *context, char *name, void *value) { + engine_value *v = (engine_value *) value; + zend_hash_str_update(&EG(symbol_table), name, strlen(name), v->internal); +} + +void context_destroy(engine_context *context) { + php_request_shutdown(NULL); + + SG(server_context) = NULL; + free(context); +} diff --git a/src/engine.c b/src/engine.c new file mode 100644 index 0000000..ff230a9 --- /dev/null +++ b/src/engine.c @@ -0,0 +1,134 @@ +// Copyright 2017 Alexander Palaistras. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +#include +#include + +#include
+#include
+#include
+#include
+#include + +#include "receiver.h" + +#include "context.h" +#include "engine.h" +#include "_cgo_export.h" + + +// The php.ini defaults for the Go-PHP engine. +const char engine_ini_defaults[] = { + "expose_php = 0\n" + "default_mimetype =\n" + "html_errors = 0\n" + "log_errors = 1\n" + "display_errors = 1\n" + "error_reporting = E_ALL\n" + "register_argc_argv = 1\n" + "implicit_flush = 1\n" + "output_buffering = 0\n" + "max_execution_time = 0\n" + "max_input_time = -1\n\0" +}; + +static size_t engine_ub_write(const char *str, size_t len){ + engine_context *context = SG(server_context); + int written = engineWriteOut(context, (void *) str, len); + if (written != len) { + php_handle_aborted_connection(); + } + return len; +} + +static int engine_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers) { + engine_context *context = SG(server_context); + switch (op) { + case SAPI_HEADER_REPLACE: + case SAPI_HEADER_ADD: + case SAPI_HEADER_DELETE: + case SAPI_HEADER_DELETE_ALL: + case SAPI_HEADER_SET_STATUS: + engineSetHeader(context, op, (void *) sapi_header->header, sapi_header->header_len); + break; + } + return 0; +} + +static void engine_send_header(sapi_header_struct *sapi_header, void *server_context) { + // Do nothing. +} + +static char *engine_read_cookies() { + return NULL; +} + +static void engine_register_variables(zval *track_vars_array) { + php_import_environment_variables(track_vars_array); +} + +static void engine_log_message(const char *str, int syslog_type_int) { + engine_context *context = SG(server_context); + engineWriteLog(context, (void *) str, strlen(str)); +} + +static sapi_module_struct engine_module = { + "gophp-engine", // Name + "Go PHP Engine Library", // Pretty Name + NULL, // Startup + php_module_shutdown_wrapper, // Shutdown + NULL, // Activate + NULL, // Deactivate + engine_ub_write, // Unbuffered Write + NULL, // Flush + NULL, // Get UID + NULL, // Getenv + php_error, // Error Handler + engine_header_handler, // Header Handler + NULL, // Send Headers Handler + engine_send_header, // Send Header Handler + NULL, // Read POST Data + engine_read_cookies, // Read Cookies + + engine_register_variables, // Register Server Variables + engine_log_message, // Log Message + NULL, // Get Request Time + NULL, // Child Terminate + + STANDARD_SAPI_MODULE_PROPERTIES +}; + + +php_engine *engine_init(void) { + php_engine *engine; + #ifdef HAVE_SIGNAL_H + #if defined(SIGPIPE) && defined(SIG_IGN) + signal(SIGPIPE, SIG_IGN); + #endif + #endif + + sapi_startup(&engine_module); + + engine_module.ini_entries = malloc(sizeof(engine_ini_defaults)); + memcpy(engine_module.ini_entries, engine_ini_defaults, sizeof(engine_ini_defaults)); + + if (php_module_startup(&engine_module, NULL, 0) == FAILURE) { + sapi_shutdown(); + + errno = 1; + return NULL; + } + + engine = malloc(sizeof(php_engine)); + errno = 0; + return engine; +} + +void engine_shutdown(php_engine *engine) { + php_module_shutdown(); + sapi_shutdown(); + + free(engine_module.ini_entries); + free(engine); +} diff --git a/src/php7/_context.c b/src/php7/_context.c deleted file mode 100644 index dc75275..0000000 --- a/src/php7/_context.c +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -static void _context_bind(char *name, zval *value) { - zend_hash_str_update(&EG(symbol_table), name, strlen(name), value); -} - -static void _context_eval(zend_op_array *op, zval *ret) { - EG(no_extensions) = 1; - - zend_try { - ZVAL_NULL(ret); - zend_execute(op, ret); - } zend_catch { - destroy_op_array(op); - efree_size(op, sizeof(zend_op_array)); - zend_bailout(); - } zend_end_try(); - - destroy_op_array(op); - efree_size(op, sizeof(zend_op_array)); - - EG(no_extensions) = 0; -} diff --git a/src/php7/_engine.c b/src/php7/_engine.c deleted file mode 100644 index fed646b..0000000 --- a/src/php7/_engine.c +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -static size_t _engine_ub_write(const char *str, size_t len) { - return engine_ub_write(str, len); -} diff --git a/src/php7/_receiver.c b/src/php7/_receiver.c deleted file mode 100644 index 2cb28c1..0000000 --- a/src/php7/_receiver.c +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -static zval *_receiver_get(zval *object, zval *member, int type, void **cache_slot, zval *retval) { - engine_value *result = receiver_get(object, member); - if (result == NULL) { - ZVAL_NULL(retval); - return retval; - } - - value_copy(retval, result->internal); - _value_destroy(result); - - return retval; -} - -static void _receiver_set(zval *object, zval *member, zval *value, void **cache_slot) { - receiver_set(object, member, value); -} - -static int _receiver_exists(zval *object, zval *member, int check, void **cache_slot) { - return receiver_exists(object, member, check); -} - -static int _receiver_method_call(zend_string *method, zend_object *object, INTERNAL_FUNCTION_PARAMETERS) { - return receiver_method_call(method->val, INTERNAL_FUNCTION_PARAM_PASSTHRU); -} - -static zend_function *_receiver_method_get(zend_object **object, zend_string *name, const zval *key) { - zend_internal_function *func = receiver_method_get(*object); - - func->function_name = zend_string_copy(name); - zend_set_function_arg_flags((zend_function *) func); - - return (zend_function *) func; -} - -static zend_function *_receiver_constructor_get(zend_object *object) { - zend_internal_function *func = receiver_constructor_get(object); - zend_set_function_arg_flags((zend_function *) func); - - return (zend_function *) func; -} - -// Free storage for allocated method receiver instance. -static void _receiver_free(zend_object *object) { - engine_receiver *this = (engine_receiver *) object; - zend_object_std_dtor(&(this->obj)); -} - -// Initialize instance of method receiver object. The method receiver itself is -// attached in the constructor function call. -static zend_object *_receiver_init(zend_class_entry *class_type) { - engine_receiver *this = emalloc(sizeof(engine_receiver)); - memset(this, 0, sizeof(engine_receiver)); - - zend_object_std_init(&(this->obj), class_type); - object_properties_init(&(this->obj), class_type); - this->obj.handlers = &receiver_handlers; - - return &(this->obj); -} - -static void _receiver_destroy(char *name) { - zval *class = zend_hash_str_find(CG(class_table), name, strlen(name)); - - if (class != NULL) { - destroy_zend_class(class); - zend_hash_str_del(CG(class_table), name, strlen(name)); - } -} - -static engine_receiver *_receiver_this(zval *object) { - return (engine_receiver *) Z_OBJ_P(object); -} - -static void _receiver_handlers_set(zend_object_handlers *handlers) { - zend_object_handlers *std = zend_get_std_object_handlers(); - - handlers->get_class_name = std->get_class_name; - handlers->free_obj = _receiver_free; -} - -// Return class name for method receiver. -char *_receiver_get_name(engine_receiver *rcvr) { - return rcvr->obj.ce->name->val; -} diff --git a/src/php7/_value.c b/src/php7/_value.c deleted file mode 100644 index e9b996c..0000000 --- a/src/php7/_value.c +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -zval *_value_init() { - zval *tmp = malloc(sizeof(zval)); - ZVAL_NULL(tmp); - - return tmp; -} - -// Destroy and free engine value. -void _value_destroy(engine_value *val) { - zval_dtor(val->internal); - free(val->internal); - free(val); -} - -int _value_truth(zval *val) { - return (Z_TYPE_P(val) == IS_TRUE) ? 1 : ((Z_TYPE_P(val) == IS_FALSE) ? 0 : -1); -} - -void _value_set_string(zval **val, char *str) { - ZVAL_STRING(*val, str); -} - -static int _value_current_key_get(HashTable *ht, zend_string **str_index, zend_ulong *num_index) { - return zend_hash_get_current_key(ht, str_index, num_index); -} - -static void _value_current_key_set(HashTable *ht, engine_value *val) { - zval tmp; - - zend_hash_get_current_key_zval(ht, &tmp); - add_next_index_zval(val->internal, &tmp); -} - -static void _value_array_next_get(HashTable *ht, engine_value *val) { - zval *tmp = NULL; - - if ((tmp = zend_hash_get_current_data(ht)) != NULL) { - value_set_zval(val, tmp); - zend_hash_move_forward(ht); - } -} - -static void _value_array_index_get(HashTable *ht, unsigned long index, engine_value *val) { - zval *tmp = NULL; - - if ((tmp = zend_hash_index_find(ht, index)) != NULL) { - value_set_zval(val, tmp); - } -} - -static void _value_array_key_get(HashTable *ht, char *key, engine_value *val) { - zval *tmp = NULL; - zend_string *str = zend_string_init(key, strlen(key), 0); - - if ((tmp = zend_hash_find(ht, str)) != NULL) { - value_set_zval(val, tmp); - } - - zend_string_release(str); -} diff --git a/src/php8/_context.c b/src/php8/_context.c deleted file mode 100644 index dc75275..0000000 --- a/src/php8/_context.c +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -static void _context_bind(char *name, zval *value) { - zend_hash_str_update(&EG(symbol_table), name, strlen(name), value); -} - -static void _context_eval(zend_op_array *op, zval *ret) { - EG(no_extensions) = 1; - - zend_try { - ZVAL_NULL(ret); - zend_execute(op, ret); - } zend_catch { - destroy_op_array(op); - efree_size(op, sizeof(zend_op_array)); - zend_bailout(); - } zend_end_try(); - - destroy_op_array(op); - efree_size(op, sizeof(zend_op_array)); - - EG(no_extensions) = 0; -} diff --git a/src/php8/_engine.c b/src/php8/_engine.c deleted file mode 100644 index fed646b..0000000 --- a/src/php8/_engine.c +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -static size_t _engine_ub_write(const char *str, size_t len) { - return engine_ub_write(str, len); -} diff --git a/src/php8/_receiver.c b/src/php8/_receiver.c deleted file mode 100644 index 2cb28c1..0000000 --- a/src/php8/_receiver.c +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -static zval *_receiver_get(zval *object, zval *member, int type, void **cache_slot, zval *retval) { - engine_value *result = receiver_get(object, member); - if (result == NULL) { - ZVAL_NULL(retval); - return retval; - } - - value_copy(retval, result->internal); - _value_destroy(result); - - return retval; -} - -static void _receiver_set(zval *object, zval *member, zval *value, void **cache_slot) { - receiver_set(object, member, value); -} - -static int _receiver_exists(zval *object, zval *member, int check, void **cache_slot) { - return receiver_exists(object, member, check); -} - -static int _receiver_method_call(zend_string *method, zend_object *object, INTERNAL_FUNCTION_PARAMETERS) { - return receiver_method_call(method->val, INTERNAL_FUNCTION_PARAM_PASSTHRU); -} - -static zend_function *_receiver_method_get(zend_object **object, zend_string *name, const zval *key) { - zend_internal_function *func = receiver_method_get(*object); - - func->function_name = zend_string_copy(name); - zend_set_function_arg_flags((zend_function *) func); - - return (zend_function *) func; -} - -static zend_function *_receiver_constructor_get(zend_object *object) { - zend_internal_function *func = receiver_constructor_get(object); - zend_set_function_arg_flags((zend_function *) func); - - return (zend_function *) func; -} - -// Free storage for allocated method receiver instance. -static void _receiver_free(zend_object *object) { - engine_receiver *this = (engine_receiver *) object; - zend_object_std_dtor(&(this->obj)); -} - -// Initialize instance of method receiver object. The method receiver itself is -// attached in the constructor function call. -static zend_object *_receiver_init(zend_class_entry *class_type) { - engine_receiver *this = emalloc(sizeof(engine_receiver)); - memset(this, 0, sizeof(engine_receiver)); - - zend_object_std_init(&(this->obj), class_type); - object_properties_init(&(this->obj), class_type); - this->obj.handlers = &receiver_handlers; - - return &(this->obj); -} - -static void _receiver_destroy(char *name) { - zval *class = zend_hash_str_find(CG(class_table), name, strlen(name)); - - if (class != NULL) { - destroy_zend_class(class); - zend_hash_str_del(CG(class_table), name, strlen(name)); - } -} - -static engine_receiver *_receiver_this(zval *object) { - return (engine_receiver *) Z_OBJ_P(object); -} - -static void _receiver_handlers_set(zend_object_handlers *handlers) { - zend_object_handlers *std = zend_get_std_object_handlers(); - - handlers->get_class_name = std->get_class_name; - handlers->free_obj = _receiver_free; -} - -// Return class name for method receiver. -char *_receiver_get_name(engine_receiver *rcvr) { - return rcvr->obj.ce->name->val; -} diff --git a/src/php8/_value.c b/src/php8/_value.c deleted file mode 100644 index e9b996c..0000000 --- a/src/php8/_value.c +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2017 Alexander Palaistras. All rights reserved. -// Use of this source code is governed by the MIT license that can be found in -// the LICENSE file. - -zval *_value_init() { - zval *tmp = malloc(sizeof(zval)); - ZVAL_NULL(tmp); - - return tmp; -} - -// Destroy and free engine value. -void _value_destroy(engine_value *val) { - zval_dtor(val->internal); - free(val->internal); - free(val); -} - -int _value_truth(zval *val) { - return (Z_TYPE_P(val) == IS_TRUE) ? 1 : ((Z_TYPE_P(val) == IS_FALSE) ? 0 : -1); -} - -void _value_set_string(zval **val, char *str) { - ZVAL_STRING(*val, str); -} - -static int _value_current_key_get(HashTable *ht, zend_string **str_index, zend_ulong *num_index) { - return zend_hash_get_current_key(ht, str_index, num_index); -} - -static void _value_current_key_set(HashTable *ht, engine_value *val) { - zval tmp; - - zend_hash_get_current_key_zval(ht, &tmp); - add_next_index_zval(val->internal, &tmp); -} - -static void _value_array_next_get(HashTable *ht, engine_value *val) { - zval *tmp = NULL; - - if ((tmp = zend_hash_get_current_data(ht)) != NULL) { - value_set_zval(val, tmp); - zend_hash_move_forward(ht); - } -} - -static void _value_array_index_get(HashTable *ht, unsigned long index, engine_value *val) { - zval *tmp = NULL; - - if ((tmp = zend_hash_index_find(ht, index)) != NULL) { - value_set_zval(val, tmp); - } -} - -static void _value_array_key_get(HashTable *ht, char *key, engine_value *val) { - zval *tmp = NULL; - zend_string *str = zend_string_init(key, strlen(key), 0); - - if ((tmp = zend_hash_find(ht, str)) != NULL) { - value_set_zval(val, tmp); - } - - zend_string_release(str); -} diff --git a/src/receiver.c b/src/receiver.c new file mode 100644 index 0000000..cea263d --- /dev/null +++ b/src/receiver.c @@ -0,0 +1,242 @@ +// Copyright 2017 Alexander Palaistras. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. +// Copyright 2017 Alexander Palaistras. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +#include +#include + +#include
+#include +#include + +#include "value.h" +#include "receiver.h" +#include "_cgo_export.h" + +/* +static engine_receiver *receiver_this(zend_object *object) { + return (engine_receiver *) object; +} +*/ + +// Call function with arguments passed and return value (if any). +static int receiver_method_call(char *name, INTERNAL_FUNCTION_PARAMETERS) { + zval args; + array_init_size(&args, ZEND_NUM_ARGS()); + if (zend_copy_parameters_array(ZEND_NUM_ARGS(), &args) == FAILURE) { + RETVAL_NULL(); + } else { + engine_value *result = engineReceiverCall(Z_OBJ_P(getThis()), name, (void *) &args); + if (result == NULL) { + RETVAL_NULL(); + } else { + value_copy(return_value, result->internal); + value_destroy(result); + } + } + + zval_dtor(&args); +} + + +// Create new method receiver instance and attach to instantiated PHP object. +// Returns an exception if method receiver failed to initialize for any reason. +static void receiver_new(INTERNAL_FUNCTION_PARAMETERS) { + zval args; + + array_init_size(&args, ZEND_NUM_ARGS()); + + if (zend_copy_parameters_array(ZEND_NUM_ARGS(), &args) == FAILURE) { + zend_throw_error(NULL, "Could not parse parameters for method receiver", 0); + } else { + // Create receiver instance. Throws an exception if creation fails. + int result = engineReceiverNew(Z_OBJ_P(getThis()), (void *) &args); + if (result != 0) { + zend_throw_error(NULL, "Failed to instantiate method receiver", 0); + } + } + + zval_dtor(&args); +} + + + + +static const zend_function_entry receiver_define_methods[] = { + ZEND_FE_END +}; + + + +void receiver_destroy(char *name) { + name = php_strtolower(name, strlen(name)); + zval *zclass = zend_hash_str_find(CG(class_table), name, strlen(name)); + if (zclass != NULL) { + destroy_zend_class(zclass); + zend_hash_str_del(CG(class_table), name, strlen(name)); + } +} + +static zval *receiver_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv) { + engine_value *result = engineReceiverGet(object, ZSTR_VAL(member));; + if (result == NULL) { + ZVAL_NULL(rv); + return rv; + } + + ZVAL_COPY(rv, result->internal); + value_destroy(result); + return rv; +} + +static zval * receiver_write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot) { + engineReceiverSet(object, ZSTR_VAL(member), (void *) value); + return value; +} + +static int receiver_has_property(zend_object *object, zend_string *member, int has_set_exists, void **cache_slot) { + if (!engineReceiverExists(object, ZSTR_VAL(member))) { + // Value does not exist. + return 0; + } else if (has_set_exists == 2) { + // Value exists. + return 1; + } + + int result = 0; + engine_value *val = engineReceiverGet(object, ZSTR_VAL(member)); + if (has_set_exists == 1) { + // Value exists and is "truthy". + convert_to_boolean(val->internal); + result = value_truth(val->internal); + } else if (has_set_exists == 0) { + // Value exists and is not null. + result = (val->kind != KIND_NULL) ? 1 : 0; + } else { + // Check value is invalid. + result = 0; + } + + value_destroy(val); + return result; +} + +static int _receiver_method_call(zend_string *method, zend_object *object, INTERNAL_FUNCTION_PARAMETERS) { +return receiver_method_call(method->val, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +// Fetch and return function definition for method receiver. The method call +// happens in the method handler, as returned by this function. + +static zend_function *receiver_get_method(zend_object **object, zend_string *name, const zval *key) { + zend_internal_function *func; + func = emalloc(sizeof(zend_internal_function)); + memset(func, 0, sizeof(zend_internal_function)); + func->type = ZEND_INTERNAL_FUNCTION; + func->handler = receiver_new; + func->arg_info = NULL; + func->num_args = 0; + func->scope = (*object)->ce; + func->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; + + func->function_name = zend_string_copy(name); + return (zend_function *) func; +} +// Fetch and return constructor function definition for method receiver. The +// construct call happens in the constructor handler, as returned by this +// function. + +static zend_function *receiver_get_constructor(zend_object *object, zend_string *name) { + zend_internal_function *func; + if (EXPECTED(EG(trampoline).common.function_name == NULL)) { + func = (zend_internal_function *) &EG(trampoline); + } else { + func = emalloc(sizeof(zend_internal_function)); + } + memset(func, 0, sizeof(zend_internal_function)); + func->type = ZEND_INTERNAL_FUNCTION; + func->handler = receiver_new; + func->arg_info = NULL; + func->num_args = 0; + func->scope = object->ce; + func->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; + func->function_name = zend_string_copy(name); + + return (zend_function *) func; +} + +// Free storage for allocated method receiver instance. +static void receiver_free_obj(zend_object *object) { +zend_object_std_dtor(object); +} + + + +// Return class name for method receiver. +char *_receiver_get_name(zend_object *rcvr) { + return rcvr->ce->name->val; +} + +static zval *receiver_read_dimension(zend_object * object, zval *value, int val, zval * dimension){ + +} +static void receiver_write_dimension(){ + +} +static zval *receiver_get_property_ptr_ptr(zend_object *object, zend_string *prop, int type, void **cache_slot){ + +} + +static int receiver_has_dimension(zend_object *object, zval *value, int type){ + +} +static void receiver_unset_property(){ + +} +static HashTable *receiver_get_properties(zend_object *object){ + +} +static zend_string *receiver_get_class_name(const zend_object *object){ + +} +static int receiver_cast_object(zend_object *object, zval *val, int type){ + +} +static void receiver_destroy_object(){ + +} +static void receiver_unset_dimension(){ + +} +static HashTable *receiver_get_gc(zend_object *object, zval **val, int *type){ + +} +static int receiver_compare(zval *value, zval *val){ + +} + +static zend_object_handlers receiver_handlers; +// Initialize instance of method receiver object. The method receiver itself is +// attached in the constructor function call. +static zend_object * receiver_init(zend_class_entry *class_type) { + zend_object *obj = zend_objects_new(class_type); + object_properties_init(obj, class_type); + obj->handlers = &receiver_handlers; + return obj; +} +void receiver_define(char *name) { + memcpy(&receiver_handlers, zend_get_std_object_handlers(), + sizeof(zend_object_handlers)); + + zend_class_entry ce; + + INIT_CLASS_ENTRY_EX(ce, *name, strlen(*name) - 1, NULL); + + zend_class_entry *class_t = zend_register_internal_class(&ce); + + class_t->create_object = receiver_init; + + class_t->ce_flags |= ZEND_ACC_FINAL; +} diff --git a/src/value.c b/src/value.c new file mode 100644 index 0000000..a57e8c3 --- /dev/null +++ b/src/value.c @@ -0,0 +1,396 @@ +// Copyright 2017 Alexander Palaistras. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. +#include +#include +#include
+#include + +#include "value.h" + +// Creates a new value and initializes type to null. +engine_value *value_new() { + engine_value *val = malloc(sizeof(engine_value)); + val->internal = malloc(sizeof(zval)); + ZVAL_NULL(val->internal); + val->kind = KIND_NULL; + errno = 0; + return val; +} + +// Creates a complete copy of a zval. +// The destination zval needs to be correctly initialized before use. +void value_copy(zval *dst, zval *src) { + ZVAL_COPY(dst, src); + zval_copy_ctor(dst); +} + +// Returns engine value type. Usually compared against KIND_* constants, defined +// in the `value.h` header file. +int value_kind(engine_value *val) { + return val->kind; +} + +// Set type and value to null. +void value_set_null(engine_value *val) { + ZVAL_NULL(val->internal); + val->kind = KIND_NULL; +} + +// Set type and value to integer. +void value_set_long(engine_value *val, long int num) { + ZVAL_LONG(val->internal, num); + val->kind = KIND_LONG; +} + +// Set type and value to floating point. +void value_set_double(engine_value *val, double num) { + ZVAL_DOUBLE(val->internal, num); + val->kind = KIND_DOUBLE; +} + +// Set type and value to boolean. +void value_set_bool(engine_value *val, bool status) { + ZVAL_BOOL(val->internal, status); + val->kind = KIND_BOOL; +} + +// Set type and value to string. +void value_set_string(engine_value *val, char *str) { + ZVAL_STRING(val->internal, str); + val->kind = KIND_STRING; +} + +// Set type and value to array with a preset initial size. +void value_set_array(engine_value *val, unsigned int size) { + array_init_size(val->internal, size); + val->kind = KIND_ARRAY; +} + +// Set type and value to object. +void value_set_object(engine_value *val) { + object_init(val->internal); + val->kind = KIND_OBJECT; +} + +// Set type and value from zval. The source zval is copied and is otherwise not +// affected. +void value_set_zval(engine_value *val, zval *src) { + int kind; + // Determine concrete type from source zval. + switch (Z_TYPE_P(src)) { + case IS_NULL: + kind = KIND_NULL; + break; + case IS_LONG: + kind = KIND_LONG; + break; + case IS_DOUBLE: + kind = KIND_DOUBLE; + break; + case IS_STRING: + kind = KIND_STRING; + break; + case IS_OBJECT: + kind = KIND_OBJECT; + break; + case IS_ARRAY: + kind = KIND_ARRAY; + HashTable *h = (Z_ARRVAL_P(src)); + + // Determine if array is associative or indexed. In the simplest case, a + // associative array will have different values for the number of elements + // and the index of the next free element. In cases where the number of + // elements and the next free index is equal, we must iterate through + // the hash table and check the keys themselves. + if (h->nNumOfElements != h->nNextFreeElement) { + kind = KIND_MAP; + break; + } + + unsigned long i = 0; + + for (zend_hash_internal_pointer_reset(h); i < h->nNumOfElements; i++) { + unsigned long index; + int type = zend_hash_get_current_key(h, NULL, &index); + if (type == HASH_KEY_IS_STRING || index != i) { + kind = KIND_MAP; + break; + } + + zend_hash_move_forward(h); + } + + break; + default: + // Booleans need special handling for different PHP versions. + if (value_truth(src) != -1) { + kind = KIND_BOOL; + break; + } + + errno = -1; + return; + } + + ZVAL_COPY(val->internal, src); + zval_copy_ctor(src); + val->kind = kind; + errno = 0; +} + +// Set next index of array or map value. +void value_array_next_set(engine_value *arr, engine_value *val) { + add_next_index_zval(arr->internal, val->internal); +} + +void value_array_index_set(engine_value *arr, unsigned long idx, engine_value *val) { + add_index_zval(arr->internal, idx, val->internal); + arr->kind = KIND_MAP; +} + +void value_array_key_set(engine_value *arr, const char *key, engine_value *val) { + add_assoc_zval(arr->internal, key, val->internal); + arr->kind = KIND_MAP; +} + +void value_object_property_set(engine_value *obj, const char *key, engine_value *val) { + add_property_zval(obj->internal, key, val->internal); +} + +int value_get_long(engine_value *val) { + zval tmp; + + // Return value directly if already in correct type. + if (val->kind == KIND_LONG) { + return Z_LVAL_P(val->internal); + } + + value_copy(&tmp, val->internal); + convert_to_long(&tmp); + + return Z_LVAL(tmp); +} + +double value_get_double(engine_value *val) { + zval tmp; + + // Return value directly if already in correct type. + if (val->kind == KIND_DOUBLE) { + return Z_DVAL_P(val->internal); + } + + value_copy(&tmp, val->internal); + convert_to_double(&tmp); + + return Z_DVAL(tmp); +} + +bool value_get_bool(engine_value *val) { + zval tmp; + + // Return value directly if already in correct type. + if (val->kind == KIND_BOOL) { + return value_truth(val->internal); + } + + value_copy(&tmp, val->internal); + convert_to_boolean(&tmp); + + return value_truth(&tmp); +} + +char *value_get_string(engine_value *val) { + zval tmp; + int result; + switch (val->kind) { + case KIND_STRING: + value_copy(&tmp, val->internal); + break; + case KIND_OBJECT: + result = zend_std_cast_object_tostring(Z_OBJ_P(val->internal), &tmp, IS_STRING); + if (result == FAILURE) { + ZVAL_EMPTY_STRING(&tmp); + } + break; + default: + value_copy(&tmp, val->internal); + convert_to_string(&tmp); + } + + int len = Z_STRLEN(tmp) + 1; + char *str = malloc(len); + memcpy(str, Z_STRVAL(tmp), len); + + zval_dtor(&tmp); + + return str; +} + +unsigned int value_array_size(engine_value *arr) { + switch (arr->kind) { + case KIND_ARRAY: + case KIND_MAP: + return Z_ARRVAL_P(arr->internal)->nNumOfElements; + case KIND_OBJECT: + // Object size is determined by the number of properties, regardless of + // visibility. + return Z_OBJPROP_P(arr->internal)->nNumOfElements; + case KIND_NULL: + // Null values are considered empty. + return 0; + } + + // Non-array or object values are considered to be single-value arrays. + return 1; +} +static void value_current_key_set(HashTable *ht, engine_value *val) { + zval tmp; + zend_hash_get_current_key_zval(ht, &tmp); + add_next_index_zval(val->internal, &tmp); +} + +engine_value *value_array_keys(engine_value *arr) { + HashTable *h = NULL; + engine_value *keys = value_new(); + + value_set_array(keys, value_array_size(arr)); + + switch (arr->kind) { + case KIND_ARRAY: + case KIND_MAP: + case KIND_OBJECT: + if (arr->kind == KIND_OBJECT) { + h = Z_OBJPROP_P(arr->internal); + } else { + h = Z_ARRVAL_P(arr->internal); + } + + unsigned long i = 0; + + for (zend_hash_internal_pointer_reset(h); i < h->nNumOfElements; i++) { + value_current_key_set(h, keys); + zend_hash_move_forward(h); + } + + break; + case KIND_NULL: + // Null values are considered empty. + break; + default: + // Non-array or object values are considered to contain a single key, '0'. + add_next_index_long(keys->internal, 0); + } + + return keys; +} + +void value_array_reset(engine_value *arr) { + HashTable *h = NULL; + + switch (arr->kind) { + case KIND_ARRAY: + case KIND_MAP: + h = Z_ARRVAL_P(arr->internal); + break; + case KIND_OBJECT: + h = Z_OBJPROP_P(arr->internal); + break; + default: + return; + } + + zend_hash_internal_pointer_reset(h); +} + +engine_value *value_array_next_get(engine_value *arr) { + HashTable *ht = NULL; + engine_value *val = value_new(); + + switch (arr->kind) { + case KIND_ARRAY: + case KIND_MAP: + ht = Z_ARRVAL_P(arr->internal); + break; + case KIND_OBJECT: + ht = Z_OBJPROP_P(arr->internal); + break; + default: + // Attempting to return the next index of a non-array value will return + // the value itself, allowing for implicit conversions of scalar values + // to arrays. + value_set_zval(val, arr->internal); + return val; + } + + zval *tmp = NULL; + if ((tmp = zend_hash_get_current_data(ht)) != NULL) { + value_set_zval(val, tmp); + zend_hash_move_forward(ht); + } + return val; +} + +engine_value *value_array_index_get(engine_value *arr, unsigned long idx) { + HashTable *ht = NULL; + engine_value *val = value_new(); + switch (arr->kind) { + case KIND_ARRAY: + case KIND_MAP: + ht = Z_ARRVAL_P(arr->internal); + break; + case KIND_OBJECT: + ht = Z_OBJPROP_P(arr->internal); + break; + default: + // Attempting to return the first index of a non-array value will return + // the value itself, allowing for implicit conversions of scalar values + // to arrays. + if (idx == 0) { + value_set_zval(val, arr->internal); + return val; + } + + return val; + } + zval *tmp = NULL; + if ((tmp = zend_hash_index_find(ht, idx)) != NULL) { + value_set_zval(val, tmp); + } + return val; +} + +engine_value *value_array_key_get(engine_value *arr, char *key) { + HashTable *ht = NULL; + engine_value *val = value_new(); + + switch (arr->kind) { + case KIND_ARRAY: + case KIND_MAP: + ht = Z_ARRVAL_P(arr->internal); + break; + case KIND_OBJECT: + ht = Z_OBJPROP_P(arr->internal); + break; + default: + return val; + } + zval *tmp = NULL; + zend_string *str = zend_string_init(key, strlen(key), 0); + if ((tmp = zend_hash_find(ht, str)) != NULL) { + value_set_zval(val, tmp); + } + zend_string_release(str); + return val; +} +// Destroy and free engine value. +void value_destroy(engine_value *val) { + zval_dtor(val->internal); + free(val->internal); + free(val); +} + +int value_truth(zval *val) { + return (Z_TYPE_P(val) == IS_TRUE) ? 1 : ((Z_TYPE_P(val) == IS_FALSE) ? 0 : -1); +} + diff --git a/value.c b/value.c index 9b8e5e7..143b8db 100644 --- a/value.c +++ b/value.c @@ -2,381 +2,4 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. -#include -#include -#include
- -#include "value.h" - -// Creates a new value and initializes type to null. -engine_value *value_new() { - engine_value *val = malloc(sizeof(engine_value)); - if (val == NULL) { - errno = 1; - return NULL; - } - - val->internal = _value_init(); - val->kind = KIND_NULL; - - errno = 0; - return val; -} - -// Creates a complete copy of a zval. -// The destination zval needs to be correctly initialized before use. -void value_copy(zval *dst, zval *src) { - ZVAL_COPY_VALUE(dst, src); - zval_copy_ctor(dst); -} - -// Returns engine value type. Usually compared against KIND_* constants, defined -// in the `value.h` header file. -int value_kind(engine_value *val) { - return val->kind; -} - -// Set type and value to null. -void value_set_null(engine_value *val) { - ZVAL_NULL(val->internal); - val->kind = KIND_NULL; -} - -// Set type and value to integer. -void value_set_long(engine_value *val, long int num) { - ZVAL_LONG(val->internal, num); - val->kind = KIND_LONG; -} - -// Set type and value to floating point. -void value_set_double(engine_value *val, double num) { - ZVAL_DOUBLE(val->internal, num); - val->kind = KIND_DOUBLE; -} - -// Set type and value to boolean. -void value_set_bool(engine_value *val, bool status) { - ZVAL_BOOL(val->internal, status); - val->kind = KIND_BOOL; -} - -// Set type and value to string. -void value_set_string(engine_value *val, char *str) { - _value_set_string(&val->internal, str); - val->kind = KIND_STRING; -} - -// Set type and value to array with a preset initial size. -void value_set_array(engine_value *val, unsigned int size) { - array_init_size(val->internal, size); - val->kind = KIND_ARRAY; -} - -// Set type and value to object. -void value_set_object(engine_value *val) { - object_init(val->internal); - val->kind = KIND_OBJECT; -} - -// Set type and value from zval. The source zval is copied and is otherwise not -// affected. -void value_set_zval(engine_value *val, zval *src) { - int kind; - - // Determine concrete type from source zval. - switch (Z_TYPE_P(src)) { - case IS_NULL: - kind = KIND_NULL; - break; - case IS_LONG: - kind = KIND_LONG; - break; - case IS_DOUBLE: - kind = KIND_DOUBLE; - break; - case IS_STRING: - kind = KIND_STRING; - break; - case IS_OBJECT: - kind = KIND_OBJECT; - break; - case IS_ARRAY: - kind = KIND_ARRAY; - HashTable *h = (Z_ARRVAL_P(src)); - - // Determine if array is associative or indexed. In the simplest case, a - // associative array will have different values for the number of elements - // and the index of the next free element. In cases where the number of - // elements and the next free index is equal, we must iterate through - // the hash table and check the keys themselves. - if (h->nNumOfElements != h->nNextFreeElement) { - kind = KIND_MAP; - break; - } - - unsigned long i = 0; - - for (zend_hash_internal_pointer_reset(h); i < h->nNumOfElements; i++) { - unsigned long index; - int type = _value_current_key_get(h, NULL, &index); - - if (type == HASH_KEY_IS_STRING || index != i) { - kind = KIND_MAP; - break; - } - - zend_hash_move_forward(h); - } - - break; - default: - // Booleans need special handling for different PHP versions. - if (_value_truth(src) != -1) { - kind = KIND_BOOL; - break; - } - - errno = 1; - return; - } - - value_copy(val->internal, src); - val->kind = kind; - - errno = 0; -} - -// Set next index of array or map value. -void value_array_next_set(engine_value *arr, engine_value *val) { - add_next_index_zval(arr->internal, val->internal); -} - -void value_array_index_set(engine_value *arr, unsigned long idx, engine_value *val) { - add_index_zval(arr->internal, idx, val->internal); - arr->kind = KIND_MAP; -} - -void value_array_key_set(engine_value *arr, const char *key, engine_value *val) { - add_assoc_zval(arr->internal, key, val->internal); - arr->kind = KIND_MAP; -} - -void value_object_property_set(engine_value *obj, const char *key, engine_value *val) { - add_property_zval(obj->internal, key, val->internal); -} - -int value_get_long(engine_value *val) { - zval tmp; - - // Return value directly if already in correct type. - if (val->kind == KIND_LONG) { - return Z_LVAL_P(val->internal); - } - - value_copy(&tmp, val->internal); - convert_to_long(&tmp); - - return Z_LVAL(tmp); -} - -double value_get_double(engine_value *val) { - zval tmp; - - // Return value directly if already in correct type. - if (val->kind == KIND_DOUBLE) { - return Z_DVAL_P(val->internal); - } - - value_copy(&tmp, val->internal); - convert_to_double(&tmp); - - return Z_DVAL(tmp); -} - -bool value_get_bool(engine_value *val) { - zval tmp; - - // Return value directly if already in correct type. - if (val->kind == KIND_BOOL) { - return _value_truth(val->internal); - } - - value_copy(&tmp, val->internal); - convert_to_boolean(&tmp); - - return _value_truth(&tmp); -} - -char *value_get_string(engine_value *val) { - zval tmp; - int result; - - switch (val->kind) { - case KIND_STRING: - value_copy(&tmp, val->internal); - break; - case KIND_OBJECT: - result = zend_std_cast_object_tostring(val->internal, &tmp, IS_STRING); - if (result == FAILURE) { - ZVAL_EMPTY_STRING(&tmp); - } - - break; - default: - value_copy(&tmp, val->internal); - convert_to_cstring(&tmp); - } - - int len = Z_STRLEN(tmp) + 1; - char *str = malloc(len); - memcpy(str, Z_STRVAL(tmp), len); - - zval_dtor(&tmp); - - return str; -} - -unsigned int value_array_size(engine_value *arr) { - switch (arr->kind) { - case KIND_ARRAY: - case KIND_MAP: - return Z_ARRVAL_P(arr->internal)->nNumOfElements; - case KIND_OBJECT: - // Object size is determined by the number of properties, regardless of - // visibility. - return Z_OBJPROP_P(arr->internal)->nNumOfElements; - case KIND_NULL: - // Null values are considered empty. - return 0; - } - - // Non-array or object values are considered to be single-value arrays. - return 1; -} - -engine_value *value_array_keys(engine_value *arr) { - HashTable *h = NULL; - engine_value *keys = value_new(); - - value_set_array(keys, value_array_size(arr)); - - switch (arr->kind) { - case KIND_ARRAY: - case KIND_MAP: - case KIND_OBJECT: - if (arr->kind == KIND_OBJECT) { - h = Z_OBJPROP_P(arr->internal); - } else { - h = Z_ARRVAL_P(arr->internal); - } - - unsigned long i = 0; - - for (zend_hash_internal_pointer_reset(h); i < h->nNumOfElements; i++) { - _value_current_key_set(h, keys); - zend_hash_move_forward(h); - } - - break; - case KIND_NULL: - // Null values are considered empty. - break; - default: - // Non-array or object values are considered to contain a single key, '0'. - add_next_index_long(keys->internal, 0); - } - - return keys; -} - -void value_array_reset(engine_value *arr) { - HashTable *h = NULL; - - switch (arr->kind) { - case KIND_ARRAY: - case KIND_MAP: - h = Z_ARRVAL_P(arr->internal); - break; - case KIND_OBJECT: - h = Z_OBJPROP_P(arr->internal); - break; - default: - return; - } - - zend_hash_internal_pointer_reset(h); -} - -engine_value *value_array_next_get(engine_value *arr) { - HashTable *ht = NULL; - engine_value *val = value_new(); - - switch (arr->kind) { - case KIND_ARRAY: - case KIND_MAP: - ht = Z_ARRVAL_P(arr->internal); - break; - case KIND_OBJECT: - ht = Z_OBJPROP_P(arr->internal); - break; - default: - // Attempting to return the next index of a non-array value will return - // the value itself, allowing for implicit conversions of scalar values - // to arrays. - value_set_zval(val, arr->internal); - return val; - } - - _value_array_next_get(ht, val); - return val; -} - -engine_value *value_array_index_get(engine_value *arr, unsigned long idx) { - HashTable *ht = NULL; - engine_value *val = value_new(); - - switch (arr->kind) { - case KIND_ARRAY: - case KIND_MAP: - ht = Z_ARRVAL_P(arr->internal); - break; - case KIND_OBJECT: - ht = Z_OBJPROP_P(arr->internal); - break; - default: - // Attempting to return the first index of a non-array value will return - // the value itself, allowing for implicit conversions of scalar values - // to arrays. - if (idx == 0) { - value_set_zval(val, arr->internal); - return val; - } - - return val; - } - - _value_array_index_get(ht, idx, val); - return val; -} - -engine_value *value_array_key_get(engine_value *arr, char *key) { - HashTable *ht = NULL; - engine_value *val = value_new(); - - switch (arr->kind) { - case KIND_ARRAY: - case KIND_MAP: - ht = Z_ARRVAL_P(arr->internal); - break; - case KIND_OBJECT: - ht = Z_OBJPROP_P(arr->internal); - break; - default: - return val; - } - - _value_array_key_get(ht, key, val); - return val; -} - -#include "_value.c" +#include "src/value.c" diff --git a/value.go b/value.go index e47a2f7..aa0e8b7 100644 --- a/value.go +++ b/value.go @@ -2,11 +2,8 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. -package php +package gophp -// #cgo CFLAGS: -I/usr/include/php8 -I/usr/include/php8/main -I/usr/include/php8/TSRM -// #cgo CFLAGS: -I/usr/include/php8/Zend -Iinclude -// // #include // #include // #include
@@ -58,7 +55,7 @@ type Value struct { func NewValue(val interface{}) (*Value, error) { ptr, err := C.value_new() if err != nil { - return nil, fmt.Errorf("Unable to instantiate PHP value") + return nil, fmt.Errorf("unable to instantiate PHP value") } v := reflect.ValueOf(val) @@ -87,7 +84,7 @@ func NewValue(val interface{}) (*Value, error) { for i := 0; i < v.Len(); i++ { vs, err := NewValue(v.Index(i).Interface()) if err != nil { - C._value_destroy(ptr) + C.value_destroy(ptr) return nil, err } @@ -103,7 +100,7 @@ func NewValue(val interface{}) (*Value, error) { for _, key := range v.MapKeys() { kv, err := NewValue(v.MapIndex(key).Interface()) if err != nil { - C._value_destroy(ptr) + C.value_destroy(ptr) return nil, err } @@ -117,7 +114,7 @@ func NewValue(val interface{}) (*Value, error) { } } } else { - return nil, fmt.Errorf("Unable to create value of unknown type '%T'", val) + return nil, fmt.Errorf("unable to create value of unknown type '%T'", val) } // Bind struct to PHP object (stdClass) type. case reflect.Struct: @@ -132,7 +129,7 @@ func NewValue(val interface{}) (*Value, error) { fv, err := NewValue(v.Field(i).Interface()) if err != nil { - C._value_destroy(ptr) + C.value_destroy(ptr) return nil, err } @@ -142,10 +139,11 @@ func NewValue(val interface{}) (*Value, error) { C.value_object_property_set(ptr, str, fv.value) } case reflect.Invalid: + fmt.Println("INVALID") C.value_set_null(ptr) default: - C._value_destroy(ptr) - return nil, fmt.Errorf("Unable to create value of unknown type '%T'", val) + C.value_destroy(ptr) + return nil, fmt.Errorf("unable to create value of unknown type '%T'", val) } return &Value{value: ptr}, nil @@ -154,16 +152,16 @@ func NewValue(val interface{}) (*Value, error) { // NewValueFromPtr creates a Value type from an existing PHP value pointer. func NewValueFromPtr(val unsafe.Pointer) (*Value, error) { if val == nil { - return nil, fmt.Errorf("Cannot create value from 'nil' pointer") + return nil, fmt.Errorf("cannot create value from 'nil' pointer") } ptr, err := C.value_new() if err != nil { - return nil, fmt.Errorf("Unable to create new PHP value") + return nil, fmt.Errorf("unable to CREATE new PHP value" + err.Error()) } if _, err := C.value_set_zval(ptr, (*C.zval)(val)); err != nil { - return nil, fmt.Errorf("Unable to set PHP value from pointer") + return nil, fmt.Errorf("unable to SET PHP value from pointer " + err.Error()) } return &Value{value: ptr}, nil @@ -278,6 +276,6 @@ func (v *Value) Destroy() { return } - C._value_destroy(v.value) + C.value_destroy(v.value) v.value = nil } diff --git a/value_test.go b/value_test.go index 76953bc..c2b57fa 100644 --- a/value_test.go +++ b/value_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. -package php +package gophp import ( "reflect"