Server.php

<?php

namespace Tlf\Tester;

/**
 * convenience methods for server-related tests
 * Server-related assertions may go here too
 *
 * @todo stop using file_get_contents() so I check for redirects & stuff
 */
trait Server {

    /**
     *
     * @param $server the server name
     * @return a string like `http://localhost:3000`
     */
    public function get_server(string $server='main'){
        $host = $this->cli->get_server_host($server);
        return $host;
    }
    
    /**
     *
     * @param $path the path component of a url 
     * @param $params an array of paramaters to pass in the url
     *
     * @note $path cannot contain query string if params are passed
     * @warning this early implementation will change
     *
     * @usage $response_content = $this->get('/', ['cat'=>'Jefurry']);
     */
    public function get($path, $params=[], $server='main'){
        $host = $this->cli->get_server_host($server);
        $url = $host.$path;
        if ($params!=[]) $url .= '?' . http_build_query($params);

        $ch = curl_init();

        curl_setopt_array($ch,
            [
            CURLOPT_URL => $url, 
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => false,
            // CURLOPT_POSTFIELDS => http_build_query($params),
            // CURLOPT_POSTFIELDS => $params,
            CURLOPT_FOLLOWLOCATION => true,
            ]
        );


        $output = curl_exec($ch);
        curl_close($ch);

        return $output;
    }



    /**
     *
     * @param $path the path component of a url 
     * @param $params an array of paramaters to pass via $_POST
     * @param $files key=>value array of files where key = short file name & value = local file path
     *
     * @note $params['key'] will be overwritten by $files['key'] for the upload
     * @note(jan 4, 2022) follows redirects ('Location:' header)
     *
     * @note $path cannot contain query string if params are passed
     *
     * @usage $response_content = $this->get('/', ['cat'=>'Jefurry']);
     */
    public function post($path, $params=[], $files=[], $server='main'){
        $host = $this->cli->get_server_host($server);
        $url = $host.$path;

        $ch = curl_init();

        $this->curl_add_files($ch, $files, $params);

        curl_setopt_array($ch,
            [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            // CURLOPT_POSTFIELDS => http_build_query($params),
            CURLOPT_POSTFIELDS => $params,
            CURLOPT_FOLLOWLOCATION => true,
            // CURLOPT_HEADER=>true,
            // CURLINFO_HEADER_OUT=>true,
            ]
        );


        $output = curl_exec($ch);
        curl_close($ch);

        return $output;
    }

    /**
     *
     * @beta
     *
     * @param $headers array like `['HeaderKey: header_value', 'Cookie'=> 'cookie_key=cookie_value;cookie2=value2;']` ... ONLY `Cookie` should have an array key. All other header keys should be in the array value and use a numeric index
     * @return array with keys header_text, body, headers, cookies
     */
    public function curl_post($path, $params=[], $files=[], $server='main', $headers=[]){
        $ch = curl_init();

        $host = $this->cli->get_server_host();
        $url = $host.$path;
        
        // echo 'zip zap';
//
        // print_r($headers);
        // exit;

        if (isset($headers['Cookie'])){
            // var_dump($headers);
            // exit;
            $cookie = $headers['Cookie'];
            unset($headers['Cookie']);
            curl_setopt($ch, CURLOPT_COOKIE, $cookie);
        }
        curl_setopt_array($ch,
            [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            // CURLOPT_POSTFIELDS => http_build_query($params),
            CURLOPT_POSTFIELDS => $params,
            CURLOPT_HEADER => true,
            CURLOPT_FOLLOWLOCATION => false,
            CURLOPT_HTTPHEADER => $headers,
            ]
        );

        $response = curl_exec($ch);
        $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        curl_close($ch);

        $header_text = substr($response,0,$header_size);

        $parts = explode("\n", $header_text);
        $parts = array_map('trim', $parts);
        $headers = [];
        $cookies = [];
        foreach ($parts as $line){
            $pos = strpos($line,':');
            if ($pos===false)continue;
            $key = substr($line,0,$pos);
            $value = trim(substr($line,$pos+1));
            if (isset($headers[$key])){
                if (!is_array($headers[$key]))$headers[$key] = [$headers[$key]];
                $headers[$key][] = $value;
            } else {
                $headers[$key] = $value;
            }
            if ($key=='Set-Cookie'){
                $cookie = $this->parse_cookie_header($value);
                $cookies[$cookie['name']] = $cookie;
            }
        }

        // TODO: parse Set-Cookie headers 

        $body = substr($response,$header_size);
        return [
            'header_text'=>$header_text,
            'body'=>$body,
            'headers'=>$headers,
            'cookies'=>$cookies,
        ];
    }

