|
1 | 1 | #include "frankenphp.h" |
2 | 2 | #include <SAPI.h> |
3 | 3 | #include <Zend/zend_alloc.h> |
| 4 | +#include <Zend/zend_enum.h> |
4 | 5 | #include <Zend/zend_exceptions.h> |
5 | 6 | #include <Zend/zend_interfaces.h> |
6 | 7 | #include <errno.h> |
@@ -675,63 +676,232 @@ PHP_FUNCTION(frankenphp_handle_request) { |
675 | 676 | RETURN_TRUE; |
676 | 677 | } |
677 | 678 |
|
| 679 | +/* Persistent enum storage */ |
| 680 | +typedef struct { |
| 681 | + zend_string *class_name; |
| 682 | + zend_string *case_name; |
| 683 | +} sidekick_enum_t; |
| 684 | + |
| 685 | +/* Validate that a zval tree contains only scalars, arrays, and enums */ |
| 686 | +static bool sidekick_validate_zval(zval *z) { |
| 687 | + switch (Z_TYPE_P(z)) { |
| 688 | + case IS_NULL: |
| 689 | + case IS_FALSE: |
| 690 | + case IS_TRUE: |
| 691 | + case IS_LONG: |
| 692 | + case IS_DOUBLE: |
| 693 | + case IS_STRING: |
| 694 | + return true; |
| 695 | + case IS_OBJECT: |
| 696 | + return (Z_OBJCE_P(z)->ce_flags & ZEND_ACC_ENUM) != 0; |
| 697 | + case IS_ARRAY: { |
| 698 | + zval *val; |
| 699 | + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z), val) { |
| 700 | + if (!sidekick_validate_zval(val)) |
| 701 | + return false; |
| 702 | + } |
| 703 | + ZEND_HASH_FOREACH_END(); |
| 704 | + return true; |
| 705 | + } |
| 706 | + default: |
| 707 | + return false; |
| 708 | + } |
| 709 | +} |
| 710 | + |
| 711 | +/* Deep-copy a zval into persistent memory */ |
| 712 | +static void sidekick_persist_zval(zval *dst, zval *src) { |
| 713 | + switch (Z_TYPE_P(src)) { |
| 714 | + case IS_NULL: |
| 715 | + case IS_FALSE: |
| 716 | + case IS_TRUE: |
| 717 | + ZVAL_COPY_VALUE(dst, src); |
| 718 | + break; |
| 719 | + case IS_LONG: |
| 720 | + ZVAL_LONG(dst, Z_LVAL_P(src)); |
| 721 | + break; |
| 722 | + case IS_DOUBLE: |
| 723 | + ZVAL_DOUBLE(dst, Z_DVAL_P(src)); |
| 724 | + break; |
| 725 | + case IS_STRING: { |
| 726 | + zend_string *ps = zend_string_init(Z_STRVAL_P(src), Z_STRLEN_P(src), 1); |
| 727 | + ZVAL_NEW_STR(dst, ps); |
| 728 | + break; |
| 729 | + } |
| 730 | + case IS_OBJECT: { |
| 731 | + /* Must be an enum (validated earlier) */ |
| 732 | + zend_class_entry *ce = Z_OBJCE_P(src); |
| 733 | + sidekick_enum_t *e = pemalloc(sizeof(sidekick_enum_t), 1); |
| 734 | + e->class_name = zend_string_init(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), 1); |
| 735 | + zval *case_name_zval = zend_enum_fetch_case_name(Z_OBJ_P(src)); |
| 736 | + e->case_name = zend_string_init(Z_STRVAL_P(case_name_zval), |
| 737 | + Z_STRLEN_P(case_name_zval), 1); |
| 738 | + ZVAL_PTR(dst, e); |
| 739 | + break; |
| 740 | + } |
| 741 | + case IS_ARRAY: { |
| 742 | + HashTable *src_ht = Z_ARRVAL_P(src); |
| 743 | + HashTable *dst_ht = pemalloc(sizeof(HashTable), 1); |
| 744 | + zend_hash_init(dst_ht, zend_hash_num_elements(src_ht), NULL, NULL, 1); |
| 745 | + ZVAL_ARR(dst, dst_ht); |
| 746 | + |
| 747 | + zend_string *key; |
| 748 | + zend_ulong idx; |
| 749 | + zval *val; |
| 750 | + ZEND_HASH_FOREACH_KEY_VAL(src_ht, idx, key, val) { |
| 751 | + zval pval; |
| 752 | + sidekick_persist_zval(&pval, val); |
| 753 | + if (key) { |
| 754 | + zend_string *pkey = zend_string_init(ZSTR_VAL(key), ZSTR_LEN(key), 1); |
| 755 | + zend_hash_add_new(dst_ht, pkey, &pval); |
| 756 | + zend_string_release(pkey); |
| 757 | + } else { |
| 758 | + zend_hash_index_add_new(dst_ht, idx, &pval); |
| 759 | + } |
| 760 | + } |
| 761 | + ZEND_HASH_FOREACH_END(); |
| 762 | + break; |
| 763 | + } |
| 764 | + default: |
| 765 | + ZVAL_NULL(dst); |
| 766 | + break; |
| 767 | + } |
| 768 | +} |
| 769 | + |
| 770 | +/* Deep-free a persistent zval tree */ |
| 771 | +static void sidekick_free_persistent_zval(zval *z) { |
| 772 | + switch (Z_TYPE_P(z)) { |
| 773 | + case IS_STRING: |
| 774 | + zend_string_free(Z_STR_P(z)); |
| 775 | + break; |
| 776 | + case IS_PTR: { |
| 777 | + sidekick_enum_t *e = (sidekick_enum_t *)Z_PTR_P(z); |
| 778 | + zend_string_free(e->class_name); |
| 779 | + zend_string_free(e->case_name); |
| 780 | + pefree(e, 1); |
| 781 | + break; |
| 782 | + } |
| 783 | + case IS_ARRAY: { |
| 784 | + zval *val; |
| 785 | + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z), val) { |
| 786 | + sidekick_free_persistent_zval(val); |
| 787 | + } |
| 788 | + ZEND_HASH_FOREACH_END(); |
| 789 | + zend_hash_destroy(Z_ARRVAL_P(z)); |
| 790 | + pefree(Z_ARRVAL_P(z), 1); |
| 791 | + break; |
| 792 | + } |
| 793 | + default: |
| 794 | + break; |
| 795 | + } |
| 796 | +} |
| 797 | + |
| 798 | +/* Deep-copy a persistent zval tree into request memory */ |
| 799 | +static void sidekick_request_copy_zval(zval *dst, zval *src) { |
| 800 | + switch (Z_TYPE_P(src)) { |
| 801 | + case IS_NULL: |
| 802 | + case IS_FALSE: |
| 803 | + case IS_TRUE: |
| 804 | + ZVAL_COPY_VALUE(dst, src); |
| 805 | + break; |
| 806 | + case IS_LONG: |
| 807 | + ZVAL_LONG(dst, Z_LVAL_P(src)); |
| 808 | + break; |
| 809 | + case IS_DOUBLE: |
| 810 | + ZVAL_DOUBLE(dst, Z_DVAL_P(src)); |
| 811 | + break; |
| 812 | + case IS_STRING: |
| 813 | + ZVAL_STRINGL(dst, Z_STRVAL_P(src), Z_STRLEN_P(src)); |
| 814 | + break; |
| 815 | + case IS_PTR: { |
| 816 | + sidekick_enum_t *e = (sidekick_enum_t *)Z_PTR_P(src); |
| 817 | + zend_class_entry *ce = zend_lookup_class(e->class_name); |
| 818 | + if (!ce || !(ce->ce_flags & ZEND_ACC_ENUM)) { |
| 819 | + zend_throw_exception_ex(spl_ce_LogicException, 0, |
| 820 | + "Sidekick enum class \"%s\" not found", |
| 821 | + ZSTR_VAL(e->class_name)); |
| 822 | + ZVAL_NULL(dst); |
| 823 | + break; |
| 824 | + } |
| 825 | + zend_object *enum_obj = zend_enum_get_case_cstr(ce, ZSTR_VAL(e->case_name)); |
| 826 | + if (!enum_obj) { |
| 827 | + zend_throw_exception_ex(spl_ce_LogicException, 0, |
| 828 | + "Sidekick enum case \"%s::%s\" not found", |
| 829 | + ZSTR_VAL(e->class_name), ZSTR_VAL(e->case_name)); |
| 830 | + ZVAL_NULL(dst); |
| 831 | + break; |
| 832 | + } |
| 833 | + ZVAL_OBJ_COPY(dst, enum_obj); |
| 834 | + break; |
| 835 | + } |
| 836 | + case IS_ARRAY: { |
| 837 | + HashTable *src_ht = Z_ARRVAL_P(src); |
| 838 | + array_init_size(dst, zend_hash_num_elements(src_ht)); |
| 839 | + HashTable *dst_ht = Z_ARRVAL_P(dst); |
| 840 | + |
| 841 | + zend_string *key; |
| 842 | + zend_ulong idx; |
| 843 | + zval *val; |
| 844 | + ZEND_HASH_FOREACH_KEY_VAL(src_ht, idx, key, val) { |
| 845 | + zval rval; |
| 846 | + sidekick_request_copy_zval(&rval, val); |
| 847 | + if (EG(exception)) { |
| 848 | + zval_ptr_dtor(&rval); |
| 849 | + break; |
| 850 | + } |
| 851 | + if (key) { |
| 852 | + zend_string *rkey = zend_string_init(ZSTR_VAL(key), ZSTR_LEN(key), 0); |
| 853 | + ZSTR_H(rkey) = ZSTR_H(key); |
| 854 | + zend_hash_add_new(dst_ht, rkey, &rval); |
| 855 | + zend_string_release(rkey); |
| 856 | + } else { |
| 857 | + zend_hash_index_add_new(dst_ht, idx, &rval); |
| 858 | + } |
| 859 | + } |
| 860 | + ZEND_HASH_FOREACH_END(); |
| 861 | + break; |
| 862 | + } |
| 863 | + default: |
| 864 | + ZVAL_NULL(dst); |
| 865 | + break; |
| 866 | + } |
| 867 | +} |
| 868 | + |
678 | 869 | PHP_FUNCTION(frankenphp_sidekick_set_vars) { |
679 | 870 | zval *vars_array = NULL; |
680 | 871 |
|
681 | 872 | ZEND_PARSE_PARAMETERS_START(1, 1); |
682 | 873 | Z_PARAM_ARRAY(vars_array); |
683 | 874 | ZEND_PARSE_PARAMETERS_END(); |
684 | 875 |
|
685 | | - /* Validate all keys and values are strings */ |
686 | | - HashTable *ht = Z_ARRVAL_P(vars_array); |
687 | | - zend_string *key; |
| 876 | + /* Validate: reject objects, resources, references */ |
688 | 877 | zval *val; |
689 | | - ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) { |
690 | | - if (key == NULL) { |
691 | | - zend_value_error("All keys must be strings"); |
692 | | - RETURN_THROWS(); |
693 | | - } |
694 | | - if (Z_TYPE_P(val) != IS_STRING) { |
695 | | - zend_value_error("All values must be strings"); |
| 878 | + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(vars_array), val) { |
| 879 | + if (!sidekick_validate_zval(val)) { |
| 880 | + zend_value_error("Values must be scalars, strings, arrays, or null; " |
| 881 | + "objects and resources are not allowed"); |
696 | 882 | RETURN_THROWS(); |
697 | 883 | } |
698 | 884 | } |
699 | 885 | ZEND_HASH_FOREACH_END(); |
700 | 886 |
|
701 | | - HashTable *persistent = pemalloc(sizeof(HashTable), 1); |
702 | | - zend_hash_init(persistent, zend_hash_num_elements(ht), NULL, NULL, 1); |
703 | | - |
704 | | - ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) { |
705 | | - zend_string *pkey = zend_string_init(ZSTR_VAL(key), ZSTR_LEN(key), 1); |
706 | | - zend_string *pval_str = |
707 | | - zend_string_init(Z_STRVAL_P(val), Z_STRLEN_P(val), 1); |
708 | | - zval pval; |
709 | | - ZVAL_NEW_STR(&pval, pval_str); |
710 | | - zend_hash_add_new(persistent, pkey, &pval); |
711 | | - zend_string_release(pkey); |
712 | | - } |
713 | | - ZEND_HASH_FOREACH_END(); |
| 887 | + /* Deep-copy to persistent memory */ |
| 888 | + zval persistent; |
| 889 | + sidekick_persist_zval(&persistent, vars_array); |
714 | 890 |
|
715 | 891 | void *old = NULL; |
716 | | - char *error = go_frankenphp_sidekick_set_vars(thread_index, persistent, &old); |
| 892 | + char *error = |
| 893 | + go_frankenphp_sidekick_set_vars(thread_index, Z_ARRVAL(persistent), &old); |
717 | 894 | if (error) { |
718 | | - zval *ev; |
719 | | - ZEND_HASH_FOREACH_VAL(persistent, ev) { zend_string_free(Z_STR_P(ev)); } |
720 | | - ZEND_HASH_FOREACH_END(); |
721 | | - zend_hash_destroy(persistent); |
722 | | - pefree(persistent, 1); |
| 895 | + sidekick_free_persistent_zval(&persistent); |
723 | 896 | zend_throw_exception(spl_ce_RuntimeException, error, 0); |
724 | 897 | free(error); |
725 | 898 | RETURN_THROWS(); |
726 | 899 | } |
727 | 900 |
|
728 | 901 | if (old != NULL) { |
729 | | - HashTable *old_ht = (HashTable *)old; |
730 | | - zval *v; |
731 | | - ZEND_HASH_FOREACH_VAL(old_ht, v) { zend_string_free(Z_STR_P(v)); } |
732 | | - ZEND_HASH_FOREACH_END(); |
733 | | - zend_hash_destroy(old_ht); |
734 | | - pefree(old_ht, 1); |
| 902 | + zval old_zval; |
| 903 | + ZVAL_ARR(&old_zval, (HashTable *)old); |
| 904 | + sidekick_free_persistent_zval(&old_zval); |
735 | 905 | } |
736 | 906 | } |
737 | 907 |
|
@@ -772,16 +942,15 @@ PHP_FUNCTION(frankenphp_sidekick_get_vars) { |
772 | 942 | RETURN_THROWS(); |
773 | 943 | } |
774 | 944 |
|
775 | | - array_init(return_value); |
776 | 945 | if (vars_ptr) { |
777 | | - HashTable *persistent = (HashTable *)vars_ptr; |
778 | | - zend_string *key; |
779 | | - zval *val; |
780 | | - ZEND_HASH_FOREACH_STR_KEY_VAL(persistent, key, val) { |
781 | | - add_assoc_stringl(return_value, ZSTR_VAL(key), Z_STRVAL_P(val), |
782 | | - Z_STRLEN_P(val)); |
| 946 | + zval src; |
| 947 | + ZVAL_ARR(&src, (HashTable *)vars_ptr); |
| 948 | + sidekick_request_copy_zval(return_value, &src); |
| 949 | + if (EG(exception)) { |
| 950 | + RETURN_THROWS(); |
783 | 951 | } |
784 | | - ZEND_HASH_FOREACH_END(); |
| 952 | + } else { |
| 953 | + array_init(return_value); |
785 | 954 | } |
786 | 955 | return; |
787 | 956 | } |
@@ -847,16 +1016,16 @@ PHP_FUNCTION(frankenphp_sidekick_get_vars) { |
847 | 1016 | idx = 0; |
848 | 1017 | ZEND_HASH_FOREACH_VAL(ht, val) { |
849 | 1018 | zval sidekick_vars; |
850 | | - array_init(&sidekick_vars); |
851 | 1019 | if (vars_ptrs[idx]) { |
852 | | - HashTable *persistent = (HashTable *)vars_ptrs[idx]; |
853 | | - zend_string *k; |
854 | | - zval *v; |
855 | | - ZEND_HASH_FOREACH_STR_KEY_VAL(persistent, k, v) { |
856 | | - add_assoc_stringl(&sidekick_vars, ZSTR_VAL(k), Z_STRVAL_P(v), |
857 | | - Z_STRLEN_P(v)); |
| 1020 | + zval src; |
| 1021 | + ZVAL_ARR(&src, (HashTable *)vars_ptrs[idx]); |
| 1022 | + sidekick_request_copy_zval(&sidekick_vars, &src); |
| 1023 | + if (EG(exception)) { |
| 1024 | + zval_ptr_dtor(&sidekick_vars); |
| 1025 | + break; |
858 | 1026 | } |
859 | | - ZEND_HASH_FOREACH_END(); |
| 1027 | + } else { |
| 1028 | + array_init(&sidekick_vars); |
860 | 1029 | } |
861 | 1030 | add_assoc_zval(return_value, Z_STRVAL_P(val), &sidekick_vars); |
862 | 1031 | idx++; |
|
0 commit comments