diff --git a/config.sh b/config.sh index dbfcc8b6f..899a77f28 100644 --- a/config.sh +++ b/config.sh @@ -248,6 +248,13 @@ export SPOTNAB_TIMER="300" ############################################################ +#use kevin123's compression mod true/false +#this will copy kevin123's compression mod to the correct location when you update_svn.sh +#this ensures that kevin123's files are present after updaing svn +export KEVIN_COMP="false" + +############################################################ + #By using this script you understand that the programmer is not responsible for any loss of data, users, or sanity. #You also agree that you were smart enough to make a backup of your database and files. Do you agree? yes/no export AGREED="no" diff --git a/kevin123/Net_NNTP/LICENSE b/kevin123/Net_NNTP/LICENSE new file mode 100755 index 000000000..cee19f91e --- /dev/null +++ b/kevin123/Net_NNTP/LICENSE @@ -0,0 +1,48 @@ ++-----------------------------------------------------------------------+ +| | +| W3C® SOFTWARE NOTICE AND LICENSE | +| http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 | +| | +| This work (and included software, documentation such as READMEs, | +| or other related items) is being provided by the copyright holders | +| under the following license. By obtaining, using and/or copying | +| this work, you (the licensee) agree that you have read, understood, | +| and will comply with the following terms and conditions. | +| | +| Permission to copy, modify, and distribute this software and its | +| documentation, with or without modification, for any purpose and | +| without fee or royalty is hereby granted, provided that you include | +| the following on ALL copies of the software and documentation or | +| portions thereof, including modifications: | +| | +| 1. The full text of this NOTICE in a location viewable to users | +| of the redistributed or derivative work. | +| | +| 2. Any pre-existing intellectual property disclaimers, notices, | +| or terms and conditions. If none exist, the W3C Software Short | +| Notice should be included (hypertext is preferred, text is | +| permitted) within the body of any redistributed or derivative | +| code. | +| | +| 3. Notice of any changes or modifications to the files, including | +| the date changes were made. (We recommend you provide URIs to | +| the location from which the code is derived.) | +| | +| THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT | +| HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, | +| INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR | +| FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE | +| OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, | +| COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. | +| | +| COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, | +| SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE | +| SOFTWARE OR DOCUMENTATION. | +| | +| The name and trademarks of copyright holders may NOT be used in | +| advertising or publicity pertaining to the software without | +| specific, written prior permission. Title to copyright in this | +| software and any associated documentation will at all times | +| remain with copyright holders. | +| | ++-----------------------------------------------------------------------+ diff --git a/kevin123/Net_NNTP/NNTP/Client.php b/kevin123/Net_NNTP/NNTP/Client.php new file mode 100755 index 000000000..500b0a6f0 --- /dev/null +++ b/kevin123/Net_NNTP/NNTP/Client.php @@ -0,0 +1,1549 @@ + + * +-----------------------------------------------------------------------+ + * | | + * | W3C® SOFTWARE NOTICE AND LICENSE | + * | http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 | + * | | + * | This work (and included software, documentation such as READMEs, | + * | or other related items) is being provided by the copyright holders | + * | under the following license. By obtaining, using and/or copying | + * | this work, you (the licensee) agree that you have read, understood, | + * | and will comply with the following terms and conditions. | + * | | + * | Permission to copy, modify, and distribute this software and its | + * | documentation, with or without modification, for any purpose and | + * | without fee or royalty is hereby granted, provided that you include | + * | the following on ALL copies of the software and documentation or | + * | portions thereof, including modifications: | + * | | + * | 1. The full text of this NOTICE in a location viewable to users | + * | of the redistributed or derivative work. | + * | | + * | 2. Any pre-existing intellectual property disclaimers, notices, | + * | or terms and conditions. If none exist, the W3C Software Short | + * | Notice should be included (hypertext is preferred, text is | + * | permitted) within the body of any redistributed or derivative | + * | code. | + * | | + * | 3. Notice of any changes or modifications to the files, including | + * | the date changes were made. (We recommend you provide URIs to | + * | the location from which the code is derived.) | + * | | + * | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT | + * | HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, | + * | INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR | + * | FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE | + * | OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, | + * | COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. | + * | | + * | COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, | + * | SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE | + * | SOFTWARE OR DOCUMENTATION. | + * | | + * | The name and trademarks of copyright holders may NOT be used in | + * | advertising or publicity pertaining to the software without | + * | specific, written prior permission. Title to copyright in this | + * | software and any associated documentation will at all times | + * | remain with copyright holders. | + * | | + * +-----------------------------------------------------------------------+ + * + * + * @category Net + * @package Net_NNTP + * @author Heino H. Gehlsen + * @copyright 2002-2011 Heino H. Gehlsen . All Rights Reserved. + * @license http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 W3C® SOFTWARE NOTICE AND LICENSE + * @version SVN: $Id: Client.php 317347 2011-09-26 20:26:12Z janpascal $ + * @link http://pear.php.net/package/Net_NNTP + * @see + * + * @filesource + */ + +/** + * + */ +require_once 'Protocol/Client.php'; + +// {{{ Net_NNTP_Client + +/** + * Implementation of the client side of NNTP (Network News Transfer Protocol) + * + * The Net_NNTP_Client class is a frontend class to the Net_NNTP_Protocol_Client class. + * + * @category Net + * @package Net_NNTP + * @version package: 1.5.0RC2 (beta) + * @version api: 0.9.0 (alpha) + * @access public + * @see Net_NNTP_Protocol_Client + */ +class Net_NNTP_Client extends Net_NNTP_Protocol_Client +{ + // {{{ properties + + /** + * Information summary about the currently selected group. + * + * @var array + * @access private + */ + var $_selectedGroupSummary = null; + + /** + * + * + * @var array + * @access private + * @since 1.3.0 + */ + var $_overviewFormatCache = null; + + // }}} + // {{{ constructor + + /** + * Constructor + * + * Usage example: + * {@example docs/examples/phpdoc/constructor.php} + * + * @access public + */ + function Net_NNTP_Client() + { + parent::Net_NNTP_Protocol_Client(); + } + + // }}} + // {{{ connect() + + /** + * Connect to a server. + * + * xxx + * + * Usage example: + * {@example docs/examples/phpdoc/connect.php} + * + * @param string $host (optional) The hostname og IP-address of the NNTP-server to connect to, defaults to localhost. + * @param mixed $encryption (optional) false|'tls'|'ssl', defaults to false. + * @param int $port (optional) The port number to connect to, defaults to 119 or 563 dependng on $encryption. + * @param int $timeout (optional) + * + * @return mixed
+ * - (bool) True when posting allowed, otherwise false + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::disconnect() + * @see Net_NNTP_Client::authenticate() + */ + function connect($host = null, $encryption = null, $port = null, $timeout = null) + { + // v1.0.x API + if (is_int($encryption)) { + trigger_error('You are using deprecated API v1.0 in Net_NNTP_Client: connect() !', E_USER_NOTICE); + $port = $encryption; + $encryption = null; + } + + return parent::connect($host, $encryption, $port, $timeout); + } + + // }}} + // {{{ disconnect() + + /** + * Disconnect from server. + * + * @return mixed
+ * - (bool) + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::connect() + */ + function disconnect() + { + return parent::disconnect(); + } + + // }}} + // {{{ quit() + + /** + * Deprecated alias for disconnect(). + * + * @access public + * @deprecated + * @ignore + */ + function quit() + { + return $this->disconnect(); + } + + // }}} + // {{{ authenticate() + + /** + * Authenticate. + * + * xxx + * + * Non-standard!
+ * This method uses non-standard commands, which is not part + * of the original RFC977, but has been formalized in RFC2890. + * + * Usage example: + * {@example docs/examples/phpdoc/authenticate.php} + * + * @param string $user The username + * @param string $pass The password + * + * @return mixed
+ * - (bool) True on successful authentification, otherwise false + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::connect() + */ + function authenticate($user, $pass) + { + // Username is a must... + if ($user == null) { + return $this->throwError('No username supplied', null); + } + + return $this->cmdAuthinfo($user, $pass); + } + + // }}} + // {{{ selectGroup() + + /** + * Selects a group. + * + * Moves the servers 'currently selected group' pointer to the group + * a new group, and returns summary information about it. + * + * Non-standard!
+ * When using the second parameter, + * This method uses non-standard commands, which is not part + * of the original RFC977, but has been formalized in RFC2890. + * + * Usage example: + * {@example docs/examples/phpdoc/selectGroup.php} + * + * @param string $group Name of the group to select + * @param mixed $articles (optional) experimental! When true the article numbers is returned in 'articles' + * + * @return mixed
+ * - (array) Summary about the selected group + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::getGroups() + * @see Net_NNTP_Client::group() + * @see Net_NNTP_Client::first() + * @see Net_NNTP_Client::last() + * @see Net_NNTP_Client::count() + */ + function selectGroup($group, $articles = false) + { + // Select group (even if $articles is set, since many servers does not select groups when the listgroup command is run) + $summary = $this->cmdGroup($group); + if (PEAR::isError($summary)) { + return $summary; + } + + // Store group info in the object + $this->_selectedGroupSummary = $summary; + + // + if ($articles !== false) { + $summary2 = $this->cmdListgroup($group, ($articles === true ? null : $articles)); + if (PEAR::isError($summary2)) { + return $summary2; + } + + // Make sure the summary array is correct... + if ($summary2['group'] == $group) { + $summary = $summary2; + + // ... even if server does not include summary in status reponce. + } else { + $summary['articles'] = $summary2['articles']; + } + } + + return $summary; + } + + // }}} + // {{{ selectPreviousArticle() + + /** + * Select the previous article. + * + * Select the previous article in current group. + * + * Usage example: + * {@example docs/examples/phpdoc/selectPreviousArticle.php} + * + * @param int $_ret (optional) Experimental + * + * @return mixed
+ * - (integer) Article number, if $ret=0 (default) + * - (string) Message-id, if $ret=1 + * - (array) Both article number and message-id, if $ret=-1 + * - (bool) False if no prevoius article exists + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::selectArticle() + * @see Net_NNTP_Client::selectNextArticle() + */ + function selectPreviousArticle($_ret = 0) + { + $response = $this->cmdLast(); + + if (PEAR::isError($response)) { + return false; + } + + switch ($_ret) { + case -1: + return array('Number' => (int) $response[0], 'Message-ID' => (string) $response[1]); + break; + case 0: + return (int) $response[0]; + break; + case 1: + return (string) $response[1]; + break; + default: + error(); // ... + } + } + + // }}} + // {{{ selectNextArticle() + + /** + * Select the next article. + * + * Select the next article in current group. + * + * Usage example: + * {@example docs/examples/phpdoc/selectNextArticle.php} + * + * @param int $_ret (optional) Experimental + * + * @return mixed
+ * - (integer) Article number, if $ret=0 (default) + * - (string) Message-id, if $ret=1 + * - (array) Both article number and message-id, if $ret=-1 + * - (bool) False if no further articles exist + * - (object) Pear_Error on unexpected failure + * @access public + * @see Net_NNTP_Client::selectArticle() + * @see Net_NNTP_Client::selectPreviousArticle() + */ + function selectNextArticle($_ret = 0) + { + $response = $this->cmdNext(); + + if (PEAR::isError($response)) { + return $response; + } + + switch ($_ret) { + case -1: + return array('Number' => (int) $response[0], 'Message-ID' => (string) $response[1]); + break; + case 0: + return (int) $response[0]; + break; + case 1: + return (string) $response[1]; + break; + default: + error(); // ... + } + } + + // }}} + // {{{ selectArticle() + + /** + * Selects an article by article message-number. + * + * xxx + * + * Usage example: + * {@example docs/examples/phpdoc/selectArticle.php} + * + * @param mixed $article The message-number (on the server) of + * the article to select as current article. + * @param int $_ret (optional) Experimental + * + * @return mixed
+ * - (integer) Article number + * - (bool) False if article doesn't exists + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::selectNextArticle() + * @see Net_NNTP_Client::selectPreviousArticle() + */ + function selectArticle($article = null, $_ret = 0) + { + $response = $this->cmdStat($article); + + if (PEAR::isError($response)) { + return $response; + } + + switch ($_ret) { + case -1: + return array('Number' => (int) $response[0], 'Message-ID' => (string) $response[1]); + break; + case 0: + return (int) $response[0]; + break; + case 1: + return (string) $response[1]; + break; + default: + error(); // ... + } + } + + // }}} + // {{{ getArticle() + + /** + * Fetch article into transfer object. + * + * Select an article based on the arguments, and return the entire + * article (raw data). + * + * Usage example: + * {@example docs/examples/phpdoc/getArticle.php} + * + * @param mixed $article (optional) Either the message-id or the + * message-number on the server of the + * article to fetch. + * @param bool $implode (optional) When true the result array + * is imploded to a string, defaults to + * false. + * + * @return mixed
+ * - (array) Complete article (when $implode is false) + * - (string) Complete article (when $implode is true) + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::getHeader() + * @see Net_NNTP_Client::getBody() + */ + function getArticle($article = null, $implode = false) + { + // v1.1.x API + if (is_string($implode)) { + trigger_error('You are using deprecated API v1.1 in Net_NNTP_Client: getHeader() !', E_USER_NOTICE); + + $class = $implode; + $implode = false; + + if (!class_exists($class)) { + return $this->throwError("Class '$class' does not exist!"); + } + } + + $data = $this->cmdArticle($article); + if (PEAR::isError($data)) { + return $data; + } + + if ($implode == true) { + $data = implode("\r\n", $data); + } + + // v1.1.x API + if (isset($class)) { + return $obj = new $class($data); + } + + // + return $data; + } + + // }}} + // {{{ getHeader() + + /** + * Fetch article header. + * + * Select an article based on the arguments, and return the article + * header (raw data). + * + * Usage example: + * {@example docs/examples/phpdoc/getHeader.php} + * + * @param mixed $article (optional) Either message-id or message + * number of the article to fetch. + * @param bool $implode (optional) When true the result array + * is imploded to a string, defaults to + * false. + * + * @return mixed
+ * - (bool) False if article does not exist + * - (array) Header fields (when $implode is false) + * - (string) Header fields (when $implode is true) + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::getArticle() + * @see Net_NNTP_Client::getBody() + */ + function getHeader($article = null, $implode = false) + { + // v1.1.x API + if (is_string($implode)) { + trigger_error('You are using deprecated API v1.1 in Net_NNTP_Client: getHeader() !', E_USER_NOTICE); + + $class = $implode; + $implode = false; + + if (!class_exists($class)) { + return $this->throwError("Class '$class' does not exist!"); + } + } + + $data = $this->cmdHead($article); + if (PEAR::isError($data)) { + return $data; + } + + if ($implode == true) { + $data = implode("\r\n", $data); + } + + // v1.1.x API + if (isset($class)) { + return $obj = new $class($data); + } + + // + return $data; + } + + // }}} + // {{{ getBody() + + /** + * Fetch article body. + * + * Select an article based on the arguments, and return the article + * body (raw data). + * + * Usage example: + * {@example docs/examples/phpdoc/getBody.php} + * + * @param mixed $article (optional) Either the message-id or the + * message-number on the server of the + * article to fetch. + * @param bool $implode (optional) When true the result array + * is imploded to a string, defaults to + * false. + * + * @return mixed
+ * - (array) Message body (when $implode is false) + * - (string) Message body (when $implode is true) + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::getHeader() + * @see Net_NNTP_Client::getArticle() + */ + function getBody($article = null, $implode = false) + { + // v1.1.x API + if (is_string($implode)) { + trigger_error('You are using deprecated API v1.1 in Net_NNTP_Client: getHeader() !', E_USER_NOTICE); + + $class = $implode; + $implode = false; + + if (!class_exists($class)) { + return $this->throwError("Class '$class' does not exist!"); + } + } + + $data = $this->cmdBody($article); + if (PEAR::isError($data)) { + return $data; + } + + if ($implode == true) { + $data = implode("\r\n", $data); + } + + // v1.1.x API + if (isset($class)) { + return $obj = new $class($data); + } + + // + return $data; + } + + // }}} + // {{{ post() + + /** + * Post a raw article to a number of groups. + * + * Usage example: + * {@example docs/examples/phpdoc/post.php} + * + * @param mixed $article
+ * - (string) Complete article in a ready to send format (lines terminated by LFCR etc.) + * - (array) First key is the article header, second key is article body - any further keys are ignored !!! + * - (mixed) Something 'callable' (which must return otherwise acceptable data as replacement) + * + * @return mixed
+ * - (string) Server response + * - (object) Pear_Error on failure + * @access public + * @ignore + */ + function post($article) + { + // API v1.0 + if (func_num_args() >= 4) { + + // + trigger_error('You are using deprecated API v1.0 in Net_NNTP_Client: post() !', E_USER_NOTICE); + + // + $groups = func_get_arg(0); + $subject = func_get_arg(1); + $body = func_get_arg(2); + $from = func_get_arg(3); + $additional = func_get_arg(4); + + return $this->mail($groups, $subject, $body, "From: $from\r\n" . $additional); + } + + // Only accept $article if array or string + if (!is_array($article) && !is_string($article)) { + return $this->throwError('Ups', null, 0); + } + + // Check if server will receive an article + $post = $this->cmdPost(); + if (PEAR::isError($post)) { + return $post; + } + + // Get article data from callback function + if (is_callable($article)) { + $article = call_user_func($article); + } + + // Actually send the article + return $this->cmdPost2($article); + } + + // }}} + // {{{ mail() + + /** + * Post an article to a number of groups - using same parameters as PHP's mail() function. + * + * Among the aditional headers you might think of adding could be: + * "From: ", which should contain the e-mail address + * of the author of the article. + * Or "Organization: " which contain the name of the organization + * the post originates from. + * Or "NNTP-Posting-Host: ", which should contain the IP-address + * of the author of the post, so the message can be traced back to him. + * + * Usage example: + * {@example docs/examples/phpdoc/mail.php} + * + * @param string $groups The groups to post to. + * @param string $subject The subject of the article. + * @param string $body The body of the article. + * @param string $additional (optional) Additional header fields to send. + * + * @return mixed
+ * - (string) Server response + * - (object) Pear_Error on failure + * @access public + */ + function mail($groups, $subject, $body, $additional = null) + { + // Check if server will receive an article + $post = $this->cmdPost(); + if (PEAR::isError($post)) { + return $post; + } + + // Construct header + $header = "Newsgroups: $groups\r\n"; + $header .= "Subject: $subject\r\n"; + $header .= "X-poster: PEAR::Net_NNTP v1.5.0RC2 (beta)\r\n"; + if ($additional !== null) { + $header .= $additional; + } + $header .= "\r\n"; + + // Actually send the article + return $this->cmdPost2(array($header, $body)); + } + + // }}} + // {{{ getDate() + + /** + * Get the server's internal date + * + * Non-standard!
+ * This method uses non-standard commands, which is not part + * of the original RFC977, but has been formalized in RFC2890. + * + * Usage example: + * {@example docs/examples/phpdoc/getDate.php} + * + * @param int $format (optional) Determines the format of returned date: + * - 0: return string + * - 1: return integer/timestamp + * - 2: return an array('y'=>year, 'm'=>month,'d'=>day) + * + * @return mixed
+ * - (mixed) + * - (object) Pear_Error on failure + * @access public + */ + function getDate($format = 1) + { + $date = $this->cmdDate(); + if (PEAR::isError($date)) { + return $date; + } + + switch ($format) { + case 0: + return $date; + break; + case 1: + return strtotime(substr($date, 0, 8).' '.substr($date, 8, 2).':'.substr($date, 10, 2).':'.substr($date, 12, 2)); + break; + case 2: + return array('y' => substr($date, 0, 4), + 'm' => substr($date, 4, 2), + 'd' => substr($date, 6, 2)); + break; + default: + error(); + } + } + + // }}} + // {{{ getNewGroups() + + /** + * Get new groups since a date. + * + * Returns a list of groups created on the server since the specified date + * and time. + * + * Usage example: + * {@example docs/examples/phpdoc/getNewGroups.php} + * + * @param mixed $time
+ * - (integer) A timestamp + * - (string) Somthing parseable by strtotime() like '-1 week' + * @param string $distributions (optional) + * + * @return mixed
+ * - (array) + * - (object) Pear_Error on failure + * @access public + */ + function getNewGroups($time, $distributions = null) + { + switch (true) { + case is_integer($time): + break; + case is_string($time): + $time = strtotime($time); + if ($time === false || ($time === -1 && version_compare(phpversion(), '5.1.0', '<'))) { + return $this->throwError('$time could not be converted into a timestamp!', null, 0); + } + break; + default: + trigger_error('$time must be either a string or an integer/timestamp!', E_USER_ERROR); + } + + return $this->cmdNewgroups($time, $distributions); + } + + // }}} + // {{{ getNewArticles() + + /** + * Get new articles since a date. + * + * Returns a list of message-ids of new articles (since the specified date + * and time) in the groups whose names match the wildmat + * + * Usage example: + * {@example docs/examples/phpdoc/getNewArticles.php} + * + * @param mixed $time
+ * - (integer) A timestamp + * - (string) Somthing parseable by strtotime() like '-1 week' + * @param string $groups (optional) + * @param string $distributions (optional) + * + * @return mixed
+ * - (array) + * - (object) Pear_Error on failure + * @access public + * @since 1.3.0 + */ + function getNewArticles($time, $groups = '*', $distribution = null) + { + switch (true) { + case is_integer($time): + break; + case is_string($time): + $time = strtotime($time); + if ($time === false || ($time === -1 && version_compare(php_version(), '5.1.0', '<'))) { + return $this->throwError('$time could not be converted into a timestamp!', null, 0); + } + break; + default: + trigger_error('$time must be either a string or an integer/timestamp!', E_USER_ERROR); + } + + return $this->cmdNewnews($time, $groups, $distribution); + } + + // }}} + // {{{ getGroups() + + /** + * Fetch valid groups. + * + * Returns a list of valid groups (that the client is permitted to select) + * and associated information. + * + * Usage example: + * {@example docs/examples/phpdoc/getGroups.php} + * + * @return mixed
+ * - (array) Nested array with information about every valid group + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::getDescriptions() + * @see Net_NNTP_Client::selectGroup() + */ + function getGroups($wildmat = null) + { + $backup = false; + + // Get groups + $groups = $this->cmdListActive($wildmat); + if (PEAR::isError($groups)) { + switch ($groups->getCode()) { + case 500: + case 501: + $backup = true; + break; + default: + return $groups; + } + } + + // + if ($backup == true) { + + // + if (!is_null($wildmat)) { + return $this->throwError("The server does not support the 'LIST ACTIVE' command, and the 'LIST' command does not support the wildmat parameter!", null, null); + } + + // + $groups2 = $this->cmdList(); + if (PEAR::isError($groups2)) { + // Ignore... + } else { + $groups = $groups2; + } + } + + if (PEAR::isError($groups)) { + return $groups; + } + + return $groups; + } + + // }}} + // {{{ getDescriptions() + + /** + * Fetch all known group descriptions. + * + * Fetches a list of known group descriptions - including groups which + * the client is not permitted to select. + * + * Non-standard!
+ * This method uses non-standard commands, which is not part + * of the original RFC977, but has been formalized in RFC2890. + * + * Usage example: + * {@example docs/examples/phpdoc/getDescriptions.php} + * + * @param mixed $wildmat (optional) + * + * @return mixed
+ * - (array) Associated array with descriptions of known groups + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::getGroups() + */ + function getDescriptions($wildmat = null) + { + if (is_array($wildmat)) { + $wildmat = implode(',', $wildmat); + } + + // Get group descriptions + $descriptions = $this->cmdListNewsgroups($wildmat); + if (PEAR::isError($descriptions)) { + return $descriptions; + } + + // TODO: add xgtitle as backup + + return $descriptions; + } + + // }}} + // {{{ getOverview() + + /** + * Fetch an overview of article(s) in the currently selected group. + * + * Returns the contents of all the fields in the database for a number + * of articles specified by either article-numnber range, a message-id, + * or nothing (indicating currently selected article). + * + * The first 8 fields per article is always as follows: + * - 'Number' - '0' or the article number of the currently selected group. + * - 'Subject' - header content. + * - 'From' - header content. + * - 'Date' - header content. + * - 'Message-ID' - header content. + * - 'References' - header content. + * - ':bytes' - metadata item. + * - ':lines' - metadata item. + * + * The server may send more fields form it's database... + * + * Non-standard!
+ * This method uses non-standard commands, which is not part + * of the original RFC977, but has been formalized in RFC2890. + * + * Usage example: + * {@example docs/examples/phpdoc/getOverview.php} + * + * @param mixed $range (optional) + * - '' + * - '-' + * - '-' + * - '' + * @param boolean $_names (optional) experimental parameter! Use field names as array kays + * @param boolean $_forceNames (optional) experimental parameter! + * + * @return mixed
+ * - (array) Nested array of article overview data + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::getHeaderField() + * @see Net_NNTP_Client::getOverviewFormat() + */ + function getOverview($range = null, $_names = true, $_forceNames = true) + { + // API v1.0 + switch (true) { + // API v1.3 + case func_num_args() != 2: + case is_bool(func_get_arg(1)): + case !is_int(func_get_arg(1)) || (is_string(func_get_arg(1)) && ctype_digit(func_get_arg(1))): + case !is_int(func_get_arg(0)) || (is_string(func_get_arg(0)) && ctype_digit(func_get_arg(0))): + break; + + default: + // + trigger_error('You are using deprecated API v1.0 in Net_NNTP_Client: getOverview() !', E_USER_NOTICE); + + // Fetch overview via API v1.3 + $overview = $this->getOverview(func_get_arg(0) . '-' . func_get_arg(1), true, false); + if (PEAR::isError($overview)) { + return $overview; + } + + // Create and return API v1.0 compliant array + $articles = array(); + foreach ($overview as $article) { + + // Rename 'Number' field into 'number' + $article = array_merge(array('number' => array_shift($article)), $article); + + // Use 'Message-ID' field as key + $articles[$article['Message-ID']] = $article; + } + return $articles; + } + + // Fetch overview from server + $overview = $this->cmdXOver($range); + if (PEAR::isError($overview)) { + return $overview; + } + + // Use field names from overview format as keys? + if ($_names) { + + // Already cached? + if (is_null($this->_overviewFormatCache)) { + // Fetch overview format + $format = $this->getOverviewFormat($_forceNames, true); + if (PEAR::isError($format)){ + return $format; + } + + // Prepend 'Number' field + $format = array_merge(array('Number' => false), $format); + + // Cache format + $this->_overviewFormatCache = $format; + + // + } else { + $format = $this->_overviewFormatCache; + } + + // Loop through all articles + foreach ($overview as $key => $article) { + + // Copy $format + $f = $format; + + // Field counter + $i = 0; + + // Loop through forld names in format + foreach ($f as $tag => $full) { + + // + $f[$tag] = $article[$i++]; + + // If prefixed by field name, remove it + if ($full === true) { + $f[$tag] = ltrim( substr($f[$tag], strpos($f[$tag], ':') + 1), " \t"); + } + } + + // Replace article + $overview[$key] = $f; + } + } + + // + switch (true) { + + // Expect one article + case is_null($range); + case is_int($range); + case is_string($range) && ctype_digit($range): + case is_string($range) && substr($range, 0, 1) == '<' && substr($range, -1, 1) == '>': + if (count($overview) == 0) { + return false; + } else { + return reset($overview); + } + break; + + // Expect multiple articles + default: + return $overview; + } + } + + // }}} + // {{{ getOverviewFormat() + + /** + * Fetch names of fields in overview database + * + * Returns a description of the fields in the database for which it is consistent. + * + * Non-standard!
+ * This method uses non-standard commands, which is not part + * of the original RFC977, but has been formalized in RFC2890. + * + * Usage example: + * {@example docs/examples/phpdoc/getOveriewFormat.php} + * + * @return mixed
+ * - (array) Overview field names + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::getOverview() + */ + function getOverviewFormat($_forceNames = true, $_full = false) + { + $format = $this->cmdListOverviewFmt(); + if (PEAR::isError($format)) { + return $format; + } + + // Force name of first seven fields + if ($_forceNames) { + array_splice($format, 0, 7); + $format = array_merge(array('Subject' => false, + 'From' => false, + 'Date' => false, + 'Message-ID' => false, + 'References' => false, + ':bytes' => false, + ':lines' => false), $format); + } + + if ($_full) { + return $format; + } else { + return array_keys($format); + } + } + + // }}} + // {{{ getHeaderField() + + /** + * Fetch content of a header field from message(s). + * + * Retreives the content of specific header field from a number of messages. + * + * Non-standard!
+ * This method uses non-standard commands, which is not part + * of the original RFC977, but has been formalized in RFC2890. + * + * Usage example: + * {@example docs/examples/phpdoc/getHeaderField.php} + * + * @param string $field The name of the header field to retreive + * @param mixed $range (optional) + * '' + * '-' + * '-' + * '' + * + * @return mixed
+ * - (array) Nested array of + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::getOverview() + * @see Net_NNTP_Client::getReferences() + */ + function getHeaderField($field, $range = null) + { + $fields = $this->cmdXHdr($field, $range); + if (PEAR::isError($fields)) { + return $fields; + } + + // + switch (true) { + + // Expect one article + case is_null($range); + case is_int($range); + case is_string($range) && ctype_digit($range): + case is_string($range) && substr($range, 0, 1) == '<' && substr($range, -1, 1) == '>': + + if (count($fields) == 0) { + return false; + } else { + return reset($fields); + } + break; + + // Expect multiple articles + default: + return $fields; + } + } + + // }}} + + + + + + + + // {{{ getGroupArticles() + + /** + * + * + * Non-standard!
+ * This method uses non-standard commands, which is not part + * of the original RFC977, but has been formalized in RFC2890. + * + * Usage example: + * {@example docs/examples/phpdoc/getGroupArticles.php} + * + * @param mixed $range (optional) Experimental! + * + * @return mixed
+ * - (array) + * - (object) Pear_Error on failure + * @access public + * @since 1.3.0 + */ + function getGroupArticles($range = null) + { + $summary = $this->cmdListgroup(); + if (PEAR::isError($summary)) { + return $summary; + } + + // Update summary cache if group was also 'selected' + if ($summary['group'] !== null) { + $this->_selectedGroupSummary($summary); + } + + // + return $summary['articles']; + } + + // }}} + // {{{ getReferences() + + /** + * Fetch reference header field of message(s). + * + * Retrieves the content of the references header field of messages via + * either the XHDR ord the XROVER command. + * + * Identical to getHeaderField('References'). + * + * Non-standard!
+ * This method uses non-standard commands, which is not part + * of the original RFC977, but has been formalized in RFC2890. + * + * Usage example: + * {@example docs/examples/phpdoc/getReferences.php} + * + * @param mixed $range (optional) + * '' + * '-' + * '-' + * '' + * + * @return mixed
+ * - (array) Nested array of references + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::getHeaderField() + */ + function getReferences($range = null) + { + $backup = false; + + $references = $this->cmdXHdr('References', $range); + if (PEAR::isError($references)) { + switch ($references->getCode()) { + case 500: + case 501: + $backup = true; + break; + default: + return $references; + } + } + + if (true && (is_array($references) && count($references) == 0)) { + $backup = true; + } + + if ($backup == true) { + $references2 = $this->cmdXROver($range); + if (PEAR::isError($references2)) { + // Ignore... + } else { + $references = $references2; + } + } + + if (PEAR::isError($references)) { + return $references; + } + + if (is_array($references)) { + foreach ($references as $key => $val) { + $references[$key] = preg_split("/ +/", trim($val), -1, PREG_SPLIT_NO_EMPTY); + } + } + + // + switch (true) { + + // Expect one article + case is_null($range); + case is_int($range); + case is_string($range) && ctype_digit($range): + case is_string($range) && substr($range, 0, 1) == '<' && substr($range, -1, 1) == '>': + if (count($references) == 0) { + return false; + } else { + return reset($references); + } + break; + + // Expect multiple articles + default: + return $references; + } + } + + // }}} + + + + + + // {{{ count() + + /** + * Number of articles in currently selected group + * + * Usage example: + * {@example docs/examples/phpdoc/count.php} + * + * @return mixed
+ * - (string) the number of article in group + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::group() + * @see Net_NNTP_Client::first() + * @see Net_NNTP_Client::last() + * @see Net_NNTP_Client::selectGroup() + * @ignore + */ + function count() + { + return $this->_selectedGroupSummary['count']; + } + + // }}} + // {{{ last() + + /** + * Maximum article number in currently selected group + * + * Usage example: + * {@example docs/examples/phpdoc/last.php} + * + * @return mixed
+ * - (string) the last article's number + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::first() + * @see Net_NNTP_Client::group() + * @see Net_NNTP_Client::count() + * @see Net_NNTP_Client::selectGroup() + * @ignore + */ + function last() + { + return $this->_selectedGroupSummary['last']; + } + + // }}} + // {{{ first() + + /** + * Minimum article number in currently selected group + * + * Usage example: + * {@example docs/examples/phpdoc/first.php} + * + * @return mixed
+ * - (string) the first article's number + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::last() + * @see Net_NNTP_Client::group() + * @see Net_NNTP_Client::count() + * @see Net_NNTP_Client::selectGroup() + * @ignore + */ + function first() + { + return $this->_selectedGroupSummary['first']; + } + + // }}} + // {{{ group() + + /** + * Currently selected group + * + * Usage example: + * {@example docs/examples/phpdoc/group.php} + * + * @return mixed
+ * - (string) group name + * - (object) Pear_Error on failure + * @access public + * @see Net_NNTP_Client::first() + * @see Net_NNTP_Client::last() + * @see Net_NNTP_Client::count() + * @see Net_NNTP_Client::selectGroup() + * @ignore + */ + function group() + { + return $this->_selectedGroupSummary['group']; + } + + // }}} + + + + + + + + // {{{ isConnected() + + /** + * Test whether a connection is currently open or closed. + * + * @return bool True if connected, otherwise false + * @access public + * @see Net_NNTP_Client::connect() + * @see Net_NNTP_Client::quit() + * @deprecated since v1.3.0 due to use of protected method: Net_NNTP_Protocol_Client::isConnected() + * @ignore + */ + function isConnected() + { + trigger_error('You are using deprecated API v1.0 in Net_NNTP_Client: isConnected() !', E_USER_NOTICE); + return parent::_isConnected(); + } + + // }}} + // {{{ getArticleRaw() + + /** + * Deprecated alias for getArticle() + * + * @deprecated + * @ignore + */ + function getArticleRaw($article, $implode = false) + { + trigger_error('You are using deprecated API v1.0 in Net_NNTP_Client: getArticleRaw() !', E_USER_NOTICE); + return $this->getArticle($article, $implode); + } + + // }}} + // {{{ getHeaderRaw() + + /** + * Deprecated alias for getHeader() + * + * @deprecated + * @ignore + */ + function getHeaderRaw($article = null, $implode = false) + { + trigger_error('You are using deprecated API v1.0 in Net_NNTP_Client: getHeaderRaw() !', E_USER_NOTICE); + return $this->getHeader($article, $implode); + } + + // }}} + // {{{ getBodyRaw() + + /** + * Deprecated alias for getBody() + * + * @deprecated + * @ignore + */ + function getBodyRaw($article = null, $implode = false) + { + trigger_error('You are using deprecated API v1.0 in Net_NNTP_Client: getBodyRaw() !', E_USER_NOTICE); + return $this->getBody($article, $implode); + } + + // }}} + // {{{ getNewNews() + + /** + * Deprecated alias for getNewArticles() + * + * @deprecated + * @ignore + */ + function getNewNews($time, $groups = '*', $distribution = null) + { + trigger_error('You are using deprecated API v1.1 in Net_NNTP_Client: getNewNews() !', E_USER_NOTICE); + return $this->getNewArticles($time, $groups, $distribution); + } + + // }}} + // {{{ getReferencesOverview() + + /** + * Deprecated alias for getReferences() + * + * @deprecated + * @ignore + */ + function getReferencesOverview($first, $last) + { + trigger_error('You are using deprecated API v1.0 in Net_NNTP_Client: getReferencesOverview() !', E_USER_NOTICE); + return $this->getReferences($first . '-' . $last); + } + + // }}} + +} + +// }}} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ + +?> diff --git a/kevin123/Net_NNTP/NNTP/Protocol/Client.php b/kevin123/Net_NNTP/NNTP/Protocol/Client.php new file mode 100755 index 000000000..64f6e6608 --- /dev/null +++ b/kevin123/Net_NNTP/NNTP/Protocol/Client.php @@ -0,0 +1,2196 @@ + + * +-----------------------------------------------------------------------+ + * | | + * | W3C® SOFTWARE NOTICE AND LICENSE | + * | http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 | + * | | + * | This work (and included software, documentation such as READMEs, | + * | or other related items) is being provided by the copyright holders | + * | under the following license. By obtaining, using and/or copying | + * | this work, you (the licensee) agree that you have read, understood, | + * | and will comply with the following terms and conditions. | + * | | + * | Permission to copy, modify, and distribute this software and its | + * | documentation, with or without modification, for any purpose and | + * | without fee or royalty is hereby granted, provided that you include | + * | the following on ALL copies of the software and documentation or | + * | portions thereof, including modifications: | + * | | + * | 1. The full text of this NOTICE in a location viewable to users | + * | of the redistributed or derivative work. | + * | | + * | 2. Any pre-existing intellectual property disclaimers, notices, | + * | or terms and conditions. If none exist, the W3C Software Short | + * | Notice should be included (hypertext is preferred, text is | + * | permitted) within the body of any redistributed or derivative | + * | code. | + * | | + * | 3. Notice of any changes or modifications to the files, including | + * | the date changes were made. (We recommend you provide URIs to | + * | the location from which the code is derived.) | + * | | + * | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT | + * | HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, | + * | INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR | + * | FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE | + * | OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, | + * | COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. | + * | | + * | COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, | + * | SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE | + * | SOFTWARE OR DOCUMENTATION. | + * | | + * | The name and trademarks of copyright holders may NOT be used in | + * | advertising or publicity pertaining to the software without | + * | specific, written prior permission. Title to copyright in this | + * | software and any associated documentation will at all times | + * | remain with copyright holders. | + * | | + * +-----------------------------------------------------------------------+ + * + * + * @category Net + * @package Net_NNTP + * @author Heino H. Gehlsen + * @copyright 2002-2011 Heino H. Gehlsen . All Rights Reserved. + * @license http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 W3C® SOFTWARE NOTICE AND LICENSE + * @version SVN: $Id: Client.php 317347 2011-09-26 20:26:12Z janpascal $ + * @link http://pear.php.net/package/Net_NNTP + * @see + */ + +/** + * + */ +require_once 'PEAR.php'; +require_once 'Responsecode.php'; + + +// {{{ constants + +/** + * Default host + * + * @access public + * @ignore + */ +define('NET_NNTP_PROTOCOL_CLIENT_DEFAULT_HOST', 'localhost'); + +/** + * Default port + * + * @access public + * @ignore + */ +define('NET_NNTP_PROTOCOL_CLIENT_DEFAULT_PORT', '119'); + +// }}} +// {{{ Net_NNTP_Protocol_Client + +/** + * Low level NNTP Client + * + * Implements the client part of the NNTP standard acording to: + * - RFC 977, + * - RFC 2980, + * - RFC 850/1036, and + * - RFC 822/2822 + * + * Each NNTP command is represented by a method: cmd*() + * + * WARNING: The Net_NNTP_Protocol_Client class is considered an internal class + * (and should therefore currently not be extended directly outside of + * the Net_NNTP package). Therefore its API is NOT required to be fully + * stable, for as long as such changes doesn't affect the public API of + * the Net_NNTP_Client class, which is considered stable. + * + * TODO: cmdListActiveTimes() + * cmdDistribPats() + * + * @category Net + * @package Net_NNTP + * @author Heino H. Gehlsen + * @version package: 1.5.0RC2 (beta) + * @version api: 0.9.0 (alpha) + * @access private + * @see Net_NNTP_Client + */ +class Net_NNTP_Protocol_Client extends PEAR +{ + // {{{ properties + + /** + * The socket resource being used to connect to the NNTP server. + * + * @var resource + * @access private + */ + var $_socket = null; + + /** + * Contains the last recieved status response code and text + * + * @var array + * @access private + */ + var $_currentStatusResponse = null; + + /** + * + * + * @var object + * @access private + */ + var $_logger = null; + + // }}} + // {{{ constructor + + /** + * Constructor + * + * @access public + */ + function Net_NNTP_Protocol_Client() { + + // +// parent::PEAR('Net_NNTP_Error'); + parent::PEAR(); + } + + // }}} + // {{{ getPackageVersion() + + /** + * + * + * @access public + */ + function getPackageVersion() { + return '1.5.0RC2'; + } + + // }}} + // {{{ getApiVersion() + + /** + * + * + * @access public + */ + function getApiVersion() { + return '0.9.0'; + } + + // }}} + // {{{ setLogger() + + /** + * + * + * @param object $logger + * + * @access protected + */ + function setLogger($logger) + { + $this->_logger = $logger; + } + + // }}} + // {{{ setDebug() + + /** + * @deprecated + */ + function setDebug($debug = true) + { + trigger_error('You are using deprecated API v1.0 in Net_NNTP_Protocol_Client: setDebug() ! Debugging in now automatically handled when a logger is given.', E_USER_NOTICE); + } + + // }}} + // {{{ _sendCommand() + + /** + * Send command + * + * Send a command to the server. A carriage return / linefeed (CRLF) sequence + * will be appended to each command string before it is sent to the IMAP server. + * + * @param string $cmd The command to launch, ie: "ARTICLE 1004853" + * + * @return mixed (int) response code on success or (object) pear_error on failure + * @access private + */ + function _sendCommand($cmd) + { + // NNTP/RFC977 only allows command up to 512 (-2) chars. + if (!strlen($cmd) > 510) { + return $this->throwError('Failed writing to socket! (Command to long - max 510 chars)'); + } + +/***************************************************************************************/ +/* Credit: Thanks to Brendan Coles (http://itsecuritysolutions.org) */ +/* for pointing out possibility to inject pipelined NNTP commands into pretty */ +/* much any Net_NNTP command-sending function with user input, by appending */ +/* a new line character followed by the injection. */ +/***************************************************************************************/ + // Prevent new line (and possible future) characters in the NNTP commands + // Net_NNTP does not support pipelined commands. Inserting a new line charecter + // allows sending multiple commands and thereby making the communication between + // NET_NNTP and the server out of sync... + if (preg_match_all('/\r?\n/', $cmd, $matches, PREG_PATTERN_ORDER)) { + foreach ($matches[0] as $key => $match) { + $this->_logger->debug("Illegal character in command: ". htmlentities(str_replace(array("\r","\n"), array("'Carriage Return'", "'New Line'"), $match))); + } + return $this->throwError("Illegal character(s) in NNTP command!"); + } + + // Check if connected + if (!$this->_isConnected()) { + return $this->throwError('Failed to write to socket! (connection lost!)'); + } + + // Send the command + $R = @fwrite($this->_socket, $cmd . "\r\n"); + if ($R === false) { + return $this->throwError('Failed to write to socket!'); + } + + // + if ($this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG)) { + $this->_logger->debug('C: ' . $cmd); + } + + // + return $this->_getStatusResponse(); + } + + // }}} + // {{{ _getStatusResponse() + + /** + * Get servers status response after a command. + * + * @return mixed (int) statuscode on success or (object) pear_error on failure + * @access private + */ + function _getStatusResponse() + { + // Retrieve a line (terminated by "\r\n") from the server. + // RFC says max is 510, but IETF says "be liberal in what you accept"... + $response = @fgets($this->_socket); + if ($response === false) { + return $this->throwError('Failed to read from socket...!'); + } + + // + if ($this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG)) { + $this->_logger->debug('S: ' . rtrim($response, "\r\n")); + } + + // Trim the start of the response in case of misplased whitespace (should not be needen!!!) + $response = ltrim($response); + + $this->_currentStatusResponse = array( + (int) substr($response, 0, 3), + (string) rtrim(substr($response, 4)) + ); + + // + return $this->_currentStatusResponse[0]; + } + + // }}} + // {{{ _getTextResponse() + + /** + * Retrieve textural data + * + * Get data until a line with only a '.' in it is read and return data. + * + * @return mixed (array) text response on success or (object) pear_error on failure + * @access private + */ + function _getTextResponse() + { + $data = array(); + $line = ''; + + // + $debug = $this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG); + + // Continue until connection is lost + while (!feof($this->_socket)) { + + // Retrieve and append up to 1024 characters from the server. + $recieved = @fgets($this->_socket, 1024); + + if ($recieved === false) { + return $this->throwError('Failed to read line from socket.', null); + } + + $line .= $recieved; + + // Continue if the line is not terminated by CRLF + if (substr($line, -2) != "\r\n" || strlen($line) < 2) { + continue; + } + + // Validate recieved line + if (false) { + // Lines should/may not be longer than 998+2 chars (RFC2822 2.3) + if (strlen($line) > 1000) { + if ($this->_logger) { + $this->_logger->notice('Max line length...'); + } + return $this->throwError('Invalid line recieved!', null); + } + } + + // Remove CRLF from the end of the line + $line = substr($line, 0, -2); + + // Check if the line terminates the textresponse + if ($line == '.') { + + if ($this->_logger) { + $this->_logger->debug('T: ' . $line); + } + + // return all previous lines + return $data; + } + + // If 1st char is '.' it's doubled (NNTP/RFC977 2.4.1) + if (substr($line, 0, 2) == '..') { + $line = substr($line, 1); + } + + // + if ($debug) { + $this->_logger->debug('T: ' . $line); + } + + // Add the line to the array of lines + $data[] = $line; + + // Reset/empty $line + $line = ''; + } + + if ($this->_logger) { + $this->_logger->warning('Broke out of reception loop! This souldn\'t happen unless connection has been lost?'); + } + + // + return $this->throwError('End of stream! Connection lost?', null); + } + + // }}} + // {{{ _sendText() + + /** + * + * + * @access private + */ + function _sendArticle($article) + { + /* data should be in the format specified by RFC850 */ + + switch (true) { + case is_string($article): + // + @fwrite($this->_socket, $article); + @fwrite($this->_socket, "\r\n.\r\n"); + + // + if ($this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG)) { + foreach (explode("\r\n", $article) as $line) { + $this->_logger->debug('D: ' . $line); + } + $this->_logger->debug('D: .'); + } + break; + + case is_array($article): + // + $header = reset($article); + $body = next($article); + +/* Experimental... + // If header is an array, implode it. + if (is_array($header)) { + $header = implode("\r\n", $header) . "\r\n"; + } +*/ + + // Send header (including separation line) + @fwrite($this->_socket, $header); + @fwrite($this->_socket, "\r\n"); + + // + if ($this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG)) { + foreach (explode("\r\n", $header) as $line) { + $this->_logger->debug('D: ' . $line); + } + } + + +/* Experimental... + // If body is an array, implode it. + if (is_array($body)) { + $header = implode("\r\n", $body) . "\r\n"; + } +*/ + + // Send body + @fwrite($this->_socket, $body); + @fwrite($this->_socket, "\r\n.\r\n"); + + // + if ($this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG)) { + foreach (explode("\r\n", $body) as $line) { + $this->_logger->debug('D: ' . $line); + } + $this->_logger->debug('D: .'); + } + break; + + default: + return $this->throwError('Ups...', null, null); + } + + return true; + } + + // }}} + // {{{ _currentStatusResponse() + + /** + * + * + * @return string status text + * @access private + */ + function _currentStatusResponse() + { + return $this->_currentStatusResponse[1]; + } + + // }}} + // {{{ _handleUnexpectedResponse() + + /** + * + * + * @param int $code Status code number + * @param string $text Status text + * + * @return mixed + * @access private + */ + function _handleUnexpectedResponse($code = null, $text = null) + { + if ($code === null) { + $code = $this->_currentStatusResponse[0]; + } + + if ($text === null) { + $text = $this->_currentStatusResponse(); + } + + switch ($code) { + case NET_NNTP_PROTOCOL_RESPONSECODE_NOT_PERMITTED: // 502, 'access restriction or permission denied' / service permanently unavailable + return $this->throwError('Command not permitted / Access restriction / Permission denied', $code, $text); + break; + default: + return $this->throwError("Unexpected response: '$text'", $code, $text); + } + } + + // }}} + +/* Session administration commands */ + + // {{{ Connect() + + /** + * Connect to a NNTP server + * + * @param string $host (optional) The address of the NNTP-server to connect to, defaults to 'localhost'. + * @param mixed $encryption (optional) + * @param int $port (optional) The port number to connect to, defaults to 119. + * @param int $timeout (optional) + * + * @return mixed (bool) on success (true when posting allowed, otherwise false) or (object) pear_error on failure + * @access protected + */ + function connect($host = null, $encryption = null, $port = null, $timeout = null) + { + // + if ($this->_isConnected() ) { + return $this->throwError('Already connected, disconnect first!', null); + } + + // v1.0.x API + if (is_int($encryption)) { + trigger_error('You are using deprecated API v1.0 in Net_NNTP_Protocol_Client: connect() !', E_USER_NOTICE); + $port = $encryption; + $encryption = false; + } + + // + if (is_null($host)) { + $host = 'localhost'; + } + + // Choose transport based on encryption, and if no port is given, use default for that encryption + switch ($encryption) { + case null: + case false: + $transport = 'tcp'; + $port = is_null($port) ? 119 : $port; + break; + case 'ssl': + case 'tls': + $transport = $encryption; + $port = is_null($port) ? 563 : $port; + break; + default: + trigger_error('$encryption parameter must be either tcp, tls or ssl.', E_USER_ERROR); + } + + // + if (is_null($timeout)) { + $timeout = 15; + } + + // Open Connection + $R = stream_socket_client($transport . '://' . $host . ':' . $port, $errno, $errstr, $timeout); + if ($R === false) { + if ($this->_logger) { + $this->_logger->notice("Connection to $transport://$host:$port failed."); + } + return $R; + } + + $this->_socket = $R; + + // + if ($this->_logger) { + $this->_logger->info("Connection to $transport://$host:$port has been established."); + } + + // Retrive the server's initial response. + $response = $this->_getStatusResponse(); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_READY_POSTING_ALLOWED: // 200, Posting allowed + // TODO: Set some variable before return + + return true; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_READY_POSTING_PROHIBITED: // 201, Posting NOT allowed + // + if ($this->_logger) { + $this->_logger->info('Posting not allowed!'); + } + + // TODO: Set some variable before return + + return false; + break; + case 400: + return $this->throwError('Server refused connection', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NOT_PERMITTED: // 502, 'access restriction or permission denied' / service permanently unavailable + return $this->throwError('Server refused connection', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ disconnect() + + /** + * alias for cmdQuit() + * + * @access protected + */ + function disconnect() + { + return $this->cmdQuit(); + } + + // }}} + // {{{ cmdCapabilities() + + /** + * Returns servers capabilities + * + * @return mixed (array) list of capabilities on success or (object) pear_error on failure + * @access protected + */ + function cmdCapabilities() + { + // tell the newsserver we want an article + $response = $this->_sendCommand('CAPABILITIES'); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_CAPABILITIES_FOLLOW: // 101, Draft: 'Capability list follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + return $data; + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdModeReader() + + /** + * + * + * @return mixed (bool) true when posting allowed, false when postind disallowed or (object) pear_error on failure + * @access protected + */ + function cmdModeReader() + { + // tell the newsserver we want an article + $response = $this->_sendCommand('MODE READER'); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_READY_POSTING_ALLOWED: // 200, RFC2980: 'Hello, you can post' + + // TODO: Set some variable before return + + return true; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_READY_POSTING_PROHIBITED: // 201, RFC2980: 'Hello, you can't post' + if ($this->_logger) { + $this->_logger->info('Posting not allowed!'); + } + + // TODO: Set some variable before return + + return false; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NOT_PERMITTED: // 502, 'access restriction or permission denied' / service permanently unavailable + return $this->throwError('Connection being closed, since service so permanently unavailable', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdQuit() + + /** + * Disconnect from the NNTP server + * + * @return mixed (bool) true on success or (object) pear_error on failure + * @access protected + */ + function cmdQuit() + { + // Tell the server to close the connection + $response = $this->_sendCommand('QUIT'); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case 205: // RFC977: 'closing connection - goodbye!' + // If socket is still open, close it. + if ($this->_isConnected()) { + fclose($this->_socket); + } + + if ($this->_logger) { + $this->_logger->info('Connection closed.'); + } + + return true; + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + +/* */ + + // {{{ cmdStartTLS() + + /** + * + * + * @return mixed (bool) on success or (object) pear_error on failure + * @access protected + */ + function cmdStartTLS() + { + $response = $this->_sendCommand('STARTTLS'); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case 382: // RFC4642: 'continue with TLS negotiation' + $encrypted = stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + switch (true) { + case $encrypted === true: + if ($this->_logger) { + $this->_logger->info('TLS encryption started.'); + } + return true; + break; + case $encrypted === true: + if ($this->_logger) { + $this->_logger->info('TLS encryption failed.'); + } + return $this->throwError('Could not initiate TLS negotiation', $response, $this->_currentStatusResponse()); + break; + case is_int($encrypted): + return $this->throwError('', $response, $this->_currentStatusResponse()); + break; + default: + return $this->throwError('Internal error - unknown response from stream_socket_enable_crypto()', $response, $this->_currentStatusResponse()); + } + break; + case 580: // RFC4642: 'can not initiate TLS negotiation' + return $this->throwError('', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + +/* Article posting and retrieval */ + + /* Group and article selection */ + + // {{{ cmdGroup() + + /** + * Selects a news group (issue a GROUP command to the server) + * + * @param string $newsgroup The newsgroup name + * + * @return mixed (array) groupinfo on success or (object) pear_error on failure + * @access protected + */ + function cmdGroup($newsgroup) + { + $response = $this->_sendCommand('GROUP '.$newsgroup); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_GROUP_SELECTED: // 211, RFC977: 'n f l s group selected' + $response_arr = explode(' ', trim($this->_currentStatusResponse())); + + if ($this->_logger) { + $this->_logger->info('Group selected: '.$response_arr[3]); + } + + return array('group' => $response_arr[3], + 'first' => $response_arr[1], + 'last' => $response_arr[2], + 'count' => $response_arr[0]); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_GROUP: // 411, RFC977: 'no such news group' + return $this->throwError('No such news group', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdListgroup() + + /** + * + * + * @param optional string $newsgroup + * @param optional mixed $range + * + * @return optional mixed (array) on success or (object) pear_error on failure + * @access protected + */ + function cmdListgroup($newsgroup = null, $range = null) + { + if (is_null($newsgroup)) { + $command = 'LISTGROUP'; + } else { + if (is_null($range)) { + $command = 'LISTGROUP ' . $newsgroup; + } else { + $command = 'LISTGROUP ' . $newsgroup . ' ' . $range; + } + } + + $response = $this->_sendCommand($command); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_GROUP_SELECTED: // 211, RFC2980: 'list of article numbers follow' + + $articles = $this->_getTextResponse(); + if (PEAR::isError($articles)) { + return $articles; + } + + $response_arr = explode(' ', trim($this->_currentStatusResponse()), 4); + + // If server does not return group summary in status response, return null'ed array + if (!is_numeric($response_arr[0]) || !is_numeric($response_arr[1]) || !is_numeric($response_arr[2]) || empty($response_arr[3])) { + return array('group' => null, + 'first' => null, + 'last' => null, + 'count' => null, + 'articles' => $articles); + } + + return array('group' => $response_arr[3], + 'first' => $response_arr[1], + 'last' => $response_arr[2], + 'count' => $response_arr[0], + 'articles' => $articles); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'Not currently in newsgroup' + return $this->throwError('Not currently in newsgroup', $response, $this->_currentStatusResponse()); + break; + case 502: // RFC2980: 'no permission' + return $this->throwError('No permission', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdLast() + + /** + * + * + * @return mixed (array) or (string) or (int) or (object) pear_error on failure + * @access protected + */ + function cmdLast() + { + // + $response = $this->_sendCommand('LAST'); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_ARTICLE_SELECTED: // 223, RFC977: 'n a article retrieved - request text separately (n = article number, a = unique article id)' + $response_arr = explode(' ', trim($this->_currentStatusResponse())); + + if ($this->_logger) { + $this->_logger->info('Selected previous article: ' . $response_arr[0] .' - '. $response_arr[1]); + } + + return array($response_arr[0], (string) $response_arr[1]); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup selected' + return $this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC977: 'no current article has been selected' + return $this->throwError('No current article has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_PREVIOUS_ARTICLE: // 422, RFC977: 'no previous article in this group' + return $this->throwError('No previous article in this group', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdNext() + + /** + * + * + * @return mixed (array) or (string) or (int) or (object) pear_error on failure + * @access protected + */ + function cmdNext() + { + // + $response = $this->_sendCommand('NEXT'); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_ARTICLE_SELECTED: // 223, RFC977: 'n a article retrieved - request text separately (n = article number, a = unique article id)' + $response_arr = explode(' ', trim($this->_currentStatusResponse())); + + if ($this->_logger) { + $this->_logger->info('Selected previous article: ' . $response_arr[0] .' - '. $response_arr[1]); + } + + return array($response_arr[0], (string) $response_arr[1]); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup selected' + return $this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC977: 'no current article has been selected' + return $this->throwError('No current article has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_NEXT_ARTICLE: // 421, RFC977: 'no next article in this group' + return $this->throwError('No next article in this group', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + + /* Retrieval of articles and article sections */ + + // {{{ cmdArticle() + + /** + * Get an article from the currently open connection. + * + * @param mixed $article Either a message-id or a message-number of the article to fetch. If null or '', then use current article. + * + * @return mixed (array) article on success or (object) pear_error on failure + * @access protected + */ + function cmdArticle($article = null) + { + if (is_null($article)) { + $command = 'ARTICLE'; + } else { + $command = 'ARTICLE ' . $article; + } + + // tell the newsserver we want an article + $response = $this->_sendCommand($command); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_ARTICLE_FOLLOWS: // 220, RFC977: 'n article retrieved - head and body follow (n = article number, = message-id)' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + if ($this->_logger) { + $this->_logger->info(($article == null ? 'Fetched current article' : 'Fetched article: '.$article)); + } + return $data; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup has been selected' + return $this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC977: 'no current article has been selected' + return $this->throwError('No current article has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER: // 423, RFC977: 'no such article number in this group' + return $this->throwError('No such article number in this group', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_ID: // 430, RFC977: 'no such article found' + return $this->throwError('No such article found', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdHead() + + /** + * Get the headers of an article from the currently open connection. + * + * @param mixed $article Either a message-id or a message-number of the article to fetch the headers from. If null or '', then use current article. + * + * @return mixed (array) headers on success or (object) pear_error on failure + * @access protected + */ + function cmdHead($article = null) + { + if (is_null($article)) { + $command = 'HEAD'; + } else { + $command = 'HEAD ' . $article; + } + + // tell the newsserver we want the header of an article + $response = $this->_sendCommand($command); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_HEAD_FOLLOWS: // 221, RFC977: 'n article retrieved - head follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + if ($this->_logger) { + $this->_logger->info(($article == null ? 'Fetched current article header' : 'Fetched article header for article: '.$article)); + } + + return $data; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup has been selected' + return $this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC977: 'no current article has been selected' + return $this->throwError('No current article has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER: // 423, RFC977: 'no such article number in this group' + return $this->throwError('No such article number in this group', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_ID: // 430, RFC977: 'no such article found' + return $this->throwError('No such article found', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdBody() + + /** + * Get the body of an article from the currently open connection. + * + * @param mixed $article Either a message-id or a message-number of the article to fetch the body from. If null or '', then use current article. + * + * @return mixed (array) body on success or (object) pear_error on failure + * @access protected + */ + function cmdBody($article = null) + { + if (is_null($article)) { + $command = 'BODY'; + } else { + $command = 'BODY ' . $article; + } + + // tell the newsserver we want the body of an article + $response = $this->_sendCommand($command); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_BODY_FOLLOWS: // 222, RFC977: 'n article retrieved - body follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + if ($this->_logger) { + $this->_logger->info(($article == null ? 'Fetched current article body' : 'Fetched article body for article: '.$article)); + } + + return $data; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup has been selected' + return $this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC977: 'no current article has been selected' + return $this->throwError('No current article has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER: // 423, RFC977: 'no such article number in this group' + return $this->throwError('No such article number in this group', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_ID: // 430, RFC977: 'no such article found' + return $this->throwError('No such article found', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdStat + + /** + * + * + * @param mixed $article + * + * @return mixed (array) or (string) or (int) or (object) pear_error on failure + * @access protected + */ + function cmdStat($article = null) + { + if (is_null($article)) { + $command = 'STAT'; + } else { + $command = 'STAT ' . $article; + } + + // tell the newsserver we want an article + $response = $this->_sendCommand($command); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_ARTICLE_SELECTED: // 223, RFC977: 'n article retrieved - request text separately' (actually not documented, but copied from the ARTICLE command) + $response_arr = explode(' ', trim($this->_currentStatusResponse())); + + if ($this->_logger) { + $this->_logger->info('Selected article: ' . $response_arr[0].' - '.$response_arr[1]); + } + + return array($response_arr[0], (string) $response_arr[1]); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup has been selected' (actually not documented, but copied from the ARTICLE command) + return $this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER: // 423, RFC977: 'no such article number in this group' (actually not documented, but copied from the ARTICLE command) + return $this->throwError('No such article number in this group', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_ID: // 430, RFC977: 'no such article found' (actually not documented, but copied from the ARTICLE command) + return $this->throwError('No such article found', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + + /* Article posting */ + + // {{{ cmdPost() + + /** + * Post an article to a newsgroup. + * + * @return mixed (bool) true on success or (object) pear_error on failure + * @access protected + */ + function cmdPost() + { + // tell the newsserver we want to post an article + $response = $this->_sendCommand('POST'); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_SEND: // 340, RFC977: 'send article to be posted. End with .' + return true; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_PROHIBITED: // 440, RFC977: 'posting not allowed' + return $this->throwError('Posting not allowed', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + + } + + // }}} + // {{{ cmdPost2() + + /** + * Post an article to a newsgroup. + * + * @param mixed $article (string/array) + * + * @return mixed (bool) true on success or (object) pear_error on failure + * @access protected + */ + function cmdPost2($article) + { + /* should be presented in the format specified by RFC850 */ + + // + $this->_sendArticle($article); + + // Retrive server's response. + $response = $this->_getStatusResponse(); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_SUCCESS: // 240, RFC977: 'article posted ok' + return true; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_FAILURE: // 441, RFC977: 'posting failed' + return $this->throwError('Posting failed', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdIhave() + + /** + * + * + * @param string $id + * + * @return mixed (bool) true on success or (object) pear_error on failure + * @access protected + */ + function cmdIhave($id) + { + // tell the newsserver we want to post an article + $response = $this->_sendCommand('IHAVE ' . $id); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_SEND: // 335 + return true; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_UNWANTED: // 435 + return $this->throwError('Article not wanted', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_FAILURE: // 436 + return $this->throwError('Transfer not possible; try again later', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdIhave2() + + /** + * + * + * @param mixed $article (string/array) + * + * @return mixed (bool) true on success or (object) pear_error on failure + * @access protected + */ + function cmdIhave2($article) + { + /* should be presented in the format specified by RFC850 */ + + // + $this->_sendArticle($article); + + // Retrive server's response. + $response = $this->_getStatusResponse(); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_SUCCESS: // 235 + return true; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_FAILURE: // 436 + return $this->throwError('Transfer not possible; try again later', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_REJECTED: // 437 + return $this->throwError('Transfer rejected; do not retry', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + +/* Information commands */ + + // {{{ cmdDate() + + /** + * Get the date from the newsserver format of returned date + * + * @return mixed (string) 'YYYYMMDDhhmmss' / (int) timestamp on success or (object) pear_error on failure + * @access protected + */ + function cmdDate() + { + $response = $this->_sendCommand('DATE'); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_SERVER_DATE: // 111, RFC2980: 'YYYYMMDDhhmmss' + return $this->_currentStatusResponse(); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + // }}} + // {{{ cmdHelp() + + /** + * Returns the server's help text + * + * @return mixed (array) help text on success or (object) pear_error on failure + * @access protected + */ + function cmdHelp() + { + // tell the newsserver we want an article + $response = $this->_sendCommand('HELP'); + if (PEAR::isError($response)) { + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_HELP_FOLLOWS: // 100 + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + return $data; + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdNewgroups() + + /** + * Fetches a list of all newsgroups created since a specified date. + * + * @param int $time Last time you checked for groups (timestamp). + * @param optional string $distributions (deprecaded in rfc draft) + * + * @return mixed (array) nested array with informations about existing newsgroups on success or (object) pear_error on failure + * @access protected + */ + function cmdNewgroups($time, $distributions = null) + { + $date = gmdate('ymd His', $time); + + if (is_null($distributions)) { + $command = 'NEWGROUPS ' . $date . ' GMT'; + } else { + $command = 'NEWGROUPS ' . $date . ' GMT <' . $distributions . '>'; + } + + $response = $this->_sendCommand($command); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_NEW_GROUPS_FOLLOW: // 231, REF977: 'list of new newsgroups follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + $groups = array(); + foreach($data as $line) { + $arr = explode(' ', trim($line)); + + $group = array('group' => $arr[0], + 'last' => $arr[1], + 'first' => $arr[2], + 'posting' => $arr[3]); + + $groups[$group['group']] = $group; + } + return $groups; + + + + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdNewnews() + + /** + * + * + * @param timestamp $time + * @param mixed $newsgroups (string or array of strings) + * @param mixed $distribution (string or array of strings) + * + * @return mixed + * @access protected + */ + function cmdNewnews($time, $newsgroups, $distribution = null) + { + $date = gmdate('ymd His', $time); + + if (is_array($newsgroups)) { + $newsgroups = implode(',', $newsgroups); + } + + if (is_null($distribution)) { + $command = 'NEWNEWS ' . $newsgroups . ' ' . $date . ' GMT'; + } else { + if (is_array($distribution)) { + $distribution = implode(',', $distribution); + } + + $command = 'NEWNEWS ' . $newsgroups . ' ' . $date . ' GMT <' . $distribution . '>'; + } + + // TODO: the lenght of the request string may not exceed 510 chars + + $response = $this->_sendCommand($command); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_NEW_ARTICLES_FOLLOW: // 230, RFC977: 'list of new articles by message-id follows' + $messages = array(); + foreach($this->_getTextResponse() as $line) { + $messages[] = $line; + } + return $messages; + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + + /* The LIST commands */ + + // {{{ cmdList() + + /** + * Fetches a list of all avaible newsgroups + * + * @return mixed (array) nested array with informations about existing newsgroups on success or (object) pear_error on failure + * @access protected + */ + function cmdList() + { + $response = $this->_sendCommand('LIST'); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_GROUPS_FOLLOW: // 215, RFC977: 'list of newsgroups follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + $groups = array(); + foreach($data as $line) { + $arr = explode(' ', trim($line)); + + $group = array('group' => $arr[0], + 'last' => $arr[1], + 'first' => $arr[2], + 'posting' => $arr[3]); + + $groups[$group['group']] = $group; + } + return $groups; + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdListActive() + + /** + * Fetches a list of all avaible newsgroups + * + * @param string $wildmat + * + * @return mixed (array) nested array with informations about existing newsgroups on success or (object) pear_error on failure + * @access protected + */ + function cmdListActive($wildmat = null) + { + if (is_null($wildmat)) { + $command = 'LIST ACTIVE'; + } else { + $command = 'LIST ACTIVE ' . $wildmat; + } + + $response = $this->_sendCommand($command); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_GROUPS_FOLLOW: // 215, RFC977: 'list of newsgroups follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + $groups = array(); + foreach($data as $line) { + $arr = explode(' ', trim($line)); + + $group = array('group' => $arr[0], + 'last' => $arr[1], + 'first' => $arr[2], + 'posting' => $arr[3]); + + $groups[$group['group']] = $group; + } + + if ($this->_logger) { + $this->_logger->info('Fetched list of available groups'); + } + + return $groups; + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdListNewsgroups() + + /** + * Fetches a list of (all) avaible newsgroup descriptions. + * + * @param string $wildmat Wildmat of the groups, that is to be listed, defaults to null; + * + * @return mixed (array) nested array with description of existing newsgroups on success or (object) pear_error on failure + * @access protected + */ + function cmdListNewsgroups($wildmat = null) + { + if (is_null($wildmat)) { + $command = 'LIST NEWSGROUPS'; + } else { + $command = 'LIST NEWSGROUPS ' . $wildmat; + } + + $response = $this->_sendCommand($command); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_GROUPS_FOLLOW: // 215, RFC2980: 'information follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + $groups = array(); + + foreach($data as $line) { + if (preg_match("/^(\S+)\s+(.*)$/", ltrim($line), $matches)) { + $groups[$matches[1]] = (string) $matches[2]; + } else { + if ($this->_logger) { + $this->_logger->warning("Recieved non-standard line: '$line'"); + } + } + } + + if ($this->_logger) { + $this->_logger->info('Fetched group descriptions'); + } + + return $groups; + break; + case 503: // RFC2980: 'program error, function not performed' + return $this->throwError('Internal server error, function not performed', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + +/* Article field access commands */ + + // {{{ cmdOver() + + /** + * Fetch message header from message number $first until $last + * + * The format of the returned array is: + * $messages[][header_name] + * + * @param optional string $range articles to fetch + * + * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure + * @access protected + */ + function cmdOver($range = null) + { + if (is_null($range)) { + $command = 'OVER'; + } else { + $command = 'OVER ' . $range; + } + + $response = $this->_sendCommand($command); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_OVERVIEW_FOLLOWS: // 224, RFC2980: 'Overview information follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + foreach ($data as $key => $value) { + $data[$key] = explode("\t", trim($value)); + } + + if ($this->_logger) { + $this->_logger->info('Fetched overview ' . ($range == null ? 'for current article' : 'for range: '.$range)); + } + + return $data; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'No news group current selected' + return $this->throwError('No news group current selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC2980: 'No article(s) selected' + return $this->throwError('No article(s) selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER: // 423:, Draft27: 'No articles in that range' + return $this->throwError('No articles in that range', $response, $this->_currentStatusResponse()); + break; + case 502: // RFC2980: 'no permission' + return $this->throwError('No permission', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdXOver() + + /** + * Fetch message header from message number $first until $last + * + * The format of the returned array is: + * $messages[message_id][header_name] + * + * @param optional string $range articles to fetch + * + * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure + * @access protected + */ + function cmdXOver($range = null) + { + // deprecated API (the code _is_ still in alpha state) + if (func_num_args() > 1 ) { + die('The second parameter in cmdXOver() has been deprecated! Use x-y instead...'); + } + + if (is_null($range)) { + $command = 'XOVER'; + } else { + $command = 'XOVER ' . $range; + } + + $response = $this->_sendCommand($command); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_OVERVIEW_FOLLOWS: // 224, RFC2980: 'Overview information follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + foreach ($data as $key => $value) { + $data[$key] = explode("\t", trim($value)); + } + + if ($this->_logger) { + $this->_logger->info('Fetched overview ' . ($range == null ? 'for current article' : 'for range: '.$range)); + } + + return $data; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'No news group current selected' + return $this->throwError('No news group current selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC2980: 'No article(s) selected' + return $this->throwError('No article(s) selected', $response, $this->_currentStatusResponse()); + break; + case 502: // RFC2980: 'no permission' + return $this->throwError('No permission', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdListOverviewFmt() + + /** + * Returns a list of avaible headers which are send from newsserver to client for every news message + * + * @return mixed (array) of header names on success or (object) pear_error on failure + * @access protected + */ + function cmdListOverviewFmt() + { + $fmtfallback = false; + $defaultfmt = "Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref:full"; + $response = $this->_sendCommand('LIST OVERVIEW.FMT'); + if (PEAR::isError($response)) + { + return $response; + } + elseif (is_int($response) && $response == 501) + { + $data = explode("\n", $defaultfmt); + $fmtfallback = true; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_GROUPS_FOLLOW: // 215, RFC2980: 'information follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + case 501: + $format = array(); + + foreach ($data as $line) { + // Check if postfixed by ':full' (case-insensitive) + if (0 == strcasecmp(substr($line, -5, 5), ':full')) { + // ':full' is _not_ included in tag, but value set to true + $format[substr($line, 0, -5)] = true; + } else { + // ':' is _not_ included in tag; value set to false + $format[substr($line, 0, -1)] = false; + } + } + + if ($this->_logger) { + $this->_logger->info('Fetched overview format ' . ($fmtfallback ? "(local overview.fmt fallback)" : "")); + } + return $format; + break; + case 503: // RFC2980: 'program error, function not performed' + return $this->throwError('Internal server error, function not performed', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdXHdr() + + /** + * + * + * The format of the returned array is: + * $messages[message_id] + * + * @param optional string $field + * @param optional string $range articles to fetch + * + * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure + * @access protected + */ + function cmdXHdr($field, $range = null) + { + if (is_null($range)) { + $command = 'XHDR ' . $field; + } else { + $command = 'XHDR ' . $field . ' ' . $range; + } + + $response = $this->_sendCommand($command); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case 221: // 221, RFC2980: 'Header follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + $return = array(); + foreach($data as $line) { + $line = explode(' ', trim($line), 2); + $return[$line[0]] = $line[1]; + } + + return $return; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'No news group current selected' + return $this->throwError('No news group current selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC2980: 'No current article selected' + return $this->throwError('No current article selected', $response, $this->_currentStatusResponse()); + break; + case 430: // 430, RFC2980: 'No such article' + return $this->throwError('No such article', $response, $this->_currentStatusResponse()); + break; + case 502: // RFC2980: 'no permission' + return $this->throwError('No permission', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + + + + + + + + + + + + + + + + + + + /** + * Fetches a list of (all) avaible newsgroup descriptions. + * Depresated as of RFC2980. + * + * @param string $wildmat Wildmat of the groups, that is to be listed, defaults to '*'; + * + * @return mixed (array) nested array with description of existing newsgroups on success or (object) pear_error on failure + * @access protected + */ + function cmdXGTitle($wildmat = '*') + { + $response = $this->_sendCommand('XGTITLE '.$wildmat); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case 282: // RFC2980: 'list of groups and descriptions follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + $groups = array(); + + foreach($data as $line) { + preg_match("/^(.*?)\s(.*?$)/", trim($line), $matches); + $groups[$matches[1]] = (string) $matches[2]; + } + + return $groups; + break; + + case 481: // RFC2980: 'Groups and descriptions unavailable' + return $this->throwError('Groups and descriptions unavailable', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdXROver() + + /** + * Fetch message references from message number $first to $last + * + * @param optional string $range articles to fetch + * + * @return mixed (array) assoc. array of message references on success or (object) pear_error on failure + * @access protected + */ + function cmdXROver($range = null) + { + // Warn about deprecated API (the code _is_ still in alpha state) + if (func_num_args() > 1 ) { + die('The second parameter in cmdXROver() has been deprecated! Use x-y instead...'); + } + + if (is_null($range)) { + $command = 'XROVER'; + } else { + $command = 'XROVER ' . $range; + } + + $response = $this->_sendCommand($command); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case NET_NNTP_PROTOCOL_RESPONSECODE_OVERVIEW_FOLLOWS: // 224, RFC2980: 'Overview information follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + $return = array(); + foreach($data as $line) { + $line = explode(' ', trim($line), 2); + $return[$line[0]] = $line[1]; + } + return $return; + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'No news group current selected' + return $this->throwError('No news group current selected', $response, $this->_currentStatusResponse()); + break; + case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC2980: 'No article(s) selected' + return $this->throwError('No article(s) selected', $response, $this->_currentStatusResponse()); + break; + case 502: // RFC2980: 'no permission' + return $this->throwError('No permission', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + + + + + // {{{ cmdXPat() + + /** + * + * + * @param string $field + * @param string $range + * @param mixed $wildmat + * + * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure + * @access protected + */ + function cmdXPat($field, $range, $wildmat) + { + if (is_array($wildmat)) { + $wildmat = implode(' ', $wildmat); + } + + $response = $this->_sendCommand('XPAT ' . $field . ' ' . $range . ' ' . $wildmat); + if (PEAR::isError($response)){ + return $response; + } + + switch ($response) { + case 221: // 221, RFC2980: 'Header follows' + $data = $this->_getTextResponse(); + if (PEAR::isError($data)) { + return $data; + } + + $return = array(); + foreach($data as $line) { + $line = explode(' ', trim($line), 2); + $return[$line[0]] = $line[1]; + } + + return $return; + break; + case 430: // 430, RFC2980: 'No such article' + return $this->throwError('No current article selected', $response, $this->_currentStatusResponse()); + break; + case 502: // RFC2980: 'no permission' + return $this->throwError('No permission', $response, $this->_currentStatusResponse()); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdAuthinfo() + + /** + * Authenticate using 'original' method + * + * @param string $user The username to authenticate as. + * @param string $pass The password to authenticate with. + * + * @return mixed (bool) true on success or (object) pear_error on failure + * @access protected + */ + function cmdAuthinfo($user, $pass) + { + // Send the username + $response = $this->_sendCommand('AUTHINFO user '.$user); + if (PEAR::isError($response)) { + return $response; + } + + // Send the password, if the server asks + if (($response == 381) && ($pass !== null)) { + // Send the password + $response = $this->_sendCommand('AUTHINFO pass '.$pass); + if (PEAR::isError($response)) { + return $response; + } + } + + switch ($response) { + case 281: // RFC2980: 'Authentication accepted' + if ($this->_logger) { + $this->_logger->info("Authenticated (as user '$user')"); + } + + // TODO: Set some variable before return + + return true; + break; + case 381: // RFC2980: 'More authentication information required' + return $this->throwError('Authentication uncompleted', $response, $this->_currentStatusResponse()); + break; + case 482: // RFC2980: 'Authentication rejected' + return $this->throwError('Authentication rejected', $response, $this->_currentStatusResponse()); + break; + case 502: // RFC2980: 'No permission' + return $this->throwError('Authentication rejected', $response, $this->_currentStatusResponse()); + break; +// case 500: +// case 501: +// return $this->throwError('Authentication failed', $response, $this->_currentStatusResponse()); +// break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + // }}} + // {{{ cmdAuthinfoSimple() + + /** + * Authenticate using 'simple' method + * + * @param string $user The username to authenticate as. + * @param string $pass The password to authenticate with. + * + * @return mixed (bool) true on success or (object) pear_error on failure + * @access protected + */ + function cmdAuthinfoSimple($user, $pass) + { + return $this->throwError("The auth mode: 'simple' is has not been implemented yet", null); + } + + // }}} + // {{{ cmdAuthinfoGeneric() + + /** + * Authenticate using 'generic' method + * + * @param string $user The username to authenticate as. + * @param string $pass The password to authenticate with. + * + * @return mixed (bool) true on success or (object) pear_error on failure + * @access protected + */ + function cmdAuthinfoGeneric($user, $pass) + { + return $this->throwError("The auth mode: 'generic' is has not been implemented yet", null); + } + + // }}} + // {{{ _isConnected() + + /** + * Test whether we are connected or not. + * + * @return bool true or false + * @access protected + */ + function _isConnected() + { + return (is_resource($this->_socket) && (!feof($this->_socket))); + } + + // }}} + +} + +// }}} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ + +?> diff --git a/kevin123/Net_NNTP/NNTP/Protocol/Responsecode.php b/kevin123/Net_NNTP/NNTP/Protocol/Responsecode.php new file mode 100755 index 000000000..6cf7a2f26 --- /dev/null +++ b/kevin123/Net_NNTP/NNTP/Protocol/Responsecode.php @@ -0,0 +1,509 @@ + + * +-----------------------------------------------------------------------+ + * | | + * | W3C® SOFTWARE NOTICE AND LICENSE | + * | http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 | + * | | + * | This work (and included software, documentation such as READMEs, | + * | or other related items) is being provided by the copyright holders | + * | under the following license. By obtaining, using and/or copying | + * | this work, you (the licensee) agree that you have read, understood, | + * | and will comply with the following terms and conditions. | + * | | + * | Permission to copy, modify, and distribute this software and its | + * | documentation, with or without modification, for any purpose and | + * | without fee or royalty is hereby granted, provided that you include | + * | the following on ALL copies of the software and documentation or | + * | portions thereof, including modifications: | + * | | + * | 1. The full text of this NOTICE in a location viewable to users | + * | of the redistributed or derivative work. | + * | | + * | 2. Any pre-existing intellectual property disclaimers, notices, | + * | or terms and conditions. If none exist, the W3C Software Short | + * | Notice should be included (hypertext is preferred, text is | + * | permitted) within the body of any redistributed or derivative | + * | code. | + * | | + * | 3. Notice of any changes or modifications to the files, including | + * | the date changes were made. (We recommend you provide URIs to | + * | the location from which the code is derived.) | + * | | + * | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT | + * | HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, | + * | INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR | + * | FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE | + * | OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, | + * | COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. | + * | | + * | COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, | + * | SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE | + * | SOFTWARE OR DOCUMENTATION. | + * | | + * | The name and trademarks of copyright holders may NOT be used in | + * | advertising or publicity pertaining to the software without | + * | specific, written prior permission. Title to copyright in this | + * | software and any associated documentation will at all times | + * | remain with copyright holders. | + * | | + * +-----------------------------------------------------------------------+ + * + * + * @category Net + * @package Net_NNTP + * @author Heino H. Gehlsen + * @copyright 2002-2011 Heino H. Gehlsen . All Rights Reserved. + * @license http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 W3C® SOFTWARE NOTICE AND LICENSE + * @version SVN: $Id: Responsecode.php 306619 2010-12-24 12:16:07Z heino $ + * @link http://pear.php.net/package/Net_NNTP + * @see + * @since File available since release 1.3.0 + */ + + + +// {{{ Constants: Connection + +/** + * 'Server ready - posting allowed' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_READY_POSTING_ALLOWED', 200); + +/** + * 'Server ready - no posting allowed' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_READY_POSTING_PROHIBITED', 201); + + +/** + * 'Closing connection - goodbye!' (RFC977) + * + * @access public + * @since ? + */ +//define('NET_NNTP_PROTOCOL_RESPONSECODE_DISCONNECTING_REQUESTED', 205); ///// goodbye + +/** + * 'Service discontinued' (RFC977) + * + * @access public + * @since ? + */ +//define('NET_NNTP_PROTOCOL_RESPONSECODE_DISCONNECTING_FORCED', 400); ///// unavailable / discontinued + + +/** + * 'Slave status noted' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_SLAVE_RECOGNIZED', 202); + + + + +// }}} +// {{{ Constants: Common errors + + + +/** + * 'Command not recognized' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_UNKNOWN_COMMAND', 500); + +/** + * 'Command syntax error' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_SYNTAX_ERROR', 501); + +/** + * 'Access restriction or permission denied' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NOT_PERMITTED', 502); + +/** + * 'Program fault - command not performed' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NOT_SUPPORTED', 503); + + + +// }}} +// {{{ Constants: Group selection + + + +/** + * 'Group selected' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_GROUP_SELECTED', 211); + +/** + * 'No such news group' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_GROUP', 411); + + + +// }}} +// {{{ Constants: Article retrieval + + + +/** + * 'Article retrieved - head and body follow' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_ARTICLE_FOLLOWS', 220); + +/** + * 'Article retrieved - head follows' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_HEAD_FOLLOWS', 221); + +/** + * 'Article retrieved - body follows' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_BODY_FOLLOWS', 222); + +/** + * 'Article retrieved - request text separately' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_ARTICLE_SELECTED', 223); + + + + + +/** + * 'No newsgroup has been selected' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED', 412); + + +/** + * 'No current article has been selected' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED', 420); + +/** + * 'No next article in this group' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NO_NEXT_ARTICLE', 421); + +/** + * 'No previous article in this group' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NO_PREVIOUS_ARTICLE', 422); + + +/** + * 'No such article number in this group' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER', 423); + +/** + * 'No such article found' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_ID', 430); + + + + + +// }}} +// {{{ Constants: Transferring + + + +/** + * 'Send article to be transferred' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_SEND', 335); + +/** + * 'Article transferred ok' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_SUCCESS', 235); + +/** + * 'Article not wanted - do not send it' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_UNWANTED', 435); + +/** + * 'Transfer failed - try again later' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_FAILURE', 436); + +/** + * 'Article rejected - do not try again' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_REJECTED', 437); + + + +// }}} +// {{{ Constants: Posting + + + +/** + * 'Send article to be posted' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_SEND', 340); + +/** + * 'Article posted ok' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_SUCCESS', 240); + +/** + * 'Posting not allowed' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_PROHIBITED', 440); + +/** + * 'Posting failed' (RFC977) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_FAILURE', 441); + + + + +// }}} +// {{{ Constants: Authorization + + + +/** + * 'Authorization required for this command' (RFC2980) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_AUTHORIZATION_REQUIRED', 450); + +/** + * 'Continue with authorization sequence' (RFC2980) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_AUTHORIZATION_CONTINUE', 350); + +/** + * 'Authorization accepted' (RFC2980) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_AUTHORIZATION_ACCEPTED', 250); + +/** + * 'Authorization rejected' (RFC2980) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_AUTHORIZATION_REJECTED', 452); + + + + +// }}} +// {{{ Constants: Authentication + + + +/** + * 'Authentication required' (RFC2980) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_AUTHENTICATION_REQUIRED', 480); + +/** + * 'More authentication information required' (RFC2980) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_AUTHENTICATION_CONTINUE', 381); + +/** + * 'Authentication accepted' (RFC2980) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_AUTHENTICATION_ACCEPTED', 281); + +/** + * 'Authentication rejected' (RFC2980) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_AUTHENTICATION_REJECTED', 482); + + +// }}} +// {{{ Constants: Misc + + + +/** + * 'Help text follows' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_HELP_FOLLOWS', 100); + +/** + * 'Capabilities list follows' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_CAPABILITIES_FOLLOW', 101); + +/** + * 'Server date and time' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_SERVER_DATE', 111); + +/** + * 'Information follows' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_GROUPS_FOLLOW', 215); + +/** + * 'Overview information follows' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_OVERVIEW_FOLLOWS', 224); + +/** + * 'Headers follow' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_HEADERS_FOLLOW', 225); + +/** + * 'List of new articles follows' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NEW_ARTICLES_FOLLOW', 230); + +/** + * 'List of new newsgroups follows' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_NEW_GROUPS_FOLLOW', 231); + +/** + * 'The server is in the wrong mode; the indicated capability should be used to change the mode' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_WRONG_MODE', 401); + +/** + * 'Internal fault or problem preventing action being taken' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_INTERNAL_FAULT', 403); + +/** + * 'Command unavailable until suitable privacy has been arranged' (Draft) + * + * (the client must negotiate appropriate privacy protection on the connection. + * This will involve the use of a privacy extension such as [NNTP-TLS].) + * + * @access public + * @since ? + */ +//define('NET_NNTP_PROTOCOL_RESPONSECODE_ENCRYPTION_REQUIRED', 483); + +/** + * 'Error in base64-encoding [RFC3548] of an argument' (Draft) + * + * @access public + */ +define('NET_NNTP_PROTOCOL_RESPONSECODE_BASE64_ENCODING_ERROR', 504); + +// }}} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ + +?> diff --git a/kevin123/nntp.php b/kevin123/nntp.php new file mode 100755 index 000000000..c3db3ec04 --- /dev/null +++ b/kevin123/nntp.php @@ -0,0 +1,584 @@ +_isConnected()) { + return true; + } + + // Attempt to connect up to 5 times before giving up. + $maxAttempts = 5; + + $connected = true; + + $s = new Sites(); + $site = $s->get(); + $this->compressedHeaders = ($site->compressedheaders == "1") ? true : false; + $enc = false; + if (defined("NNTP_SSLENABLED") && NNTP_SSLENABLED == true) + $enc = 'ssl'; + + $ret = $this->connect(NNTP_SERVER, $enc, NNTP_PORT); + if(PEAR::isError($ret)) + { + $err = "Cannot connect to server ".NNTP_SERVER.(!$enc?" (nonssl) ":"(ssl) ").": ". $ret->getMessage(); + echo $err; + $connected = false; + } + if(!defined(NNTP_USERNAME) && NNTP_USERNAME!="" ) + { + $ret2 = $this->authenticate(NNTP_USERNAME, NNTP_PASSWORD); + if(PEAR::isError($ret2)) + { + $err = "Cannot authenticate to server ".NNTP_SERVER.(!$enc?" (nonssl) ":" (ssl) ")." - ". NNTP_USERNAME." (".$ret2->getMessage().")"; + echo $err; + $connected = false; + } + } + if ($this->compressedHeaders) + { + if ($comp==true) + { + $response = $this->_sendCommand('XFEATURE COMPRESS GZIP'); + if (PEAR::isError($response) || $response != 290) + { + //echo "NNTP: XFeature not supported.\n"; + } + else + { + $this->enableXFCompression(); + } + } + } + if ($attempt < $maxAttempts && !$connected) { + sleep(5); + $connected = $this->doConnect($attempt+1); + } + + if (!$connected && $attempt == 1) { + echo "\nTried to connect ".$maxAttempts." times, but couldn't. Check your settings and connection.\n"; + } + + return $connected; + } + /** + * End an NNTP connection. + */ + function doQuit() + { + $this->quit(); + } + + /** + * Retrieve an NNTP message and decode it. + */ + function getMessage($groupname, $partMsgId) + { + $summary = $this->selectGroup($groupname); + $message = $dec = ''; + + if (PEAR::isError($summary)) + { + echo "NntpPrc : ".substr($summary->getMessage(), 0, 30)."\n"; + return false; + } + $body = $this->getBody('<'.$partMsgId.'>', true); + if (PEAR::isError($body)) + { + printf("NntpPrc : Error fetching part number %s in %s (Server response: %s)\n", $partMsgId, $groupname, $body->getMessage()); + return false; + } + $message = $this->decodeYenc($body); + if (!$message) + { + // + // Yenc decode failed + // + return false; + } + return $message; + } + + /** + * Retrieve a series of NNTP messages and decode them. + */ + function getMessages($groupname, $msgIds) + { + $summary = $this->selectGroup($groupname); + $message = $dec = ''; + + if (PEAR::isError($summary)) + { + echo "NntpPrc : ".substr($summary->getMessage(), 0, 30)."\n"; + return false; + } + foreach($msgIds as $msgId) + { + $messageID = '<'.$msgId.'>'; + $body = $this->getBody($messageID, true); + if (PEAR::isError($body)) + { + printf("NntpPrc : Error fetching part number %s in %s (Server response: %s)\n", $messageID, $groupname, $body->getMessage()); + return false; + } + $dec = $this->decodeYenc($body); + if (!$dec) + { + // + // Yenc decode failed + // + return false; + } + $message .= $dec; + } + return $message; + } + + /** + * Retrieve all NNTP messages associated with a binaries.ID + */ + function getBinary($binaryId, $isNfo=false) + { + $db = new DB(); + $bin = new Binaries(); + + $binary = $bin->getById($binaryId); + if (!$binary) + { + printf("NntpPrc: Unable to locate binary: %s\n", $binaryId); + return false; + } + $summary = $this->selectGroup($binary['groupname']); + $message = $dec = ''; + + if (PEAR::isError($summary)) + { + echo "NntpPrc : ".substr($summary->getMessage(), 0, 30)."\n"; + return false; + } + $resparts = $db->query(sprintf("SELECT size, partnumber, messageID FROM parts WHERE binaryID = %d ORDER BY partnumber", $binaryId)); + + // Dont attempt to download nfos which are larger than one part. + if (sizeof($resparts) > 1 && $isNfo === true) + { + echo "NntpPrc : Error Nfo is too large... skipping.\n"; + return false; + } + foreach($resparts as $part) + { + $messageID = '<'.$part['messageID'].'>'; + $body = $this->getBody($messageID, true); + if (PEAR::isError($body)) + { + printf("NntpPrc : Error fetching part number %s in %s (Server response: %s)\n", $part['messageID'], $binary['groupname'], $body->getMessage()); + return false; + } + $dec = $this->decodeYenc($body); + if (!$dec) + { + printf("NntpPrc: Unable to decode body of binary: %s\n", $binaryId); + + // Yenc decode failed + return false; + } + $message .= $dec; + } + return $message; + } + + /** + * Decode a yenc encoded string. + */ + function decodeYenc($yencodedvar) + { + $input = array(); + preg_match("/^(=ybegin.*=yend[^$]*)$/ims", $yencodedvar, $input); + if (isset($input[1])) + { + $ret = ""; + $input = trim(preg_replace("/\r\n/im", "", preg_replace("/(^=yend.*)/im", "", preg_replace("/(^=ypart.*\\r\\n)/im", "", preg_replace("/(^=ybegin.*\\r\\n)/im", "", $input[1], 1), 1), 1))); + + for( $chr = 0; $chr < strlen($input) ; $chr++) + $ret .= ($input[$chr] != "=" ? chr(ord($input[$chr]) - 42) : chr((ord($input[++$chr]) - 64) - 42)); + + return $ret; + } + return false; + } + + /** + * Get XZVER for a range of NNTP messages. + */ + function getXOverview($range, $_names = true, $_forceNames = true) + { + $overview = $this->cmdXOver($range); + if (PEAR::isError($overview)) { + return $overview; } + + // Use field names from overview format as keys? + if ($_names) + { + // Already cached? + if (is_null($this->_overviewFormatCache)) { + // Fetch overview format + $format = $this->getOverviewFormat($_forceNames, true); + if (PEAR::isError($format)){ + return $format; + } + // Prepend 'Number' field + $format = array_merge(array('Number' => false), $format); + + // Cache format + $this->_overviewFormatCache = $format; + } + else + { + $format = $this->_overviewFormatCache; + } + // Loop through all articles + foreach ($overview as $key => $article) + { + if (sizeof($format) == sizeof($article)) + { + //Replace overview using $format as keys, $article as values + $overview[$key] = array_combine(array_keys($format), $article); + + // If article prefixed by field name, remove it + foreach($format as $fkey=>$fval) + { + if ($fval === true) + { + $overview[$key][$fkey] = trim(str_replace($fkey.':', '', $overview[$key][$fkey])); + } + } + } + } + } + switch (true) + { + // Expect one article + case is_null($range); + case is_int($range); + case is_string($range) && ctype_digit($range): + case is_string($range) && substr($range, 0, 1) == '<' && substr($range, -1, 1) == '>': + if (count($overview) == 0) { + return false; + } else { + return reset($overview); + } + break; + + // Expect multiple articles + default: + return $overview; + } + } + + // }}} + // {{{ cmdXZver() + /* + * Based on code from http://wonko.com/software/yenc/, but + * simplified because XZVER and the likes don't implement + * yenc properly + */ + private function yencDecode($string, $destination = "") { + $encoded = array(); + $header = array(); + $decoded = ''; + + # Extract the yEnc string itself + preg_match("/^(=ybegin.*=yend[^$]*)$/ims", $string, $encoded); + $encoded = $encoded[1]; + + # Extract the filesize and filename from the yEnc header + preg_match("/^=ybegin.*size=([^ $]+).*name=([^\\r\\n]+)/im", $encoded, $header); + $filesize = $header[1]; + $filename = $header[2]; + + # Remove the header and footer from the string before parsing it. + $encoded = preg_replace("/(^=ybegin.*\\r\\n)/im", "", $encoded, 1); + $encoded = preg_replace("/(^=yend.*)/im", "", $encoded, 1); + + # Remove linebreaks and whitespace from the string + $encoded = trim(str_replace("\r\n", "", $encoded)); + + // Decode + $strLength = strlen($encoded); + for($i = 0; $i < $strLength; $i++) { + $c = $encoded[$i]; + + if ($c == '=') { + $i++; + $decoded .= chr((ord($encoded[$i]) - 64) - 42); + } else { + $decoded .= chr(ord($c) - 42); + } + } + // Make sure the decoded filesize is the same as the size specified in the header. + if (strlen($decoded) != $filesize) { + throw new Exception("Filesize in yEnc header en filesize found do not match up"); + } + return $decoded; + } + + /** + * Fetch message header from message number $first until $last + * The format of the returned array is: + * $messages[message_id][header_name] + * @param optional string $range articles to fetch + * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure + * @access protected + */ + function cmdXZver($range = null) + { + if (is_null($range)) + $command = 'XZVER'; + else + $command = 'XZVER ' . $range; + $response = $this->_sendCommand($command); + + switch ($response) { + case 224: // RFC2980: 'Overview information follows' + $data = $this->_getCompressedResponse(); + foreach ($data as $key => $value) + $data[$key] = explode("\t", trim($value)); + + return $data; + break; + case 412: // RFC2980: 'No news group current selected' + return $this->throwError('No news group current selected', $response, $this->_currentStatusResponse()); + break; + case 420: // RFC2980: 'No article(s) selected' + return $this->throwError('No article(s) selected', $response, $this->_currentStatusResponse()); + break; + case 502: // RFC2980: 'no permission' + return $this->throwError('No permission', $response, $this->_currentStatusResponse()); + break; + case 500: // RFC2980: 'unknown command' + $this->throwError("XZver not supported ({$this->_currentStatusResponse()})", $response); + break; + default: + return $this->_handleUnexpectedResponse($response); + } + } + + /** + * Retrieve blob + * Get data and assume we do not hit any blindspots + * @return mixed (array) text response on success or (object) pear_error on failure + * @access private + */ + function _getCompressedResponse() + { + $data = array(); + + // We can have two kinds of compressed support: + // - yEnc encoding + // - Just a gzip drop + // We try to autodetect which one this uses + + $line = @fread($this->_socket, 1024); + if (substr($line, 0, 7) == '=ybegin') { + $data = $this->_getTextResponse(); + $data = $line . "\r\n" . implode("", $data); + $data = $this->yencDecode($data); + $data = explode("\r\n", gzinflate($data)); + return $data; + } + // We cannot use blocked I/O on this one + $streamMetadata = stream_get_meta_data($this->_socket); + stream_set_blocking($this->_socket, false); + + // Continue until connection is lost or we don't receive any data anymore + $tries = 0; + $uncompressed = ''; + while (!feof($this->_socket)) { + + # Retrieve and append up to 32k characters from the server + $received = @fread($this->_socket, 32768); + if (strlen($received) == 0) { + $tries++; + # Try decompression + $uncompressed = @gzuncompress($line); + if (($uncompressed !== false) || ($tries > 500)) { + break; + } + if ($tries % 50 == 0) { + } + } + # an error occured + if ($received === false) { + @fclose($this->_socket); + $this->_socket = false; + } + $line .= $received; + } + # and set the stream to its original blocked(?) value + stream_set_blocking($this->_socket, $streamMetadata['blocked']); + $data = explode("\r\n", $uncompressed); + $dataCount = count($data); + + # Gzipped compress includes the "." and linefeed in the compressed stream, skip those. + if ($dataCount >= 2) { + if (($data[($dataCount - 2)] == ".") && (empty($data[($dataCount - 1)]))) { + array_pop($data); + array_pop($data); + } + $data = array_filter($data); + } + return $data; + } + + /** + * Enable XFeature compression support for the current connection. + */ + function enableXFCompression() + { + $response = $this->_sendCommand('XFEATURE COMPRESS GZIP'); + + if (PEAR::isError($response) || $response != 290) { + echo "Xfeature compression not supported!\n"; + return false; + } + + $this->XFCompression = true; + echo "XFeature compression enabled\n"; + return true; + } + + /** + * Override to intercept any Xfeature compressed responses. + */ + function _getTextResponse() + { + if ($this->XFCompression && isset($this->_currentStatusResponse[1]) + && stripos($this->_currentStatusResponse[1], 'COMPRESS=GZIP') !== false) + { + return $this->_getXFCompressedTextResponse(); + } + return parent::_getTextResponse(); + } + + function _getXFCompressedTextResponse() + { + $tries = 0; + $bytesreceived = 0; + $totalbytesreceived = 0; + $completed = false; + $data = null; + //build binary array that represents zero results basically a compressed empty string terminated with .(period) char(13) char(10) + $emptyreturnend = chr(0x03).chr(0x00).chr(0x00).chr(0x00).chr(0x00).chr(0x01).chr(0x2e).chr(0x0d).chr(0x0a); + $emptyreturn = chr(0x78).chr(0x9C).$emptyreturnend; + $emptyreturn2 = chr(0x78).chr(0x01).$emptyreturnend; + $emptyreturn3 = chr(0x78).chr(0x5e).$emptyreturnend; + $emptyreturn4 = chr(0x78).chr(0xda).$emptyreturnend; + + while (!feof($this->_socket)) + { + $completed = false; + //get data from the stream + $buffer = fgets($this->_socket); + //get byte count and update total bytes + $bytesreceived = strlen($buffer); + //if we got no bytes at all try one more time to pull data. + if ($bytesreceived == 0) + { + $buffer = fgets($this->_socket); + } + //get any socket error codes + $errorcode = socket_last_error(); + + //if the buffer is zero its zero... + if ($bytesreceived === 0) + return $this->throwError('No data returned.', 1000); + //did we have any socket errors? + if ($errorcode === 0) + { + //append buffer to final data object + $data .= $buffer; + $totalbytesreceived = $totalbytesreceived+$bytesreceived; + + //output byte count in real time once we have 1MB of data + if ($totalbytesreceived > 10240) + if ($totalbytesreceived%128 == 0) + { + echo "bytes recived: "; + echo $totalbytesreceived; + echo "\r"; + } + + //check to see if we have the magic terminator on the byte stream + $b1 = null; + if ($bytesreceived > 2) + if (ord($buffer[$bytesreceived-3]) == 0x2e && ord($buffer[$bytesreceived-2]) == 0x0d && ord($buffer[$bytesreceived-1]) == 0x0a)//substr($buffer,-3) == ".\r\n" + { + //check to see if the returned binary string is 11 bytes long generally and indcator + //of an compressed empty string probably don't need this check + if ($totalbytesreceived==11) + { + //compare the data to the empty string if the data is a compressed empty string + //throw an error else return the data + if (($data === $emptyreturn)||($data === $emptyreturn2)||($data === $emptyreturn3)||($data === $emptyreturn4)) + { + echo "empty gzip stream\n"; + return $this->throwError('No data returned.', 1000); + } + } + else + { + echo "\n"; + $completed = true; + } + } + } + else + { + echo "failed to read from socket\n"; + return $this->throwError('Failed to read line from socket.', 1000); + } + + if ($completed) + { + //check to see if the header is valid for a gzip stream + if(ord($data[0]) == 0x78 && in_array(ord($data[1]),array(0x01,0x5e,0x9c,0xda))) + { + $decomp = @gzuncompress(mb_substr ( $data , 0 ,-3, '8bit' )); + } + else + { + echo "Invalid header on gzip stream.\n"; + return $this->throwError('Invalid gzip stream.', 1000); + } + + if ($decomp != false) + { + $decomp = explode("\r\n", trim($decomp)); + return $decomp; + } + else + { + $tries++; + echo "Decompression Failed Retry Number: $tries \n"; + } + } + } + //throw an error if we get out of the loop + if (!feof($this->_socket)) + { + return "Error: unexpected fgets() fail\n"; + } + return $this->throwError('Decompression Failed, connection closed.', 1000); + } +} diff --git a/scripts/update_svn.sh b/scripts/update_svn.sh index acb4d4c7a..ed4008638 100755 --- a/scripts/update_svn.sh +++ b/scripts/update_svn.sh @@ -8,6 +8,7 @@ fi #EDIT_THESE export NEWZPATH="/var/www/newznab" +source $NEWZPATH"/misc/update_scripts/nix_scripts/tmux/defaults.sh" export PASSWORD="password" svn co --force --username svnplus --password $PASSWORD svn://svn.newznab.com/nn/branches/nnplus $NEWZPATH/ @@ -16,6 +17,12 @@ svn export --force --username svnplus --password $PASSWORD svn://svn.newznab.com cd $NEWZPATH"/misc/update_scripts" php5 update_database_version.php + +if [[ $KEVINS_COMP == "true" ]]; then + cd $NEWZPATH"/misc/update_scripts/nix_scripts/tmux/scripts" + cp -fr ../kevin123/* $NEWZPATH/www/lib/ +fi + cd $NEWZPATH"/misc/update_scripts/nix_scripts/tmux/scripts" ./set_perms.sh