    /**
     *
     * @beta(may 4, 2022)
     *
     * @see curl_post(). It is the same except no `$files` param here
     */
    public function curl_get($path, $params=[], $server='main', $headers=[]){
        $ch = curl_init();

        $host = $this->cli->get_server_host();
        $url = $host.$path;
        if ($params!=[]) $url .= '?' . http_build_query($params);

        // echo 'zip zap';
//
        // print_r($headers);
        // exit;

        if (isset($headers['Cookie'])){
            // var_dump($headers);
            // exit;
            $cookie = $headers['Cookie'];
            unset($headers['Cookie']);
            curl_setopt($ch, CURLOPT_COOKIE, $cookie);
        }
        curl_setopt_array($ch,
            [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => false,
            // CURLOPT_POSTFIELDS => http_build_query($params),
            // CURLOPT_POSTFIELDS => $params,
            CURLOPT_HEADER => true,
            CURLOPT_FOLLOWLOCATION => false,
            CURLOPT_HTTPHEADER => $headers,
            ]
        );

        $response = curl_exec($ch);
        $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        curl_close($ch);

        $header_text = substr($response,0,$header_size);

        $parts = explode("\n", $header_text);
        $parts = array_map('trim', $parts);
        $headers = [];
        $cookies = [];
        foreach ($parts as $line){
            $pos = strpos($line,':');
            if ($pos===false)continue;
            $key = substr($line,0,$pos);
            $value = trim(substr($line,$pos+1));
            if (isset($headers[$key])){
                if (!is_array($headers[$key]))$headers[$key] = [$headers[$key]];
                $headers[$key][] = $value;
            } else {
                $headers[$key] = $value;
            }
            if ($key=='Set-Cookie'){
                $cookie = $this->parse_cookie_header($value);
                $cookies[$cookie['name']] = $cookie;
            }
        }

        // TODO: parse Set-Cookie headers 

        $body = substr($response,$header_size);
        return [
            'header_text'=>$header_text,
            'body'=>$body,
            'headers'=>$headers,
            'cookies'=>$cookies,
        ];
    }


    /**
     * @param $header_value, for `Set-cookie: whatever whatever`, this should be `whatever whatever`
     * @return array parsed cookie
     *
     *
     * @issue has no handling for a cookie value containing a semi-colon or an equal sign
     */
    public function parse_cookie_header($header_value): array {
        $cookie = [];
        $parts = explode(';', $header_value);
        $parts = array_map('trim',$parts);

        $main = array_shift($parts);
        $main_parts = explode('=', $main);
        $cookie['name'] = $main_parts[0];
        $cookie['value'] = $main_parts[1];


        foreach ($parts as $str){
            $kv_parts = explode('=', $str);
            if (count($kv_parts)==1)$cookie[$str] = true;
            else $cookie[$kv_parts[0]] = $kv_parts[1];
        }
        return $cookie;
    }

    /**
     * @param $ch curl handle
     * @param $files array of key=>absolute file paths
     * @param $params array to use as CURLOPT_POSTFIELDS
     */
    public function curl_add_files($ch, array $files, array &$params){
        foreach ($files as $name=>$path){
            $mimetype = mime_content_type($path);
            // $mimetype = false;
            if ($mimetype==false){
                $ext = pathinfo($path,PATHINFO_EXTENSION);
                $list = require(dirname(__DIR__).'/mime_type_map.php');
                $mimetype = $list['mimes'][$ext][0];
            }
            $params[$name] = new \CURLFile($path, $mimetype, basename($path));
        }
    }
}