home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2000 January / PCW0001.ISO / software / sw / outils / rebol031.exe / nntp.r < prev    next >
Encoding:
Rebol source code  |  1999-05-21  |  20.9 KB  |  673 lines

  1. REBOL [
  2.     Title: "REBOL news Protocol $Revision: 1.1 $"
  3.     Date: cvs-date "$Date: 1999/05/22 00:10:40 $"
  4. ]
  5.  
  6. net-watch: off
  7.  
  8. ;-- Header for use to post -------------------------------------
  9.  
  10. generic-post-header: make object! [
  11.  
  12.     ;-- You'll have to answer host and email every 
  13.     ;   time until they are defined in your user.r.  
  14.     Path: reform [
  15.         either found? system/schemes/default/host [
  16.             system/schemes/default/host 
  17.         ][
  18.             system/schemes/default/host: ask "Please enter your host name:"
  19.         ] "!" either found? system/user/email [
  20.             system/user/email
  21.         ][
  22.             system/user/email: to-email ask "Please enter your email address:"
  23.         ]
  24.     ]
  25.     Sender: Reply-to: from: system/user/email
  26.     Subject: none    ;-- filled in with first line of post
  27.     Newsgroups: none ;-- either user of protocol fills this in
  28.     Message-ID: none ;-- filled in by protocol
  29.     Organization: "REBOL-Usenet-service"
  30. ]
  31.  
  32. ;-- other header fields include: --------------------------------
  33. ; Followup-To: Distribution: Keywords: 
  34. ;          Summary: Approved: 
  35. ; as well as all sorts of X-whatevers: 
  36. ;
  37. ; These may be included if someone clones
  38. ; one of the generic-post-headers.
  39. ;----------------------------------------------------------------
  40.  
  41. news-protocol: make Root-Protocol [
  42.     "REBOL Network News Port."
  43.     
  44.     ;------ Internals -------------------------------------------
  45.  
  46.     scheme: 'news
  47.     port-id: 119
  48.     port-flags: system/standard/port-flags/pass-thru
  49.     
  50.     ;-- Checks
  51.     open-check:      [none    "20"]  
  52.     close-check:     ["QUIT" "205"]
  53.     group-check:     [none   "211"]
  54.     list-check:      ["LIST" "215"]
  55.     listgroup-check: [none   "211"]
  56.     head-check:      [none   "221"]
  57.     body-check:      [none   "222"]
  58.     article-check:   [none   "220"]
  59.     post-check:      ["POST"   "3"]  
  60.     done-post-check: ["."      "2"]
  61.     authorize-check: [none    "30"]
  62.  
  63.     result: make block! 10000 ;-- all work done in buffers
  64.     buf: make string! 10000  
  65.  
  66.     ;-- The states of the machine
  67.     _HEAD:       1
  68.     _BODY:       2
  69.     _ARTICLES:   3
  70.     _XHDR:       4
  71.     _HEAD-BODY:  5
  72.     _NEWSGROUPS: 6
  73.     _POST:       7
  74.     _COUNT:      8
  75.     _OTHERWISE:  100
  76.  
  77.     ;-- Some common error messages
  78.     sorry:  "I can only do one request at a time. Sorry"
  79.     sorry2: "Conflicting directives.  Does not compute. Sorry."
  80.  
  81.     ;-- We'll need this for posting
  82.     user-name: copy/part system/user/email find system/user/email "@"
  83.     
  84.     state: 0            ;-- initial state
  85.  
  86.     ; Info to keep track of
  87.     noisy: zero-articles: cross-post: keep-M-ID: false   
  88.     newsCaps: x-what: howdey: which-articles: 
  89.     which-groups: find-content: to-post: post-header: none
  90.  
  91.     ;-- These are what get interpreted 
  92.     ;   if present in the inserted block
  93.     commands: [verbose post x-post of capabilities? 
  94.                newsgroups articles from to help keep-ID
  95.  
  96.                headers bodies headers-bodies 
  97.                with using please wouldya count] 
  98.  
  99.     ;---- Utility functions -------------------------------------
  100.  
  101.     reset-myself: func [
  102.         "Things that need to be reset each time"
  103.     ][
  104.         ;-- some other internal values are
  105.         ;-- also reset per function usage, such as
  106.         ;-- zero-articles, the flag to signal an empty
  107.         ;-- newsgroup.
  108.         set in news-protocol 'state 0
  109.         foreach item [which-articles which-groups x-what
  110.                       find-content to-post post-header][
  111.             set in news-protocol item none
  112.         ]
  113.  
  114.         foreach item [noisy zero-articles cross-post keep-M-ID][
  115.             set in news-protocol item false
  116.         ]
  117.         clear buf  ;-- also cleared in 'get-content and 'open
  118.     ]
  119.  
  120.     error: func [
  121.         "Something's fouled up.  Error message and exit"
  122.         str [string! block!] "Error string/block"
  123.     ][
  124.         print form str
  125.         reset-myself
  126.         halt
  127.     ]
  128.  
  129.     filtrate: func [
  130.         { Return true (ie. go ahead and insert into result) 
  131.           if we're not filtering or filter based on our search criteria }
  132.         line [string!] "Possible search item"
  133.     ][
  134.         either found? find-content [
  135.             foreach item find-content [
  136.                 if found? find line item [
  137.                     return true
  138.                 ]
  139.             ]
  140.             return false
  141.         ][true]
  142.     ]
  143.  
  144.     smush-time: func [
  145.         { Smush time makes a big number out of the current time.  
  146.           Used for message-ID }
  147.     ][
  148.         rejoin [first now second now third now first fourth now 
  149.                 second fourth now third fourth now]
  150.     ]
  151.  
  152.     add-commas-nls: func [
  153.         "Add commas to the newsgroups line"
  154.         string-block [block!] /local ix
  155.     ][
  156.         if (length? string-block) < 2 [string-block]
  157.         forall string-block [ 
  158.             either any[(ix: index? string-block) = 1 tail? string-block][][
  159.                 system/words/insert string-block "," 
  160.                 string-block: next string-block
  161.                 if (ix // 9) = 0 [ ;-- Five groups per line
  162.                     system/words/insert string-block "^/ "
  163.                     string-block: next string-block
  164.                 ]
  165.             ]
  166.         ]
  167.         string-block: head string-block
  168.     ]
  169.  
  170.     get-new-message-id: func [
  171.         "Returns a new message ID"
  172.     ][
  173.         rejoin ["<" smush-time random 99999 "." user-name "@"
  174.                 system/schemes/default/host ">"]
  175.     ]
  176.     
  177.     ;---- Dialect Functions -------------------------------------
  178.  
  179.     please: wouldya: none  ;-- just for fun
  180.     
  181.     help: func [
  182.         "Returns the command dialect"
  183.     ][
  184.         state: _OTHERWISE
  185.         print ["I know these words:" newline]
  186.         for i 1 (length? commands) 1 [
  187.             prin [form system/words/pick commands i " "]
  188.             if (i // 5) = 0 [print ""]
  189.         ]
  190.     ]
  191.  
  192.     keep-ID: func [][
  193.         keep-M-ID: true
  194.     ]
  195.  
  196.     count: func [
  197.         "count articles in a group"
  198.     ][
  199.         state: _COUNT
  200.     ]
  201.  
  202.     capabilities?: func [
  203.         "Return what help reports on server"
  204.     ][
  205.         state: _OTHERWISE
  206.         append result newsCaps
  207.     ]
  208.  
  209.  
  210.     verbose: func [
  211.         "Be annoyingly verbose"
  212.     ][
  213.         noisy: true
  214.     ]
  215.  
  216.     xhdr: func [
  217.         { If available, xhdr retrieves different header 
  218.           fields  over a supplied range }
  219.         what [string! block!]
  220.     ][
  221.         either state = 0 [
  222.             either string? what [x-what: what][
  223.                 x-what: rejoin what
  224.             ]
  225.             state: _XHDR
  226.         ][error sorry]
  227.     ]
  228.  
  229.     articles: func [
  230.         "Retrieve articles"
  231.     ][
  232.         either state = 0 [
  233.             state: _ARTICLES
  234.         ][error sorry]
  235.     ]
  236.  
  237.     headers: func [
  238.         "Retrieve headers"
  239.     ][ 
  240.         either state = 0 [
  241.             state: _HEAD
  242.         ][error sorry]
  243.     ]
  244.  
  245.     bodies: func [
  246.         "Retrieve bodies"
  247.     ][
  248.         either state = 0 [
  249.             state: _BODY
  250.         ][error sorry]
  251.     ]
  252.  
  253.     headers-bodies: func [
  254.         "Retrieve headers and bodies separately"
  255.     ][
  256.         either state = 0 [
  257.             state: _HEAD-BODY
  258.         ][error sorry]
  259.     ]
  260.  
  261.     newsgroups: func [
  262.         "Get newsgroup list"
  263.     ][
  264.         either state = 0 [
  265.             state: _NEWSGROUPS
  266.         ][error sorry]
  267.     ]
  268.  
  269.     post: func [
  270.         "Post a message"
  271.         what [string!] "The article to post"
  272.     ][
  273.         either state = 0 [
  274.             state: _POST
  275.             to-post: what
  276.         ][error sorry]
  277.     ]
  278.  
  279.     x-post: func [
  280.         "Cross post a message"
  281.         what [string!] "The article to post"
  282.     ][
  283.         cross-post: true
  284.         post what
  285.     ]
  286.  
  287.     using: func [
  288.         "Use a passed in header object to post"
  289.         header-obj [object!] "The header object to use"
  290.     ][
  291.         post-header: header-obj
  292.     ]
  293.  
  294.     of: func [
  295.         "Use article numbers or message IDs"
  296.         arts [block! string!] "This is the article numbers"
  297.     ][
  298.         either block? arts [which-articles: arts][
  299.             which-articles: reduce [arts]
  300.         ]
  301.     ]
  302.     
  303.     to: from: func [
  304.         "Set the newsgroup in question"
  305.         where [block! string!] "This is the source"
  306.     ][
  307.         either block? where [which-groups: where][which-groups: reduce [where]]
  308.     ]
  309.  
  310.     with: func [
  311.         "Filter inquiry based on passed in content"
  312.         what [block! string! object!] "this is search content"
  313.     ][
  314.         either object? what [using what][  ;-- must have meant 'using
  315.             either block? what [find-content: what][
  316.                 find-content: reduce [what]
  317.             ]
  318.         ]
  319.     ]
  320.     
  321.     ;--------- Interpreter --------------------------------------
  322.  
  323.     interpret: func [
  324.         "Interpret the request based on the machine states"
  325.         port [port!]
  326.         /local x
  327.     ][
  328.  
  329.         if state = 0 [error "Nothing asked to do."]
  330.         if state = _OTHERWISE [exit] ;-- No interpretation necessary
  331.  
  332.         either state = _POST [
  333.             either not found? which-groups [
  334.                 error "Don't know where to post."
  335.             ][
  336.                 if noisy [
  337.                     print ["Posting to:" newline which-groups 
  338.                            newline "this message: " newline to-post ]
  339.                     
  340.                     if found? post-header [
  341.                         print "Using passed in header object:"
  342.                         print net-utils/export post-header
  343.                     ]
  344.                 ]
  345.             ]
  346.             ;-- Post to multiple groups at once, or individually? 
  347.             either cross-post [
  348.  
  349.                 go-post port
  350.             ][
  351.                 ;-- Make sure they really want to spam
  352.                 if all [(x: length? which-groups) > 15 not find/match ask [
  353.                         "Are you sure you want to individually post to" 
  354.                         x "newsgroups? "] "y"][
  355.                     print "Whew! You might have been royally flamed!"
  356.                     exit
  357.                 ]
  358.                 forall which-groups [
  359.                     go-post port
  360.                 ]
  361.             ]
  362.         ][ ;-- Not posting so...
  363.  
  364.             either state = _NEWSGROUPS [
  365.                 if any [found? which-groups found? which-articles][
  366.                     error sorry2
  367.                 ]
  368.                 get-groups port
  369.             ][
  370.  
  371.                 ;-- Getting a group count
  372.                 if state = _COUNT [
  373.                     either found? which-groups [
  374.                         foreach group which-groups [
  375.                             if noisy [prin "."]
  376.                             go-group port group
  377.                         ]
  378.                         exit
  379.                      ][error "Which group do you want a count of?"]
  380.                 ]
  381.  
  382.                 either found? which-groups [
  383.                     foreach group which-groups [
  384.                         go-group port group
  385.                         get-data port
  386.                     ]
  387.                 ][
  388.                     either found? which-articles [
  389.                         get-data port ][error "Not enough info to do that."]
  390.                 ]
  391.             ]
  392.         ]
  393.     ]
  394.  
  395.  
  396.     ;---- Public interface --------------------------------------
  397.  
  398.     open: func [
  399.         port
  400.         /local capstring
  401.     ][
  402.         howdey: open-proto port
  403.         capstring: caps port/sub-port
  404.         if find capstring "MODE" [
  405.             clear buf howdey: mode-reader port 
  406.             capstring: caps port/sub-port
  407.         ]
  408.         if find capstring "xhdr" [system/words/insert commands 'xhdr]
  409.  
  410.         port/state/flags: port/state/flags or port-flags
  411.         clear buf ;-- buf was used up in caps
  412.  
  413.         if none? find howdey "200" [authenticate port]
  414.     ] 
  415.     
  416.     insert: func [
  417.         { Insert takes a block of a dialect the news 
  418.           port and then has it interpreted and executed }
  419.         port [port!] "The port"
  420.         block [block!] "The news command block"
  421.         /local tokens total-result temp-toke name
  422.     ][ 
  423.         reset-myself
  424.         clear result ;-- last result sitting there.
  425.  
  426.         tokens: []
  427.         clear tokens
  428.  
  429.         ;-- Here we look for pieces of the 
  430.         ;   dialect, and make them meaningful if found.
  431.         foreach item block [
  432.             name: item
  433.             either word? item [
  434.                 either found? find commands item [
  435.                     if found? temp-toke: get in news-protocol item [
  436.                         append tokens :temp-toke
  437.                     ]
  438.                 ][
  439.                     item: get item
  440.                     either object? :item [
  441.                         system/words/insert tail tokens item
  442.                     ][
  443.                         ;-- Now the 'got item may be a block or a string! 
  444.                         either any [string? :item block? :item] [
  445.                             system/words/insert/only tail tokens item
  446.                         ][error reform ["I don't understand: " name]]
  447.                     ]
  448.                 ]
  449.             ][ 
  450.                 ;-- Item came in literal
  451.                 either any [string? item block? item] [
  452.                     system/words/insert/only tail tokens item
  453.                 ][error reform ["I don't understand: " name]]
  454.             ]
  455.         ]
  456.         do tokens 
  457.         interpret port
  458.         reset-myself
  459.         clear tokens
  460.         total-result: copy head result
  461.         clear result
  462.         total-result ;-- it's your memory now
  463.     ]
  464.     
  465.     ;--------- NNTP Command functions ---------------------------
  466.  
  467.     caps: func [
  468.         "Find out what the server can do."
  469.         port
  470.     ][
  471.         system/words/insert port "HELP"
  472.         read-message port buf 
  473.         newsCaps: copy buf
  474.     ]
  475.  
  476.     authenticate: func [
  477.         "Authenticate ourselves to the server"
  478.         port [port!] /local pass
  479.     ][
  480.         if none? system/schemes/default/pass [
  481.             pass: ask "Hey, gimmie your password!:"]
  482.  
  483.         system/words/insert port/sub-port rejoin ["AUTHINFO USER " user-name]
  484.         net-utils/confirm authorize-check
  485.         system/words/insert port/sub-port rejoin ["AUTHINFO PASS " pass]
  486.         system/words/pick port/sub-port 1 ;-- hmm..
  487.     ]
  488.  
  489.     mode-reader: func [
  490.         "Some servers may require you to go mode reader first"
  491.         port [port!]
  492.     ][
  493.         system/words/insert port/sub-port "MODE READER"
  494.         net-utils/confirm port/sub-port open-check
  495.     ]    
  496.  
  497.     read-message: func [
  498.         "Read a message from the NEWS server"
  499.         port [port!]
  500.         buf [string!]
  501.         /local line
  502.     ][
  503.         while [(line: system/words/pick port 1) <> "."] [
  504.             system/words/insert tail buf line
  505.             system/words/insert tail buf newline
  506.         ]
  507.         buf
  508.     ]
  509.  
  510.     go-group: func [
  511.         "Enter into a newsgroup"
  512.         port name [string!] "The group's name" 
  513.         /local response msg-cnt
  514.     ][
  515.         ;-- some memory saving functions
  516.         group-command: "GROUP "
  517.         group-string: func [value][
  518.             append group-command value
  519.             group-command
  520.         ]
  521.         group-reset: func [][
  522.             remove/part skip group-command 6 tail group-command
  523.         ]       
  524.         zero-articles: false ;-- flag empty groups
  525.         group-reset
  526.  
  527.         system/words/insert port/sub-port group-string name        
  528.         response: load net-utils/confirm port/sub-port group-check
  529.         if state = _COUNT [system/words/insert tail result copy 
  530.                            reduce [response/2 response/3 response/4 
  531.                                    form response/5]]
  532.  
  533.         if response/2 = 0 [zero-articles: true] 
  534.         group-reset
  535.     ]
  536.  
  537.     get-data: func [
  538.         "Gets data from the server"
  539.         port [port!] "The entire port, please"
  540.         /local first-time prev-filt
  541.     ][  
  542.         if zero-articles [exit] ;-- No articles to get here...
  543.         
  544.         get-content: func [/wart article-number /local response prev-filt][
  545.             cool-response: func [][none? find/match response "4"] ;-- 4's error
  546.             if-filt-ins: func [][
  547.                 if all [any [filtrate buf prev-filt] cool-response][
  548.                     system/words/insert tail result copy buf prev-filt: on]
  549.             ]
  550.             read-mpb: func [][read-message port/sub-port buf]
  551.             with-other:    func [][either wart [article-number][
  552.                     either found? x-what [x-what][""]]]
  553.             respo:    func [][response: system/words/pick port/sub-port 1]
  554.             keep-going?: func [str blk1 blk2][
  555.                 either find/match str "2" blk1 blk2
  556.             ]
  557.  
  558.             ;-- if filtering head-body, include bodies for matches in head 
  559.             prev-filt: off 
  560.  
  561.             either state <> _HEAD-BODY [
  562.                 system/words/insert port/sub-port append copy 
  563.                     system/words/pick  [
  564.                         "HEAD " "BODY " "ARTICLE " "XHDR "
  565.                     ] state with-other
  566.                 keep-going? respo [read-mpb if-filt-ins clear buf][exit]
  567.                 not cool-response
  568.             ][
  569.                 system/words/insert port/sub-port append copy "HEAD " 
  570.                     with-other
  571.                 keep-going? respo [read-mpb if-filt-ins clear buf][exit]
  572.                 system/words/insert port/sub-port append copy "BODY " 
  573.                     with-other
  574.                 keep-going? respo [read-mpb if-filt-ins clear buf][exit]
  575.                 not cool-response
  576.             ]
  577.         ]
  578.  
  579.         first-time: true
  580.  
  581.         ;-- we have a block of articles?
  582.         either found? which-articles [
  583.             foreach article which-articles [
  584.                 if noisy [prin "."]
  585.                 get-content/wart article
  586.             ]
  587.         ][
  588.             ;-- we're xhdring? 
  589.             either state = _XHDR [get-content][
  590.                 ;-- otherwise, start iterating through all articles!
  591.                 until [
  592.                     either first-time [first-time: false get-content][
  593.                         system/words/insert port/sub-port "NEXT"
  594.                         response: system/words/pick port/sub-port 1
  595.                         if noisy [prin "."]
  596.                         either found? find/match response "4" [
  597.                             true ][ get-content
  598.                         ]
  599.                     ]
  600.                 ]
  601.             ]
  602.         ]
  603.     ]
  604.  
  605.     get-groups: func [
  606.         "Retrieve the list of newsgroups"
  607.         port [port!] "Entire port, please"
  608.     ][
  609.         net-utils/confirm port/sub-port list-check 
  610.         while [(line: system/words/pick port/sub-port 1) <> "."] [
  611.             if filtrate line [
  612.                 system/words/insert tail result first parse line none
  613.             ]
  614.         ] 
  615.     ]
  616.  
  617.     go-post: func [
  618.         "Post to Usenet"
  619.         port
  620.     ][
  621.  
  622.         either none? post-header [
  623.             either none? which-groups [error "Where do you want to post?"][
  624.                 post-header: make generic-post-header [
  625.                     newsgroups: either cross-post [
  626.                         rejoin add-commas-nls copy which-groups][
  627.                         first which-groups
  628.                     ]
  629.                     Message-ID: get-new-message-ID            
  630.                     Subject: copy/part to-post any [find to-post newline 50]
  631.                 ]
  632.             ]
  633.         ][
  634.             if found? which-groups [
  635.                 ;  This may overwrite what someone filled in 
  636.                 ;  in the newsgroups field.                  
  637.                 post-header/newsgroups: either cross-post [
  638.                     rejoin add-commas-nls copy which-groups][
  639.                     first which-groups]
  640.             ]
  641.             if none? post-header/Subject [
  642.                 post-header/Subject: copy/part to-post any [
  643.                     find to-post newline 50
  644.                 ]
  645.             ]
  646.  
  647.             if any [none? post-header/message-ID not keep-M-ID][
  648.                 post-header/message-ID: get-new-message-ID
  649.             ]
  650.         ]
  651.  
  652.         net-utils/confirm port/sub-port post-check
  653.  
  654.         system/words/insert system/words/insert to-post 
  655.             net-utils/export post-header newline 
  656.  
  657.         system/words/insert port/sub-port to-post
  658.         net-utils/confirm port/sub-port done-post-check
  659.         if noisy [
  660.             print ["Posted message titled: " post-header/subject newline
  661.                    "to:" either cross-post [which-groups][first which-groups]
  662.                    newline "with message-ID: " post-header/message-ID]
  663.         ]
  664.         append result post-header/message-ID
  665.     ]
  666.  
  667.     ;--- Register ourselves. 
  668.     net-utils/net-install news self 119
  669.  
  670. ;-- Thank you and have a pleasant time 
  671. ;   newsing around with REBOL/core 2.0! 
  672.