Seditio Source
Root |
./othercms/dotclear-2.22/inc/libs/clearbricks/net.nntp/class.net.nntp.php
<?php
/**
 * @class netNntp
 *
 * @package Clearbricks
 * @subpackage Network
 *
 * @copyright Olivier Meunier & Association Dotclear
 * @copyright GPL-2.0-only
 */
class netNntp extends netSocket
{
    public const
SERVER_READY         = 200;
    public const
SERVER_READY_NO_POST = 201;
    public const
GROUP_SELECTED       = 211;
    public const
INFORMATION_FOLLOWS  = 215;
    public const
ARTICLE_HEAD_BODY    = 220;
    public const
ARTICLE_HEAD         = 221;
    public const
ARTICLE_BODY         = 222;
    public const
ARTICLE_OVERVIEW     = 224;
    public const
NEW_ARTICLES         = 230;
    public const
ARTICLE_POST_OK      = 240;
    public const
ARTICLE_POST_READY   = 340;
    public const
AUTH_ACCEPT          = 281;
    public const
MORE_AUTH_INFO       = 381;
    public const
AUTH_REQUIRED        = 480;
    public const
AUTH_REJECTED        = 482;
    public const
NOT_IMPLEMENTED      = 500;
    public const
NO_PERMISSION        = 502;

    protected
$host;
    protected
$port;
    protected
$user;
    protected
$password;

    protected
$proxy_host;
    protected
$proxy_port;
    protected
$proxy_user;
    protected
$proxy_pass;
    protected
$use_proxy;

   
/**
     * Constructs a new instance.
     *
     * @param      mixed   $host      The host
     * @param      int     $port      The port
     * @param      mixed   $user      The user
     * @param      mixed   $password  The password
     * @param      int     $timeout   The timeout
     */
   
public function __construct($host, $port = 119, $user = null, $password = null, $timeout = 10)
    {
       
$this->host     = $host;
       
$this->port     = (int) $port;
       
$this->user     = $user;
       
$this->password = $password;
       
$this->_timeout = $timeout;
    }

   
/**
     * Send data
     *
     * @param      mixed  $data   The data
     *
     * @return     mixed
     */
   
public function write($data)
    {
        if (!
is_array($data)) {
           
$data = $data . "\r\n";
        }

        return
parent::write($data);
    }

    public function
close()
    {
        if (
$this->isOpen()) {
           
$this->sendRequest('quit');
           
parent::close();
        }
    }

   
/**
     * Open a connection
     *
     * @throws     Exception
     *
     * @return     mixed
     */
   
public function open()
    {
        if (
$this->isOpen()) {
            return
true;
        }

        if (
$this->use_proxy) {
           
$this->_host = $this->proxy_host;
           
$this->_port = $this->proxy_port;
        } else {
           
$this->_host = $this->host;
           
$this->_port = $this->port;
        }

       
$rsp = parent::open();

        if (
$this->isOpen()) {
            if (
$this->use_proxy) {
               
$data[] = 'CONNECT ' . $this->host . ':' . $this->port . ' HTTP/1.0';
                if (
$this->proxy_user && $this->proxy_pass) {
                   
$data[] = 'Proxy-Authorization: Basic ' .
                   
base64_encode($this->proxy_user . ':' . $this->proxy_pass);
                }

                foreach (
$this->write($data) as $i => $v) {
                    if (
$i == 0) {
                        if (
stristr($v, '200 Connection established')) {
                            continue;
                        }
                       
$rsp = [
                           
'status'  => self::NO_PERMISSION, # Assign it to something dummy
                           
'message' => 'No permission',
                        ];

                        break;
                    }
                    if (
$i == 2) {
                       
$rsp = $this->parseResponse($v);

                        break;
                    }
                }
            } else {
               
$rsp = $this->parseResponse($rsp->current());
            }

            if ((
$rsp['status'] == self::SERVER_READY) || ($rsp['status'] == self::SERVER_READY_NO_POST)) {
               
$this->sendRequest('mode reader');
                if (
$this->user) {
                   
$rsp = $this->parseResponse($this->sendRequest('authinfo user ' . $this->user));

                    if (
$rsp['status'] == self::MORE_AUTH_INFO) {
                       
$rsp = $this->parseResponse($this->sendRequest('authinfo pass ' . $this->password));

                        if (
$rsp['status'] == self::AUTH_ACCEPT) {
                            return
true;
                        }
                    }
                } else {
                    return
true;
                }
            }

            throw new
Exception($rsp['status'] . ' - ' . $rsp['message']);
        }

        return
false;
    }

