( ′∀`)σ≡σ☆))Д′)レ(゚∀゚;)ヘ=З=З=Зε≡(ノ´_ゝ`)ノ
<?php
// CubicleSoft PHP web browser state emulation class.
// (C) 2021 CubicleSoft. All Rights Reserved.
// Requires the CubicleSoft PHP HTTP class for HTTP/HTTPS.
class WebBrowser
{
private $data, $html;
public function __construct($prevstate = array())
{
if (!class_exists("HTTP", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/http.php";
$this->ResetState();
$this->SetState($prevstate);
$this->html = false;
}
public function ResetState()
{
$this->data = array(
"allowedprotocols" => array("http" => true, "https" => true),
"allowedredirprotocols" => array("http" => true, "https" => true),
"hostauths" => array(),
"cookies" => array(),
"referer" => "",
"autoreferer" => true,
"useragent" => "firefox",
"followlocation" => true,
"maxfollow" => 20,
"extractforms" => false,
"httpopts" => array(),
);
}
public function SetState($options = array())
{
$this->data = array_merge($this->data, $options);
}
public function GetState()
{
return $this->data;
}
public function ProcessState(&$state)
{
while ($state["state"] !== "done")
{
switch ($state["state"])
{
case "initialize":
{
if (!isset($this->data["allowedprotocols"][$state["urlinfo"]["scheme"]]) || !$this->data["allowedprotocols"][$state["urlinfo"]["scheme"]])
{
return array("success" => false, "error" => self::WBTranslate("Protocol '%s' is not allowed in '%s'.", $state["urlinfo"]["scheme"], $state["url"]), "errorcode" => "allowed_protocols");
}
$filename = HTTP::ExtractFilename($state["urlinfo"]["path"]);
$pos = strrpos($filename, ".");
$fileext = ($pos !== false ? strtolower(substr($filename, $pos + 1)) : "");
// Set up some standard headers.
$headers = array();
$profile = strtolower($state["profile"]);
$tempprofile = explode("-", $profile);
if (count($tempprofile) == 2)
{
$profile = $tempprofile[0];
$fileext = $tempprofile[1];
}
if (substr($profile, 0, 2) == "ie" || ($profile == "auto" && substr($this->data["useragent"], 0, 2) == "ie"))
{
if ($fileext == "css") $headers["Accept"] = "text/css";
else if ($fileext == "png" || $fileext == "jpg" || $fileext == "jpeg" || $fileext == "gif" || $fileext == "svg") $headers["Accept"] = "image/png, image/svg+xml, image/*;q=0.8, */*;q=0.5";
else if ($fileext == "js") $headers["Accept"] = "application/javascript, */*;q=0.8";
else if ($this->data["referer"] != "" || $fileext == "" || $fileext == "html" || $fileext == "xhtml" || $fileext == "xml") $headers["Accept"] = "text/html, application/xhtml+xml, */*";
else $headers["Accept"] = "*/*";
$headers["Accept-Language"] = "en-US";
$headers["User-Agent"] = HTTP::GetUserAgent(substr($profile, 0, 2) == "ie" ? $profile : $this->data["useragent"]);
}
else if ($profile == "firefox" || ($profile == "auto" && $this->data["useragent"] == "firefox"))
{
if ($fileext == "css") $headers["Accept"] = "text/css,*/*;q=0.1";
else if ($fileext == "png" || $fileext == "jpg" || $fileext == "jpeg" || $fileext == "gif" || $fileext == "svg") $headers["Accept"] = "image/png,image/*;q=0.8,*/*;q=0.5";
else if ($fileext == "js") $headers["Accept"] = "*/*";
else $headers["Accept"] = "text/html, application/xhtml+xml, */*";
$headers["Accept-Language"] = "en-us,en;q=0.5";
$headers["Cache-Control"] = "max-age=0";
$headers["User-Agent"] = HTTP::GetUserAgent("firefox");
}
else if ($profile == "opera" || ($profile == "auto" && $this->data["useragent"] == "opera"))
{
// Opera has the right idea: Just send the same thing regardless of the request type.
$headers["Accept"] = "text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1";
$headers["Accept-Language"] = "en-US,en;q=0.9";
$headers["Cache-Control"] = "no-cache";
$headers["User-Agent"] = HTTP::GetUserAgent("opera");
}
else if ($profile == "safari" || $profile == "edge" || $profile == "chrome" || ($profile == "auto" && ($this->data["useragent"] == "safari" || $this->data["useragent"] == "edge" || $this->data["useragent"] == "chrome")))
{
if ($fileext == "css") $headers["Accept"] = "text/css,*/*;q=0.1";
else if ($fileext == "png" || $fileext == "jpg" || $fileext == "jpeg" || $fileext == "gif" || $fileext == "svg" || $fileext == "js") $headers["Accept"] = "*/*";
else $headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
$headers["Accept-Charset"] = "ISO-8859-1,utf-8;q=0.7,*;q=0.3";
$headers["Accept-Language"] = "en-US,en;q=0.8";
$headers["User-Agent"] = HTTP::GetUserAgent($profile == "safari" || $profile == "chrome" ? $profile : $this->data["useragent"]);
}
if ($this->data["referer"] != "") $headers["Referer"] = $this->data["referer"];
// Generate the final headers array.
$headers = array_merge($headers, $state["httpopts"]["headers"], $state["tempoptions"]["headers"]);
// Calculate the host and reverse host and remove port information.
$host = (isset($headers["Host"]) ? $headers["Host"] : $state["urlinfo"]["host"]);
$pos = strpos($host, "]");
if (substr($host, 0, 1) == "[" && $pos !== false)
{
$host = substr($host, 0, $pos + 1);
}
else
{
$pos = strpos($host, ":");
if ($pos !== false) $host = substr($host, 0, $pos);
}
$dothost = $host;
$dothost = strtolower($dothost);
if (substr($dothost, 0, 1) != ".") $dothost = "." . $dothost;
$state["dothost"] = $dothost;
// Append Authorization header.
if (isset($headers["Authorization"])) $this->data["hostauths"][$host] = $headers["Authorization"];
else if (isset($this->data["hostauths"][$host])) $headers["Authorization"] = $this->data["hostauths"][$host];
// Append cookies and delete old, invalid cookies.
$secure = ($state["urlinfo"]["scheme"] == "https");
$cookiepath = $state["urlinfo"]["path"];
if ($cookiepath == "") $cookiepath = "/";
$pos = strrpos($cookiepath, "/");
if ($pos !== false) $cookiepath = substr($cookiepath, 0, $pos + 1);
$state["cookiepath"] = $cookiepath;
$cookies = array();
foreach ($this->data["cookies"] as $domain => $paths)
{
if (strlen($dothost) >= strlen($domain) && substr($dothost, -strlen($domain)) === $domain)
{
foreach ($paths as $path => $cookies2)
{
if (substr($cookiepath, 0, strlen($path)) == $path)
{
foreach ($cookies2 as $num => $info)
{
if (isset($info["expires_ts"]) && $this->GetExpiresTimestamp($info["expires_ts"]) < time()) unset($this->data["cookies"][$domain][$path][$num]);
else if ($secure || !isset($info["secure"])) $cookies[$info["name"]] = $info["value"];
}
if (!count($this->data["cookies"][$domain][$path])) unset($this->data["cookies"][$domain][$path]);
}
}
if (!count($this->data["cookies"][$domain])) unset($this->data["cookies"][$domain]);
}
}
$cookies2 = array();
foreach ($cookies as $name => $value) $cookies2[] = rawurlencode($name) . "=" . rawurlencode($value);
$headers["Cookie"] = implode("; ", $cookies2);
if ($headers["Cookie"] == "") unset($headers["Cookie"]);
// Generate the final options array.
$state["options"] = array_merge($state["httpopts"], $state["tempoptions"]);
$state["options"]["headers"] = $headers;
if ($state["timeout"] !== false) $state["options"]["timeout"] = HTTP::GetTimeLeft($state["startts"], $state["timeout"]);
// Let a callback handle any additional state changes.
if (isset($state["options"]["pre_retrievewebpage_callback"]) && is_callable($state["options"]["pre_retrievewebpage_callback"]) && !call_user_func_array($state["options"]["pre_retrievewebpage_callback"], array(&$state)))
{
return array("success" => false, "error" => self::WBTranslate("Pre-RetrieveWebpage callback returned with a failure condition for '%s'.", $state["url"]), "errorcode" => "pre_retrievewebpage_callback");
}
// Process the request.
$result = HTTP::RetrieveWebpage($state["url"], $state["options"]);
$result["url"] = $state["url"];
unset($state["options"]["files"]);
unset($state["options"]["body"]);
unset($state["tempoptions"]["headers"]["Content-Type"]);
$result["options"] = $state["options"];
$result["firstreqts"] = $state["startts"];
$result["numredirects"] = $state["numredirects"];
$result["redirectts"] = $state["redirectts"];
if (isset($result["rawsendsize"])) $state["totalrawsendsize"] += $result["rawsendsize"];
$result["totalrawsendsize"] = $state["totalrawsendsize"];
if (!$result["success"]) return array("success" => false, "error" => self::WBTranslate("Unable to retrieve content. %s", $result["error"]), "info" => $result, "state" => $state, "errorcode" => "retrievewebpage");
if (isset($state["options"]["async"]) && $state["options"]["async"])
{
$state["async"] = true;
$state["httpstate"] = $result["state"];
$state["state"] = "process_async";
}
else
{
$state["result"] = $result;
$state["state"] = "post_retrieval";
}
break;
}
case "process_async":
{
// Run a cycle of the HTTP state processor.
$result = HTTP::ProcessState($state["httpstate"]);
if (!$result["success"]) return $result;
$result["url"] = $state["url"];
$result["options"] = $state["options"];
unset($result["options"]["files"]);
unset($result["options"]["body"]);
$result["firstreqts"] = $state["startts"];
$result["numredirects"] = $state["numredirects"];
$result["redirectts"] = $state["redirectts"];
if (isset($result["rawsendsize"])) $state["totalrawsendsize"] += $result["rawsendsize"];
$result["totalrawsendsize"] = $state["totalrawsendsize"];
$state["httpstate"] = false;
$state["result"] = $result;
$state["state"] = "post_retrieval";
break;
}
case "post_retrieval":
{
// Set up structures for another round.
if ($this->data["autoreferer"]) $this->data["referer"] = $state["url"];
if (isset($state["result"]["headers"]["Location"]) && $this->data["followlocation"])
{
$state["redirectts"] = microtime(true);
unset($state["tempoptions"]["method"]);
unset($state["tempoptions"]["write_body_callback"]);
unset($state["tempoptions"]["body"]);
unset($state["tempoptions"]["postvars"]);
unset($state["tempoptions"]["files"]);
$state["tempoptions"]["headers"]["Referer"] = $state["url"];
$state["url"] = $state["result"]["headers"]["Location"][0];
// Generate an absolute URL.
if ($this->data["referer"] != "") $state["url"] = HTTP::ConvertRelativeToAbsoluteURL($this->data["referer"], $state["url"]);
$urlinfo2 = HTTP::ExtractURL($state["url"]);
if (!isset($this->data["allowedredirprotocols"][$urlinfo2["scheme"]]) || !$this->data["allowedredirprotocols"][$urlinfo2["scheme"]])
{
return array("success" => false, "error" => self::WBTranslate("Protocol '%s' is not allowed. Server attempted to redirect to '%s'.", $urlinfo2["scheme"], $state["url"]), "info" => $state["result"], "errorcode" => "allowed_redir_protocols");
}
if ($urlinfo2["host"] != $state["urlinfo"]["host"])
{
unset($state["tempoptions"]["headers"]["Host"]);
unset($state["httpopts"]["headers"]["Host"]);
unset($state["httpopts"]["headers"]["Authorization"]);
unset($state["tempoptions"]["headers"]["Authorization"]);
}
$state["urlinfo"] = $urlinfo2;
$state["numredirects"]++;
}
// Handle any 'Set-Cookie' headers.
if (isset($state["result"]["headers"]["Set-Cookie"]))
{
foreach ($state["result"]["headers"]["Set-Cookie"] as $cookie)
{
$items = explode(";", $cookie);
$item = trim(array_shift($items));
if ($item != "")
{
$cookie2 = array();
$pos = strpos($item, "=");
if ($pos === false)
{
$cookie2["name"] = urldecode($item);
$cookie2["value"] = "";
}
else
{
$cookie2["name"] = urldecode(substr($item, 0, $pos));
$cookie2["value"] = urldecode(substr($item, $pos + 1));
}
$cookie = array();
foreach ($items as $item)
{
$item = trim($item);
if ($item != "")
{
$pos = strpos($item, "=");
if ($pos === false) $cookie[strtolower(trim(urldecode($item)))] = "";
else $cookie[strtolower(trim(urldecode(substr($item, 0, $pos))))] = urldecode(substr($item, $pos + 1));
}
}
$cookie = array_merge($cookie, $cookie2);
if (isset($cookie["expires"]))
{
$ts = HTTP::GetDateTimestamp($cookie["expires"]);
$cookie["expires_ts"] = gmdate("Y-m-d H:i:s", ($ts === false ? time() - 24 * 60 * 60 : $ts));
}
else if (isset($cookie["max-age"]))
{
$cookie["expires_ts"] = gmdate("Y-m-d H:i:s", time() + (int)$cookie["max-age"]);
}
else
{
unset($cookie["expires_ts"]);
}
if (!isset($cookie["domain"])) $cookie["domain"] = $state["dothost"];
if (!isset($cookie["path"])) $cookie["path"] = $state["cookiepath"];
$this->SetCookie($cookie);
}
}
}
if ($state["numfollow"] > 0) $state["numfollow"]--;
// If this is a redirect, handle it by starting over.
if (isset($state["result"]["headers"]["Location"]) && $this->data["followlocation"] && $state["numfollow"])
{
$state["result"] = false;
$state["state"] = "initialize";
}
else
{
$state["result"]["numredirects"] = $state["numredirects"];
$state["result"]["redirectts"] = $state["redirectts"];
// Extract the forms from the page in a parsed format.
// Call WebBrowser::GenerateFormRequest() to prepare an actual request for Process().
if ($this->data["extractforms"]) $state["result"]["forms"] = $this->ExtractForms($state["result"]["url"], $state["result"]["body"], (isset($state["tempoptions"]["extractforms_hint"]) ? $state["tempoptions"]["extractforms_hint"] : false));
$state["state"] = "done";
}
break;
}
}
}
return $state["result"];
}
public function Process($url, $tempoptions = array())
{
$startts = microtime(true);
$redirectts = $startts;
// Handle older function call: Process($url, $profile, $tempoptions)
if (is_string($tempoptions))
{
$args = func_get_args();
if (count($args) < 3) $tempoptions = array();
else $tempoptions = $args[2];
$tempoptions["profile"] = $args[1];
}
$profile = (isset($tempoptions["profile"]) ? $tempoptions["profile"] : "auto");
if (isset($tempoptions["timeout"])) $timeout = $tempoptions["timeout"];
else if (isset($this->data["httpopts"]["timeout"])) $timeout = $this->data["httpopts"]["timeout"];
else $timeout = false;
// Deal with possible application hanging issues.
if (isset($tempoptions["streamtimeout"])) $streamtimeout = $tempoptions["streamtimeout"];
else if (isset($this->data["httpopts"]["streamtimeout"])) $streamtimeout = $this->data["httpopts"]["streamtimeout"];
else $streamtimeout = 300;
$tempoptions["streamtimeout"] = $streamtimeout;
if (!isset($this->data["httpopts"]["headers"])) $this->data["httpopts"]["headers"] = array();
$this->data["httpopts"]["headers"] = HTTP::NormalizeHeaders($this->data["httpopts"]["headers"]);
unset($this->data["httpopts"]["method"]);
unset($this->data["httpopts"]["write_body_callback"]);
unset($this->data["httpopts"]["body"]);
unset($this->data["httpopts"]["postvars"]);
unset($this->data["httpopts"]["files"]);
$httpopts = $this->data["httpopts"];
$numfollow = $this->data["maxfollow"];
$numredirects = 0;
$totalrawsendsize = 0;
if (!isset($tempoptions["headers"])) $tempoptions["headers"] = array();
$tempoptions["headers"] = HTTP::NormalizeHeaders($tempoptions["headers"]);
if (isset($tempoptions["headers"]["Referer"])) $this->data["referer"] = $tempoptions["headers"]["Referer"];
// If a referrer is specified, use it to generate an absolute URL.
if ($this->data["referer"] != "") $url = HTTP::ConvertRelativeToAbsoluteURL($this->data["referer"], $url);
$urlinfo = HTTP::ExtractURL($url);
// Initialize the process state array.
$state = array(
"async" => false,
"startts" => $startts,
"redirectts" => $redirectts,
"timeout" => $timeout,
"tempoptions" => $tempoptions,
"httpopts" => $httpopts,
"numfollow" => $numfollow,
"numredirects" => $numredirects,
"totalrawsendsize" => $totalrawsendsize,
"profile" => $profile,
"url" => $url,
"urlinfo" => $urlinfo,
"state" => "initialize",
"httpstate" => false,
"result" => false,
);
// Run at least one state cycle to properly initialize the state array.
$result = $this->ProcessState($state);
// Return the state for async calls. Caller must call ProcessState().
if ($state["async"]) return array("success" => true, "state" => $state);
return $result;
}
// Implements the correct MultiAsyncHelper responses for WebBrowser instances.
public function ProcessAsync__Handler($mode, &$data, $key, &$info)
{
switch ($mode)
{
case "init":
{
if ($info["init"]) $data = $info["keep"];
else
{
$info["result"] = $this->Process($info["url"], $info["tempoptions"]);
if (!$info["result"]["success"])
{
$info["keep"] = false;
if (is_callable($info["callback"])) call_user_func_array($info["callback"], array($key, $info["url"], $info["result"]));
}
else
{
$info["state"] = $info["result"]["state"];
// Move to the live queue.
$data = true;
}
}
break;
}
case "update":
case "read":
case "write":
{
if ($info["keep"])
{
$info["result"] = $this->ProcessState($info["state"]);
if ($info["result"]["success"] || $info["result"]["errorcode"] !== "no_data") $info["keep"] = false;
if (is_callable($info["callback"])) call_user_func_array($info["callback"], array($key, $info["url"], $info["result"]));
if ($mode === "update") $data = $info["keep"];
}
break;
}
case "readfps":
{
if ($info["state"]["httpstate"] !== false && HTTP::WantRead($info["state"]["httpstate"])) $data[$key] = $info["state"]["httpstate"]["fp"];
break;
}
case "writefps":
{
if ($info["state"]["httpstate"] !== false && HTTP::WantWrite($info["state"]["httpstate"])) $data[$key] = $info["state"]["httpstate"]["fp"];
break;
}
case "cleanup":
{
// When true, caller is removing. Otherwise, detaching from the queue.
if ($data === true)
{
if (isset($info["state"]))
{
if ($info["state"]["httpstate"] !== false) HTTP::ForceClose($info["state"]["httpstate"]);
unset($info["state"]);
}
$info["keep"] = false;
}
break;
}
}
}
public function ProcessAsync($helper, $key, $callback, $url, $tempoptions = array())
{
$tempoptions["async"] = true;
// Handle older function call: ProcessAsync($helper, $key, $callback, $url, $profile, $tempoptions)
if (is_string($tempoptions))
{
$args = func_get_args();
if (count($args) < 6) $tempoptions = array();
else $tempoptions = $args[5];
$tempoptions["profile"] = $args[4];
}
$profile = (isset($tempoptions["profile"]) ? $tempoptions["profile"] : "auto");
$info = array(
"init" => false,
"keep" => true,
"callback" => $callback,
"url" => $url,
"tempoptions" => $tempoptions,
"result" => false
);
$helper->Set($key, $info, array($this, "ProcessAsync__Handler"));
return array("success" => true);
}
public function ExtractForms($baseurl, $data, $hint = false)
{
$result = array();
$lasthint = "";
$hintmap = array();
if ($this->html === false)
{
if (!class_exists("simple_html_dom", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/simple_html_dom.php";
$this->html = new simple_html_dom();
}
$this->html->load($data);
$rows = $this->html->find("label[for]");
foreach ($rows as $row)
{
$hintmap[trim($row->for)] = trim($row->plaintext);
}
$html5rows = $this->html->find("input[form],textarea[form],select[form],button[form],datalist[id]" . ($hint !== false ? "," . $hint : ""));
$rows = $this->html->find("form");
foreach ($rows as $row)
{
$info = array();
if (isset($row->id)) $info["id"] = trim($row->id);
if (isset($row->name)) $info["name"] = (string)$row->name;
$info["action"] = (isset($row->action) ? HTTP::ConvertRelativeToAbsoluteURL($baseurl, (string)$row->action) : $baseurl);
$info["method"] = (isset($row->method) && strtolower(trim($row->method)) == "post" ? "post" : "get");
if ($info["method"] == "post") $info["enctype"] = (isset($row->enctype) ? strtolower($row->enctype) : "application/x-www-form-urlencoded");
if (isset($row->{"accept-charset"})) $info["accept-charset"] = (string)$row->{"accept-charset"};
$fields = array();
$rows2 = $row->find("input,textarea,select,button" . ($hint !== false ? "," . $hint : ""));
foreach ($rows2 as $row2)
{
if (!isset($row2->form))
{
if (isset($row2->id) && $row2->id != "" && isset($hintmap[trim($row2->id)])) $lasthint = $hintmap[trim($row2->id)];
$this->ExtractFieldFromDOM($fields, $row2, $lasthint);
}
}
// Handle HTML5.
if (isset($info["id"]) && $info["id"] != "")
{
foreach ($html5rows as $row2)
{
if (strpos(" " . $info["id"] . " ", " " . $row2->form . " ") !== false)
{
if (isset($hintmap[$info["id"]])) $lasthint = $hintmap[$info["id"]];
$this->ExtractFieldFromDOM($fields, $row2, $lasthint);
}
}
}
$form = new WebBrowserForm();
$form->info = $info;
$form->fields = $fields;
$result[] = $form;
}
return $result;
}
private function ExtractFieldFromDOM(&$fields, $row, &$lasthint)
{
switch ($row->tag)
{
case "input":
{
if (!isset($row->name) && ($row->type === "submit" || $row->type === "image")) $row->name = "";
if (isset($row->name) && is_string($row->name))
{
$field = array(
"id" => (isset($row->id) ? (string)$row->id : false),
"type" => "input." . (isset($row->type) ? strtolower($row->type) : "text"),
"name" => $row->name,
"value" => (isset($row->value) ? html_entity_decode($row->value, ENT_COMPAT, "UTF-8") : "")
);
if ($field["type"] == "input.radio" || $field["type"] == "input.checkbox")
{
$field["checked"] = (isset($row->checked));
if ($field["value"] === "") $field["value"] = "on";
}
if (isset($row->placeholder)) $field["hint"] = trim($row->placeholder);
else if ($field["type"] == "input.submit" || $field["type"] == "input.image") $field["hint"] = $field["type"] . "|" . $field["value"];
else if ($lasthint !== "") $field["hint"] = $lasthint;
$fields[] = $field;
$lasthint = "";
}
break;
}
case "textarea":
{
if (isset($row->name) && is_string($row->name))
{
$field = array(
"id" => (isset($row->id) ? (string)$row->id : false),
"type" => "textarea",
"name" => $row->name,
"value" => html_entity_decode($row->innertext, ENT_COMPAT, "UTF-8")
);
if (isset($row->placeholder)) $field["hint"] = trim($row->placeholder);
else if ($lasthint !== "") $field["hint"] = $lasthint;
$fields[] = $field;
$lasthint = "";
}
break;
}
case "select":
{
if (isset($row->name) && is_string($row->name))
{
if (isset($row->multiple))
{
// Change the type into multiple checkboxes.
$rows = $row->find("option");
foreach ($rows as $row2)
{
$field = array(
"id" => (isset($row->id) ? (string)$row->id : false),
"type" => "input.checkbox",
"name" => $row->name,
"value" => (isset($row2->value) ? html_entity_decode($row2->value, ENT_COMPAT, "UTF-8") : ""),
"display" => (string)$row2->innertext
);
if ($lasthint !== "") $field["hint"] = $lasthint;
$fields[] = $field;
}
}
else
{
$val = false;
$options = array();
$rows = $row->find("option");
foreach ($rows as $row2)
{
$options[$row2->value] = (string)$row2->innertext;
if ($val === false && isset($row2->selected)) $val = html_entity_decode($row2->value, ENT_COMPAT, "UTF-8");
}
if ($val === false && count($options))
{
$val = array_keys($options);
$val = $val[0];
}
if ($val === false) $val = "";
$field = array(
"id" => (isset($row->id) ? (string)$row->id : false),
"type" => "select",
"name" => $row->name,
"value" => $val,
"options" => $options
);
if ($lasthint !== "") $field["hint"] = $lasthint;
$fields[] = $field;
}
$lasthint = "";
}
break;
}
case "button":
{
if (isset($row->name) && is_string($row->name))
{
$field = array(
"id" => (isset($row->id) ? (string)$row->id : false),
"type" => "button." . (isset($row->type) ? strtolower($row->type) : "submit"),
"name" => $row->name,
"value" => (isset($row->value) ? html_entity_decode($row->value, ENT_COMPAT, "UTF-8") : "")
);
$field["hint"] = (trim($row->plaintext) !== "" ? trim($row->plaintext) : "button|" . $field["value"]);
$fields[] = $field;
$lasthint = "";
}
break;
}
case "datalist":
{
// Do nothing since browsers don't actually enforce this tag's values.
break;
}
default:
{
// Hint for the next element.
$lasthint = (string)$row->plaintext;
break;
}
}
}
public static function InteractiveFormFill($forms, $showselected = false)
{
if (!is_array($forms)) $forms = array($forms);
if (!count($forms)) return false;
if (count($forms) == 1) $form = reset($forms);
else
{
echo self::WBTranslate("There are multiple forms available to fill out:\n");
foreach ($forms as $num => $form)
{
echo self::WBTranslate("\t%d:\n", $num + 1);
foreach ($form->info as $key => $val) echo self::WBTranslate("\t\t%s: %s\n", $key, $val);
echo self::WBTranslate("\t\tfields: %d\n", count($form->GetVisibleFields(false)));
echo self::WBTranslate("\t\tbuttons: %d\n", count($form->GetVisibleFields(true)) - count($form->GetVisibleFields(false)));
echo "\n";
}
do
{
echo self::WBTranslate("Select: ");
$num = (int)trim(fgets(STDIN)) - 1;
} while (!isset($forms[$num]));
$form = $forms[$num];
}
if ($showselected)
{
echo self::WBTranslate("Selected form:\n");
foreach ($form->info as $key => $val) echo self::WBTranslate("\t%s: %s\n", $key, $val);
echo "\n";
}
if (count($form->GetVisibleFields(false)))
{
echo self::WBTranslate("Select form fields by field number to edit a field. When ready to submit the form, leave 'Field number' empty.\n\n");
do
{
echo self::WBTranslate("Editable form fields:\n");
foreach ($form->fields as $num => $field)
{
if ($field["type"] == "input.hidden" || $field["type"] == "input.submit" || $field["type"] == "input.image" || $field["type"] == "input.button" || substr($field["type"], 0, 7) == "button.") continue;
echo self::WBTranslate("\t%d: %s - %s\n", $num + 1, $field["name"], (is_array($field["value"]) ? json_encode($field["value"], JSON_PRETTY_PRINT) : $field["value"]) . (($field["type"] == "input.radio" || $field["type"] == "input.checkbox") ? ($field["checked"] ? self::WBTranslate(" [Y]") : self::WBTranslate(" [N]")) : "") . (isset($field["hint"]) && $field["hint"] !== "" ? " [" . $field["hint"] . "]" : ""));
}
echo "\n";
do
{
echo self::WBTranslate("Field number: ");
$num = trim(fgets(STDIN));
if ($num === "") break;
$num = (int)$num - 1;
} while (!isset($form->fields[$num]) || $form->fields[$num]["type"] == "input.hidden" || $form->fields[$num]["type"] == "input.submit" || $form->fields[$num]["type"] == "input.image" || $form->fields[$num]["type"] == "input.button" || substr($form->fields[$num]["type"], 0, 7) == "button.");
if ($num === "")
{
echo "\n";
break;
}
$field = $form->fields[$num];
$prefix = (isset($field["hint"]) && $field["hint"] !== "" ? $field["hint"] . " | " : "") . $field["name"];
if ($field["type"] == "select")
{
echo self::WBTranslate("[%s] Options:\n", $prefix);
foreach ($field["options"] as $key => $val)
{
echo self::WBTranslate("\t%s: %s\n");
}
do
{
echo self::WBTranslate("[%s] Select: ", $prefix);
$select = rtrim(fgets(STDIN));
} while (!isset($field["options"][$select]));
$form->fields[$num]["value"] = $select;
}
else if ($field["type"] == "input.radio")
{
$form->SetFormValue($field["name"], $field["value"], true, "input.radio");
}
else if ($field["type"] == "input.checkbox")
{
$form->fields[$num]["checked"] = !$field["checked"];
}
else if ($field["type"] == "input.file")
{
do
{
echo self::WBTranslate("[%s] Filename: ", $prefix);
$filename = rtrim(fgets(STDIN));
} while ($filename !== "" && !file_exists($filename));
if ($filename === "") $form->fields[$num]["value"] = "";
else
{
$form->fields[$num]["value"] = array(
"filename" => $filename,
"type" => "application/octet-stream",
"datafile" => $filename
);
}
}
else
{
echo self::WBTranslate("[%s] New value: ", $prefix);
$form->fields[$num]["value"] = rtrim(fgets(STDIN));
}
echo "\n";
} while (1);
}
$submitoptions = array(array("name" => self::WBTranslate("Default action"), "value" => self::WBTranslate("Might not work"), "hint" => "Default action"));
foreach ($form->fields as $num => $field)
{
if ($field["type"] != "input.submit" && $field["type"] != "input.image" && $field["type"] != "input.button" && $field["type"] != "button.submit") continue;
$submitoptions[] = $field;
}
if (count($submitoptions) <= 2) $num = count($submitoptions) - 1;
else
{
echo self::WBTranslate("Available submit buttons:\n");
foreach ($submitoptions as $num => $field)
{
echo self::WBTranslate("\t%d: %s - %s\n", $num, $field["name"], $field["value"] . (isset($field["hint"]) && $field["hint"] !== "" ? " [" . $field["hint"] . "]" : ""));
}
echo "\n";
do
{
echo self::WBTranslate("Select: ");
$num = (int)fgets(STDIN);
} while (!isset($submitoptions[$num]));
echo "\n";
}
$result = $form->GenerateFormRequest(($num ? $submitoptions[$num]["name"] : false), ($num ? $submitoptions[$num]["value"] : false));
return $result;
}
public function GetCookies()
{
return $this->data["cookies"];
}
public function SetCookie($cookie)
{
if (!isset($cookie["domain"]) || !isset($cookie["path"]) || !isset($cookie["name"]) || !isset($cookie["value"])) return array("success" => false, "error" => self::WBTranslate("SetCookie() requires 'domain', 'path', 'name', and 'value' to be options."), "errorcode" => "missing_information");
$cookie["domain"] = strtolower($cookie["domain"]);
if (substr($cookie["domain"], 0, 1) != ".") $cookie["domain"] = "." . $cookie["domain"];
$cookie["path"] = str_replace("\\", "/", $cookie["path"]);
if (substr($cookie["path"], -1) != "/") $cookie["path"] = "/";
if (!isset($this->data["cookies"][$cookie["domain"]])) $this->data["cookies"][$cookie["domain"]] = array();
if (!isset($this->data["cookies"][$cookie["domain"]][$cookie["path"]])) $this->data["cookies"][$cookie["domain"]][$cookie["path"]] = array();
$this->data["cookies"][$cookie["domain"]][$cookie["path"]][$cookie["name"]] = $cookie;
return array("success" => true);
}
// Simulates closing a web browser.
public function DeleteSessionCookies()
{
foreach ($this->data["cookies"] as $domain => $paths)
{
foreach ($paths as $path => $cookies)
{
foreach ($cookies as $num => $info)
{
if (!isset($info["expires_ts"])) unset($this->data["cookies"][$domain][$path][$num]);
}
if (!count($this->data["cookies"][$domain][$path])) unset($this->data["cookies"][$domain][$path]);
}
if (!count($this->data["cookies"][$domain])) unset($this->data["cookies"][$domain]);
}
}
public function DeleteCookies($domainpattern, $pathpattern, $namepattern)
{
foreach ($this->data["cookies"] as $domain => $paths)
{
if ($domainpattern == "" || substr($domain, -strlen($domainpattern)) == $domainpattern)
{
foreach ($paths as $path => $cookies)
{
if ($pathpattern == "" || substr($path, 0, strlen($pathpattern)) == $pathpattern)
{
foreach ($cookies as $num => $info)
{
if ($namepattern == "" || strpos($info["name"], $namepattern) !== false) unset($this->data["cookies"][$domain][$path][$num]);
}
if (!count($this->data["cookies"][$domain][$path])) unset($this->data["cookies"][$domain][$path]);
}
}
if (!count($this->data["cookies"][$domain])) unset($this->data["cookies"][$domain]);
}
}
}
private function GetExpiresTimestamp($ts)
{
$year = (int)substr($ts, 0, 4);
$month = (int)substr($ts, 5, 2);
$day = (int)substr($ts, 8, 2);
$hour = (int)substr($ts, 11, 2);
$min = (int)substr($ts, 14, 2);
$sec = (int)substr($ts, 17, 2);
return gmmktime($hour, $min, $sec, $month, $day, $year);
}
public static function WBTranslate()
{
$args = func_get_args();
if (!count($args)) return "";
return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args);
}
}
class WebBrowserForm
{
public $info, $fields;
public function __construct()
{
$this->info = array();
$this->fields = array();
}
public function FindFormFields($name = false, $value = false, $type = false)
{
$fields = array();
foreach ($this->fields as $num => $field)
{
if (($type === false || $field["type"] === $type) && ($name === false || $field["name"] === $name) && ($value === false || $field["value"] === $value))
{
$fields[] = $field;
}
}
return $fields;
}
public function GetHintMap()
{
$result = array();
foreach ($this->fields as $num => $field)
{
if (isset($field["hint"])) $result[$field["hint"]] = $field["name"];
}
return $result;
}
public function GetVisibleFields($submit)
{
$result = array();
foreach ($this->fields as $num => $field)
{
if ($field["type"] == "input.hidden" || (!$submit && ($field["type"] == "input.submit" || $field["type"] == "input.image" || $field["type"] == "input.button" || substr($field["type"], 0, 7) == "button."))) continue;
$result[$num] = $field;
}
return $result;
}
public function GetFormValue($name, $checkval = false, $type = false)
{
$val = false;
foreach ($this->fields as $field)
{
if (($type === false || $field["type"] === $type) && $field["name"] === $name)
{
if (is_string($checkval))
{
if ($checkval === $field["value"])
{
if ($field["type"] == "input.radio" || $field["type"] == "input.checkbox") $val = $field["checked"];
else $val = $field["value"];
}
}
else if (($field["type"] != "input.radio" && $field["type"] != "input.checkbox") || $field["checked"])
{
$val = $field["value"];
}
}
}
return $val;
}
public function SetFormValue($name, $value, $checked = false, $type = false, $create = false)
{
$result = false;
foreach ($this->fields as $num => $field)
{
if (($type === false || $field["type"] === $type) && $field["name"] === $name)
{
if ($field["type"] == "input.radio")
{
$this->fields[$num]["checked"] = ($field["value"] === $value ? $checked : false);
$result = true;
}
else if ($field["type"] == "input.checkbox")
{
if ($field["value"] === $value) $this->fields[$num]["checked"] = $checked;
$result = true;
}
else if ($field["type"] != "select" || !isset($field["options"]) || isset($field["options"][$value]))
{
$this->fields[$num]["value"] = $value;
$result = true;
}
}
}
// Add the field if it doesn't exist.
if (!$result && $create)
{
$this->fields[] = array(
"id" => false,
"type" => ($type !== false ? $type : "input.text"),
"name" => $name,
"value" => $value,
"checked" => $checked
);
}
return $result;
}
public function GenerateFormRequest($submitname = false, $submitvalue = false)
{
$method = $this->info["method"];
$fields = array();
$files = array();
foreach ($this->fields as $field)
{
if ($field["type"] == "input.file")
{
if (is_array($field["value"]))
{
$field["value"]["name"] = $field["name"];
$files[] = $field["value"];
$method = "post";
}
}
else if ($field["type"] == "input.reset" || $field["type"] == "button.reset")
{
}
else if ($field["type"] == "input.submit" || $field["type"] == "input.image" || $field["type"] == "button.submit")
{
if (($submitname === false || $field["name"] === $submitname) && ($submitvalue === false || $field["value"] === $submitvalue))
{
if ($submitname !== "")
{
if (!isset($fields[$field["name"]])) $fields[$field["name"]] = array();
$fields[$field["name"]][] = $field["value"];
}
if ($field["type"] == "input.image")
{
if (!isset($fields["x"])) $fields["x"] = array();
$fields["x"][] = "1";
if (!isset($fields["y"])) $fields["y"] = array();
$fields["y"][] = "1";
}
}
}
else if (($field["type"] != "input.radio" && $field["type"] != "input.checkbox") || $field["checked"])
{
if (!isset($fields[$field["name"]])) $fields[$field["name"]] = array();
$fields[$field["name"]][] = $field["value"];
}
}
if ($method == "get")
{
$url = HTTP::ExtractURL($this->info["action"]);
unset($url["query"]);
$url["queryvars"] = $fields;
$result = array(
"url" => HTTP::CondenseURL($url),
"options" => array()
);
}
else
{
$result = array(
"url" => $this->info["action"],
"options" => array(
"postvars" => $fields,
"files" => $files
)
);
}
return $result;
}
}
?>