(Original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
class OpenSearchEngineUrl extends Object {
var $params = array();
var $method, $template;
function OpenSearchEngineUrl($method, $template) {
$this->method = $method;
$this->template = $template;
}
/**
* Adds a parameter to this URL.
* @param string $name
* @param string $value
*/
function addParam($name, $value) {
if ($name != '' && $value != '') {
array_push($this->params, array($name, $value));
}
}
/**
* Performs OpenSearch parameter replacement on $str.
* This function is meant to eliminate dynamic parameters from the
* OpenSearch description so that it can be serialized to Sherlock, which
* doesn't support any dynamic parameters. This function uses the same
* logic as Firefox to choose reasonable default values for required
* parameters in the input string. See:
* http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla/browser/components/search/nsSearchService.js&rev=1.88#754
* @param string $inputEncoding the value used to replace the
* {inputEncoding} parameter, if present.
* @param string $str the string containing the dynamic parameters to
* replace.
*/
function paramSubstitution($inputEncoding, $str) {
// Insert the OpenSearch parameters we're confident about
$supportedParams = array('/\{inputEncoding\??\}/', '/\{language\??\}/',
'/\{outputEncoding\??\}/');
// Specified inputEncoding, "*" for "all languages", and "UTF-8" for
// outputEncoding. This matches the values Firefox uses by default.
$values = array($inputEncoding, '*', 'UTF-8');
// Replace supported parameters
$str = preg_replace($supportedParams, $values, $str);
// Remove all optional parameters (any parameter with a trailing "?")
$str = preg_replace('/\{\w+\?\}/', '', $str);
// Replace any remaining parameters that we recognize with reasonable
// default values.
$otherParams = array('/\{count\}/', '/\{startIndex\}/',
'/\{startPage\}/');
// 20 search results, start at page 1, index 1. These values are also
// based on Firefox's defaults.
$str = preg_replace($otherParams, array('20', '1', '1'), $str);
return $str;
}
function getTemplate($inputEncoding) {
return $this->paramSubstitution($inputEncoding, $this->template);
}
function getParams($inputEncoding) {
$params = array();
foreach ($this->params as $param) {
$name = $param[0];
$value = $this->paramSubstitution($inputEncoding, $param[1]);
if ($value) {
array_push($params, array($name, $value));
}
}
return $params;
}
}
class OpenSearchEngine extends Object {
var $urls = array();
var $name, $alias, $description, $inputEncoding, $outputEncoding;
var $searchForm, $updateInterval, $updateUrl, $iconUpdateUrl;
var $imageUrl;
/**
* Gets an array of parameters corresponding to a given URL.
* @param string $urlType the content type of the URL
* Returns NULL if this engine has no URL of type $urlType.
*/
function getParams($urlType) {
if (!isset($this->urls[$urlType])) {
return NULL;
}
return $this->urls[$urlType]->getParams($this->inputEncoding);
}
/**
* Gets the template parameter for a given URL.
* @param string $urlType the content type of the URL
* Returns NULL if this engine has no URL of type $urlType.
*/
function getTemplate($urlType) {
if (!isset($this->urls[$urlType])) {
return NULL;
}
return $this->urls[$urlType]->getTemplate($this->inputEncoding);
}
// Utility function to print a Sherlock attribute.
function prAttr($attr, $val) {
if ($val) {
return " $attr=\"" . $val . "\"\n";
}
return '';
}
/**
* Serialize this engine to a to a valid Sherlock string. Returns NULL if
* the engine can't be represented by a Sherlock description file.
*/
function toSherlock() {
$htmlUrl =& $this->urls['text/html'];
if ($htmlUrl->method != 'GET') {
// Sherlock only supports GET plugins
return NULL;
}
$template = $this->getTemplate('text/html');
$firstInput = '';
$foundUserInput = false;
if (preg_match('/{searchTerms}/', $template)) {
if (!preg_match('/{searchTerms}$/', $template)) {
// Sherlock can't deal with templates that have the search
// terms embedded in them, unless the search terms are simply
// appended to the template.
return NULL;
}
$template = preg_replace('/{searchTerms}$/', '', $template);
// Add a special input that tells Firefox to simply append the
// user's search terms to the end of the template. This has to be
// the first input for this to work. See:
// http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla/xpfe/components/search/src/nsInternetSearchService.cpp&rev=1.251#4637
$firstInput = "\n";
$foundUserInput = true;
}
$queryCharset = $this->inputEncoding;
// Search section
$srcString = "prAttr('name', $this->name);
$srcString .= $this->prAttr('action', $template);
$srcString .= $this->prAttr('method', $htmlUrl->method);
$srcString .= $this->prAttr('searchForm', $this->searchForm);
$srcString .= $this->prAttr('description', $this->description);
$srcString .= $this->prAttr('queryCharset', $queryCharset);
$srcString .= ">\n";
// print inputs
$srcString .= $firstInput;
$params = $this->getParams('text/html');
foreach ($params as $param) {
$name = $param[0]; $value = $param[1];
if (preg_match('/{searchTerms}/', $value)) {
if (!preg_match('/^{searchTerms}$/', $value)) {
return NULL;
}
$srcString .= "\n";
$foundUserInput = true;
} else {
$srcString .= "\n";
}
}
// Check that we found a valid user input
if (!$foundUserInput) {
return NULL;
}
$srcString .= "\n";
// Browser section (Mozilla extension to Sherlock, used for updates)
if ($this->updateUrl || $this->iconUpdateUrl || $this->updateInterval) {
$srcString .= "\nprAttr('update', $this->updateUrl);
$srcString .= $this->prAttr('updateIcon', $this->iconUpdateUrl);
$srcString .= $this->prAttr('updateCheckDays', $this->updateInterval);
$srcString .= ">\n";
}
return $srcString;
}
// Returns raw bytes of the engine's image, if it has one, or NULL
// otherwise
function getImage() {
if (!$this->imageUrl ||
!preg_match('/^data\:/', $this->imageUrl)) {
return NULL;
}
$base64Str = preg_replace('/^data\:.+base64,/', '', $this->imageUrl);
if (!($bytes = base64_decode($base64Str))) {
return NULL;
}
return $bytes;
}
}
class OpensearchComponent extends Object {
// Initialize variables
var $in = false;
var $wasIn = false;
var $curEl = NULL;
var $curUrlType = NULL;
var $attrs = array();
var $tagCount = array();
var $gotData = array();
var $depth = 0;
var $engine = NULL;
/**
* XML parsing callback functions.
*/
function start_element($parser, $name, $attrs) {
if ($name == "OpenSearchDescription") {
if ($this->in || $this->wasIn) {
// Multiple OpenSearchDescription elements - bail out
return;
}
// Test the namespace
// These are the namespaces that Firefox supports
$validNamespaces =
array("http://a9.com/-/spec/opensearch/1.0/",
"http://a9.com/-/spec/opensearch/1.1/",
"http://a9.com/-/spec/opensearchdescription/1.1/",
"http://a9.com/-/spec/opensearchdescription/1.0/");
if (isset($attrs['xmlns']) &&
in_array($attrs['xmlns'], $validNamespaces)) {
$this->in = true;
$this->wasIn = true;
$this->engine =& new OpenSearchEngine();
return;
}
}
if (!$this->in) {
return;
}
$this->depth++;
// Since s are the only elements that can have child elements, and
// their only valid child elements are s, we can ignore this
// element if it has a depth greater than 1 and isn't a that's
// a child of a .
$processingParam = ($this->curUrlType && $name == "Param");
if ($this->depth != 1 && !$processingParam) {
return;
}
if ($processingParam) {
$urls =& $this->engine->urls;
$urls[$this->curUrlType]->addParam($attrs['name'], $attrs['value']);
return;
}
switch ($name) {
case "ShortName":
case "Description":
case "InputEncoding":
case "UpdateUrl":
case "UpdateInterval":
case "IconUpdateUrl":
case "Alias":
case "SearchForm":
case "OutputEncoding":
$this->tagCount[$name]++;
$this->curEl = $name;
break;
case "Image":
// Ignore image elements that aren't supported by Firefox
if (isset($attrs["width"]) && $attrs["width"] == 16 &&
isset($attrs["height"]) && $attrs["height"] == 16) {
$this->tagCount[$name]++;
$this->curEl = $name;
}
break;
case "Url":
// Validate the URL element. We only care about https?://
// templates, and text/html types.
$OS_SUPPORTED_TYPES = array("text/html", "application/x-suggestions+json");
$OS_SUPPORTED_METHODS = array("POST", "GET");
$type = strtolower($attrs["type"]);
$method = (isset($attrs["method"])) ? strtoupper($attrs["method"])
: 'GET';
$template = $attrs["template"];
if (preg_match('/^https?/i', $template) &&
in_array($type, $OS_SUPPORTED_TYPES) &&
in_array($method, $OS_SUPPORTED_METHODS)) {
$this->curUrlType = $type;
$this->engine->urls[$type] =& new OpenSearchEngineUrl($method, $template);
}
break;
}
}
function stop_element($parser, $name) {
if ($name == "OpenSearchDescription") {
$this->in = false;
return;
}
$this->depth--;
$this->curEl = NULL;
if ($name == "Url") {
$this->curUrlType = NULL;
}
}
function char_data($parser, $data) {
if ($this->curEl) {
$name = $this->curEl;
if (!$this->gotData[$name]) {
$this->gotData[$name] = $this->tagCount[$name];
}
if ($this->gotData[$name] != $this->tagCount[$name]) {
// We hit a new tag, clobber the existing data.
$this->attrs[$name] = $data;
$this->gotData[$name] = $this->tagCount[$name];
} else {
$this->attrs[$name] .= $data;
}
}
}
/**
* Resets the component's state so that it's ready to parse another file.
*/
function reset() {
$this->in = false;
$this->wasIn = false;
$this->curEl = NULL;
$this->curUrlType = NULL;
// Define empty elements to avoid "undefined index" errors
// Surely there is a better way to do this?
$this->attrs = array('ShortName' => '',
'Description' => '',
'InputEncoding' => '',
'UpdateUrl' => '',
'UpdateInterval' => '',
'IconUpdateUrl' => '',
'Alias' => '',
'SearchForm' => '',
'OutputEncoding' => '',
'Image' => '');
$this->tagCount = array('ShortName' => '',
'Description' => '',
'InputEncoding' => '',
'UpdateUrl' => '',
'UpdateInterval' => '',
'IconUpdateUrl' => '',
'Alias' => '',
'SearchForm' => '',
'OutputEncoding' => '',
'Image' => '');
$this->gotData = array('ShortName' => '',
'Description' => '',
'InputEncoding' => '',
'UpdateUrl' => '',
'UpdateInterval' => '',
'IconUpdateUrl' => '',
'Alias' => '',
'SearchForm' => '',
'OutputEncoding' => '',
'Image' => '');
$this->depth = 0;
$this->engine = NULL;
}
/**
* Parses OpenSearch description files and returns an OpenSearchEngine
* object.
* @param $file either a filename pointing to an XML OpenSearch
* description file, or a string containing an OS description file's
* contents.
*/
function parse($file) {
// Reset state
$this->reset();
if (file_exists($file)) {
$contents = file_get_contents($file);
} else {
$contents = $file;
}
// Create Expat parser
$parser = xml_parser_create();
// Set handler functions
xml_set_object($parser, $this);
xml_set_element_handler($parser, "start_element", "stop_element");
xml_set_character_data_handler($parser, "char_data");
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
// Parse the file
$ret = xml_parse($parser, $contents);
xml_parser_free($parser);
if (!$ret) {
return NULL;
}
// Check to ensure we found all required elements (name and valid Url)
if ($this->attrs['ShortName'] == '' ||
!isset($this->engine->urls['text/html'])) {
return NULL;
}
$this->engine->name = $this->attrs['ShortName'];
$this->engine->alias = $this->attrs['Alias'];
$this->engine->inputEncoding = $this->attrs['InputEncoding'];
$this->engine->updateUrl = $this->attrs['UpdateUrl'];
$this->engine->description = $this->attrs['Description'];
$this->engine->updateInterval = $this->attrs['UpdateInterval'];
$this->engine->iconUpdateUrl = $this->attrs['IconUpdateUrl'];
$this->engine->searchForm = $this->attrs['SearchForm'];
$this->engine->outputEncoding = $this->attrs['OutputEncoding'];
$this->engine->imageUrl = $this->attrs['Image'];
return $this->engine;
}
}
?>