ingredient/php: Update PHP Serialization
This commit is contained in:
parent
42b8cbd865
commit
a6501b42c7
3 changed files with 82 additions and 52 deletions
33
internal/wisski/ingredient/php/marshal.go
Normal file
33
internal/wisski/ingredient/php/marshal.go
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package php
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Marshal marshals data as a PHP expression, meaning it can safely be used inside code.
|
||||||
|
//
|
||||||
|
// Typically data is marshaled using [json.Marshal] and decoded in PHP using 'json_decode'.
|
||||||
|
// Special cases may exist for specific datatypes.
|
||||||
|
func Marshal(data any) (string, error) {
|
||||||
|
switch d := data.(type) {
|
||||||
|
case string:
|
||||||
|
return MarshalString(d), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "json_decode(" + MarshalString(string(bytes)) + ")", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var replacer = strings.NewReplacer("'", "\\'", "\\", "\\\\")
|
||||||
|
|
||||||
|
// MarshalString marshals s as a php string that can be used safely as a PHP expression.
|
||||||
|
//
|
||||||
|
// See [https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.single].
|
||||||
|
func MarshalString(s string) string {
|
||||||
|
return "'" + replacer.Replace(s) + "'"
|
||||||
|
}
|
||||||
|
|
@ -80,7 +80,6 @@ func (php *PHP) NewServer() (*Server, error) {
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// start the server
|
// start the server
|
||||||
io := stream.NewIOStream(ow, nil, ir, 0)
|
io := stream.NewIOStream(ow, nil, ir, 0)
|
||||||
php.Barrel.Shell(io, "-c", shellescape.QuoteCommand([]string{"drush", "php:eval", serverPHP}))
|
php.Barrel.Shell(io, "-c", shellescape.QuoteCommand([]string{"drush", "php:eval", serverPHP}))
|
||||||
|
|
@ -122,14 +121,9 @@ func (server *Server) MarshalEval(value any, code string) error {
|
||||||
return errPHPClosed
|
return errPHPClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
// marshal the code, and send it to the server
|
// find a delimiter for the code, and then send
|
||||||
bytes, err := json.Marshal(code)
|
delim := findDelimiter(code)
|
||||||
if err != nil {
|
io.WriteString(server.in, delim+"\n"+code+"\n"+delim+"\n")
|
||||||
return ServerError{Message: errPHPMarshal, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// send it to the server
|
|
||||||
io.WriteString(server.in, string(bytes)+"\n")
|
|
||||||
|
|
||||||
// read the next line (as a response)
|
// read the next line (as a response)
|
||||||
data, err := nobufio.ReadLine(server.out)
|
data, err := nobufio.ReadLine(server.out)
|
||||||
|
|
@ -169,22 +163,30 @@ func (server *Server) Eval(code string) (value any, err error) {
|
||||||
//
|
//
|
||||||
// Return values are received as in [MarshalEval].
|
// Return values are received as in [MarshalEval].
|
||||||
func (server *Server) MarshalCall(value any, function string, args ...any) error {
|
func (server *Server) MarshalCall(value any, function string, args ...any) error {
|
||||||
// marshal a code for the call
|
|
||||||
userFunction, err := marshalPHP(function)
|
|
||||||
if err != nil {
|
|
||||||
return ServerError{Message: errPHPMarshal, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
userFunctionArgs := "[]"
|
// name of function to call
|
||||||
if len(args) > 0 {
|
name := MarshalString(function)
|
||||||
userFunctionArgs, err = marshalPHP(args)
|
|
||||||
|
// generate code to call
|
||||||
|
var code string
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
code = "return call_user_func(" + name + ");"
|
||||||
|
case 1:
|
||||||
|
param, err := Marshal(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ServerError{Message: errPHPMarshal, Err: err}
|
return err
|
||||||
}
|
}
|
||||||
|
code = "return call_user_func(" + name + "," + param + ");"
|
||||||
|
default:
|
||||||
|
params, err := Marshal(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
code = "return call_user_func_array(" + name + "," + params + ");"
|
||||||
}
|
}
|
||||||
code := "return call_user_func_array(" + userFunction + "," + userFunctionArgs + ");"
|
|
||||||
|
|
||||||
// and return the evaluated code!
|
// and evaluate the code
|
||||||
return server.MarshalEval(value, code)
|
return server.MarshalEval(value, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,27 +196,14 @@ func (server *Server) Call(function string, args ...any) (value any, err error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const marshalRune = 'F' // press to pay respect
|
const delimiterRune = 'F' // press to pay respect
|
||||||
|
|
||||||
// marshalPHP marshals some data which can be marshaled using [json.Encode] into a PHP Expression.
|
// findDelimiter finds a delimiter that does not occur in the input string
|
||||||
// the string can be safely used directly within php.
|
func findDelimiter(input string) string {
|
||||||
func marshalPHP(data any) (string, error) {
|
// find the longest sequence of delimiter rune
|
||||||
// this function uses json as a data format to transport the data into php.
|
|
||||||
// then we build a heredoc to encode it safely, and decode it in php
|
|
||||||
|
|
||||||
// Step 1: Encode the data as json
|
|
||||||
jbytes, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
jstring := string(jbytes)
|
|
||||||
|
|
||||||
// Step 2: Find a delimiter for the heredoc.
|
|
||||||
// Step 2a: Find the longest sequence of [marshalRune]s inside the encoded string.
|
|
||||||
var current, longest int
|
var current, longest int
|
||||||
for _, r := range jstring {
|
for _, r := range input {
|
||||||
|
if r == delimiterRune {
|
||||||
if r == marshalRune {
|
|
||||||
current++
|
current++
|
||||||
} else {
|
} else {
|
||||||
current = 0
|
current = 0
|
||||||
|
|
@ -224,12 +213,8 @@ func marshalPHP(data any) (string, error) {
|
||||||
longest = current
|
longest = current
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Step 2b: Build a string of marshalRune that is one longer!
|
// and then return it multipled longer than that
|
||||||
delim := strings.Repeat(string(marshalRune), longest+1)
|
return strings.Repeat(string(delimiterRune), longest+1)
|
||||||
|
|
||||||
// Step 3: Assemble the encoded string!
|
|
||||||
result := "call_user_func(function(){$x=<<<'" + delim + "'\n" + jstring + "\n" + delim + ";return json_decode(trim($x));})" // press to doubt
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes this server and prevents any further code from being run.
|
// Close closes this server and prevents any further code from being run.
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,33 @@
|
||||||
//
|
//
|
||||||
// As such it is preprocessed and shortened.
|
// As such it is preprocessed and shortened.
|
||||||
// It should only contain comments at the beginning of each line, and only starting with '//'.
|
// It should only contain comments at the beginning of each line, and only starting with '//'.
|
||||||
// See wisski_php_server.go.
|
// See server.go.
|
||||||
|
|
||||||
// don't buffer stdin!
|
// prevent STDIN from being buffered
|
||||||
stream_set_read_buffer(STDIN,0);
|
stream_set_read_buffer(STDIN,0);
|
||||||
|
|
||||||
// stop all other output
|
// stop outputting errors when executing
|
||||||
ob_start(null,0,PHP_OUTPUT_HANDLER_CLEANABLE);
|
ob_start(null,0,PHP_OUTPUT_HANDLER_CLEANABLE);
|
||||||
|
|
||||||
while($line = fgets(STDIN)){
|
|
||||||
// decode the command to run
|
while(1){
|
||||||
$code=@json_decode($line);
|
// read the next line to get an end-of-line marker
|
||||||
|
$marker = fgets(STDIN);
|
||||||
|
if (!$marker) break;
|
||||||
|
|
||||||
|
// accumulate the buffer until the marker is reached
|
||||||
|
// bail out if there is an unexpected end of input
|
||||||
|
$buffer = "";
|
||||||
|
while(1) {
|
||||||
|
$line = fgets(STDIN);
|
||||||
|
if (!$line) break 2;
|
||||||
|
if ($line === $marker) break;
|
||||||
|
$buffer .= $line . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
// execute it
|
// execute it
|
||||||
try{
|
try{
|
||||||
$json = json_encode([eval($code),""]);
|
$json = json_encode([eval($buffer),""]);
|
||||||
}catch(Throwable $t){
|
}catch(Throwable $t){
|
||||||
$json = json_encode([null,(string)$t]);
|
$json = json_encode([null,(string)$t]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue