AI
Animation
CGI
Compression
Console
Database
Debug
Dialects
Dialogs
Editor
Email
Encryption
Extension
External Library
File
File Handling
Files
Financial
FTP
Game
Games
Graphics
GUI
HTML
HTTP
Internet
LDC
Markup
Math
Module
Network
Networking
None
Other - Net
Parse
Patch
Printing
Protocol
Rebol
Scheme
Scientific
SDK
Security
Shell
Sound
SQL
TCP
Testing
Text
Text Processing
UI
User Interface
Util
Utility
VID
Visualization
Web
Win API
X-File
XML
; (TGD) ; #!../rebol -cs
REBOL [
    Title: "RBBS - REBOL Bulletin Board Tutorial"
    File: %rbbs.r     ; (TGD) ;
    Version: 1.0.0
    Created: 14-Nov-2004
    Date: 25-Jan-2005
    Author: [
        "Carl Sassenrath"
        "Gregg Irwin"
        "Volker Nitsch"
        "Tom Conlin"
    ]
    Copyright: "REBOL Technologies"
    License: "BSD (www.opensource.org/licenses/bsd-license.php)"
    Web: http://www.rebol.com/docs/cgi-bbs.html
    Note: "Go to the above URL for the tutorial document."
    Purpose: "A CGI Web Bulletin Board / Message Board"                            ; (TGD) ;
    Needs: "Serve-It! or any other webserver featuring REBOL CGI-Scripts." ; (TGD) ;
    Library: [                                                                                               ; (TGD) ;
        level: 'intermediate
        platform: 'all
        type: [tool]
        domain: [cgi html http]
        tested-under: [view 1.2.1.1.1 on "AmigaOS" view 1.2.1.3.1 on "Windows 2000"]
        support: none
        license: 'BSD
        see-also: "Serve-It!, at http://www.TGD-Consulting.de/Download.html"
        ]
]

; (TGD) ; Modified by Dirk Weyand to work smoothly with Serve-It!,
; (TGD) ; the smart server-engine based on REBOL/View.
; (TGD) ; All modifications of the original script are marked with ; (TGD) ;
; (TGD) ; Download Serve-It! @ http://www.TGD-Consulting.de/Download.html#reblets 

; (TGD) ; print "content-type: text/html^/"

;-- Configuration Settings ---------------------------------------------------

