00001
00002
00003
00004
00005
00006
00007
00008
00009
00010 package require Tcl 8.2
00011 package provide nntp 0.2.1
00012
00013 namespace ::nntp {
00014
00015 variable socks
00016
00017
00018 variable counter 0
00019
00020
00021 variable commands [list \
00022 "article" \
00023 "authinfo" \
00024 "body" \
00025 "date" \
00026 "group" \
00027 "head" \
00028 "help" \
00029 "last" \
00030 "list" \
00031 "listgroup" \
00032 "mode_reader" \
00033 "newgroups" \
00034 "newnews" \
00035 "next" \
00036 "post" \
00037 "stat" \
00038 "quit" \
00039 "xgtitle" \
00040 "xhdr" \
00041 "xover" \
00042 "xpat" \
00043 ]
00044
00045 ::nntp = ::eol "\n"
00046
00047
00048
00049 namespace export nntp
00050
00051 }
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068 ret ::nntp::nntp (optional server ="" , optional port ="" , optional name ="") {
00069 global env
00070 variable connections
00071 variable counter
00072 variable socks
00073
00074 # If a name wasn't specified for the connection, create a new 'unique'
00075 # name for the connection
00076
00077 if { [llength [info level 0]] < 4 } {
00078 set counter 0
00079 set name "nntp${counter}"
00080 while {[lsearch -exact [info commands] $name] >= 0} {
00081 incr counter
00082 set name "nntp${counter}"
00083 }
00084 }
00085
00086 if { ![string equal [info commands ::$name] ""] } {
00087 error "command \"$name\" already exists, unable to create nntp connection"
00088 }
00089
00090 upvar 0 ::nntp::${name}data data
00091
00092 set socks($name) [list ]
00093
00094 # Initialize instance specific variables
00095
00096 set data(debug) 0
00097 set data(eol) "\n"
00098
00099 # Logic to determine whether to use the specified nntp server, or to use
00100 # the default
00101
00102 if {$server == ""} {
00103 if {[info exists env(NNTPSERVER)]} {
00104 set data(host) "$env(NNTPSERVER)"
00105 } else {
00106 set data(host) "news"
00107 }
00108 } else {
00109 set data(host) $server
00110 }
00111
00112 # Logic to determine whether to use the specified nntp port, or to use the
00113 # default.
00114
00115 if {$port == ""} {
00116 if {[info exists env(NNTPPORT)]} {
00117 set data(port) $env(NNTPPORT)
00118 } else {
00119 set data(port) 119
00120 }
00121 } else {
00122 set data(port) $port
00123 }
00124
00125 set data(code) 0
00126 set data(mesg) ""
00127 set data(addr) ""
00128 set data(binary) 0
00129
00130 set sock [socket $data(host) $data(port)]
00131
00132 set data(sock) $sock
00133
00134 # Create the command to manipulate the nntp connection
00135
00136 interp alias {} ::$name {} ::nntp::NntpProc $name
00137
00138 ::nntp::response $name
00139
00140 return $name
00141 }
00142
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155 ret ::nntp::NntpProc (type name , optional cmd ="" , type args) {
00156
00157 # Do minimal args checks here
00158
00159 if { [llength [info level 0]] < 3 } {
00160 error "wrong # args: should be \"$name option ?arg arg ...?\""
00161 }
00162
00163 # Split the args into command and args components
00164
00165 if { [llength [info commands ::nntp::_$cmd]] == 0 } {
00166 variable commands
00167 set optlist [join $commands ", "]
00168 set optlist [linsert $optlist "end-1" "or"]
00169 error "bad option \"$cmd\": must be $optlist"
00170 }
00171
00172 # Call the appropriate command with its arguments
00173
00174 return [eval [linsert $args 0 ::nntp::_$cmd $name]]
00175 }
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190 ret ::nntp::okprint (type name) {
00191 upvar 0 ::nntp::${name}data data
00192
00193 if {$data(code) >=400} {
00194 set val [expr {(0 < $data(code)) && ($data(code) < 400)}]
00195 error "NNTPERROR: $data(code) $data(mesg)"
00196 }
00197
00198 # Codes less than 400 are good
00199
00200 return [expr {(0 < $data(code)) && ($data(code) < 400)}]
00201 }
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216 ret ::nntp::message (type name) {
00217 upvar 0 ::nntp::${name}data data
00218
00219 return "$data(mesg)$data(eol)"
00220 }
00221
00222
00223
00224
00225
00226
00227 ret ::nntp::_cget (type name , type option) {
00228 upvar 0 ::nntp::${name}data data
00229
00230 if {[string equal $option -binary]} {
00231 return $data(binary)
00232 } else {
00233 return -code error \
00234 "Illegal option \"$option\", expected \"-binary\""
00235 }
00236 }
00237
00238 ret ::nntp::_configure (type name , type args) {
00239 upvar 0 ::nntp::${name}data data
00240
00241 if {[llength $args] == 0} {
00242 return [list -binary $data(binary)]
00243 }
00244 if {[llength $args] == 1} {
00245 return [_cget $name [lindex $args 0]]
00246 }
00247 if {([llength $args] % 2) == 1} {
00248 return -code error \
00249 "wrong#args: expected even number of elements"
00250 }
00251 foreach {o v} $args {
00252 if {[string equal $o -binary]} {
00253 if {![string is boolean -strict $v]} {
00254 return -code error \
00255 "Expected boolean, got \"$v\""
00256 }
00257 set data(binary) $v
00258 } else {
00259 return -code error \
00260 "Illegal option \"$o\", expected \"-binary\""
00261 }
00262 }
00263 return {}
00264 }
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296 ret ::nntp::_article (type name , optional msgid ="") {
00297 upvar 0 ::nntp::${name}data data
00298
00299 set data(cmnd) "fetch"
00300 return [::nntp::command $name "ARTICLE $msgid"]
00301 }
00302
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317 ret ::nntp::_authinfo (type name , optional user ="guest" , optional pass ="foobar") {
00318 upvar 0 ::nntp::${name}data data
00319
00320 set data(cmnd) ""
00321 set res [::nntp::command $name "AUTHINFO USER $user"]
00322 if {$res} {
00323 set res [expr {$res && [::nntp::command $name "AUTHINFO PASS $pass"]}]
00324 }
00325 return $res
00326 }
00327
00328
00329
00330
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346 ret ::nntp::_body (type name , optional msgid ="") {
00347 upvar 0 ::nntp::${name}data data
00348
00349 set data(cmnd) "fetch"
00350 return [::nntp::command $name "BODY $msgid"]
00351 }
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375 ret ::nntp::_group (type name , optional group ="") {
00376 upvar 0 ::nntp::${name}data data
00377
00378 set data(cmnd) "groupinfo"
00379 if {$group == ""} {
00380 set group $data(group)
00381 }
00382 return [::nntp::command $name "GROUP $group"]
00383 }
00384
00385
00386
00387
00388
00389
00390
00391
00392
00393
00394
00395
00396
00397
00398
00399
00400
00401
00402
00403 ret ::nntp::_head (type name , optional msgid ="") {
00404 upvar 0 ::nntp::${name}data data
00405
00406 set data(cmnd) "fetch"
00407 return [::nntp::command $name "HEAD $msgid"]
00408 }
00409
00410
00411
00412
00413
00414
00415
00416
00417
00418
00419
00420
00421 ret ::nntp::_help (type name) {
00422 upvar 0 ::nntp::${name}data data
00423
00424 set data(cmnd) "fetch"
00425 return [::nntp::command $name "HELP"]
00426 }
00427
00428 ret ::nntp::_ihave (type name , optional msgid ="" , type args) {
00429 upvar 0 ::nntp::${name}data data
00430
00431 set data(cmnd) "fetch"
00432 if {![::nntp::command $name "IHAVE $msgid"]} {
00433 return ""
00434 }
00435 return [::nntp::squirt $name "$args"]
00436 }
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449 ret ::nntp::_last (type name) {
00450 upvar 0 ::nntp::${name}data data
00451
00452 set data(cmnd) "msgid"
00453 return [::nntp::command $name "LAST"]
00454 }
00455
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465
00466
00467
00468
00469
00470
00471 ret ::nntp::_list (type name , optional type ="") {
00472 upvar 0 ::nntp::${name}data data
00473
00474 set data(cmnd) "fetch"
00475 return [::nntp::command $name "LIST $type"]
00476 }
00477
00478
00479
00480
00481
00482
00483
00484
00485
00486
00487
00488
00489
00490
00491 ret ::nntp::_newgroups (type name , type since , type args) {
00492 upvar 0 ::nntp::${name}data data
00493
00494 set since [clock format [clock scan "$since"] -format "%y%m%d %H%M%S"]
00495 set dist ""
00496 set data(cmnd) "fetch"
00497 return [::nntp::command $name "NEWGROUPS $since $dist"]
00498 }
00499
00500
00501
00502
00503
00504
00505
00506
00507
00508
00509
00510
00511
00512
00513
00514
00515 ret ::nntp::_newnews (type name , optional group ="" , optional since ="") {
00516 upvar 0 ::nntp::${name}data data
00517
00518 if {$group != ""} {
00519 if {[regexp -- {^[\w\.\-]+$} $group] == 0} {
00520 set since $group
00521 set group ""
00522 }
00523 }
00524 if {![info exists group] || ($group == "")} {
00525 if {[info exists data(group)] && ($data(group) != "")} {
00526 set group $data(group)
00527 } else {
00528 set group "*"
00529 }
00530 }
00531 if {"$since" == ""} {
00532 set since [clock format [clock scan "now - 1 day"]]
00533 }
00534 set since [clock format [clock scan $since] -format "%y%m%d %H%M%S"]
00535 set dist ""
00536 set data(cmnd) "fetch"
00537 return [::nntp::command $name "NEWNEWS $group $since $dist"]
00538 }
00539
00540
00541
00542
00543
00544
00545
00546
00547
00548
00549
00550
00551 ret ::nntp::_next (type name) {
00552 upvar 0 ::nntp::${name}data data
00553
00554 set data(cmnd) "msgid"
00555 return [::nntp::command $name "NEXT"]
00556 }
00557
00558
00559
00560
00561
00562
00563
00564
00565
00566
00567
00568
00569
00570
00571
00572
00573
00574
00575
00576 ret ::nntp::_post (type name , type article) {
00577
00578 if {![::nntp::command $name "POST"]} {
00579 return ""
00580 }
00581 return [::nntp::squirt $name "$article"]
00582 }
00583
00584
00585
00586
00587
00588
00589
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599
00600
00601
00602
00603 ret ::nntp::_slave (type name) {
00604 return [::nntp::command $name "SLAVE"]
00605 }
00606
00607
00608
00609
00610
00611
00612
00613
00614
00615
00616
00617
00618
00619
00620
00621
00622
00623
00624
00625
00626 ret ::nntp::_stat (type name , optional msgid ="") {
00627 upvar 0 ::nntp::${name}data data
00628
00629 set data(cmnd) "status"
00630 return [::nntp::command $name "STAT $msgid"]
00631 }
00632
00633
00634
00635
00636
00637
00638
00639
00640
00641
00642
00643
00644
00645 ret ::nntp::_quit (type name) {
00646 upvar 0 ::nntp::${name}data data
00647
00648 set ret [::nntp::command $name "QUIT"]
00649 close $data(sock)
00650 rename ${name} {}
00651 return $ret
00652 }
00653
00654
00655
00656
00657
00658
00659 ret ::nntp::_date (type name) {
00660 upvar 0 ::nntp::${name}data data
00661
00662 set data(cmnd) "msg"
00663 return [::nntp::command $name "DATE"]
00664 }
00665
00666 ret ::nntp::_listgroup (type name , optional group ="") {
00667 upvar 0 ::nntp::${name}data data
00668
00669 set data(cmnd) "fetch"
00670 return [::nntp::command $name "LISTGROUP $group"]
00671 }
00672
00673 ret ::nntp::_mode_reader (type name) {
00674 upvar 0 ::nntp::${name}data data
00675
00676 set data(cmnd) "msg"
00677 return [::nntp::command $name "MODE READER"]
00678 }
00679
00680 ret ::nntp::_xgtitle (type name , optional group_pattern ="") {
00681 upvar 0 ::nntp::${name}data data
00682
00683 set data(cmnd) "fetch"
00684 return [::nntp::command $name "XGTITLE $group_pattern"]
00685 }
00686
00687 ret ::nntp::_xhdr (type name , optional header ="message-id" , optional list ="" , optional last ="") {
00688 upvar 0 ::nntp::${name}data data
00689
00690 if {![regexp -- {\d+-\d+} $list]} {
00691 if {"$last" != ""} {
00692 set list "$list-$last"
00693 } else {
00694 set list ""
00695 }
00696 }
00697 set data(cmnd) "fetch"
00698 return [::nntp::command $name "XHDR $header $list"]
00699 }
00700
00701 ret ::nntp::_xindex (type name , optional group ="") {
00702 upvar 0 ::nntp::${name}data data
00703
00704 if {("$group" == "") && [info exists data(group)]} {
00705 set group $data(group)
00706 }
00707 set data(cmnd) "fetch"
00708 return [::nntp::command $name "XINDEX $group"]
00709 }
00710
00711 ret ::nntp::_xmotd (type name , optional since ="") {
00712 upvar 0 ::nntp::${name}data data
00713
00714 if {"$since" != ""} {
00715 set since [clock seconds]
00716 }
00717 set since [clock format [clock scan $since] -format "%y%m%d %H%M%S"]
00718 set data(cmnd) "fetch"
00719 return [::nntp::command $name "XMOTD $since"]
00720 }
00721
00722 ret ::nntp::_xover (type name , optional list ="" , optional last ="") {
00723 upvar 0 ::nntp::${name}data data
00724 if {![regexp -- {\d+-\d+} $list]} {
00725 if {"$last" != ""} {
00726 set list "$list-$last"
00727 } else {
00728 set list ""
00729 }
00730 }
00731 set data(cmnd) "fetch"
00732 return [::nntp::command $name "XOVER $list"]
00733 }
00734
00735 ret ::nntp::_xpat (type name , optional header ="subject" , optional list =1 , optional last ="" , type args) {
00736 upvar 0 ::nntp::${name}data data
00737
00738 set patterns ""
00739
00740 if {![regexp -- {\d+-\d+} $list]} {
00741 if {("$last" != "") && ([string is digit $last])} {
00742 set list "$list-$last"
00743 }
00744 } elseif {"$last" != ""} {
00745 set patterns "$last"
00746 }
00747
00748 if {[llength $args] > 0} {
00749 set patterns "$patterns $args"
00750 }
00751
00752 if {"$patterns" == ""} {
00753 set patterns "*"
00754 }
00755
00756 set data(cmnd) "fetch"
00757 return [::nntp::command $name "XPAT $header $list $patterns"]
00758 }
00759
00760 ret ::nntp::_xpath (type name , optional msgid ="") {
00761 upvar 0 ::nntp::${name}data data
00762
00763 set data(cmnd) "msg"
00764 return [::nntp::command $name "XPATH $msgid"]
00765 }
00766
00767 ret ::nntp::_xsearch (type name , type args) {
00768 set res [::nntp::command $name "XSEARCH"]
00769 if {!$res} {
00770 return ""
00771 }
00772 return [::nntp::squirt $name "$args"]
00773 }
00774
00775 ret ::nntp::_xthread (type name , type args) {
00776 upvar 0 ::nntp::${name}data data
00777
00778 if {[llength $args] > 0} {
00779 set filename "dbinit"
00780 } else {
00781 set filename "thread"
00782 }
00783 set data(cmnd) "fetchbinary"
00784 return [::nntp::command $name "XTHREAD $filename"]
00785 }
00786
00787
00788
00789
00790
00791
00792 ret ::nntp::cmd (type name , type cmd) {
00793 upvar 0 ::nntp::${name}data data
00794
00795 set eol "\015\012"
00796 set sock $data(sock)
00797 if {$data(debug)} {
00798 puts stderr "$sock command $cmd"
00799 }
00800 puts $sock "$cmd"
00801 flush $sock
00802 return
00803 }
00804
00805 ret ::nntp::command (type name , type args) {
00806 set res [eval [linsert $args 0 ::nntp::cmd $name]]
00807
00808 return [::nntp::response $name]
00809 }
00810
00811 ret ::nntp::msg (type name) {
00812 upvar 0 ::nntp::${name}data data
00813
00814 set res [::nntp::okprint $name]
00815 if {!$res} {
00816 return ""
00817 }
00818 return $data(mesg)
00819 }
00820
00821 ret ::nntp::groupinfo (type name) {
00822 upvar 0 ::nntp::${name}data data
00823
00824 set data(group) ""
00825
00826 if {[::nntp::okprint $name] && [regexp -- {(\d+)\s+(\d+)\s+(\d+)\s+([\w\.]+)} \
00827 $data(mesg) match count first last data(group)]} {
00828 return [list $count $first $last $data(group)]
00829 }
00830 return ""
00831 }
00832
00833 ret ::nntp::msgid (type name) {
00834 upvar 0 ::nntp::${name}data data
00835
00836 set result ""
00837 if {[::nntp::okprint $name] && \
00838 [regsub -- {\s+<[^>]+>} $data(mesg) {} result]} {
00839 return $result
00840 } else {
00841 return ""
00842 }
00843 }
00844
00845 ret ::nntp::status (type name) {
00846 upvar 0 ::nntp::${name}data data
00847
00848 set result ""
00849 if {[::nntp::okprint $name] && \
00850 [regexp -- {\d+\s+<[^>]+>} $data(mesg) result]} {
00851 return $result
00852 } else {
00853 return ""
00854 }
00855 }
00856
00857 ret ::nntp::fetch (type name) {
00858 upvar 0 ::nntp::${name}data data
00859
00860 set eol "\012"
00861
00862 if {![::nntp::okprint $name]} {
00863 return ""
00864 }
00865 set sock $data(sock)
00866
00867 if {$data(binary)} {
00868 set oldenc [fconfigure $sock -encoding]
00869 fconfigure $sock -encoding binary
00870 }
00871
00872 set result [list ]
00873 while {![eof $sock]} {
00874 gets $sock line
00875 regsub -- {\015?\012$} $line $data(eol) line
00876
00877 if {[string match "." $line]} {
00878 break
00879 }
00880 if { [string match "..*" $line] } {
00881 lappend result [string range $line 1 end]
00882 } else {
00883 lappend result $line
00884 }
00885 }
00886
00887 if {$data(binary)} {
00888 fconfigure $sock -encoding $oldenc
00889 }
00890
00891 return $result
00892 }
00893
00894 ret ::nntp::response (type name) {
00895 upvar 0 ::nntp::${name}data data
00896
00897 set eol "\012"
00898
00899 set sock $data(sock)
00900
00901 gets $sock line
00902 set data(code) 0
00903 set data(mesg) ""
00904
00905 if {$line == ""} {
00906 error "nntp: unexpected EOF on $sock\n"
00907 }
00908
00909 regsub -- {\015?\012$} $line "" line
00910
00911 set result [regexp -- {^((\d\d)(\d))\s*(.*)} $line match \
00912 data(code) val1 val2 data(mesg)]
00913
00914 if {$result == 0} {
00915 puts stderr "nntp garbled response: $line\n";
00916 return ""
00917 }
00918
00919 if {$val1 == 20} {
00920 set data(post) [expr {!$val2}]
00921 }
00922
00923 if {$data(debug)} {
00924 puts stderr "val1 $val1 val2 $val2"
00925 puts stderr "code '$data(code)'"
00926 puts stderr "mesg '$data(mesg)'"
00927 if {[info exists data(post)]} {
00928 puts stderr "post '$data(post)'"
00929 }
00930 }
00931
00932 return [::nntp::returnval $name]
00933 }
00934
00935 ret ::nntp::returnval (type name) {
00936 upvar 0 ::nntp::${name}data data
00937
00938 if {([info exists data(cmnd)]) \
00939 && ($data(cmnd) != "")} {
00940 set command $data(cmnd)
00941 } else {
00942 set command okprint
00943 }
00944
00945 if {$data(debug)} {
00946 puts stderr "returnval command '$command'"
00947 }
00948
00949 set data(cmnd) ""
00950 return [::nntp::$command $name]
00951 }
00952
00953 ret ::nntp::squirt (type name , optional body ="") {
00954 upvar 0 ::nntp::${name}data data
00955
00956 set body [split $body \n]
00957
00958 if {$data(debug)} {
00959 puts stderr "$data(sock) sending [llength $body] lines\n";
00960 }
00961
00962 foreach line $body {
00963 # Print each line, possibly prepending a dot for lines
00964 # starting with a dot and trimming any trailing \n.
00965 if { [string match ".*" $line] } {
00966 set line ".$line"
00967 }
00968 puts $data(sock) $line
00969 }
00970 puts $data(sock) "."
00971 flush $data(sock)
00972
00973 if {$data(debug)} {
00974 puts stderr "$data(sock) is finished sending"
00975 }
00976 return [::nntp::response $name]
00977 }
00978
00979
00980