    public function
setUser($user, $password = null)
    {
       
$this->user     = $user;
       
$this->password = $password;

        if (
$this->isOpen()) {
           
$this->close();
           
$this->open();
        }
    }

    public function
setProxy($proxy_host, $proxy_port = null, $proxy_user = null, $proxy_pass = null)
    {
       
$this->proxy_host = $proxy_host;
       
$this->proxy_port = $proxy_port;
       
$this->proxy_user = $proxy_user;
       
$this->proxy_pass = $proxy_pass;

        if ((
strcmp($this->proxy_host, '') != 0) && (strcmp($this->proxy_port, '') != 0)) {
           
$this->use_proxy = true;
        } else {
           
$this->use_proxy = false;
        }
    }

    public function
getGroupsList($group_pattern = null)
    {
       
$rsp = $this->write('list active ' . $group_pattern);
       
$r   = $this->parseResponse($rsp->current());

        if (
$r['status'] == self::INFORMATION_FOLLOWS) {
           
# List groups
           
$result = [];
            foreach (
$rsp as $buf) {
                if (
preg_match('/^\.\s*$/', $buf)) {
                    break;
                }

                [
$group, $last, $first, $post] = preg_split('/\s+/', $buf, 4);
               
$result[$group]                = [
                   
'desc'  => '',
                   
'last'  => trim((string) $last),
                   
'first' => trim((string) $first),
                   
'post'  => strtolower(trim((string) $post)),
                ];
            }

           
# Get groups descriptions
           
$rsp = $this->write(['list newsgroups ' . $group_pattern]);
           
$r   = $this->parseResponse($rsp->current());

            if (
$r['status'] == self::INFORMATION_FOLLOWS) {
                foreach (
$rsp as $buf) {
                    if (
self::eot($buf)) {
                        break;
                    }

                    [
$group, $desc] = preg_split('/\s+/', $buf, 2);
                    if (isset(
$result[$group])) {
                       
$result[$group]['desc'] = text::toUTF8(trim((string) $desc));
                    }
                }
            }

            return
$result;
        }

        throw new
Exception($r['message'] . ' - (' . $r['status'] . ')');
    }

    public function
joinGroup($group)
    {
       
$rsp = $this->parseResponse($this->sendRequest('group ' . $group));

        if (
$rsp['status'] == self::GROUP_SELECTED) {
           
$result = preg_split("/\s/", $rsp['message']);

            return [
               
'count'    => $result[0],
               
'start_id' => $result[1],
               
'end_id'   => $result[2],
               
'group'    => $result[3],
            ];
        }

        throw new
Exception($rsp['message'] . ' - (' . $rsp['status'] . ')');
    }

    public function
getArticleList($group = null)
    {
       
$rsp = $this->write('listgroup ' . $group);
       
$r   = $this->parseResponse($rsp->current());

        if (
$r['status'] == self::GROUP_SELECTED) {
           
$res = [];
            foreach (
$rsp as $i => $buf) {
                if (
self::eot($buf)) {
                    break;
                }
               
$res[] = trim((string) $buf);
            }

            return
$res;
        }

        throw new
Exception($r['message'] . ' - (' . $r['status'] . ')');
    }

