diff options
author | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 19:39:39 (GMT) |
---|---|---|
committer | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 19:39:39 (GMT) |
commit | ea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch) | |
tree | 6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/modules/nntp | |
parent | b5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff) | |
parent | 7c621da28f07e449ad90c387344f07a453927569 (diff) | |
download | blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2 |
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/modules/nntp')
-rw-r--r-- | tcllib/modules/nntp/ChangeLog | 154 | ||||
-rw-r--r-- | tcllib/modules/nntp/nntp.man | 338 | ||||
-rw-r--r-- | tcllib/modules/nntp/nntp.tcl | 979 | ||||
-rw-r--r-- | tcllib/modules/nntp/pkgIndex.tcl | 12 |
4 files changed, 1483 insertions, 0 deletions
diff --git a/tcllib/modules/nntp/ChangeLog b/tcllib/modules/nntp/ChangeLog new file mode 100644 index 0000000..29fcc8b --- /dev/null +++ b/tcllib/modules/nntp/ChangeLog @@ -0,0 +1,154 @@ +2013-03-11 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * rfc977.txt: Removed copies of RFC documents. Keep only links. + +2013-02-01 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.15 ======================== + * + +2011-12-13 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.14 ======================== + * + +2011-01-24 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.13 ======================== + * + +2009-12-07 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.12 ======================== + * + +2008-12-12 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.11.1 ======================== + * + +2008-10-16 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.11 ======================== + * + +2007-09-12 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.10 ======================== + * + +2007-03-21 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * nntp.man: Fixed all warnings due to use of now deprecated + commands. Added a section about how to give feedback. + +2006-10-03 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.9 ======================== + * + +2005-10-06 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.8 ======================== + * + +2004-10-05 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.7 ======================== + * + +2004-05-23 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.6.1 ======================== + * + +2004-02-15 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.6 ======================== + * + +2003-05-05 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.4 ======================== + * + +2003-04-11 Andreas Kupries <andreask@activestate.com> + + * nntp.man: + * nntp.tcl: + * pkgIndex.tcl: Set version of the package to to 0.2.1 + +2003-02-24 David N. Welton <davidw@dedasys.com> + + * nntp.tcl (::nntp::squirt): Use if, string match instead of + regsub. + +2003-02-06 David N. Welton <davidw@dedasys.com> + + * nntp.tcl (::nntp::fetch): Use 'string match' instead of regexp. + Use if string match ... string range instead of regsub (it's + about twice as fast in a small test I ran). + +2003-01-16 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * nntp.man: More semantic markup, less visual one. + +2002-08-19 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * nntp.man: Added example, updated reference from rfc 850 to rfc + 1036. See Tcllib SF #597102, by Jussi Kuosa + <Jussi.Kuosa@tellabs.com>. + * nntp.n: Out of date. Deprecated. + +2002-03-25 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * nntp.man: New file, doctools manpage. + +2002-01-16 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * Bumped version to 0.2 + +2002-01-16 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * nntp.tcl: Fixed bug #502250 reported by Andreas Otto + <aotto@t-online.de> which caused the package to wrap each + message into braces, causing nntp servers to reject the data. + +2001-07-10 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * nntp.tcl: Frink 2.2 run, fixed dubious code. + +2001-06-21 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * nntp.tcl: Fixed dubious code reported by frink. + +2000-06-20 Dan Kuchler <kuchler@ajubasolutions.com> + + * Code cleanup and bug fixes + +2000-06-18 Dan Kuchler <kuchler@ajubasolutions.com> + + * Fixed documentation bug in man page for xpat + +2000-06-16 Dan Kuchler <kuchler@ajubasolutions.com> + + * rfc977.txt: RFC for NNTP + + * pkgIndex.tcl + * nntp.tcl: Initial implementation of a nntp client package. + + * nntp.n: Initial documentation for the package. + diff --git a/tcllib/modules/nntp/nntp.man b/tcllib/modules/nntp/nntp.man new file mode 100644 index 0000000..8b06ec4 --- /dev/null +++ b/tcllib/modules/nntp/nntp.man @@ -0,0 +1,338 @@ +[comment {-*- tcl -*- doctools manpage}] +[manpage_begin nntp n 1.5.1] +[keywords news] +[keywords nntp] +[keywords nntpclient] +[keywords {rfc 977}] +[keywords {rfc 1036}] +[moddesc {Tcl NNTP Client Library}] +[titledesc {Tcl client for the NNTP protocol}] +[category Networking] +[require Tcl 8.2] +[require nntp [opt 0.2.1]] +[description] + +The package [package nntp] provides a simple Tcl-only client library +for the NNTP protocol. It works by opening the standard NNTP socket +on the server, and then providing a Tcl API to access the NNTP +protocol commands. All server errors are returned as Tcl errors +(thrown) which must be caught with the Tcl [cmd catch] command. + +[section COMMANDS] + +[list_begin definitions] + +[call [cmd ::nntp::nntp] [opt [arg host]] [opt [arg port]] [opt [arg nntpName]]] + +The command opens a socket connection to the specified NNTP server and +creates a new nntp object with an associated global Tcl command whose +name is [arg nntpName]. This command may be used to access the various +NNTP protocol commands for the new connection. The default [arg port] +number is "119" and the default [arg host] is "news". These defaults +can be overridden with the environment variables [var NNTPPORT] and +[var NNTPHOST] respectively. + +[para] + +Some of the commands supported by this package are not part of the +nntp rfc 977 ([uri http://www.rfc-editor.org/rfc/rfc977.txt]) and will +not be available (or implemented) on all nntp servers. + +[para] + +The access command [arg nntpName] has the following general form: + +[list_begin definitions] + +[call [arg nntpName] [method method] [opt [arg "arg arg ..."]]] + +[arg Option] and the [arg arg]s determine the exact behavior of the +command. + +[list_end] + +[call [arg nntpName] [method article] [opt [arg msgid]]] + +Query the server for article [arg msgid] from the current group. The article +is returned as a valid tcl list which contains the headers, followed by +a blank line, and then followed by the body of the article. Each element +in the list is one line of the article. + +[call [arg nntpName] [method authinfo] [opt [arg user]] [opt [arg pass]]] + +Send authentication information (username and password) to the server. + +[call [arg nntpName] [method body] [opt [arg msgid]]] + +Query the server for the body of the article [arg msgid] from the current +group. The body of the article is returned as a valid tcl list. Each element +in the list is one line of the body of the article. + +[call [arg nntpName] [method configure]] +[call [arg nntpName] [method configure] [arg option]] +[call [arg nntpName] [method configure] [arg option] [arg value] ...] +[call [arg nntpName] [method cget] [arg option]] + +Query and configure options of the nntp connection object. Currently +only one option is supported, [option -binary]. When set articles are +retrieved as binary data instead of text. The only methods affected by +this are [method article] and [method body]. + +[para] + +One application of this option would be the download of articles +containing yEnc encoded images. Although encoded the data is still +mostly binary and retrieving it as text will corrupt the information. + +[para] + +See package [package yencode] for both encoder and decoder of such data. + +[call [arg nntpName] [method date]] + +Query the server for the servers current date. The date is returned in the +format [emph YYYYMMDDHHMMSS]. + +[call [arg nntpName] [method group] [opt [arg group]]] + +Optionally set the current group, and retrieve information about the +currently selected group. Returns the estimated number of articles in +the group followed by the number of the first article in the group, followed +by the last article in the group, followed by the name of the group. + +[call [arg nntpName] [method head] [opt [arg msgid]]] + +Query the server for the headers of the article [arg msgid] from the current +group. The headers of the article are returned as a valid tcl list. Each element +in the list is one line of the headers of the article. + +[call [arg nntpName] [method help]] + +Retrieves a list of the commands that are supported by the news server that +is currently attached to. + +[call [arg nntpName] [method last]] + +Sets the current article pointer to point to the previous message (if there is +one) and returns the msgid of that message. + +[call [arg nntpName] [method list]] + +Returns a tcl list of valid newsgroups and associated information. Each +newsgroup is returned as an element in the tcl list with the following format: +[example { + group last first p +}] +where <group> is the name of the newsgroup, <last> is the number of +the last known article currently in that newsgroup, <first> is the +number of the first article currently in the newsgroup, and <p> is +either 'y' or 'n' indicating whether posting to this newsgroup is +allowed ('y') or prohibited ('n'). +[para] +The <first> and <last> fields will always be numeric. They may have +leading zeros. If the <last> field evaluates to less than the +<first> field, there are no articles currently on file in the +newsgroup. + +[call [arg nntpName] [method listgroup] [opt [arg group]]] + +Query the server for a list of all the messages (message numbers) in the +group specified by the argument [arg group] or by the current group if +the [arg group] argument was not passed. + +[call [arg nntpName] [method mode_reader]] + +Query the server for its nntp 'MODE READER' response string. + +[call [arg nntpName] [method newgroups] [arg since]] + +Query the server for a list of all the new newsgroups created since the time +specified by the argument [arg since]. The argument [arg since] can be any +time string that is understood by [cmd {clock scan}]. The tcl list of newsgroups +is returned in a similar form to the list of groups returned by the +[cmd {nntpName list}] command. Each element of the list has the form: + +[example { + group last first p +}] +where <group> is the name of the newsgroup, <last> is the number of +the last known article currently in that newsgroup, <first> is the +number of the first article currently in the newsgroup, and <p> is +either 'y' or 'n' indicating whether posting to this newsgroup is +allowed ('y') or prohibited ('n'). + +[call [arg nntpName] [method newnews]] + +Query the server for a list of new articles posted to the current group in the +last day. + +[call [arg nntpName] [method newnews] [arg since]] + +Query the server for a list of new articles posted to the current group since +the time specified by the argument [arg since]. The argument [arg since] can +be any time string that is understood by [cmd {clock scan}]. + +[call [arg nntpName] [method newnews] [arg group] [opt [arg since]]] + +Query the server for a list of new articles posted to the group specified by +the argument [arg group] since the time specified by the argument [arg since] +(or in the past day if no [arg since] argument is passed. The argument +[arg since] can be any time string that is understood by [cmd {clock scan}]. + +[call [arg nntpName] [method next]] + +Sets the current article pointer to point to the next message (if there is +one) and returns the msgid of that message. + +[call [arg nntpName] [method post] [arg article]] + +Posts an article of the form specified in +RFC 1036 ([uri http://www.rfc-editor.org/rfc/rfc1036.txt], successor +to RFC 850) to the current news group. + +[call [arg nntpName] [method slave]] + +Identifies a connection as being made from a slave nntp server. This might +be used to indicate that the connection is serving multiple people and should +be given priority. Actual use is entirely implementation dependent and may +vary from server to server. + +[call [arg nntpName] [method stat] [opt [arg msgid]]] + +The stat command is similar to the article command except that no +text is returned. When selecting by message number within a group, +the stat command serves to set the current article pointer without +sending text. The returned acknowledgment response will contain the +message-id, which may be of some value. Using the stat command to +select by message-id is valid but of questionable value, since a +selection by message-id does NOT alter the "current article pointer" + +[call [arg nntpName] [method quit]] + +Gracefully close the connection after sending a NNTP QUIT command down +the socket. + +[call [arg nntpName] [method xgtitle] [opt [arg group_pattern]]] + +Returns a tcl list where each element is of the form: +[example { +newsgroup description +}] +If a [arg group_pattern] is specified then only newsgroups that match +the pattern will have their name and description returned. + +[call [arg nntpName] [method xhdr] [arg field] [opt [arg range]]] + +Returns the specified header field value for the current message or for a +list of messages from the current group. [arg field] is the title of a +field in the header such as from, subject, date, etc. If [arg range] is +not specified or is "" then the current message is queried. The command +returns a list of elements where each element has the form of: +[example { + msgid value +}] +Where msgid is the number of the message and value is the value set for the +queried field. The [arg range] argument can be in any of the following forms: + +[list_begin definitions] + +[def [const {""}]] + +The current message is queried. + +[def [arg msgid1]-[arg msgid2]] + +All messages between [arg msgid1] and [arg msgid2] +(including [arg msgid1] and [arg msgid2]) are queried. + +[def "[arg msgid1] [arg msgid2]"] + +All messages between [arg msgid1] and [arg msgid2] +(including [arg msgid1] and [arg msgid2]) are queried. + +[list_end] + +[call [arg nntpName] [method xover] [opt [arg range]]] + +Returns header information for the current message or for a range of messages +from the current group. The information is returned in a tcl list +where each element is of the form: +[example { + msgid subject from date idstring bodysize headersize xref +}] +If [arg range] is not specified or is "" then the current message is queried. +The [arg range] argument can be in any of the following forms: + +[list_begin definitions] + +[def [const {""}]] + +The current message is queried. + +[def [arg msgid1]-[arg msgid2]] + +All messages between [arg msgid1] and [arg msgid2] +(including [arg msgid1] and [arg msgid2]) are queried. + +[def "[arg msgid1] [arg msgid2]"] + +All messages between [arg msgid1] and [arg msgid2] +(including [arg msgid1] and [arg msgid2]) are queried. + +[list_end] + +[call [arg nntpName] [method xpat] [arg field] [arg range] [opt [arg pattern_list]]] + +Returns the specified header field value for a specified message or for a +list of messages from the current group where the messages match the +pattern(s) given in the pattern_list. [arg field] is the title of a +field in the header such as from, subject, date, etc. The information is +returned in a tcl list where each element is of the form: +[example { + msgid value +}] +Where msgid is the number of the message and value is the value set for the +queried field. The [arg range] argument can be in any of the following forms: + +[list_begin definitions] + +[def [arg msgid]] + +The message specified by [arg msgid] is queried. + +[def [arg msgid1]-[arg msgid2]] + +All messages between [arg msgid1] and [arg msgid2] +(including [arg msgid1] and [arg msgid2]) are queried. + +[def "[arg msgid1] [arg msgid2]"] + +All messages between [arg msgid1] and [arg msgid2] +(including [arg msgid1] and [arg msgid2]) are queried. + +[list_end] +[list_end] + +[section EXAMPLE] + +A bigger example for posting a single article. + +[para] +[example { + package require nntp + set n [nntp::nntp NNTP_SERVER] + $n post "From: USER@DOMAIN.EXT (USER_FULL) + Path: COMPUTERNAME!USERNAME + Newsgroups: alt.test + Subject: Tcl test post -ignore + Message-ID: <[pid][clock seconds] + @COMPUTERNAME> + Date: [clock format [clock seconds] -format "%a, %d % + b %y %H:%M:%S GMT" -gmt true] + + Test message body" +}] + +[vset CATEGORY nntp] +[include ../doctools2base/include/feedback.inc] +[manpage_end] diff --git a/tcllib/modules/nntp/nntp.tcl b/tcllib/modules/nntp/nntp.tcl new file mode 100644 index 0000000..58b1c73 --- /dev/null +++ b/tcllib/modules/nntp/nntp.tcl @@ -0,0 +1,979 @@ +# nntp.tcl -- +# +# nntp implementation for Tcl. +# +# Copyright (c) 1998-2000 by Ajuba Solutions. +# All rights reserved. +# +# RCS: @(#) $Id: nntp.tcl,v 1.13 2004/05/03 22:56:25 andreas_kupries Exp $ + +package require Tcl 8.2 +package provide nntp 0.2.1 + +namespace eval ::nntp { + # The socks variable holds the handle to the server connections + variable socks + + # The counter is used to help create unique connection names + variable counter 0 + + # commands is the list of subcommands recognized by nntp + variable commands [list \ + "article" \ + "authinfo" \ + "body" \ + "date" \ + "group" \ + "head" \ + "help" \ + "last" \ + "list" \ + "listgroup" \ + "mode_reader" \ + "newgroups" \ + "newnews" \ + "next" \ + "post" \ + "stat" \ + "quit" \ + "xgtitle" \ + "xhdr" \ + "xover" \ + "xpat" \ + ] + + set ::nntp::eol "\n" + + # only export one command, the one used to instantiate a new + # nntp connection + namespace export nntp + +} + +# ::nntp::nntp -- +# +# Create a new nntp connection. +# +# Arguments: +# server - The name of the nntp server to connect to (optional). +# port - The port number to connect to (optional). +# name - The name of the nntp connection to create (optional). +# +# Results: +# Creates a connection to the a nntp server. By default the +# connection is established with the machine 'news' at port '119' +# These defaults can be overridden with the environment variables +# NNTPPORT and NNTPHOST, or can be passed as optional arguments + +proc ::nntp::nntp {{server ""} {port ""} {name ""}} { + global env + variable connections + variable counter + variable socks + + # If a name wasn't specified for the connection, create a new 'unique' + # name for the connection + + if { [llength [info level 0]] < 4 } { + set counter 0 + set name "nntp${counter}" + while {[lsearch -exact [info commands] $name] >= 0} { + incr counter + set name "nntp${counter}" + } + } + + if { ![string equal [info commands ::$name] ""] } { + error "command \"$name\" already exists, unable to create nntp connection" + } + + upvar 0 ::nntp::${name}data data + + set socks($name) [list ] + + # Initialize instance specific variables + + set data(debug) 0 + set data(eol) "\n" + + # Logic to determine whether to use the specified nntp server, or to use + # the default + + if {$server == ""} { + if {[info exists env(NNTPSERVER)]} { + set data(host) "$env(NNTPSERVER)" + } else { + set data(host) "news" + } + } else { + set data(host) $server + } + + # Logic to determine whether to use the specified nntp port, or to use the + # default. + + if {$port == ""} { + if {[info exists env(NNTPPORT)]} { + set data(port) $env(NNTPPORT) + } else { + set data(port) 119 + } + } else { + set data(port) $port + } + + set data(code) 0 + set data(mesg) "" + set data(addr) "" + set data(binary) 0 + + set sock [socket $data(host) $data(port)] + + set data(sock) $sock + + # Create the command to manipulate the nntp connection + + interp alias {} ::$name {} ::nntp::NntpProc $name + + ::nntp::response $name + + return $name +} + +# ::nntp::NntpProc -- +# +# Command that processes all nntp object commands. +# +# Arguments: +# name name of the nntp object to manipulate. +# args command name and args for the command. +# +# Results: +# Calls the appropriate nntp procedure for the command specified in +# 'args' and passes 'args' to the command/procedure. + +proc ::nntp::NntpProc {name {cmd ""} args} { + + # Do minimal args checks here + + if { [llength [info level 0]] < 3 } { + error "wrong # args: should be \"$name option ?arg arg ...?\"" + } + + # Split the args into command and args components + + if { [llength [info commands ::nntp::_$cmd]] == 0 } { + variable commands + set optlist [join $commands ", "] + set optlist [linsert $optlist "end-1" "or"] + error "bad option \"$cmd\": must be $optlist" + } + + # Call the appropriate command with its arguments + + return [eval [linsert $args 0 ::nntp::_$cmd $name]] +} + +# ::nntp::okprint -- +# +# Used to test the return code stored in data(code) to +# make sure that it is alright to right to the socket. +# +# Arguments: +# name name of the nntp object. +# +# Results: +# Either throws an error describing the failure, or +# 'args' and passes 'args' to the command/procedure or +# returns 1 for 'OK' and 0 for error states. + +proc ::nntp::okprint {name} { + upvar 0 ::nntp::${name}data data + + if {$data(code) >=400} { + set val [expr {(0 < $data(code)) && ($data(code) < 400)}] + error "NNTPERROR: $data(code) $data(mesg)" + } + + # Codes less than 400 are good + + return [expr {(0 < $data(code)) && ($data(code) < 400)}] +} + +# ::nntp::message -- +# +# Used to format data(mesg) for printing to the socket +# by appending the appropriate end of line character which +# is stored in data(eol). +# +# Arguments: +# name name of the nntp object. +# +# Results: +# Returns a string containing the message from data(mesg) followed +# by the eol character(s) stored in data(eol) + +proc ::nntp::message {name} { + upvar 0 ::nntp::${name}data data + + return "$data(mesg)$data(eol)" +} + +################################################# +# +# NNTP Methods +# + +proc ::nntp::_cget {name option} { + upvar 0 ::nntp::${name}data data + + if {[string equal $option -binary]} { + return $data(binary) + } else { + return -code error \ + "Illegal option \"$option\", expected \"-binary\"" + } +} + +proc ::nntp::_configure {name args} { + upvar 0 ::nntp::${name}data data + + if {[llength $args] == 0} { + return [list -binary $data(binary)] + } + if {[llength $args] == 1} { + return [_cget $name [lindex $args 0]] + } + if {([llength $args] % 2) == 1} { + return -code error \ + "wrong#args: expected even number of elements" + } + foreach {o v} $args { + if {[string equal $o -binary]} { + if {![string is boolean -strict $v]} { + return -code error \ + "Expected boolean, got \"$v\"" + } + set data(binary) $v + } else { + return -code error \ + "Illegal option \"$o\", expected \"-binary\"" + } + } + return {} +} + + +# ::nntp::_article -- +# +# Internal article proc. Called by the 'nntpName article' command. +# Retrieves the article specified by msgid, in the group specified by +# the 'nntpName group' command. If no msgid is specified the current +# (or first) article in the group is retrieved +# +# Arguments: +# name name of the nntp object. +# msgid The article number to retrieve +# +# Results: +# Returns the message (if there is one) from the specified group as +# a valid tcl list where each element is a line of the message. +# If no article is found, the "" string is returned. +# +# According to RFC 977 the responses are: +# +# 220 n article retrieved - head and body follow +# (n = article number, = message-id) +# 221 n article retrieved - head follows +# 222 n article retrieved - body follows +# 223 n article retrieved - request text separately +# 412 no newsgroup has been selected +# 420 no current article has been selected +# 423 no such article number in this group +# 430 no such article found +# + +proc ::nntp::_article {name {msgid ""}} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "fetch" + return [::nntp::command $name "ARTICLE $msgid"] +} + +# ::nntp::_authinfo -- +# +# Internal authinfo proc. Called by the 'nntpName authinfo' command. +# Passes the username and password for a nntp server to the nntp server. +# +# Arguments: +# name Name of the nntp object. +# user The username for the nntp server. +# pass The password for 'username' on the nntp server. +# +# Results: +# Returns the result of the attempts to set the username and password +# on the nntp server ( 1 if successful, 0 if failed). + +proc ::nntp::_authinfo {name {user "guest"} {pass "foobar"}} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "" + set res [::nntp::command $name "AUTHINFO USER $user"] + if {$res} { + set res [expr {$res && [::nntp::command $name "AUTHINFO PASS $pass"]}] + } + return $res +} + +# ::nntp::_body -- +# +# Internal body proc. Called by the 'nntpName body' command. +# Retrieves the body of the article specified by msgid from the group +# specified by the 'nntpName group' command. If no msgid is specified +# the current (or first) message body is returned +# +# Arguments: +# name Name of the nntp object. +# msgid The number of the body of the article to retrieve +# +# Results: +# Returns the body of article 'msgid' from the group specified through +# 'nntpName group'. If msgid is not specified or is "" then the body of +# the current (or the first) article in the newsgroup will be returned +# as a valid tcl list. The "" string will be returned if there is no +# article 'msgid' or if no group has been specified. + +proc ::nntp::_body {name {msgid ""}} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "fetch" + return [::nntp::command $name "BODY $msgid"] +} + +# ::nntp::_group -- +# +# Internal group proc. Called by the 'nntpName group' command. +# Sets the current group on the nntp server to the group passed in. +# +# Arguments: +# name Name of the nntp object. +# group The name of the group to set as the default group. +# +# Results: +# Sets the default group to the group specified. If no group is specified +# or if an invalid group is specified an error is thrown. +# +# According to RFC 977 the responses are: +# +# 211 n f l s group selected +# (n = estimated number of articles in group, +# f = first article number in the group, +# l = last article number in the group, +# s = name of the group.) +# 411 no such news group + +proc ::nntp::_group {name {group ""}} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "groupinfo" + if {$group == ""} { + set group $data(group) + } + return [::nntp::command $name "GROUP $group"] +} + +# ::nntp::_head -- +# +# Internal head proc. Called by the 'nntpName head' command. +# Retrieves the header of the article specified by msgid from the group +# specified by the 'nntpName group' command. If no msgid is specified +# the current (or first) message header is returned +# +# Arguments: +# name Name of the nntp object. +# msgid The number of the header of the article to retrieve +# +# Results: +# Returns the header of article 'msgid' from the group specified through +# 'nntpName group'. If msgid is not specified or is "" then the header of +# the current (or the first) article in the newsgroup will be returned +# as a valid tcl list. The "" string will be returned if there is no +# article 'msgid' or if no group has been specified. + +proc ::nntp::_head {name {msgid ""}} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "fetch" + return [::nntp::command $name "HEAD $msgid"] +} + +# ::nntp::_help -- +# +# Internal help proc. Called by the 'nntpName help' command. +# Retrieves a list of the valid nntp commands accepted by the server. +# +# Arguments: +# name Name of the nntp object. +# +# Results: +# Returns the NNTP commands expected by the NNTP server. + +proc ::nntp::_help {name} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "fetch" + return [::nntp::command $name "HELP"] +} + +proc ::nntp::_ihave {name {msgid ""} args} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "fetch" + if {![::nntp::command $name "IHAVE $msgid"]} { + return "" + } + return [::nntp::squirt $name "$args"] +} + +# ::nntp::_last -- +# +# Internal last proc. Called by the 'nntpName last' command. +# Sets the current message to the message before the current message. +# +# Arguments: +# name Name of the nntp object. +# +# Results: +# None. + +proc ::nntp::_last {name} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "msgid" + return [::nntp::command $name "LAST"] +} + +# ::nntp::_list -- +# +# Internal list proc. Called by the 'nntpName list' command. +# Lists all groups or (optionally) all groups of a specified type. +# +# Arguments: +# name Name of the nntp object. +# Type The type of groups to return (active active.times newsgroups +# distributions distrib.pats moderators overview.fmt +# subscriptions) - optional. +# +# Results: +# Returns a tcl list of all groups or the groups that match 'type' if +# a type is specified. + +proc ::nntp::_list {name {type ""}} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "fetch" + return [::nntp::command $name "LIST $type"] +} + +# ::nntp::_newgroups -- +# +# Internal newgroups proc. Called by the 'nntpName newgroups' command. +# Lists all new groups since a specified time. +# +# Arguments: +# name Name of the nntp object. +# since The time to find new groups since. The time can be in any +# format that is accepted by 'clock scan' in tcl. +# +# Results: +# Returns a tcl list of all new groups added since the time specified. + +proc ::nntp::_newgroups {name since args} { + upvar 0 ::nntp::${name}data data + + set since [clock format [clock scan "$since"] -format "%y%m%d %H%M%S"] + set dist "" + set data(cmnd) "fetch" + return [::nntp::command $name "NEWGROUPS $since $dist"] +} + +# ::nntp::_newnews -- +# +# Internal newnews proc. Called by the 'nntpName newnews' command. +# Lists all new news in the specified group since a specified time. +# +# Arguments: +# name Name of the nntp object. +# group Name of the newsgroup to query. +# since The time to find new groups since. The time can be in any +# format that is accepted by 'clock scan' in tcl. Defaults to +# "1 day ago" +# +# Results: +# Returns a tcl list of all new messages since the time specified. + +proc ::nntp::_newnews {name {group ""} {since ""}} { + upvar 0 ::nntp::${name}data data + + if {$group != ""} { + if {[regexp -- {^[\w\.\-]+$} $group] == 0} { + set since $group + set group "" + } + } + if {![info exists group] || ($group == "")} { + if {[info exists data(group)] && ($data(group) != "")} { + set group $data(group) + } else { + set group "*" + } + } + if {"$since" == ""} { + set since [clock format [clock scan "now - 1 day"]] + } + set since [clock format [clock scan $since] -format "%y%m%d %H%M%S"] + set dist "" + set data(cmnd) "fetch" + return [::nntp::command $name "NEWNEWS $group $since $dist"] +} + +# ::nntp::_next -- +# +# Internal next proc. Called by the 'nntpName next' command. +# Sets the current message to the next message after the current message. +# +# Arguments: +# name Name of the nntp object. +# +# Results: +# None. + +proc ::nntp::_next {name} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "msgid" + return [::nntp::command $name "NEXT"] +} + +# ::nntp::_post -- +# +# Internal post proc. Called by the 'nntpName post' command. +# Posts a message to a newsgroup. +# +# Responses (according to RFC 977) to a post request: +# 240 article posted ok +# 340 send article to be posted. End with . +# 440 posting not allowed +# 441 posting failed +# +# Arguments: +# name Name of the nntp object. +# article A message of the form specified in RFC 850 +# +# Results: +# None. + +proc ::nntp::_post {name article} { + + if {![::nntp::command $name "POST"]} { + return "" + } + return [::nntp::squirt $name "$article"] +} + +# ::nntp::_slave -- +# +# Internal slave proc. Called by the 'nntpName slave' command. +# Identifies a connection as being made from a slave nntp server. +# This might be used to indicate that the connection is serving +# multiple people and should be given priority. Actual use is +# entirely implementation dependant and may vary from server to +# server. +# +# Arguments: +# name Name of the nntp object. +# +# Results: +# None. +# +# According to RFC 977 the only response is: +# +# 202 slave status noted + +proc ::nntp::_slave {name} { + return [::nntp::command $name "SLAVE"] +} + +# ::nntp::_stat -- +# +# Internal stat proc. Called by the 'nntpName stat' command. +# The stat command is similar to the article command except that no +# text is returned. When selecting by message number within a group, +# the stat command serves to set the current article pointer without +# sending text. The returned acknowledgement response will contain the +# message-id, which may be of some value. Using the stat command to +# select by message-id is valid but of questionable value, since a +# selection by message-id does NOT alter the "current article pointer" +# +# Arguments: +# name Name of the nntp object. +# msgid The number of the message to stat (optional) default is to +# stat the current article +# +# Results: +# Returns the statistics for the article. + +proc ::nntp::_stat {name {msgid ""}} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "status" + return [::nntp::command $name "STAT $msgid"] +} + +# ::nntp::_quit -- +# +# Internal quit proc. Called by the 'nntpName quit' command. +# Quits the nntp session and closes the socket. Deletes the command +# that was created for the connection. +# +# Arguments: +# name Name of the nntp object. +# +# Results: +# Returns the return value from the quit command. + +proc ::nntp::_quit {name} { + upvar 0 ::nntp::${name}data data + + set ret [::nntp::command $name "QUIT"] + close $data(sock) + rename ${name} {} + return $ret +} + +############################################################# +# +# Extended methods (not available on all NNTP servers +# + +proc ::nntp::_date {name} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "msg" + return [::nntp::command $name "DATE"] +} + +proc ::nntp::_listgroup {name {group ""}} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "fetch" + return [::nntp::command $name "LISTGROUP $group"] +} + +proc ::nntp::_mode_reader {name} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "msg" + return [::nntp::command $name "MODE READER"] +} + +proc ::nntp::_xgtitle {name {group_pattern ""}} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "fetch" + return [::nntp::command $name "XGTITLE $group_pattern"] +} + +proc ::nntp::_xhdr {name {header "message-id"} {list ""} {last ""}} { + upvar 0 ::nntp::${name}data data + + if {![regexp -- {\d+-\d+} $list]} { + if {"$last" != ""} { + set list "$list-$last" + } else { + set list "" + } + } + set data(cmnd) "fetch" + return [::nntp::command $name "XHDR $header $list"] +} + +proc ::nntp::_xindex {name {group ""}} { + upvar 0 ::nntp::${name}data data + + if {("$group" == "") && [info exists data(group)]} { + set group $data(group) + } + set data(cmnd) "fetch" + return [::nntp::command $name "XINDEX $group"] +} + +proc ::nntp::_xmotd {name {since ""}} { + upvar 0 ::nntp::${name}data data + + if {"$since" != ""} { + set since [clock seconds] + } + set since [clock format [clock scan $since] -format "%y%m%d %H%M%S"] + set data(cmnd) "fetch" + return [::nntp::command $name "XMOTD $since"] +} + +proc ::nntp::_xover {name {list ""} {last ""}} { + upvar 0 ::nntp::${name}data data + if {![regexp -- {\d+-\d+} $list]} { + if {"$last" != ""} { + set list "$list-$last" + } else { + set list "" + } + } + set data(cmnd) "fetch" + return [::nntp::command $name "XOVER $list"] +} + +proc ::nntp::_xpat {name {header "subject"} {list 1} {last ""} args} { + upvar 0 ::nntp::${name}data data + + set patterns "" + + if {![regexp -- {\d+-\d+} $list]} { + if {("$last" != "") && ([string is digit $last])} { + set list "$list-$last" + } + } elseif {"$last" != ""} { + set patterns "$last" + } + + if {[llength $args] > 0} { + set patterns "$patterns $args" + } + + if {"$patterns" == ""} { + set patterns "*" + } + + set data(cmnd) "fetch" + return [::nntp::command $name "XPAT $header $list $patterns"] +} + +proc ::nntp::_xpath {name {msgid ""}} { + upvar 0 ::nntp::${name}data data + + set data(cmnd) "msg" + return [::nntp::command $name "XPATH $msgid"] +} + +proc ::nntp::_xsearch {name args} { + set res [::nntp::command $name "XSEARCH"] + if {!$res} { + return "" + } + return [::nntp::squirt $name "$args"] +} + +proc ::nntp::_xthread {name args} { + upvar 0 ::nntp::${name}data data + + if {[llength $args] > 0} { + set filename "dbinit" + } else { + set filename "thread" + } + set data(cmnd) "fetchbinary" + return [::nntp::command $name "XTHREAD $filename"] +} + +###################################################### +# +# Helper methods +# + +proc ::nntp::cmd {name cmd} { + upvar 0 ::nntp::${name}data data + + set eol "\015\012" + set sock $data(sock) + if {$data(debug)} { + puts stderr "$sock command $cmd" + } + puts $sock "$cmd" + flush $sock + return +} + +proc ::nntp::command {name args} { + set res [eval [linsert $args 0 ::nntp::cmd $name]] + + return [::nntp::response $name] +} + +proc ::nntp::msg {name} { + upvar 0 ::nntp::${name}data data + + set res [::nntp::okprint $name] + if {!$res} { + return "" + } + return $data(mesg) +} + +proc ::nntp::groupinfo {name} { + upvar 0 ::nntp::${name}data data + + set data(group) "" + + if {[::nntp::okprint $name] && [regexp -- {(\d+)\s+(\d+)\s+(\d+)\s+([\w\.]+)} \ + $data(mesg) match count first last data(group)]} { + return [list $count $first $last $data(group)] + } + return "" +} + +proc ::nntp::msgid {name} { + upvar 0 ::nntp::${name}data data + + set result "" + if {[::nntp::okprint $name] && \ + [regsub -- {\s+<[^>]+>} $data(mesg) {} result]} { + return $result + } else { + return "" + } +} + +proc ::nntp::status {name} { + upvar 0 ::nntp::${name}data data + + set result "" + if {[::nntp::okprint $name] && \ + [regexp -- {\d+\s+<[^>]+>} $data(mesg) result]} { + return $result + } else { + return "" + } +} + +proc ::nntp::fetch {name} { + upvar 0 ::nntp::${name}data data + + set eol "\012" + + if {![::nntp::okprint $name]} { + return "" + } + set sock $data(sock) + + if {$data(binary)} { + set oldenc [fconfigure $sock -encoding] + fconfigure $sock -encoding binary + } + + set result [list ] + while {![eof $sock]} { + gets $sock line + regsub -- {\015?\012$} $line $data(eol) line + + if {[string match "." $line]} { + break + } + if { [string match "..*" $line] } { + lappend result [string range $line 1 end] + } else { + lappend result $line + } + } + + if {$data(binary)} { + fconfigure $sock -encoding $oldenc + } + + return $result +} + +proc ::nntp::response {name} { + upvar 0 ::nntp::${name}data data + + set eol "\012" + + set sock $data(sock) + + gets $sock line + set data(code) 0 + set data(mesg) "" + + if {$line == ""} { + error "nntp: unexpected EOF on $sock\n" + } + + regsub -- {\015?\012$} $line "" line + + set result [regexp -- {^((\d\d)(\d))\s*(.*)} $line match \ + data(code) val1 val2 data(mesg)] + + if {$result == 0} { + puts stderr "nntp garbled response: $line\n"; + return "" + } + + if {$val1 == 20} { + set data(post) [expr {!$val2}] + } + + if {$data(debug)} { + puts stderr "val1 $val1 val2 $val2" + puts stderr "code '$data(code)'" + puts stderr "mesg '$data(mesg)'" + if {[info exists data(post)]} { + puts stderr "post '$data(post)'" + } + } + + return [::nntp::returnval $name] +} + +proc ::nntp::returnval {name} { + upvar 0 ::nntp::${name}data data + + if {([info exists data(cmnd)]) \ + && ($data(cmnd) != "")} { + set command $data(cmnd) + } else { + set command okprint + } + + if {$data(debug)} { + puts stderr "returnval command '$command'" + } + + set data(cmnd) "" + return [::nntp::$command $name] +} + +proc ::nntp::squirt {name {body ""}} { + upvar 0 ::nntp::${name}data data + + set body [split $body \n] + + if {$data(debug)} { + puts stderr "$data(sock) sending [llength $body] lines\n"; + } + + foreach line $body { + # Print each line, possibly prepending a dot for lines + # starting with a dot and trimming any trailing \n. + if { [string match ".*" $line] } { + set line ".$line" + } + puts $data(sock) $line + } + puts $data(sock) "." + flush $data(sock) + + if {$data(debug)} { + puts stderr "$data(sock) is finished sending" + } + return [::nntp::response $name] +} +#eof + diff --git a/tcllib/modules/nntp/pkgIndex.tcl b/tcllib/modules/nntp/pkgIndex.tcl new file mode 100644 index 0000000..ac36bfe --- /dev/null +++ b/tcllib/modules/nntp/pkgIndex.tcl @@ -0,0 +1,12 @@ +# Tcl package index file, version 1.1 +# This file is generated by the "pkg_mkIndex" command +# and sourced either when an application starts up or +# by a "package unknown" script. It invokes the +# "package ifneeded" command to set up package-related +# information so that packages will be loaded automatically +# in response to "package require" commands. When this +# script is sourced, the variable $dir must contain the +# full path name of this file's directory. + +if {![package vsatisfies [package provide Tcl] 8.2]} {return} +package ifneeded nntp 0.2.1 [list source [file join $dir nntp.tcl]] |