home *** CD-ROM | disk | FTP | other *** search
/ H4CK3R 5 / hacker05 / 05_HACK_05.ISO / programacao / freewrap / TCLLIBsampleApp.exe / sample / tcllib / tcllib1.0 / nntp / nntp.tcl next >
Encoding:
Text File  |  2001-08-17  |  23.9 KB  |  924 lines

  1. # nntp.tcl --
  2. #
  3. #       nntp implementation for Tcl.
  4. #
  5. # Copyright (c) 1998-2000 by Ajuba Solutions.
  6. # All rights reserved.
  7. # RCS: @(#) $Id: nntp.tcl,v 1.5 2001/08/02 16:38:07 andreas_kupries Exp $
  8.  
  9. package require Tcl 8.2
  10. package provide nntp 0.1
  11.  
  12. namespace eval ::nntp {
  13.     # The socks variable holds the handle to the server connections
  14.     variable socks
  15.  
  16.     # The counter is used to help create unique connection names
  17.     variable counter 0
  18.  
  19.     # commands is the list of subcommands recognized by nntp
  20.     variable commands [list \
  21.             "article"     \
  22.             "authinfo"    \
  23.             "body"        \
  24.             "date"        \
  25.             "group"       \
  26.             "head"        \
  27.             "help"        \
  28.             "last"        \
  29.             "list"        \
  30.             "listgroup"   \
  31.             "mode_reader" \
  32.             "newgroups"   \
  33.             "newnews"     \
  34.             "next"        \
  35.             "post"        \
  36.             "stat"        \
  37.             "quit"        \
  38.             "xgtitle"     \
  39.             "xhdr"        \
  40.             "xover"       \
  41.             "xpat"        \
  42.             ]
  43.  
  44.     set ::nntp::eol "\n"
  45.  
  46.     # only export one command, the one used to instantiate a new
  47.     # nntp connection 
  48.     namespace export nntp
  49.  
  50. }
  51.  
  52. # ::nntp::nntp --
  53. #
  54. #       Create a new nntp connection.
  55. #
  56. # Arguments:
  57. #        server -   The name of the nntp server to connect to (optional).
  58. #        port -     The port number to connect to (optional).
  59. #        name -     The name of the nntp connection to create (optional).
  60. #
  61. # Results:
  62. #    Creates a connection to the a nntp server.  By default the
  63. #    connection is established with the machine 'news' at port '119'
  64. #    These defaults can be overridden with the environment variables
  65. #    NNTPPORT and NNTPHOST, or can be passed as optional arguments
  66.  
  67. proc ::nntp::nntp {{server ""} {port ""} {name ""}} {
  68.     global env
  69.     variable connections
  70.     variable counter
  71.     variable socks
  72.  
  73.     # If a name wasn't specified for the connection, create a new 'unique'
  74.     # name for the connection 
  75.  
  76.     if { [llength [info level 0]] < 4 } {
  77.         set counter 0
  78.         set name "nntp${counter}"
  79.         while {[lsearch -exact [info commands] $name] >= 0} {
  80.             incr counter
  81.             set name "nntp${counter}"
  82.         }
  83.     }
  84.  
  85.     if { ![string equal [info commands ::$name] ""] } {
  86.         error "command \"$name\" already exists, unable to create nntp connection"
  87.     }
  88.  
  89.     upvar 0 ::nntp::${name}data data
  90.  
  91.     set socks($name) [list ]
  92.  
  93.     # Initialize instance specific variables
  94.  
  95.     set data(debug) 0
  96.     set data(eol) "\n"
  97.  
  98.     # Logic to determine whether to use the specified nntp server, or to use
  99.     # the default
  100.  
  101.     if {$server == ""} {
  102.         if {[info exists env(NNTPSERVER)]} {
  103.             set data(host) "$env(NNTPSERVER)"
  104.         } else {
  105.             set data(host) "news"
  106.         }
  107.     } else {
  108.         set data(host) $server
  109.     }
  110.  
  111.     # Logic to determine whether to use the specified nntp port, or to use the
  112.     # default.
  113.  
  114.     if {$port == ""} {
  115.         if {[info exists env(NNTPPORT)]} {
  116.             set data(port) $env(NNTPPORT)
  117.         } else {    
  118.             set data(port) 119
  119.         }
  120.     } else {
  121.         set data(port) $port
  122.     }
  123.  
  124.     set data(code) 0
  125.     set data(mesg) ""
  126.     set data(addr) ""
  127.  
  128.     set sock [socket $data(host) $data(port)]
  129.  
  130.     set data(sock) $sock
  131.  
  132.     # Create the command to manipulate the nntp connection
  133.  
  134.     interp alias {} ::$name {} ::nntp::NntpProc $name
  135.     
  136.     ::nntp::response $name
  137.  
  138.     return $name
  139. }
  140.  
  141. # ::nntp::NntpProc --
  142. #
  143. #       Command that processes all nntp object commands.
  144. #
  145. # Arguments:
  146. #       name    name of the nntp object to manipulate.
  147. #       args    command name and args for the command.
  148. #
  149. # Results:
  150. #       Calls the appropriate nntp procedure for the command specified in
  151. #       'args' and passes 'args' to the command/procedure.
  152.  
  153. proc ::nntp::NntpProc {name {cmd ""} args} {
  154.  
  155.     # Do minimal args checks here
  156.  
  157.     if { [llength [info level 0]] < 3 } {
  158.         error "wrong # args: should be \"$name option ?arg arg ...?\""
  159.     }
  160.  
  161.     # Split the args into command and args components
  162.  
  163.     if { [llength [info commands ::nntp::_$cmd]] == 0 } {
  164.         variable commands
  165.         set optlist [join $commands ", "]
  166.         set optlist [linsert $optlist "end-1" "or"]
  167.         error "bad option \"$cmd\": must be $optlist"
  168.     }
  169.  
  170.     # Call the appropriate command with its arguments
  171.  
  172.     return [eval [list ::nntp::_$cmd $name] $args]
  173. }
  174.  
  175. # ::nntp::okprint --
  176. #
  177. #       Used to test the return code stored in data(code) to
  178. #       make sure that it is alright to right to the socket.
  179. #
  180. # Arguments:
  181. #       name    name of the nntp object.
  182. #
  183. # Results:
  184. #       Either throws an error describing the failure, or
  185. #       'args' and passes 'args' to the command/procedure or
  186. #       returns 1 for 'OK' and 0 for error states.   
  187.  
  188. proc ::nntp::okprint {name} {
  189.     upvar 0 ::nntp::${name}data data
  190.  
  191.     if {$data(code) >=400} {
  192.         set val [expr {(0 < $data(code)) && ($data(code) < 400)}]
  193.         error "NNTPERROR: $data(code) $data(mesg)"
  194.     }
  195.  
  196.     # Codes less than 400 are good
  197.  
  198.     return [expr {(0 < $data(code)) && ($data(code) < 400)}]
  199. }
  200.  
  201. # ::nntp::message --
  202. #
  203. #       Used to format data(mesg) for printing to the socket
  204. #       by appending the appropriate end of line character which
  205. #       is stored in data(eol).
  206. #
  207. # Arguments:
  208. #       name    name of the nntp object.
  209. #
  210. # Results:
  211. #       Returns a string containing the message from data(mesg) followed
  212. #       by the eol character(s) stored in data(eol)
  213.  
  214. proc ::nntp::message {name} {
  215.     upvar 0 ::nntp::${name}data data
  216.  
  217.     return "$data(mesg)$data(eol)"
  218. }
  219.  
  220. #################################################
  221. #
  222. # NNTP Methods
  223. #
  224.  
  225. # ::nntp::_article --
  226. #
  227. #       Internal article proc.  Called by the 'nntpName article' command.
  228. #       Retrieves the article specified by msgid, in the group specified by
  229. #       the 'nntpName group' command.  If no msgid is specified the current 
  230. #       (or first) article in the group is retrieved
  231. #
  232. # Arguments:
  233. #       name    name of the nntp object.
  234. #       msgid   The article number to retrieve
  235. #
  236. # Results:
  237. #       Returns the message (if there is one) from the specified group as
  238. #       a valid tcl list where each element is a line of the message.
  239. #       If no article is found, the "" string is returned.
  240. #
  241. # According to RFC 977 the responses are:
  242. #
  243. #   220 n  article retrieved - head and body follow
  244. #           (n = article number,  = message-id)
  245. #   221 n  article retrieved - head follows
  246. #   222 n  article retrieved - body follows
  247. #   223 n  article retrieved - request text separately
  248. #   412 no newsgroup has been selected
  249. #   420 no current article has been selected
  250. #   423 no such article number in this group
  251. #   430 no such article found
  252. #
  253.  
  254. proc ::nntp::_article {name {msgid ""}} {
  255.     upvar 0 ::nntp::${name}data data
  256.  
  257.     set data(cmnd) "fetch"
  258.     return [::nntp::command $name "ARTICLE $msgid"]
  259. }
  260.  
  261. # ::nntp::_authinfo --
  262. #
  263. #       Internal authinfo proc.  Called by the 'nntpName authinfo' command.
  264. #       Passes the username and password for a nntp server to the nntp server. 
  265. #
  266. # Arguments:
  267. #       name    Name of the nntp object.
  268. #       user    The username for the nntp server.
  269. #       pass    The password for 'username' on the nntp server.
  270. #
  271. # Results:
  272. #       Returns the result of the attempts to set the username and password
  273. #       on the nntp server ( 1 if successful, 0 if failed).
  274.  
  275. proc ::nntp::_authinfo {name {user "guest"} {pass "foobar"}} {
  276.     upvar 0 ::nntp::${name}data data
  277.  
  278.     set data(cmnd) ""
  279.     set res [::nntp::command $name "AUTHINFO USER $user"]
  280.     if {$res} {
  281.         set res [expr {$res && [::nntp::command $name "AUTHINFO PASS $pass"]}]
  282.     }
  283.     return $res
  284. }
  285.  
  286. # ::nntp::_body --
  287. #
  288. #       Internal body proc.  Called by the 'nntpName body' command.
  289. #       Retrieves the body of the article specified by msgid from the group
  290. #       specified by the 'nntpName group' command. If no msgid is specified
  291. #       the current (or first) message body is returned  
  292. #
  293. # Arguments:
  294. #       name    Name of the nntp object.
  295. #       msgid   The number of the body of the article to retrieve
  296. #
  297. # Results:
  298. #       Returns the body of article 'msgid' from the group specified through
  299. #       'nntpName group'. If msgid is not specified or is "" then the body of
  300. #       the current (or the first) article in the newsgroup will be returned 
  301. #       as a valid tcl list.  The "" string will be returned if there is no
  302. #       article 'msgid' or if no group has been specified.
  303.  
  304. proc ::nntp::_body {name {msgid ""}} {
  305.     upvar 0 ::nntp::${name}data data
  306.  
  307.     set data(cmnd) "fetch"
  308.     return [::nntp::command $name "BODY $msgid"]
  309. }
  310.  
  311. # ::nntp::_group --
  312. #
  313. #       Internal group proc.  Called by the 'nntpName group' command.
  314. #       Sets the current group on the nntp server to the group passed in.
  315. #
  316. # Arguments:
  317. #       name    Name of the nntp object.
  318. #       group   The name of the group to set as the default group.
  319. #
  320. # Results:
  321. #    Sets the default group to the group specified. If no group is specified
  322. #    or if an invalid group is specified an error is thrown.
  323. #
  324. # According to RFC 977 the responses are:
  325. #
  326. #  211 n f l s group selected
  327. #           (n = estimated number of articles in group,
  328. #           f = first article number in the group,
  329. #           l = last article number in the group,
  330. #           s = name of the group.)
  331. #  411 no such news group
  332.  
  333. proc ::nntp::_group {name {group ""}} {
  334.     upvar 0 ::nntp::${name}data data
  335.  
  336.     set data(cmnd) "groupinfo"
  337.     if {$group == ""} {
  338.         set group $data(group)
  339.     }
  340.     return [::nntp::command $name "GROUP $group"]
  341. }
  342.  
  343. # ::nntp::_head --
  344. #
  345. #       Internal head proc.  Called by the 'nntpName head' command.
  346. #       Retrieves the header of the article specified by msgid from the group
  347. #       specified by the 'nntpName group' command. If no msgid is specified
  348. #       the current (or first) message header is returned  
  349. #
  350. # Arguments:
  351. #       name    Name of the nntp object.
  352. #       msgid   The number of the header of the article to retrieve
  353. #
  354. # Results:
  355. #       Returns the header of article 'msgid' from the group specified through
  356. #       'nntpName group'. If msgid is not specified or is "" then the header of
  357. #       the current (or the first) article in the newsgroup will be returned 
  358. #       as a valid tcl list.  The "" string will be returned if there is no
  359. #       article 'msgid' or if no group has been specified.
  360.  
  361. proc ::nntp::_head {name {msgid ""}} {
  362.     upvar 0 ::nntp::${name}data data
  363.  
  364.     set data(cmnd) "fetch"
  365.     return [::nntp::command $name "HEAD $msgid"]
  366. }
  367.  
  368. # ::nntp::_help --
  369. #
  370. #       Internal help proc.  Called by the 'nntpName help' command.
  371. #       Retrieves a list of the valid nntp commands accepted by the server.
  372. #
  373. # Arguments:
  374. #       name    Name of the nntp object.
  375. #
  376. # Results:
  377. #       Returns the NNTP commands expected by the NNTP server.
  378.  
  379. proc ::nntp::_help {name} {
  380.     upvar 0 ::nntp::${name}data data
  381.  
  382.     set data(cmnd) "fetch"
  383.     return [::nntp::command $name "HELP"]
  384. }
  385.  
  386. proc ::nntp::_ihave {name {msgid ""} args} {
  387.     upvar 0 ::nntp::${name}data data
  388.  
  389.     set data(cmnd) "fetch"
  390.     if {![::nntp::command $name "IHAVE $msgid"]} {
  391.         return ""
  392.     }
  393.     return [::nntp::squirt $name "$args"]    
  394. }
  395.  
  396. # ::nntp::_last --
  397. #
  398. #       Internal last proc.  Called by the 'nntpName last' command.
  399. #       Sets the current message to the message before the current message.
  400. #
  401. # Arguments:
  402. #       name    Name of the nntp object.
  403. #
  404. # Results:
  405. #       None.
  406.  
  407. proc ::nntp::_last {name} {
  408.     upvar 0 ::nntp::${name}data data
  409.  
  410.     set data(cmnd) "msgid"
  411.     return [::nntp::command $name "LAST"]
  412. }
  413.  
  414. # ::nntp::_list --
  415. #
  416. #       Internal list proc.  Called by the 'nntpName list' command.
  417. #       Lists all groups or (optionally) all groups of a specified type.
  418. #
  419. # Arguments:
  420. #       name    Name of the nntp object.
  421. #       Type    The type of groups to return (active active.times newsgroups
  422. #               distributions distrib.pats moderators overview.fmt
  423. #               subscriptions) - optional.
  424. #
  425. # Results:
  426. #       Returns a tcl list of all groups or the groups that match 'type' if
  427. #       a type is specified.
  428.  
  429. proc ::nntp::_list {name {type ""}} {
  430.     upvar 0 ::nntp::${name}data data
  431.  
  432.     set data(cmnd) "fetch"
  433.     return [::nntp::command $name "LIST $type"]
  434. }
  435.  
  436. # ::nntp::_newgroups --
  437. #
  438. #       Internal newgroups proc.  Called by the 'nntpName newgroups' command.
  439. #       Lists all new groups since a specified time.
  440. #
  441. # Arguments:
  442. #       name    Name of the nntp object.
  443. #       since   The time to find new groups since.  The time can be in any
  444. #               format that is accepted by 'clock scan' in tcl.
  445. #
  446. # Results:
  447. #       Returns a tcl list of all new groups added since the time specified. 
  448.  
  449. proc ::nntp::_newgroups {name since args} {
  450.     upvar 0 ::nntp::${name}data data
  451.  
  452.     set since [clock format [clock scan "$since"] -format "%y%m%d %H%M%S"]
  453.     set dist ""
  454.     set data(cmnd) "fetch"
  455.     return [::nntp::command $name "NEWGROUPS $since $dist"]
  456. }
  457.  
  458. # ::nntp::_newnews --
  459. #
  460. #       Internal newnews proc.  Called by the 'nntpName newnews' command.
  461. #       Lists all new news in the specified group since a specified time.
  462. #
  463. # Arguments:
  464. #       name    Name of the nntp object.
  465. #       group   Name of the newsgroup to query.
  466. #       since   The time to find new groups since.  The time can be in any
  467. #               format that is accepted by 'clock scan' in tcl. Defaults to
  468. #               "1 day ago"
  469. #
  470. # Results:
  471. #       Returns a tcl list of all new messages since the time specified. 
  472.  
  473. proc ::nntp::_newnews {name {group ""} {since ""}} {
  474.     upvar 0 ::nntp::${name}data data
  475.  
  476.     if {$group != ""} {
  477.         if {[regexp -- {^[\w\.\-]+$} $group] == 0} {
  478.             set since $group
  479.             set group ""
  480.         }
  481.     }
  482.     if {![info exists group] || ($group == "")} {
  483.         if {[info exists data(group)] && ($data(group) != "")} {
  484.             set group $data(group)
  485.         } else {
  486.             set group "*"
  487.         }
  488.     }
  489.     if {"$since" == ""} {
  490.         set since [clock format [clock scan "now - 1 day"]]
  491.     }
  492.     set since [clock format [clock scan $since] -format "%y%m%d %H%M%S"]
  493.     set dist "" 
  494.     set data(cmnd) "fetch"
  495.     return [::nntp::command $name "NEWNEWS $group $since $dist"]
  496. }
  497.  
  498. # ::nntp::_next --
  499. #
  500. #       Internal next proc.  Called by the 'nntpName next' command.
  501. #       Sets the current message to the next message after the current message.
  502. #
  503. # Arguments:
  504. #       name    Name of the nntp object.
  505. #
  506. # Results:
  507. #       None.
  508.  
  509. proc ::nntp::_next {name} {
  510.     upvar 0 ::nntp::${name}data data
  511.  
  512.     set data(cmnd) "msgid"
  513.     return [::nntp::command $name "NEXT"]
  514. }
  515.  
  516. # ::nntp::_post --
  517. #
  518. #       Internal post proc.  Called by the 'nntpName post' command.
  519. #       Posts a message to a newsgroup.
  520. #
  521. # Responses (according to RFC 977) to a post request:
  522. #  240 article posted ok
  523. #  340 send article to be posted. End with .
  524. #  440 posting not allowed
  525. #  441 posting failed
  526. #
  527. # Arguments:
  528. #       name    Name of the nntp object.
  529. #       args    A message of the form specified in RFC 850
  530. #
  531. # Results:
  532. #       None.
  533.  
  534. proc ::nntp::_post {name args} {
  535.     
  536.     if {![::nntp::command $name "POST"]} {
  537.         return ""
  538.     }
  539.     return [::nntp::squirt $name "$args"]
  540. }
  541.  
  542. # ::nntp::_slave --
  543. #
  544. #       Internal slave proc.  Called by the 'nntpName slave' command.
  545. #       Identifies a connection as being made from a slave nntp server.
  546. #       This might be used to indicate that the connection is serving
  547. #       multiple people and should be given priority.  Actual use is 
  548. #       entirely implementation dependant and may vary from server to
  549. #       server.
  550. #
  551. # Arguments:
  552. #       name    Name of the nntp object.
  553. #
  554. # Results:
  555. #       None.
  556. #
  557. # According to RFC 977 the only response is:
  558. #
  559. #    202 slave status noted
  560.  
  561. proc ::nntp::_slave {name} {
  562.     return [::nntp::command $name "SLAVE"]
  563. }
  564.  
  565. # ::nntp::_stat --
  566. #
  567. #       Internal stat proc.  Called by the 'nntpName stat' command.
  568. #       The stat command is similar to the article command except that no
  569. #       text is returned.  When selecting by message number within a group,
  570. #       the stat command serves to set the current article pointer without
  571. #       sending text. The returned acknowledgement response will contain the
  572. #       message-id, which may be of some value.  Using the stat command to
  573. #       select by message-id is valid but of questionable value, since a
  574. #       selection by message-id does NOT alter the "current article pointer"
  575. #
  576. # Arguments:
  577. #       name    Name of the nntp object.
  578. #       msgid   The number of the message to stat (optional) default is to
  579. #               stat the current article
  580. #
  581. # Results:
  582. #       Returns the statistics for the article.
  583.  
  584. proc ::nntp::_stat {name {msgid ""}} {
  585.     upvar 0 ::nntp::${name}data data
  586.  
  587.     set data(cmnd) "status"
  588.     return [::nntp::command $name "STAT $msgid"]
  589. }
  590.  
  591. # ::nntp::_quit --
  592. #
  593. #       Internal quit proc.  Called by the 'nntpName quit' command.
  594. #       Quits the nntp session and closes the socket.  Deletes the command
  595. #       that was created for the connection.
  596. #
  597. # Arguments:
  598. #       name    Name of the nntp object.
  599. #
  600. # Results:
  601. #       Returns the return value from the quit command.
  602.  
  603. proc ::nntp::_quit {name} {
  604.     upvar 0 ::nntp::${name}data data
  605.  
  606.     set ret [::nntp::command $name "QUIT"]
  607.     close $data(sock)
  608.     rename ${name} {}
  609.     return $ret
  610. }
  611.  
  612. #############################################################
  613. #
  614. # Extended methods (not available on all NNTP servers
  615. #
  616.  
  617. proc ::nntp::_date {name} {
  618.     upvar 0 ::nntp::${name}data data
  619.  
  620.     set data(cmnd) "msg"
  621.     return [::nntp::command $name "DATE"]
  622. }
  623.  
  624. proc ::nntp::_listgroup {name {group ""}} {
  625.     upvar 0 ::nntp::${name}data data
  626.  
  627.     set data(cmnd) "fetch"
  628.     return [::nntp::command $name "LISTGROUP $group"]
  629. }
  630.  
  631. proc ::nntp::_mode_reader {name} {
  632.     upvar 0 ::nntp::${name}data data
  633.  
  634.     set data(cmnd) "msg"
  635.     return [::nntp::command $name "MODE READER"]
  636. }
  637.  
  638. proc ::nntp::_xgtitle {name {group_pattern ""}} {
  639.     upvar 0 ::nntp::${name}data data
  640.  
  641.     set data(cmnd) "fetch"
  642.     return [::nntp::command $name "XGTITLE $group_pattern"]
  643. }
  644.  
  645. proc ::nntp::_xhdr {name {header "message-id"} {list ""} {last ""}} {
  646.     upvar 0 ::nntp::${name}data data
  647.  
  648.     if {![regexp -- {\d+-\d+} $list]} {
  649.         if {"$last" != ""} {
  650.             set list "$list-$last"
  651.         } else {
  652.             set list ""
  653.     }
  654.     }
  655.     set data(cmnd) "fetch"
  656.     return [::nntp::command $name "XHDR $header $list"]    
  657. }
  658.  
  659. proc ::nntp::_xindex {name {group ""}} {
  660.     upvar 0 ::nntp::${name}data data
  661.  
  662.     if {("$group" == "") && [info exists data(group)]} {
  663.         set group $data(group)
  664.     }
  665.     set data(cmnd) "fetch"
  666.     return [::nntp::command $name "XINDEX $group"]    
  667. }
  668.  
  669. proc ::nntp::_xmotd {name {since ""}} {
  670.     upvar 0 ::nntp::${name}data data
  671.  
  672.     if {"$since" != ""} {
  673.         set since [clock seconds]
  674.     }
  675.     set since [clock format [clock scan $since] -format "%y%m%d %H%M%S"]
  676.     set data(cmnd) "fetch"
  677.     return [::nntp::command $name "XMOTD $since"]    
  678. }
  679.  
  680. proc ::nntp::_xover {name {list ""} {last ""}} {
  681.     upvar 0 ::nntp::${name}data data
  682.     if {![regexp -- {\d+-\d+} $list]} {
  683.         if {"$last" != ""} {
  684.             set list "$list-$last"
  685.         } else {
  686.             set list ""
  687.     }
  688.     }
  689.     set data(cmnd) "fetch"
  690.     return [::nntp::command $name "XOVER $list"]
  691. }
  692.  
  693. proc ::nntp::_xpat {name {header "subject"} {list 1} {last ""} args} {
  694.     upvar 0 ::nntp::${name}data data
  695.  
  696.     set patterns ""
  697.  
  698.     if {![regexp -- {\d+-\d+} $list]} {
  699.         if {("$last" != "") && ([string is digit $last])} {
  700.             set list "$list-$last"
  701.         }
  702.     } elseif {"$last" != ""} {
  703.         set patterns "$last"
  704.     }
  705.     
  706.     if {[llength $args] > 0} {
  707.         set patterns "$patterns $args"
  708.     }
  709.  
  710.     if {"$patterns" == ""} {
  711.         set patterns "*"
  712.     }
  713.     
  714.     set data(cmnd) "fetch"
  715.     return [::nntp::command $name "XPAT $header $list $patterns"]
  716. }
  717.  
  718. proc ::nntp::_xpath {name {msgid ""}} {
  719.     upvar 0 ::nntp::${name}data data
  720.  
  721.     set data(cmnd) "msg"
  722.     return [::nntp::command $name "XPATH $msgid"]
  723. }
  724.  
  725. proc ::nntp::_xsearch {name args} {
  726.     set res [::nntp::command $name "XSEARCH"]
  727.     if {!$res} {
  728.         return ""
  729.     }
  730.     return [::nntp::squirt $name "$args"]    
  731. }
  732.  
  733. proc ::nntp::_xthread {name args} {
  734.     upvar 0 ::nntp::${name}data data
  735.  
  736.     if {[llength $args] > 0} {
  737.         set filename "dbinit"
  738.     } else {
  739.         set filename "thread"
  740.     }
  741.     set data(cmnd) "fetchbinary"
  742.     return [::nntp::command $name "XTHREAD $filename"]
  743. }
  744.  
  745. ######################################################
  746. #
  747. # Helper methods
  748. #
  749.  
  750. proc ::nntp::cmd {name cmd} {
  751.     upvar 0 ::nntp::${name}data data
  752.  
  753.     set eol "\015\012"
  754.     set sock $data(sock)
  755.     if {$data(debug)} {
  756.         puts stderr "$sock command $cmd"
  757.     }
  758.     puts $sock "$cmd"
  759.     flush $sock
  760.     return
  761. }
  762.  
  763. proc ::nntp::command {name args} {
  764.     set res [eval [list ::nntp::cmd $name] $args]
  765.     
  766.     return [::nntp::response $name]
  767. }
  768.  
  769. proc ::nntp::msg {name} {
  770.     upvar 0 ::nntp::${name}data data
  771.  
  772.     set res [::nntp::okprint $name]
  773.     if {!$res} {
  774.         return ""
  775.     }
  776.     return $data(mesg)
  777. }
  778.  
  779. proc ::nntp::groupinfo {name} {
  780.     upvar 0 ::nntp::${name}data data
  781.  
  782.     set data(group) ""
  783.  
  784.     if {[::nntp::okprint $name] && [regexp -- {(\d+)\s+(\d+)\s+(\d+)\s+([\w\.]+)} \
  785.             $data(mesg) match count first last data(group)]} {
  786.         return [list $count $first $last $data(group)]
  787.     }
  788.     return ""
  789. }
  790.  
  791. proc ::nntp::msgid {name} {
  792.     upvar 0 ::nntp::${name}data data
  793.  
  794.     set result ""
  795.     if {[::nntp::okprint $name] && \
  796.             [regsub -- {\s+<[^>]+>} $data(mesg) {} result]} {
  797.         return $result
  798.     } else {
  799.         return ""
  800.     }
  801. }
  802.  
  803. proc ::nntp::status {name} {
  804.     upvar 0 ::nntp::${name}data data
  805.  
  806.     set result ""
  807.     if {[::nntp::okprint $name] && \
  808.             [regexp -- {\d+\s+<[^>]+>} $data(mesg) result]} {
  809.         return $result
  810.     } else {
  811.         return ""
  812.     }
  813. }
  814.  
  815. proc ::nntp::fetch {name} {
  816.     upvar 0 ::nntp::${name}data data
  817.  
  818.     set eol "\012"
  819.  
  820.     if {![::nntp::okprint $name]} {
  821.         return ""
  822.     }
  823.     set sock $data(sock)
  824.  
  825.     set result [list ]
  826.     while {![eof $sock]} {
  827.         gets $sock line
  828.         regsub -- {\015?\012$} $line $data(eol) line
  829.  
  830.         if {[regexp -- {^\.$} $line]} {
  831.             break
  832.         }
  833.         regsub -- {^\.\.} $line {.} line
  834.         lappend result $line
  835.     }
  836.     return $result
  837. }
  838.  
  839. proc ::nntp::response {name} {
  840.     upvar 0 ::nntp::${name}data data
  841.  
  842.     set eol "\012"
  843.  
  844.     set sock $data(sock)
  845.  
  846.     gets $sock line
  847.     set data(code) 0
  848.     set data(mesg) ""
  849.  
  850.     if {$line == ""} {
  851.         error "nntp: unexpected EOF on $sock\n"
  852.     }
  853.  
  854.     regsub -- {\015?\012$} $line "" line
  855.  
  856.     set result [regexp -- {^((\d\d)(\d))\s*(.*)} $line match \
  857.             data(code) val1 val2 data(mesg)]
  858.     
  859.     if {$result == 0} {
  860.         puts stderr "nntp garbled response: $line\n";
  861.         return ""
  862.     }
  863.  
  864.     if {$val1 == 20} {
  865.         set data(post) [expr {!$val2}]
  866.     }
  867.  
  868.     if {$data(debug)} {
  869.         puts stderr "val1 $val1 val2 $val2"
  870.         puts stderr "code '$data(code)'"
  871.         puts stderr "mesg '$data(mesg)'"
  872.         if {[info exists data(post)]} {
  873.             puts stderr "post '$data(post)'"
  874.         }
  875.     } 
  876.  
  877.     return [::nntp::returnval $name]
  878. }
  879.  
  880. proc ::nntp::returnval {name} {
  881.     upvar 0 ::nntp::${name}data data
  882.  
  883.     if {([info exists data(cmnd)]) \
  884.             && ($data(cmnd) != "")} {
  885.         set command $data(cmnd)
  886.     } else {
  887.         set command okprint
  888.     }
  889.     
  890.     if {$data(debug)} {
  891.         puts stderr "returnval command '$command'"
  892.     }
  893.  
  894.     set data(cmnd) ""
  895.     return [::nntp::$command $name]
  896. }
  897.  
  898. proc ::nntp::squirt {name {body ""}} {
  899.     upvar 0 ::nntp::${name}data data
  900.  
  901.     set body [split $body \n]
  902.  
  903.     if {$data(debug)} {
  904.         puts stderr "$data(sock) sending [llength $body] lines\n";
  905.     }
  906.  
  907.     foreach line $body {
  908.         # Print each line, possibly prepending a dot for lines
  909.         # starting with a dot and trimming any trailing \n.
  910.         regsub -- {^\.} $line {..} line
  911.         puts $data(sock) $line
  912.     }
  913.     puts $data(sock) "."
  914.     flush $data(sock)
  915.  
  916.     if {$data(debug)} {
  917.         puts stderr "$data(sock) is finished sending"
  918.     }
  919.     return [::nntp::response $name]
  920. }
  921. #eof
  922.  
  923.