    public function
getNewArticles($ts, $group)
    {
       
$ts = $ts + dt::getTimeOffset('UTC', $ts);

       
# First try with newnews
       
$rsp = $this->write('newnews ' . $group . ' ' . dt::str('%y%m%d %H%M%S', $ts) . ' GMT');
       
$r   = $this->parseResponse($rsp->current());

        if (
$r['status'] == self::NEW_ARTICLES) {
           
$res = [];
           
$rsp->current(); # we don't want first matched article
           
foreach ($rsp as $buf) {
                if (
self::eot($buf)) {
                    break;
                }
               
$res[] = trim((string) $buf);
            }

            return
$res;
        }
       
# newnews is not implemented, use xhdr instead
        # First, we need to join the group
       
$g = $this->joinGroup($group);

        if (
$g['count'] > 1000) {
           
$g['start_id'] = $g['end_id'] - 1000;
        }

       
# Then, xhdr on all group messages
       
$rsp = $this->write('xhdr date ' . $g['start_id'] . '-');
       
$r   = $this->parseResponse($rsp->current());

        if (
$r['status'] == self::ARTICLE_HEAD) {
           
$ts = $ts + dt::getTimeOffset('UTC', $ts);

           
$res = [];
            foreach (
$rsp as $buf) {
                if (
self::eot($buf)) {
                    break;
                }
               
$buf = preg_split('/\s/', $buf, 2);
                if (
strtotime($buf[1]) > $ts) {
                   
$res[] = $buf[0];
                }
            }

            return
$res;
        }

        throw new
Exception($r['message'] . ' - (' . $r['status'] . ')');
    }

    public function
getHeader($message_id, &$header = '')
    {
       
$rsp = $this->write('head ' . $message_id);
       
$r   = $this->parseResponse($rsp->current());

        if (
$r['status'] == self::ARTICLE_HEAD || $r['status'] == self::ARTICLE_HEAD_BODY) {
           
$header = '';
            foreach (
$rsp as $buf) {
                if (
self::eot($buf)) {
                    break;
                }
               
$header .= $buf;
            }

            return new
nntpMessage($header);
        }

        throw new
Exception($r['message'] . ' - (' . $r['status'] . ')');
    }

    public function
getArticle($message_id, &$article = '')
    {
       
$rsp = $this->write('article ' . $message_id);
       
$r   = $this->parseResponse($rsp->current());

        if (
$r['status'] == self::ARTICLE_BODY || $r['status'] == self::ARTICLE_HEAD_BODY) {
           
$article = '';
            foreach (
$rsp as $buf) {
                if (
self::eot($buf)) {
                    break;
                }
               
$article .= $buf;
            }

            return new
nntpMessage($article);
        }

        throw new
Exception($r['message'] . ' - (' . $r['status'] . ')');
    }

    public function
postArticle($headers = [], $content = '')
    {
        if (!
is_array($headers)) {
            return
false;
        }

        if (empty(
$headers['From'])) {
            throw new
Exception('No "From" header in message');
        }

       
$headers['Mime-Version']              = '1.0';
       
$headers['Content-Type']              = 'text/plain; charset=UTF-8';
       
$headers['Content-Transfer-Encoding'] = 'quoted-printable';

       
$content = mailConvert::rewrap($content);
       
$content = preg_replace('/^\./msu', '..$1', $content);
       
$content = text::QPEncode($content);

       
$data = [];
       
# Headers
       
foreach ($headers as $k => $v) {
           
$data[] = $k . ': ' . $v;
        }

       
# Blank line
       
$data[] = '';

       
# Body
       
foreach (preg_split("/\r?\n/msu", $content) as $l) {
           
$data[] = $l;
        }

       
# EOT
       
$data[] = '.';

       
$this->sendRequest('post');

       
$rsp = $this->write($data);
       
$r   = $this->parseResponse($rsp->current());
        if (
$r['status'] == self::ARTICLE_POST_OK) {
            return
true;
        }

        throw new
Exception($r['message'] . ' - (' . $r['status'] . ')');
    }

    protected static function
eot($l)
    {
        return
preg_match('/^\.\s*$/', $l);
    }

    protected function
assertOpen()
    {
        if (!
$this->isOpen()) {
            throw new
Exception('NNTP connexion not available');
        }
    }

    protected function
parseResponse($rsp)
    {
        return [
           
'status'  => substr($rsp, 0, 3),
           
'message' => rtrim(substr($rsp, 4), "\r\n"),
        ];
    }

    protected function
sendRequest($request)
    {
       
$this->assertOpen();

       
$rsp = $this->write($request);
       
$this->flush();

        return
$rsp->current();
    }
}