#!reb278 -cs
REBOL [
title: "RebelBB"
purpose: "REBOL CGI to make a Bulletin Board."
license: "GNU GPL http://www.gnu.org/licenses/gpl.txt"
author: "Gregory Pecheret"
credits: {
Didier Cadieu, parser lecture-forum
Viktor Pavlu, cookies
RBBS http://www.rebol.com/docs/cgi-bbs.html, nice-date
}
;http://www.rebol.com/docs/cgi1.html
;http://www.rebol.com/docs/cgi2.html
;http://www.rebol.net/cookbook/recipes/0045.html
;http://www.codeconscious.com/rebol/rebol-net.html
]
do %rebelBB.config
; example of rebelBB.config file:
;config: context [
; key: "scramble"
; path-forum: %foo1
; path-backup: %foo2
;]
thread: context [
id:
title:
replies:
start:
end:
msgs: none
]
headers: copy []
threads: do read config/path-forum
set-thread-at: func [seq] [
thread/id: none
threads: head threads
all [
seq > 0
seq <= length? threads
thread/msgs: copy pick threads seq
thread/id: last thread/msgs
remove back tail thread/msgs
thread/title: last thread/msgs
remove back tail thread/msgs
thread/replies: length? thread/msgs
thread/start: copy last thread/msgs
remove back tail thread/start
thread/end: copy first thread/msgs
remove back tail thread/end
true
]
]
find-thread-id: func [id] [
while [not any [tail? threads equal? id last first threads]][threads threads: next threads]
]
set-thread-id: func [id] [
find-thread-id id
set-thread-at index? threads
]
lock: does [
while [exists? join config/path-forum %.lock][wait divide random 10 10]
write join config/path-forum %.lock ""
threads: do read config/path-forum
write/binary join config/path-backup rejoin parse to-string now/precise "-/:.+-" compress mold threads
]
unlock: does [
delete join config/path-forum %.lock
]
reply-thread: func [id usr msg /local _idx][
lock
find-thread-id id
set-thread-at _idx: index? threads
remove at threads _idx
insert/only thread/msgs reduce [now usr msg]
insert/only threads compose/deep [(thread/msgs) (thread/title) (thread/id)]
write config/path-forum mold threads
unlock
]
new-thread: func [usr title msg][
lock
insert/only threads compose/deep [[(now) (usr) (msg)] (title) (rejoin parse to-string now/precise "-/:.+-")]
write config/path-forum mold threads
unlock
]
set-headers: func [idx len] [
clear headers
loop len [
if set-thread-at idx [
append/only headers reduce [thread/id thread/title thread/replies thread/start thread/end]
]
idx: idx + 1
]
]
search-threads: func [pattern][
clear headers
while [not tail? threads][
thread/msgs: first threads
thread/id: last thread/msgs
remove back tail thread/msgs
thread/title: last thread/msgs
remove back tail thread/msgs
while [not any [tail? thread/msgs find third first thread/msgs pattern]][thread/msgs: next thread/msgs]
if not tail? thread/msgs [
append/only headers reduce [thread/id thread/title]
]
threads: next threads
]
]
set-cookie: func ["Sets a cookie" key [string!] value /expires "set expiration date" exp-date [date!]][
print rejoin [
"Set-Cookie: " key "=" trim value ";" ; why is there a space sometimes?
either expires [join " expires=" to-idate exp-date][""]
]
print ""
]
get-cookie: func ["Returns value of a cookie" name [string!]][
select parse to-string select system/options/cgi/other-headers "HTTP_COOKIE" ";=" name
]
session: context [
user: none
pass: none
ip: none
ip-cgi: none
set: func[][
people: load-people
sha1-pass: select people input/user
either all [sha1-pass equal? sha1-pass checksum/secure input/pass] [
user: input/user
set-cookie "session" enbase/base encloak mold compose [user: (input/user) pass: (input/pass) ip: (ip-cgi)] config/key 16
]
[reset/cookie]
]
reset: func/cookie][
user: pass: ip: none
if cookie [set-cookie "session" ""]
]
get: func[][
ip-cgi: system/options/cgi/remote-addr
if error? try [
do bind do decloak to-string debase/base get-cookie "session" 16 config/key 'user
if not equal? ip ip-cgi [user: pass: none]
]
[reset]
]
]
session/get
system/locale/days: ["Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"]
load-people: does [either exists? %people.dat [do load %people.dat][copy []]]
input: make object! [
user: "guest" ; user posting
pass: none ; user's password
subject: none ; thread subject
bla: none ; what the user says
submit: none ; value of user's command
tid: none ; thread ID to reply to
thread: none ; thread index to read
page: 1 ; page index
search: none ; search criteria
code: none ; display code if set
attempt [
switch system/options/cgi/request-method [
"POST" [
data: make string! 1020
buffer: make string! 16380
while [positive? read-io system/ports/input buffer 16380][
append data buffer
clear buffer
]
do bind decode-cgi data 'bla
]
"GET" [do bind decode-cgi system/options/cgi/query-string 'thread]
]
]
; added the following lines to make it works with xitami
; because xitami adds "^/"
attempt [replace tid "^/" ""]
attempt [replace submit "^/" ""]
]
input/page: to-integer input/page
; in order to handle everything within "switch"
if input/code [input/submit: "Code"]
if input/thread [
input/submit: "Pick"
; compatible with prior forum
if find input/thread #"<" [remove input/thread remove back tail input/thread]
]
print either find ["Logout" "SignIn"] input/submit ["Content-type: text/html"]["Content-type: text/html^/"]
html: context [
cgi: to-string last split-path system/options/script
title: "Forum REBOL"
cmd: "Refresh"
thread: none
colortype: none
id: none
subject: none
replies: none
user-first: none
user-last: none
date-first: none
date-last: none
who: none
when: none
what: none
qty: none
pages: none
user1: user2: user3: none
commands:
{<form method="POST" action=<%html/cgi%>>
<input type="SUBMIT" name="submit" value="<%html/cmd%>">
<%either session/user [{<input type="SUBMIT" name="submit" value="Post">}][{}]%>
<input type="text" name="search">
<input type="SUBMIT" name="submit" value="Search">
<input type="SUBMIT" name="submit" value="Help">
<input type="SUBMIT" name="submit" value="Members">
<input type="SUBMIT" name="submit" value=<%either session/user ["Logout"]["Login"]%>>
<b><%any [session/user ""]%></b>
</form>}
body:
{<html>
<head>
<title><%html/title%></title>
<style type="text/css">
body, p, td {font-family: arial, sans-serif, helvetica; font-size: 10pt; background-color:#E8E8E8;}
h1 {font-size: 14pt;}
h2 {font-size: 12pt; color: #2030a0; width: 100%; border-bottom: 1px solid #c09060;}
h3 {font-size: 10pt; color: #2030a0;}
tt {font-family: "courier new", monospace, courier; font-size: 9pt; color: darkgreen;}
pre {font: bold 10pt "courier new", monospace, console; background-color: #e0e0e0; padding: 16px; border: solid #a0a0a0 1px;}
.ligne0 {padding-left: 10px; padding-right: 7px; margin-left: 15px; background-color: black; color: white; font-weight: bold;}
.ligne1 {nowrap="nowrap" padding-left: 10px; padding-right: 7px; margin-left: 15px; background-color:#C8C8C8; color: black;}
.ligne2 {nowrap="nowrap" padding-left: 10px; padding-right: 7px; margin-left: 15px; background-color:#FAFAFA; color: black;}
.post_sujet {text-decoration: none; color:#000000; font-size:90%; font-weight: bold;}
</style>
</head>
<body>
<center>
<table>
<tr>
<td><img src="http://www.digicamsoft.com/rebelBB.jpg" border="0"></td>
<td>
<ul>
<li><a href="http://www.rebol.com" target="_blank">REBOL</a></li>
<li><a href="https://www.red-lang.org" target="_blank">Red Language</a></li>
<li><a href="https://github.com/rebol/rebol" target="_blank">Carl's Rebol3</a></li>
</ul>
</td>
<td>
<ul>
<li><a href="https://github.com/Oldes/Rebol3" target="_blank">Oldes' Rebol3</a></li>
<li><a href="https://www.atronixengineering.com/downloads" target="_blank">Atronix's Rebol3</a></li>
</ul>
</td>
</tr>
</table>}
pick:
{<tr><td class="ligne1" align="left"><b><%html/who%></b></td><td class="ligne1" align="right"><i><%html/when%></i></td></tr>
<tr><td class="ligne2" colspan="2"><%html/what%></td></tr>}
threads-hdr-page:
{<table border="0" cellspacing="0" cellpadding="0"><tr><td class="ligne0">TOPICS ( total: <%html/qty%> page: <%input/page%>/<%html/pages%> )</td><td class="ligne0">REPLIES</td><td class="ligne0" colspan="2">FIRST</td><td class="ligne0" colspan="2">LAST</td></tr>}
thread-line:
{<tr><td class="<%html/colortype%>"><a class="post_sujet" href="<%html/cgi%>?thread=<%html/id%>"><%html/subject%></a></td><td class="<%html/colortype%>"><%html/replies%></td><td class="<%html/colortype%>"><%html/user-first%></td><td class="<%html/colortype%>"><i><%html/date-first%></i></td><td class="<%html/colortype%>"><%html/user-last%></td><td class="<%html/colortype%>"><i><%html/date-last%></i></td></tr>}
threads-hdr-search:
{<table border="0" cellspacing="0" cellpadding="0"><tr><td class="ligne0">SEARCH FOR <%input/search%> (<%html/qty%> Results)</td></tr>}
thread-line-search:
{<tr><td class="<%html/colortype%>"><a class="post_sujet" href="<%html/cgi%>?thread=<%html/id%>"><%html/subject%></a></td></tr>}
reply:
{<br>
<form method="POST" action=<%html/cgi%>>
Reply: <br>
<textarea name="bla" rows="10" cols="60"></textarea>
<p>
<input type="SUBMIT" name="submit" value="Submit">
<input type="HIDDEN" name="tid" value="<%input/thread%>">
</form>}
post:
{<table width="80%"><tr><td>
<form method="POST" action=<%html/cgi%>>
Subject:<br>
<input type="TEXT" name="subject" size="40">
<p>
Message: <br>
<textarea name="bla" rows="10" cols="60"></textarea>
<p>
<input type="SUBMIT" name="submit" value="Submit">
</form>
</td></tr></table>}
login:
{<form method="POST" action=<%html/cgi%>>
Enter your name or nickname:<br>
<input type="TEXT" name="user" size="40">
<p>
Enter your password:<br>
<input type="PASSWORD" name="pass" size="40">
<p>
<input type="SUBMIT" name="submit" value="SignIn">
</form>
<br>
<br>
<hr>
Not a member? Click Members to register.}
people:
{<h2>Register</h2>
<form method="POST" action=<%html/cgi%>>
Enter your name or nickname:<br>
<input type="TEXT" name="user" size="40">
<p>
Enter your password:<br>
<input type="PASSWORD" name="pass" size="40">
<p>
<input type="SUBMIT" name="submit" value="Register">
</form>
<br>
<br>
<br>
<h2><%html/qty%> Members</h2>}
show-member:
{<tr><td><b><%html/user1%></b></td><td><b><%html/user2%></b></td><td><b><%html/user3%></b></td></tr>}
help:
{<h1>Help</h1>
<center><table cellspacing="4">
<tr><td>example</td><td>example</td></tr>
<tr><td>[b]example[/b]</td><td><b>example</b></td></tr>
<tr><td>[i]example[/i]</td><td><i>example</i></td></tr>
<tr><td>[code]print "Hello!"[/code]</td><td><pre>print "Hello!"</pre></td></tr>
<tr><td>:-)</td><td><img src="http://www.digicamsoft.com/emoticons/Msn/003.gif" border="0"></td></tr>
<tr><td>:)</td><td><img src="http://www.digicamsoft.com/emoticons/Msn/003.gif" border="0"></td></tr>
<tr><td>:-))</td><td><img src="http://www.digicamsoft.com/emoticons/Msn/018.gif" border="0"></td></tr>
<tr><td>:))</td><td><img src="http://www.digicamsoft.com/emoticons/Msn/018.gif" border="0"></td></tr>
<tr><td>:-(</td><td><img src="http://www.digicamsoft.com/emoticons/Msn/016.gif" border="0"></td></tr>
<tr><td>;-)</td><td><img src="http://www.digicamsoft.com/emoticons/Msn/dft012.gif" border="0"></td></tr>
<tr><td>;)</td><td><img src="http://www.digicamsoft.com/emoticons/Msn/dft012.gif" border="0"></td></tr>
<tr><td>http://www.rebol.com</td><td><a href="http://www.rebol.com">http://www.rebol.com</a></td></tr>
<tr><td>http://www.rebol.com/graphics/pwr-rebol100.gif</td><td><img src="http://www.rebol.com/graphics/pwr-rebol100.gif" border="0"></td></tr>
</table></center>}
sent:
{<h1>Message Posted!</h1>
<center><table width="80%" border="1"><tr><td><%html/what%></td></tr></table></center>}
auth-error:
{<b>
Wrong User/Password combination!<br>
Click on Members to register.
</b>}
end:
{<br><br>
<center><font color="#808080" size=1>Powered by <a href="<%html/cgi%>?code=1">RebelBB</a> and REBOL <%system/version%></font></center>
</body></html>}
]
render-msg: context [
msg-block: copy ""
process-url: func [txt /image /local t] [
t: copy txt
parse txt [["ftp." (insert t "ftp://") | "www." (insert t "http://")] to end]
either find [".jpeg" ".jpg" ".gif" ".png" ".bmp"] find/last txt "."
[rejoin [{<img src="} t {" border="0">}]]
[rejoin [{<a href="} t {" target="_blank">} txt {</a>}]]
]
;all-chars: exclude charset [#" " - #"^(FF)"] charset "[<"
all-chars: charset [#" " - #"^(FF)"]
non-white-space: complement charset " ^/^-^M<>"
to-space: [some non-white-space | end]
is-url: complement charset " ^/^-^M<>()"
end-url: [some is-url | end]
deb: fin: url: none
rules: [
"[code]" copy url to "[/code]" 7 skip (if error? try [replace/all url "<" "<" replace/all url ">" ">" append msg-block rejoin ["<pre>" url "</pre>"]][append msg-block "[code][/code]"])
| "[code]" copy url to "[code]" 6 skip (if error? try [replace/all url "<" "<" replace/all url ">" ">" append msg-block rejoin ["<pre>" url "</pre>"]][append msg-block "[code][/code]"])
;"[code]" copy url to "[/code]" 7 skip (if error? try [append msg-block color-code url] [append msg-block "[code][/code]"])
;| "[code]" copy url to "[code]" 6 skip (if error? try [append msg-block color-code url] [append msg-block "[code][code]"])
;| "[makedoc]" copy url to "[/makedoc]" 10 skip (append msg-block makedoc url)
| deb: ["http" opt "s" "://" | "www." | "ftp://" | "ftp." ] end-url fin: (append msg-block process-url copy/part deb fin)
| [":-))" | ":))"] (append msg-block {<img src="http://www.digicamsoft.com/emoticons/Msn/018.gif" border="0">})
| [":-)" | ":)"] (append msg-block {<img src="http://www.digicamsoft.com/emoticons/Msn/003.gif" border="0">})
| ":-(" (append msg-block {<img src="http://www.digicamsoft.com/emoticons/Msn/016.gif" border="0">})
| [";-)" | ";)"] (append msg-block {<img src="http://www.digicamsoft.com/emoticons/Msn/dft012.gif" border="0">})
| [crlf | cr | lf] (append msg-block "<br>")
| tab (append msg-block " ")
| "[i]" (append msg-block "<i>")
| "[/i]" (append msg-block "</i>")
| "[b]" (append msg-block "<b>")
| "[/b]" (append msg-block "</b>")
| "<" (append msg-block "<")
| ">" (append msg-block ">")
| "é" (append msg-block "é")
| "è" (append msg-block "è")
| "à" (append msg-block "à")
| deb: all-chars fin: (append msg-block copy/part deb fin)
]
process: func [data /local msg] [
clear msg-block
parse/all trim/head/tail data [some rules]
copy msg-block
]
]
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: 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]
]
show-threads: func[] [
html/qty: length? threads
chunk: 30
html/pages: round/ceiling divide html/qty chunk
set-headers (chunk * (input/page - 1) + 1) chunk
print build-markup html/threads-hdr-page
color: 0
foreach hdr headers [
color: + 1 color
html/colortype: either even? color ["ligne1"]["ligne2"]
html/id: first hdr
html/subject: second hdr
html/replies: third hdr
html/date-first: nice-date first fourth hdr
html/user-first: second fourth hdr
html/date-last: nice-date first fifth hdr
html/user-last: second fifth hdr
print build-markup html/thread-line
]
print "</table>"
page: 1
loop html/pages [
print build-markup either = page input/page
[{<b><%page%></b>}]
[{<a href="<%html/cgi%>?page=<%page%>"><%page%></a>}]
page: + 1 page
]
]
switch/default input/submit [
"Pick" [
html/cmd: "Topics"
set-thread-id input/thread
html/title: thread/title
print build-markup html/body
print build-markup html/commands
print build-markup {<table width="80%" cellspacing="0" cellpadding="0"><tr><td class="ligne0" colspan="2" align="center"><b><%html/title%></b></td></tr>}
if thread/id [
foreach reply reverse thread/msgs [
html/when: first reply
html/who: second reply
html/what: render-msg/process third reply
print build-markup html/pick
]
]
print {<tr><td colspan="2">}
print either all [session/user thread/id] [build-markup html/reply]["<br><i>Login required to Post.</i>"]
print "</td></tr></table>"
]
"Post" [
html/cmd: "Topics"
print build-markup html/body
print build-markup html/commands
print build-markup html/post
]
"Submit" [
html/cmd: "Topics"
either input/tid
[reply-thread input/tid session/user input/bla]
[new-thread session/user input/subject input/bla]
print build-markup html/body
print build-markup html/commands
html/what: render-msg/process input/bla
print build-markup html/sent
]
"Code" [
html/cmd: "Topics"
print build-markup html/body
print build-markup html/commands
cgi-info: info? to-file html/cgi
print build-markup {<b>Script size:</b> <%to-integer divide cgi-info/size 1024%>Kb <b>Script date:</b> <%cgi-info/date%>}
print {<table><tr><td>}
do %color-code.r
print color-code read to-file html/cgi
print {</td></tr></table>}
]
"Help" [
html/cmd: "Topics"
print build-markup html/body
print build-markup html/commands
print build-markup html/help
]
"Search" [
html/cmd: "Topics"
print build-markup html/body
print build-markup html/commands
either empty? trim input/search [
show-threads
][
search-threads input/search
html/qty: length? headers
print build-markup html/threads-hdr-search
color: 0
while [not tail? headers] [
color: + 1 color
html/colortype: either even? color ["ligne1"]["ligne2"]
html/id: first first headers
html/subject: second first headers
print build-markup html/thread-line-search
headers: next headers
]
print "</table>"
]
]
"Members" [
html/cmd: "Topics"
print build-markup html/body
print build-markup html/commands
people: sort/skip load-people 2
html/qty: divide length? people 2
print build-markup html/people
print "<table>"
foreach [name1 pass1 name2 pass2 name3 pass3] people [
html/user1: name1
html/user2: name2
html/user3: name3
print build-markup html/show-member
]
print "</table>"
]
"Login" [
html/cmd: "Topics"
print build-markup html/body
print build-markup html/commands
print build-markup html/login
]
"Logout" [
session/reset/cookie
html/cmd: "Refresh"
print build-markup html/body
print build-markup html/commands
show-threads
]
;"SignIn" [
; session/set
; html/cmd: "Refresh"
; print build-markup html/body
; print build-markup html/commands
; either session/user [show-threads][print build-markup html/auth-error]
;]
"Register" [
html/cmd: "Topics"
print build-markup html/body
print build-markup html/commands
either equal? "" trim input/user [print "User name not valid..."]
[
people: sort/skip load-people 2
either find people input/user [
print "This user already exists!<br>"
][
print "You're now regitered!<br>"
append people input/user
append people checksum/secure input/pass
]
foreach [name pass] people [print name]
save %people.dat mold people
]
]
][ ; default behavior: shows messages
print build-markup html/body
print build-markup html/commands
show-threads
]
print build-markup html/end
|