config: context [

    title: "Simple REBOL Message Board"
    cgi-path: %/cgi-bin/rbbs.r

    base-dir:  %../rbbs/ ; (TGD) ; Document-Root
    topic-id: join base-dir %id.r
    topic-db: join base-dir %topics.db
    msg-dir:  join base-dir %messages/

    html-template: join base-dir %template.html
    html-form: join base-dir %form.html

    max-days: 60  ; delete msgs older than this if...
    max-msgs: 100 ; max messages is reached.

    msg-order: none ; or 'new-first for reverse order

    tags-allowed: [  


 
] ] if not exists? config/msg-dir [make-dir/deep config/msg-dir] ;-- Various Utility Functions ------------------------------------------------ abort: false ; (TGD) ; quit or halt shutdowns REBOL based Servers attempt: func [ ; (TGD) ; function not implemented in REBOL/View 1.2.1.1.1 {Tries to evaluate and returns result or NONE on error.} value][ if not error? set/any 'value try :value [get/any 'value]] remove-each: func [ ; (TGD) ; function not implemented in REBOL/View 1.2.1.1.1 {Removes a value from a series for each block that returns TRUE.} 'word [get-word! word! block!] {Word or block of words to set each time (will be local)} data [series!] "The series to traverse" body [block!] "Block to evaluate. Return TRUE to remove." ][ while [not tail? data] [ set word first data either do body [remove data] [data: next data] ] unset word ] build-tag: func [ ; (TGD) ; fixed version of function implemented in REBOL/View 1.2.1.1.1 "Generates a tag from a composed block." values [block!] "Block of parens to evaluate and other data." /local tag value-rule xml? name attribute value ][ tag: make string! 7 * length? values value-rule: [ set value issue! (value: mold value) | set value file! (value: replace/all copy value #" " " ") | set value any-type! ] xml?: false parse compose values [ [ set name ['?xml (xml?: true) | word!] (append tag name) any [ set attribute [word! | url!] value-rule ( repend tag [#" " attribute {="} value {"}] ) | value-rule (repend tag [#" " value]) ] end (if xml? [append tag #"?"]) ] | [set name refinement! to end (tag: mold name)] ] to tag! tag ] seconds: func [ ; (TGD) ; difference function in REBOL/View 1.2.1.1.1 doesn´t support date! "Compute difference between dates in seconds." a [date!] "first date" b [date!] "second date" ] [ b - a * 86400 + (to decimal! b/time) - (to decimal! a/time) + (a/zone/hour - b/zone/hour * 3600) ] ; If not in CGI environment, set Test-Mode. test-mode: not system/options/cgi/request-method system/options/binary-base: 64 html: make string! 5000 emit: func [data] [append repend html data newline] href: func [data] [build-tag [a href (reduce data)]] ; href [name ".txt"] encode-html: func [ "Make HTML tags into HTML viewable escapes (for posting code)" text ][ foreach [from to] ["&" "&" "<" "<" ">" ">"] [ replace/all text from to ] ] ;-- Nicely Format the Date --------------------------------------------------- nice-date: func [ "Convert date/time to a friendly format." date [date!] /local n day time diff ][ n: now time: date/time diff: n/date - date/date if not day: any [ if diff < 2 [ time: to time! seconds date n ; TGD ; time: difference n date time/3: 0 return reform [time "hrs ago"] ] if diff < 7 [pick system/locale/days date/weekday] ][ day: form date/date if n/date/year = date/date/year [clear find/last day #"-"] ] join day [
time " ET"] ] ;-- Read CGI Request --------------------------------------------------------- read-cgi: func [ "Read CGI data. Return data as string or NONE." /limit size "Limit to this number of bytes" /local data buffer ][ if none? limit [size: 300000] switch system/options/cgi/request-method [ "POST" [data: system/script/args] ; (TGD) ; Serve-It! liefert POST-Data ; (TGD) ; per system/script/args an das CGI-script ; (TGD) ; "POST" [ ; (TGD) ; data: make string! 1020 ; (TGD) ; buffer: make string! 16380 ; (TGD) ; while [positive? read-io system/ports/input buffer 16380][ ; (TGD) ; append data buffer ; (TGD) ; clear buffer ; (TGD) ; if (length? data) > size [ ; (TGD) ; print ["aborted - posting is too long:" ; (TGD) ; length? data "limit:" size] ; (TGD) ; quit ; (TGD) ; ] ; (TGD) ; ] ; (TGD) ; ] "GET" [data: system/options/cgi/query-string] ] any [data ""] ; (TGD) ; ] ;-- Read HTML File Body ------------------------------------------------------ read-body: func [ "Extract the body contents of an HTML file." html [file!] ][ html: read html remove/part html find/tail find html "" clear find html html ] ;-- Send HTML Page to Browser ------------------------------------------------ show-page: func [ "Merge template with title and contents, and output it." title ; page title content ; page contents /local template ][ template: read config/html-template replace/all template "$title" title replace/all template "$date" now/date replace/all template "$version" system/script/header/version replace template "$content" content either test-mode [ write %temp-page.html template browse %temp-page.html ; (TGD) ; halt ][ print template ; (TGD) ; quit ] abort: true ; (TGD) ; ] show-error: func [ "Tell user about an error." block "Block to be formed." ][ show-page "An Error Occurred..." reform block ] ;-- Filter HTML Tags --------------------------------------------------------- filter-tags: func [ "Filter HTML to only allow specific tags." page [string!] /local block extended ][ block: load/markup page extended: make block! length? block foreach tag config/tags-allowed [append extended append to-string tag " "] remove-each item block [ if tag? item [ not any [ find config/tags-allowed item ; allow all [item/1 = slash find config/tags-allowed next item] foreach tag extended [ if find/match item tag [break/return true] ] ] ] ] to-string block ] ;-- Emit the Web Form -------------------------------------------------------- emit-form: func [ "Emit the submission form (for both topics and messages)." topic-id [integer! none!] ; Use NONE to allow topic input /local text type ][ text: read-body config/html-form type: 'topic if topic-id [ ; Remove subject field from the form: remove/part find text find/tail text ; Add a hidden field for the topic id: append text build-tag [input type hidden name id value (topic-id)] type: 'msg ] emit [ build-tag [form action (config/cgi-path) method post] build-tag [input type hidden name cmd value (type)] text ] ] ;-- Topic Functions --------------------------------------------------------- ; Each topic is given a unique ID number to identify it. The ; messages for a topic are stored in a file that uses that id ; number. A master topics.db file holds the list of topics ; as a block of blocks. Each block has the format: ; ; [topic id create-date modified-date msg-count last-from] ; ; Each time a new topic is created, it is added to the ; topics file. Each time a message is added, the topics file ; is updated to show the new mod-date and msg-count. next-topic-id: func [ "Create next topic id #" /local n ][ ; TGD ; save %id.r n: 1 + any [attempt [load config/topic-id] 0] save config/topic-id n: 1 + any [attempt [load config/topic-id] 0] n ] load-topics: does [any [attempt [load/all config/topic-db] []]] save-topics: func [data] [write config/topic-db mold/only data] add-topic: func [ {Add a new topic. Store it in topic file. Return id.} topic ][ id: next-topic-id write/append config/topic-db append remold [topic id now now 0 ""] newline id ] must-find-topic: func [ "Return topic record or show an error" topic-id ][ foreach topic load-topics [ if topic/2 = topic-id [return topic] ] show-error "Invalid message topic. Contact the administrator." not abort ] update-topic: func [ "Update message status for topic" topic-id count "number of messages" name "last message from" /local topics ][ topics: load-topics foreach topic topics [ if topic/2 = topic-id [ topic/4: now topic/5: count if not topic/6 [append topic none] topic/6: name sort/reverse/compare topics 4 save-topics topics exit ] ] ] link-topic: func [ "Create an HREF link to a message topic" topic-id /bookmark name /local path ][ path: join config/cgi-path ["?cmd=msgs&id=" topic-id "&"] if bookmark [repend path [#"#" name]] href path ] emit-topics: func [ "Generate listing of all topics" ][ emit [ ] foreach topic load-topics [ emit [ ] ] emit
"Msgs" "Topic" "Last Posting" "From"

topic/5

link-topic topic/2 topic/1 nice-date topic/4 topic/6
] ;-- Message Functions -------------------------------------------------------- ; Each message file is stored under the topic id number for it. ; Message records have the format: ; ; [name email date message] ; ; The message is stored as binary to avoid any possible problems ; related to delimiting it as a REBOL value. load-messages: func [ "Load messages for a specific topic." topic-id ][ any [attempt [load/all config/msg-dir/:topic-id] []] ] save-messages: func [ "Save messages for a specific topic." topic-id messages ][ write config/msg-dir/:topic-id mold/only messages ] add-message: func [ {Add a new message.} topic-id name email message ][ write/append config/msg-dir/:topic-id append remold [name email now to-binary message] newline ] purge-messages: func [ "If message limit is exceeded, purge older messages." msgs /local today ][ if (length? msgs) > config/max-msgs [ today: now remove-each msg msgs [ msg/3/date + confg/max-days < today ] save-messages topic-id msgs ] ] obscure-email: func [ "Make email more difficult for harvesters" email ][ either any-string? email [replace email #"@"
][""] ] emit-messages: func [ "Generate listing of messages" msgs "block of messages" ][ emit [ ] foreach msg msgs [ emit [ ] ] emit
"Sender" "Message" "When Sent"
msg/1
obscure-email msg/2
to-string msg/4 nice-date msg/3
] list-messages: func [ "Emit message list with form. Return title." topic-id /update "Update message count" /local rec ][ if rec: must-find-topic topic-id [ ; (TGD) ; emit [ href config/cgi-path "Return to Topics"
" | " href #end "Go to End" " | " link-topic topic-id "Refresh"

] msgs: load-messages topic-id if all [update not empty? msgs] [ purge-messages msgs update-topic topic-id length? msgs first last msgs ] if config/msg-order = 'new-first [msgs: head reverse msgs] emit-messages msgs emit [

href config/cgi-path "Return to Topics" " | " link-topic/bookmark topic-id "end" "Refresh"

] emit {

Add a Message:

} emit-form topic-id reform ["Messages for:" rec/1] ] ; (TGD) ; ] ;-- CGI Command Processing --------------------------------------------------- ; Read CGI request and convert it to a standard object: ; (TGD) ;if not cgi: read-cgi [quit] ; (TGD) ;cgi: construct/with decode-cgi cgi context [ ; (TGD) ; cmd: id: name: email: subject: message: none ; (TGD) ;] if cgi: read-cgi [ cgi: make context [ cmd: id: name: email: subject: message: none ] decode-cgi cgi ; Filter out restricted HTML tags from being submitted to any field. foreach word next first cgi [ val: get in cgi word if string? val [set in cgi word filter-tags val] ] ; Convert CGI fields as needed: cgi/cmd: attempt [to-word cgi/cmd] cgi/id: attempt [to-integer cgi/id] if not email? cgi/email: attempt [load cgi/email] [cgi/email: none] check-fields: func [/subject][ if all [not abort subject empty? trim cgi/subject] [show-error "Subject required"] ; (TGD) ; if all [not abort empty? trim cgi/name] [show-error "Name field required"] ; (TGD) ; if all [not abort empty? trim cgi/message] [show-error "Message is required"] ; (TGD) ; not abort ; (TGD) ; ] ; Process the CGI command: switch/default cgi/cmd [ msgs [ title: list-messages cgi/id ] msg [ if all [check-fields rec: must-find-topic cgi/id] [ ; (TGD) ; ; (TGD) ; rec: must-find-topic cgi/id add-message cgi/id cgi/name cgi/email cgi/message title: list-messages/update cgi/id ] ; (TGD) ; ] topic [ if check-fields/subject [ ; (TGD) ; id: add-topic cgi/subject add-message id cgi/name cgi/email cgi/message title: list-messages/update id ] ; (TGD) ; ] source [ title: "REBOL Message Board Source" emit [

"REBOL Code"

 detab encode-html read %rbbs.r 

"HTML Form Code (form.html)"

 detab encode-html read config/html-form 

"HTML Template Code (template.html)"

 detab encode-html read config/html-template 
] ] ][ title: config/title emit-topics emit {

Add a New Topic:

} emit-form none ] if not abort [show-page title html] ] ; (TGD) ; ; halt ; (TGD) ;
Copyright © 2018 Rebol Software Foundation