TFWebForum.Mod 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238
  1. MODULE TFWebForum; (** AUTHOR "TF"; PURPOSE "CGI based forum system"; *)
  2. IMPORT
  3. Dates, Strings,
  4. XML, XMLObjects, XMLScanner, XMLParser,
  5. Commands, Files, Streams, IP, Kernel, KernelLog,
  6. WebHTTP, WebCGI, HTTPSupport;
  7. CONST
  8. (* MaxAuthor = 16; *)
  9. ForumConfigFile = "WebForums.dat";
  10. TYPE
  11. String = Strings.String;
  12. HTMLWriter= OBJECT
  13. VAR w* : Streams.Writer;
  14. PROCEDURE &New*(w : Streams.Writer);
  15. BEGIN SELF.w := w;
  16. END New;
  17. PROCEDURE Head*(CONST title : ARRAY OF CHAR);
  18. BEGIN
  19. w.String('<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>');
  20. w.String(title);
  21. w.String("</title></head>");
  22. w.String("<body>");
  23. END Head;
  24. PROCEDURE Br*;
  25. BEGIN
  26. w.String("<br/>");
  27. END Br;
  28. PROCEDURE Nbsp*;
  29. BEGIN
  30. w.String("&nbsp;");
  31. END Nbsp;
  32. PROCEDURE InputText*(CONST name : ARRAY OF CHAR; value : String);
  33. BEGIN
  34. w.String('<input type="text" name="'); w.String(name); w.String('" ');
  35. IF value # NIL THEN w.String('value="'); HTMLString(value^); w.String('" ') END;
  36. w.String('/>');
  37. END InputText;
  38. PROCEDURE Hide*(CONST name, value : ARRAY OF CHAR);
  39. BEGIN
  40. w.String('<input type="hidden" name="'); w.String(name); w.String('" ');
  41. w.String('value="'); HTMLString(value); w.String('" ');
  42. w.String('/>');
  43. END Hide;
  44. PROCEDURE BeginOptionField*(CONST name, value: ARRAY OF CHAR);
  45. BEGIN
  46. w.String('<select name="'); w.String(name); w.String('" ');
  47. IF value # "" THEN w.String(' value="'); w.String(value); w.String('"') END;
  48. w.String('>');
  49. END BeginOptionField;
  50. PROCEDURE Option*(CONST text : ARRAY OF CHAR);
  51. BEGIN
  52. w.String('<option>'); HTMLString(text); w.String('</option>');
  53. END Option;
  54. PROCEDURE EndOptionField*;
  55. BEGIN
  56. w.String('</select>');
  57. END EndOptionField;
  58. PROCEDURE Submit(CONST text : ARRAY OF CHAR);
  59. BEGIN
  60. w.String('<input type="submit" value="');
  61. w.String(text);
  62. w.String('" />');
  63. END Submit;
  64. PROCEDURE InputArea*(CONST name : ARRAY OF CHAR; value : String);
  65. BEGIN
  66. w.String('<textarea cols="80" rows="10" name="'); w.String(name); w.String('"> ');
  67. IF value # NIL THEN TAHTMLString(value^); END;
  68. w.String('</textarea>');
  69. END InputArea;
  70. PROCEDURE TextLink*(CONST text, target : ARRAY OF CHAR);
  71. BEGIN
  72. w.String('<a href="'); w.String(target); w.String('">'); w.String(text); w.String("</a>")
  73. END TextLink;
  74. PROCEDURE Tail*;
  75. BEGIN
  76. w.String("</body></html>");
  77. END Tail;
  78. PROCEDURE TAHTMLString(CONST s : ARRAY OF CHAR);
  79. VAR i : LONGINT;
  80. BEGIN
  81. i := 0;
  82. WHILE s[i] # 0X DO
  83. CASE s[i] OF
  84. |"<" : w.String("&lt;");
  85. |">" : w.String("&gt;");
  86. |"&" : w.String("&amp;");
  87. |'"' : w.String("&quot;");
  88. ELSE w.Char(s[i])
  89. END;
  90. INC(i)
  91. END
  92. END TAHTMLString;
  93. PROCEDURE HTMLString(CONST s : ARRAY OF CHAR);
  94. VAR i : LONGINT;
  95. BEGIN
  96. i := 0;
  97. WHILE s[i] # 0X DO
  98. CASE s[i] OF
  99. |"<" : w.String("&lt;");
  100. |">" : w.String("&gt;");
  101. |"&" : w.String("&amp;");
  102. |'"' : w.String("&quot;");
  103. |0DX : w.String("<br/>");
  104. ELSE w.Char(s[i])
  105. END;
  106. INC(i)
  107. END
  108. END HTMLString;
  109. (* PROCEDURE URIString(VAR s : ARRAY OF CHAR);
  110. VAR i : LONGINT;
  111. BEGIN
  112. i := 0;
  113. WHILE s[i] # 0X DO
  114. IF uriLiteral[ORD(s[i])] THEN w.Char(s[i])
  115. ELSE w.Char("%"); w.Hex(ORD(s[i]), -2)
  116. END;
  117. INC(i)
  118. END
  119. END URIString;
  120. *)
  121. END HTMLWriter;
  122. EntryInfo = RECORD
  123. subject, id, datetime, author : String;
  124. entry : XML.Element;
  125. level : LONGINT;
  126. END;
  127. EntryList = POINTER TO ARRAY OF EntryInfo;
  128. Forum= OBJECT
  129. VAR doc : XML.Document;
  130. forum : XML.Element;
  131. errors : BOOLEAN;
  132. entryList : EntryList;
  133. nofEntries : LONGINT;
  134. title, editor, password : Strings.String;
  135. filename : ARRAY 128 OF CHAR;
  136. PROCEDURE &Create*;
  137. BEGIN
  138. NEW(doc);
  139. NEW(forum);
  140. title:= empty; editor := empty; password := empty;
  141. forum.SetName("Forum");
  142. doc.AddContent(forum);
  143. END Create;
  144. PROCEDURE SetTitle(CONST title : ARRAY OF CHAR);
  145. BEGIN
  146. SELF.title := Strings.NewString(title);
  147. forum.SetAttributeValue("title", title);
  148. END SetTitle;
  149. PROCEDURE SetEditor(CONST editor, password : ARRAY OF CHAR);
  150. BEGIN
  151. SELF.editor := Strings.NewString(editor);
  152. forum.SetAttributeValue("editor", editor);
  153. SELF.password := Strings.NewString(password);
  154. forum.SetAttributeValue("password", password);
  155. END SetEditor;
  156. PROCEDURE Fail(pos, line, row: LONGINT; CONST msg: ARRAY OF CHAR);
  157. BEGIN
  158. errors := TRUE;
  159. KernelLog.String("Version load failed : "); KernelLog.String("pos= "); KernelLog.Int(pos, 0); KernelLog.String("msg= "); KernelLog.String(msg); KernelLog.Ln;
  160. END Fail;
  161. PROCEDURE Load(CONST filename : ARRAY OF CHAR) : BOOLEAN;
  162. VAR s : XMLScanner.Scanner;
  163. p : XMLParser.Parser;
  164. d : XML.Document;
  165. f : Files.File;
  166. r : Files.Reader;
  167. BEGIN {EXCLUSIVE}
  168. f := Files.Old(filename); COPY(filename, SELF.filename);
  169. KernelLog.String("loading filename= "); KernelLog.String(filename); KernelLog.Ln;
  170. IF f = NIL THEN RETURN FALSE END;
  171. Files.OpenReader(r, f, 0);
  172. NEW(s, r); NEW(p, s); p.reportError := Fail;
  173. errors := FALSE;
  174. d := p.Parse();
  175. IF errors THEN RETURN FALSE END;
  176. doc := d;
  177. forum := doc.GetRoot();
  178. title := forum.GetAttributeValue("title");
  179. IF title = NIL THEN title := empty END;
  180. editor := forum.GetAttributeValue("editor");
  181. IF editor = NIL THEN editor := empty END;
  182. password := forum.GetAttributeValue("password");
  183. IF password = NIL THEN password := empty END;
  184. RETURN TRUE
  185. END Load;
  186. PROCEDURE StoreInternal(CONST filename : ARRAY OF CHAR);
  187. VAR f : Files.File;
  188. w : Files.Writer;
  189. BEGIN
  190. f := Files.New(filename);
  191. Files.OpenWriter(w, f, 0);
  192. doc.Write(w, NIL, 0);
  193. w.Update;
  194. Files.Register(f);
  195. f.Update
  196. END StoreInternal;
  197. PROCEDURE Store(CONST filename : ARRAY OF CHAR);
  198. BEGIN {EXCLUSIVE}
  199. StoreInternal(filename)
  200. END Store;
  201. PROCEDURE AddEntryToParent*(parent, entry : XML.Element);
  202. VAR id, t : LONGINT;
  203. s : String;
  204. ids : ARRAY 10 OF CHAR;
  205. contents : XMLObjects.Enumerator;
  206. content : ANY;
  207. BEGIN {EXCLUSIVE}
  208. ASSERT((parent # NIL) & (entry # NIL));
  209. entryList := NIL; nofEntries := 0;
  210. id := -1;
  211. contents := parent.GetContents();
  212. WHILE contents.HasMoreElements() DO
  213. content := contents.GetNext();
  214. IF content IS XML.Element THEN
  215. s := content(XML.Element).GetName();
  216. IF (s # NIL) & (s^ = "Entry") THEN
  217. s := content(XML.Element).GetAttributeValue("id");
  218. IF s # NIL THEN
  219. Strings.StrToInt(s^, t);
  220. IF t > id THEN id := t END;
  221. END
  222. END
  223. END;
  224. END;
  225. Strings.IntToStr(id + 1, ids);
  226. entry.SetAttributeValue("id", ids);
  227. parent.AddContent(entry);
  228. END AddEntryToParent;
  229. PROCEDURE FindElement*(CONST path : ARRAY OF CHAR) : XML.Element;
  230. VAR i, j, l : SIZE;
  231. id : ARRAY 32 OF CHAR;
  232. s : String;
  233. e, next : XML.Element;
  234. contents : XMLObjects.Enumerator;
  235. content : ANY;
  236. BEGIN {EXCLUSIVE}
  237. i := 0; j := 0; l := Strings.Length(path);
  238. e := forum;
  239. IF forum = NIL THEN RETURN NIL END;
  240. WHILE j < l DO
  241. j := Strings.IndexOfByte("/", i, path);
  242. IF j < 0 THEN j := l END;
  243. Strings.Copy(path, i, j - i, id);
  244. i := j + 1;
  245. contents := e.GetContents();
  246. next := NIL;
  247. WHILE contents.HasMoreElements() & (next = NIL) DO
  248. content := contents.GetNext();
  249. IF content IS XML.Element THEN
  250. s := content(XML.Element).GetName();
  251. IF (s # NIL) & (s^ = "Entry") THEN
  252. s := content(XML.Element).GetAttributeValue("id");
  253. IF s # NIL THEN
  254. IF s^ = id THEN next := content(XML.Element) END;
  255. END;
  256. END
  257. END
  258. END;
  259. IF next = NIL THEN RETURN NIL END;
  260. e := next;
  261. END;
  262. RETURN e
  263. END FindElement;
  264. PROCEDURE GetEntryPath(e : XML.Element; VAR path : ARRAY OF CHAR);
  265. VAR s : String; p : XML.Element;
  266. BEGIN
  267. p := e.GetParent();
  268. IF (p # NIL) & (p # forum) THEN GetEntryPath(p, path) END;
  269. IF (e.GetParent() # forum) THEN Strings.Append(path, "/") END;
  270. s := e.GetAttributeValue("id");
  271. IF s # NIL THEN
  272. Strings.Append(path, s^);
  273. END;
  274. END GetEntryPath;
  275. PROCEDURE AddEntryToList(e : XML.Element);
  276. VAR subject, author, email, datetime, ip, text : String;
  277. path : ARRAY 512 OF CHAR;
  278. new : EntryList; i : LONGINT;
  279. t : XML.Element;
  280. BEGIN
  281. IF entryList = NIL THEN NEW(entryList, 1024) END;
  282. IF nofEntries >= LEN(entryList) THEN
  283. NEW(new, LEN(entryList) * 2);
  284. FOR i := 0 TO nofEntries - 1 DO new[i] := entryList[i] END;
  285. entryList := new
  286. END;
  287. ReadEntry(e, subject, author, email, datetime, ip, text);
  288. GetEntryPath(e, path);
  289. entryList[nofEntries].subject := subject;
  290. entryList[nofEntries].author := author;
  291. entryList[nofEntries].datetime := datetime;
  292. entryList[nofEntries].id := Strings.NewString(path);
  293. entryList[nofEntries].level := 0;
  294. entryList[nofEntries].entry := e;
  295. t := e;
  296. WHILE (t.GetParent() # NIL) & (t.GetParent() # forum) DO INC(entryList[nofEntries].level); t := t.GetParent() END;
  297. INC(nofEntries)
  298. END AddEntryToList;
  299. PROCEDURE Traverse (c : XML. Content; data: ANY);
  300. VAR name : String;
  301. BEGIN
  302. IF (c # NIL) & (c IS XML.Element) THEN
  303. name := c(XML.Element).GetName();
  304. IF (name # NIL) & (name^ = "Entry") THEN
  305. AddEntryToList(c(XML.Element));
  306. END
  307. END;
  308. END Traverse;
  309. PROCEDURE GetSubjectList*(VAR e : EntryList; VAR nof : LONGINT);
  310. BEGIN {EXCLUSIVE}
  311. IF entryList = NIL THEN forum.Traverse(Traverse, NIL) END;
  312. e := entryList;
  313. nof := nofEntries
  314. END GetSubjectList;
  315. PROCEDURE AddEntry*(CONST path : ARRAY OF CHAR; entry : XML.Element);
  316. VAR parent : XML.Element;
  317. f : Files.File;
  318. w : Files.Writer;
  319. s : ARRAY 100 OF CHAR;
  320. BEGIN
  321. ASSERT(entry # NIL);
  322. parent := FindElement(path);
  323. IF parent # NIL THEN
  324. AddEntryToParent(parent, entry);
  325. IF filename # "" THEN Store(filename) END;
  326. ELSE
  327. KernelLog.String("Lost entry stored in LostForumEntries.txt"); KernelLog.Ln;
  328. BEGIN {EXCLUSIVE}
  329. f := Files.Old("LostForumEntries.txt");
  330. IF f = NIL THEN
  331. f := Files.New("LostForumEntries.txt");
  332. END;
  333. Files.OpenWriter(w, f, f.Length());
  334. w.Ln;
  335. Strings.FormatDateTime("@ yyyy.mm.dd hh.nn.ss", Dates.Now(), s);
  336. w.String("Was not stored in "); w.String(filename); w.String(s); w.Ln;
  337. entry.Write(w, NIL, 0);
  338. w.Update;
  339. Files.Register(f);
  340. f.Update;
  341. END;
  342. END
  343. END AddEntry;
  344. PROCEDURE DeleteEntry*(CONST path : ARRAY OF CHAR);
  345. VAR entry, parent : XML.Element;
  346. f : Files.File;
  347. w : Files.Writer;
  348. s : ARRAY 100 OF CHAR;
  349. BEGIN
  350. entry := FindElement(path);
  351. ASSERT(entry # NIL);
  352. parent := entry.GetParent();
  353. IF parent # NIL THEN
  354. parent.RemoveContent(entry);
  355. IF filename # "" THEN Store(filename) END;
  356. KernelLog.String("deleted entry stored in DeletedEntries.txt"); KernelLog.Ln;
  357. BEGIN {EXCLUSIVE}
  358. entryList := NIL; nofEntries := 0; (* kill the cached list *)
  359. f := Files.Old("DeletedEntries.txt");
  360. IF f = NIL THEN
  361. f := Files.New("DeletedEntries.txt");
  362. END;
  363. Files.OpenWriter(w, f, f.Length());
  364. w.Ln;
  365. Strings.FormatDateTime("@ yyyy.mm.dd hh.nn.ss", Dates.Now(), s);
  366. w.String("Deleted from "); w.String(filename); w.String(s); w.Ln;
  367. entry.Write(w, NIL, 0);
  368. w.Update;
  369. Files.Register(f);
  370. f.Update;
  371. END;
  372. END
  373. END DeleteEntry;
  374. PROCEDURE EditEntry(parent: XML.Element; subject, author, email, datetime, ip, text : String);
  375. PROCEDURE Set(CONST name : ARRAY OF CHAR; value : String);
  376. VAR e : XML.Element;
  377. c : XML.CDataSect;
  378. BEGIN
  379. e := GetSubElementByType(parent, name); IF e # NIL THEN parent.RemoveContent(e) END;
  380. NEW(e);
  381. e.SetName(name); parent.AddContent(e);
  382. NEW(c); c.SetStr(value^); e.AddContent(c);
  383. END Set;
  384. BEGIN
  385. entryList := NIL; nofEntries := 0;
  386. BEGIN {EXCLUSIVE}
  387. Set("Subject", subject);
  388. Set("Author", author);
  389. Set("Email", email);
  390. Set("DateTime", datetime);
  391. Set("IP", ip);
  392. Set("Text", text);
  393. END;
  394. IF filename # "" THEN Store(filename) END;
  395. END EditEntry;
  396. END Forum;
  397. ForumInfo = RECORD
  398. id : ARRAY 256 OF CHAR;
  399. fileName : ARRAY 256 OF CHAR;
  400. content : Forum;
  401. END;
  402. ForumList = POINTER TO ARRAY OF ForumInfo;
  403. VAR
  404. uriLiteral : ARRAY 256 OF BOOLEAN;
  405. empty : String;
  406. forumList : ForumList;
  407. nofForum : LONGINT;
  408. PROCEDURE GetSubElementByType*(parent: XML.Element; CONST type : ARRAY OF CHAR): XML.Element;
  409. VAR enum: XMLObjects.Enumerator; p: ANY; e: XML.Element; s: XML.String;
  410. BEGIN
  411. enum := parent.GetContents();
  412. WHILE enum.HasMoreElements() DO
  413. p := enum.GetNext();
  414. IF p IS XML.Element THEN
  415. e := p(XML.Element); s := e.GetName();
  416. IF (s # NIL) & (s^ = type) THEN (* correct element name *)
  417. RETURN e
  418. END
  419. END
  420. END;
  421. RETURN NIL
  422. END GetSubElementByType;
  423. PROCEDURE MakeEntry*(subject, author, email, datetime, ip, text : String): XML.Element;
  424. VAR r, e : XML.Element;
  425. c : XML.CDataSect;
  426. BEGIN
  427. NEW(r);
  428. r.SetName("Entry");
  429. NEW(e); e.SetName("Subject"); r.AddContent(e);
  430. NEW(c); c.SetStr(subject^); e.AddContent(c);
  431. NEW(e); e.SetName("Author"); r.AddContent(e);
  432. NEW(c); c.SetStr(author^); e.AddContent(c);
  433. NEW(e); e.SetName("Email"); r.AddContent(e);
  434. NEW(c); c.SetStr(email^); e.AddContent(c);
  435. NEW(e); e.SetName("DateTime"); r.AddContent(e);
  436. NEW(c); c.SetStr(datetime^); e.AddContent(c);
  437. NEW(e); e.SetName("IP"); r.AddContent(e);
  438. NEW(c); c.SetStr(ip^); e.AddContent(c);
  439. NEW(e); e.SetName("Text"); r.AddContent(e);
  440. NEW(c); c.SetStr(text^); e.AddContent(c);
  441. RETURN r
  442. END MakeEntry;
  443. PROCEDURE PostingToHTML*(w : Streams.Writer; h : HTMLWriter; subject, author, email, datetime, ip, text : String);
  444. BEGIN
  445. w.String('<table border="0" bgcolor="#F0F0F0">'); w.Ln;
  446. w.String('<tr><td>');
  447. w.String("Subject : "); w.String("<b>"); h.HTMLString(subject^);w.String("</b>");
  448. w.String('</td></tr>'); w.Ln;
  449. w.String('<tr><td>');
  450. w.String("Author : "); h.HTMLString(author^); h.Br;
  451. w.String('</td></tr>'); w.Ln;
  452. w.String('<tr><td>');
  453. w.String("Email : ");h.HTMLString(email^); h.Br;
  454. w.String('</td></tr>'); w.Ln;
  455. w.String('<tr><td>');
  456. w.String("Date : ");h.HTMLString(datetime^); h.Br;
  457. w.String('</td></tr>'); w.Ln;
  458. w.String("</table>");
  459. w.String('<table border="1" width="100%" cellpadding="0" cellspacing="0" bordercolor="#111111" bgcolor="#CCFFFF"><tr><td>');
  460. h.HTMLString(text^); h.Br;
  461. w.String("</td></tr></table>");
  462. END PostingToHTML;
  463. PROCEDURE ReadEntry*(entry : XML.Element; VAR subject, author, email, datetime, ip, text : String);
  464. VAR
  465. enum: XMLObjects.Enumerator; obj : ANY;
  466. e: XML.Element; str : String;
  467. PROCEDURE GetCDataContent(e : XML.Element) : String;
  468. VAR en : XMLObjects.Enumerator;
  469. p : ANY;
  470. BEGIN
  471. en := e.GetContents();
  472. p := en.GetNext();
  473. IF p # NIL THEN
  474. IF p IS XML.CDataSect THEN
  475. RETURN p(XML.CDataSect).GetStr()
  476. END
  477. END;
  478. RETURN NIL
  479. END GetCDataContent;
  480. BEGIN
  481. subject := empty; author := empty; email := empty; datetime := empty; ip := empty; text := empty;
  482. enum := entry.GetContents();
  483. WHILE enum.HasMoreElements() DO
  484. obj := enum.GetNext();
  485. IF obj IS XML.Element THEN
  486. e := obj(XML.Element); str := e.GetName();
  487. IF str^ = "Subject" THEN subject := GetCDataContent(e) END;
  488. IF str^ = "Author" THEN author := GetCDataContent(e) END;
  489. IF str^ = "Email" THEN email := GetCDataContent(e) END;
  490. IF str^ = "DateTime" THEN datetime := GetCDataContent(e) END;
  491. IF str^ = "IP" THEN ip := GetCDataContent(e) END;
  492. IF str^ = "Text" THEN text := GetCDataContent(e) END;
  493. END
  494. END;
  495. END ReadEntry;
  496. PROCEDURE ListLink(VAR forumID, link : ARRAY OF CHAR);
  497. BEGIN
  498. COPY("Forum?forum=", link);
  499. Strings.Append(link, forumID);
  500. Strings.Append(link, "&action=List")
  501. END ListLink;
  502. PROCEDURE ShowLink(VAR forumID, entryID, link : ARRAY OF CHAR);
  503. BEGIN
  504. COPY("Forum?forum=", link);
  505. Strings.Append(link, forumID);
  506. Strings.Append(link, "&action=Show&entry=");
  507. Strings.Append(link, entryID);
  508. END ShowLink;
  509. PROCEDURE ReplyLink(VAR forumID, entryID, link : ARRAY OF CHAR);
  510. BEGIN
  511. COPY("Forum?forum=", link);
  512. Strings.Append(link, forumID);
  513. Strings.Append(link, "&action=Reply");
  514. IF entryID # "" THEN
  515. Strings.Append(link, "&entry=");
  516. Strings.Append(link, entryID)
  517. END
  518. END ReplyLink;
  519. PROCEDURE PublishPostLink(VAR forumID, entryID, link : ARRAY OF CHAR);
  520. BEGIN
  521. COPY("Forum?forum=", link);
  522. Strings.Append(link, forumID);
  523. Strings.Append(link, "&action=Publish");
  524. IF entryID # "" THEN
  525. Strings.Append(link, "&entry=");
  526. Strings.Append(link, entryID)
  527. END
  528. END PublishPostLink;
  529. PROCEDURE DeletePostLink(VAR forumID, entryID, link : ARRAY OF CHAR);
  530. BEGIN
  531. COPY("Forum?forum=", link);
  532. Strings.Append(link, forumID);
  533. Strings.Append(link, "&action=PublishDelete");
  534. IF entryID # "" THEN
  535. Strings.Append(link, "&entry=");
  536. Strings.Append(link, entryID)
  537. END
  538. END DeletePostLink;
  539. PROCEDURE GetParentLink(VAR forumID, entryID, link : ARRAY OF CHAR) : BOOLEAN;
  540. VAR p : LONGINT;
  541. parentID : ARRAY 512 OF CHAR;
  542. BEGIN
  543. p := Strings.LastIndexOfByte2("/", entryID);
  544. IF p > 0 THEN
  545. Strings.Copy(entryID, 0, p, parentID);
  546. ShowLink(forumID, parentID, link);
  547. RETURN TRUE
  548. ELSE RETURN FALSE
  549. END;
  550. END GetParentLink;
  551. PROCEDURE List*(forum : Forum; forumID : ARRAY OF CHAR; context: WebCGI.CGIContext);
  552. VAR
  553. w : Streams.Writer;
  554. chunker : WebHTTP.ChunkedOutStream;
  555. h : HTMLWriter;
  556. entryList : EntryList;
  557. nof, i, j : LONGINT;
  558. link : ARRAY 256 OF CHAR;
  559. e : ARRAY 2 OF CHAR;
  560. BEGIN
  561. forum.GetSubjectList(entryList, nof);
  562. (* reply *)
  563. NEW(chunker, w, context.w, context.request.header, context.reply);
  564. context.reply.statuscode := WebHTTP.OK;
  565. context.reply.contenttype := "text/html; charset=UTF-8";
  566. WebHTTP.SendResponseHeader(context.reply, context.w);
  567. NEW(h, w);
  568. h.Head(forum.title^);
  569. w.String("<H1>");
  570. h.HTMLString(forum.title^);
  571. w.String("</H1>"); w.Ln;
  572. FOR i := 0 TO nof - 1 DO
  573. FOR j := 0 TO entryList[i].level - 1 DO h.Nbsp; h.Nbsp END;
  574. ShowLink(forumID, entryList[i].id^, link);
  575. h.TextLink(entryList[i].subject^, link);
  576. w.String("<i> (");
  577. w.String(entryList[i].author^);
  578. w.String( " @ ");
  579. w.String(entryList[i].datetime^);
  580. w.String(")</i>");
  581. h.Br;
  582. END;
  583. e := "";
  584. h.Br;
  585. ReplyLink(forumID, e, link);
  586. h.TextLink("Write new message", link);
  587. h.Tail;
  588. w.Ln; w.Update;
  589. chunker.Close;
  590. END List;
  591. PROCEDURE Show*(forum : Forum; forumID, entryID : ARRAY OF CHAR; context: WebCGI.CGIContext);
  592. VAR
  593. w : Streams.Writer;
  594. chunker : WebHTTP.ChunkedOutStream;
  595. h : HTMLWriter;
  596. nof, i, j, thisIndent : LONGINT;
  597. entry : XML.Element;
  598. firstReply : BOOLEAN;
  599. link, title : ARRAY 256 OF CHAR;
  600. subject, author, email, datetime, ip, text : String;
  601. entries : EntryList;
  602. BEGIN
  603. (* reply *)
  604. NEW(chunker, w, context.w, context.request.header, context.reply);
  605. context.reply.statuscode := WebHTTP.OK;
  606. context.reply.contenttype := "text/html; charset=UTF-8";
  607. WebHTTP.SendResponseHeader(context.reply, context.w);
  608. NEW(h, w);
  609. entry := forum.FindElement(entryID);
  610. IF entry # NIL THEN
  611. forum.GetSubjectList(entries, nof);
  612. ReadEntry(entry, subject, author, email, datetime, ip, text);
  613. COPY(forum.title^, title);
  614. Strings.Append(title, " - "); Strings.Append(title, subject^);
  615. h.Head(title);
  616. ListLink(forumID, link);
  617. h.TextLink("List", link); h.Nbsp;
  618. IF GetParentLink(forumID, entryID, link) THEN h.TextLink("Parent", link) END;
  619. h.Br;
  620. i := 0; WHILE (entryID # entries[i].id^) & (i < nof) DO INC(i) END;
  621. IF i > 0 THEN
  622. ShowLink(forumID, entries[i -1].id^, link);
  623. h.TextLink("Previous", link)
  624. END;
  625. IF i < nof - 1 THEN
  626. h.Nbsp;
  627. ShowLink(forumID, entries[i + 1].id^, link);
  628. h.TextLink("Next", link)
  629. END;
  630. h.Br;
  631. PostingToHTML(w, h, subject, author, email, datetime, ip, text);
  632. h.Br;
  633. firstReply := TRUE;
  634. FOR i := 0 TO nof - 1 DO
  635. IF Strings.StartsWith2(entryID, entries[i].id^) THEN
  636. IF (entryID = entries[i].id^) THEN
  637. thisIndent := entries[i].level;
  638. ELSE
  639. IF firstReply THEN w.String("<b>Replies</b>"); h.Br; firstReply := FALSE END;
  640. FOR j := 0 TO entries[i].level - thisIndent - 1 DO h.Nbsp; h.Nbsp END;
  641. ShowLink(forumID, entries[i].id^, link);
  642. h.TextLink(entries[i].subject^, link);
  643. w.String("<i> (");
  644. w.String(entries[i].author^);
  645. w.String( " @ ");
  646. w.String(entries[i].datetime^);
  647. w.String(")</i>");
  648. h.Br;
  649. END
  650. END
  651. END;
  652. h.Br;
  653. ReplyLink(forumID, entryID, link);
  654. h.TextLink("Write a new reply", link); h.Br;
  655. ELSE
  656. ListLink(forumID, link);
  657. h.TextLink("list", link); h.Nbsp;
  658. link := "entry not found";
  659. h.HTMLString(link);
  660. END;
  661. h.Tail;
  662. w.Ln; w.Update;
  663. chunker.Close;
  664. END Show;
  665. PROCEDURE QueryPost(forum : Forum; forumID, entryID : ARRAY OF CHAR; context: WebCGI.CGIContext);
  666. VAR
  667. w : Streams.Writer;
  668. chunker : WebHTTP.ChunkedOutStream;
  669. h : HTMLWriter;
  670. entry : XML.Element;
  671. link, s : ARRAY 256 OF CHAR;
  672. subject, author, email, datetime, ip, text : String;
  673. BEGIN
  674. (* reply *)
  675. NEW(chunker, w, context.w, context.request.header, context.reply);
  676. context.reply.statuscode := WebHTTP.OK;
  677. context.reply.contenttype := "text/html; charset=UTF-8";
  678. WebHTTP.SendResponseHeader(context.reply, context.w);
  679. NEW(h, w);
  680. entry := forum.FindElement(entryID);
  681. IF (entry # NIL) & (entry # forum.forum) THEN
  682. ReadEntry(entry, subject, author, email, datetime, ip, text);
  683. s := "Reply to ";
  684. Strings.Append(s, subject^);
  685. h.Head(s);
  686. ListLink(forumID, link);
  687. h.TextLink("list", link); h.Nbsp;
  688. IF GetParentLink(forumID, entryID, link) THEN h.TextLink("parent", link); h.Br END;
  689. h.Br;
  690. PostingToHTML(w, h, subject, author, email, datetime, ip, text);
  691. ELSE
  692. h.Head("Create a new thread");
  693. w.String("Create a new thread"); h.Br;
  694. END;
  695. PublishPostLink(forumID, entryID, link);
  696. w.String('<form action="');w.String(link); w.String('"method="POST" accept-charset="UTF-8" >'); w.Ln;
  697. h.Br; w.String("<hr/>"); w.Ln;
  698. w.String('Subject : '); h.InputText("subject", subject); h.Br; w.Ln;
  699. w.String("Author : "); h.InputText("author", empty); h.Br; w.Ln;
  700. w.String("Email : "); h.InputText("email", empty); w.String("<i>optional</i>"); h.Br; w.Ln;
  701. w.String("Text : "); h.InputArea("text", empty); h.Br; w.Ln;
  702. h.Submit("Post");
  703. w.String('</form>');
  704. h.Tail;
  705. w.Ln; w.Update;
  706. chunker.Close;
  707. END QueryPost;
  708. PROCEDURE QueryEditPost(forum : Forum; forumID, entryID : ARRAY OF CHAR; context: WebCGI.CGIContext);
  709. VAR
  710. w : Streams.Writer;
  711. chunker : WebHTTP.ChunkedOutStream;
  712. h : HTMLWriter;
  713. entry : XML.Element;
  714. link, s : ARRAY 256 OF CHAR;
  715. subject, author, email, datetime, ip, text : String;
  716. BEGIN
  717. (* reply *)
  718. NEW(chunker, w, context.w, context.request.header, context.reply);
  719. context.reply.statuscode := WebHTTP.OK;
  720. context.reply.contenttype := "text/html; charset=UTF-8";
  721. WebHTTP.SendResponseHeader(context.reply, context.w);
  722. NEW(h, w);
  723. entry := forum.FindElement(entryID);
  724. IF (entry # NIL) & (entry # forum.forum) THEN
  725. ReadEntry(entry, subject, author, email, datetime, ip, text);
  726. s := "Edit ";
  727. Strings.Append(s, subject^);
  728. h.Head(s);
  729. ListLink(forumID, link);
  730. h.TextLink("list", link); h.Nbsp;
  731. IF GetParentLink(forumID, entryID, link) THEN h.TextLink("parent", link); h.Br END;
  732. h.Br;
  733. PostingToHTML(w, h, subject, author, email, datetime, ip, text);
  734. PublishPostLink(forumID, entryID, link);
  735. w.String('<form action="');w.String(link); w.String('"method="POST" accept-charset="UTF-8" >'); w.Ln;
  736. h.Br; w.String("<hr/>"); w.Ln;
  737. w.String("<b>Accreditiation:</b><br/>");
  738. w.String('Editor : '); h.InputText("editor", NIL); w.String('Authorization : '); h.InputText("password", NIL);
  739. h.Br;
  740. w.String('Subject : '); h.InputText("subject", subject); h.Br; w.Ln;
  741. w.String("Author : "); h.InputText("author", author); h.Br; w.Ln;
  742. w.String("Email : "); h.InputText("email", email); w.String("<i>optional</i>"); h.Br; w.Ln;
  743. w.String("Text : "); h.InputArea("text", text); h.Br; w.Ln;
  744. h.Hide("ip", ip^);
  745. h.Hide("datetime", datetime^);
  746. h.Hide("replace", "true");
  747. h.Submit("Edit");
  748. w.String('</form>');
  749. h.Tail;
  750. END;
  751. w.Ln; w.Update;
  752. chunker.Close;
  753. END QueryEditPost;
  754. PROCEDURE QueryDeletePost(forum : Forum; forumID, entryID : ARRAY OF CHAR; context: WebCGI.CGIContext);
  755. VAR
  756. w : Streams.Writer;
  757. chunker : WebHTTP.ChunkedOutStream;
  758. h : HTMLWriter;
  759. entry : XML.Element;
  760. link, s : ARRAY 256 OF CHAR;
  761. subject, author, email, datetime, ip, text : String;
  762. BEGIN
  763. (* reply *)
  764. NEW(chunker, w, context.w, context.request.header, context.reply);
  765. context.reply.statuscode := WebHTTP.OK;
  766. context.reply.contenttype := "text/html; charset=UTF-8";
  767. WebHTTP.SendResponseHeader(context.reply, context.w);
  768. NEW(h, w);
  769. entry := forum.FindElement(entryID);
  770. IF (entry # NIL) & (entry # forum.forum) THEN
  771. ReadEntry(entry, subject, author, email, datetime, ip, text);
  772. s := "Delete ";
  773. Strings.Append(s, subject^);
  774. h.Head(s);
  775. ListLink(forumID, link);
  776. h.TextLink("list", link); h.Nbsp;
  777. IF GetParentLink(forumID, entryID, link) THEN h.TextLink("parent", link); h.Br END;
  778. h.Br;
  779. PostingToHTML(w, h, subject, author, email, datetime, ip, text);
  780. ELSE
  781. END;
  782. DeletePostLink(forumID, entryID, link);
  783. w.String('<form action="');w.String(link); w.String('"method="POST" accept-charset="UTF-8" >'); w.Ln;
  784. h.Br; w.String("<hr/>"); w.Ln;
  785. w.String("<b>Accreditiation:</b><br/>");
  786. w.String('Editor : '); h.InputText("editor", NIL); w.String('Authorization : '); h.InputText("password", NIL);
  787. h.Submit("Delete");
  788. w.String('</form>');
  789. h.Tail;
  790. w.Ln; w.Update;
  791. chunker.Close;
  792. END QueryDeletePost;
  793. PROCEDURE PublishPost(forum : Forum; forumID, entryID : ARRAY OF CHAR; context: WebCGI.CGIContext);
  794. VAR
  795. w : Streams.Writer;
  796. chunker : WebHTTP.ChunkedOutStream;
  797. h : HTMLWriter;
  798. entry, parent : XML.Element;
  799. link, s, editor, password : ARRAY 256 OF CHAR;
  800. subject, author, email, datetime, ip, text : String;
  801. var: HTTPSupport.HTTPVariable;
  802. replace : BOOLEAN;
  803. BEGIN
  804. (* reply *)
  805. NEW(chunker, w, context.w, context.request.header, context.reply);
  806. context.reply.statuscode := WebHTTP.OK;
  807. context.reply.contenttype := "text/html; charset=UTF-8";
  808. WebHTTP.SendResponseHeader(context.reply, context.w);
  809. NEW(h, w);
  810. var := context.request.GetVariableByName("replace");
  811. IF (var # NIL) & (var.value # "") THEN replace := var.value = "true"
  812. ELSE replace := FALSE
  813. END;
  814. IF replace THEN KernelLog.String("Replace entry") ELSE KernelLog.String("New Entry"); KernelLog.Ln; END;
  815. var := context.request.GetVariableByName("subject");
  816. IF (var # NIL) & (var.value # "") THEN subject := Strings.NewString(var.value)
  817. ELSE subject := Strings.NewString("anonymous");
  818. END;
  819. var := context.request.GetVariableByName("author");
  820. IF (var # NIL) & (var.value # "") THEN author := Strings.NewString(var.value)
  821. ELSE author := Strings.NewString("anonymous");
  822. END;
  823. var := context.request.GetVariableByName("email");
  824. IF (var # NIL) & (var.value # "") THEN email := Strings.NewString(var.value)
  825. ELSE email:= Strings.NewString("");
  826. END;
  827. var := context.request.GetVariableByName("text");
  828. IF (var # NIL) & (var.value # "") THEN text := Strings.NewString(var.value)
  829. ELSE text := Strings.NewString("");
  830. END;
  831. IP.AdrToStr(context.request.header.fadr, s);
  832. ip := Strings.NewString(s);
  833. Strings.FormatDateTime("yyyy.mm.dd hh.nn.ss", Dates.Now(), s);
  834. datetime := Strings.NewString(s);
  835. IF ~replace THEN
  836. entry := MakeEntry(subject, author, email, datetime, ip, text);
  837. forum.AddEntry(entryID, entry);
  838. parent := forum.FindElement(entryID);
  839. IF parent # NIL THEN
  840. h.Head(subject^);
  841. ListLink(forumID, link); h.TextLink("list", link); h.Nbsp;
  842. ShowLink(forumID, entryID, link); h.TextLink("parent", link); h.Br;
  843. ELSE h.Head("New thread created");
  844. ListLink(forumID, link); h.TextLink("list", link); h.Nbsp;
  845. END;
  846. ELSE
  847. h.Head(subject^);
  848. var := context.request.GetVariableByName("editor");
  849. IF (var # NIL) THEN COPY(var.value, editor) END;
  850. var := context.request.GetVariableByName("password");
  851. IF (var # NIL) THEN COPY(var.value, password) END;
  852. IF (editor = forum.editor^) & (password = forum.password^) THEN
  853. ListLink(forumID, link); h.TextLink("list", link); h.Nbsp;
  854. entry := forum.FindElement(entryID);
  855. IF entry # NIL THEN
  856. forum.EditEntry(entry, subject, author, email, datetime, ip, text)
  857. END;
  858. ELSE
  859. w.String("<h1>Your accredition was not accepted.</h1>"); w.Ln;
  860. END
  861. END;
  862. PostingToHTML(w, h, subject, author, email, datetime, ip, text);
  863. h.Br;
  864. h.Tail;
  865. w.Ln; w.Update;
  866. chunker.Close;
  867. END PublishPost;
  868. PROCEDURE DeletePost(forum : Forum; forumID, entryID : ARRAY OF CHAR; context: WebCGI.CGIContext);
  869. VAR
  870. w : Streams.Writer;
  871. chunker : WebHTTP.ChunkedOutStream;
  872. h : HTMLWriter;
  873. link, editor, password : ARRAY 256 OF CHAR;
  874. var: HTTPSupport.HTTPVariable;
  875. BEGIN
  876. (* reply *)
  877. NEW(chunker, w, context.w, context.request.header, context.reply);
  878. context.reply.statuscode := WebHTTP.OK;
  879. context.reply.contenttype := "text/html; charset=UTF-8";
  880. WebHTTP.SendResponseHeader(context.reply, context.w);
  881. NEW(h, w);
  882. h.Head("Deleting Post");
  883. var := context.request.GetVariableByName("editor");
  884. IF (var # NIL) THEN COPY(var.value, editor) END;
  885. var := context.request.GetVariableByName("password");
  886. IF (var # NIL) THEN COPY(var.value, password) END;
  887. IF (editor = forum.editor^) & (password = forum.password^) THEN
  888. ListLink(forumID, link); h.TextLink("list", link); h.Nbsp;
  889. forum.DeleteEntry(entryID);
  890. w.String("Entry deleted.");
  891. ELSE
  892. w.String("<h1>Your accredition was not accepted.</h1>"); w.Ln;
  893. END;
  894. h.Tail;
  895. w.Ln; w.Update;
  896. chunker.Close;
  897. END DeletePost;
  898. PROCEDURE Access*(context : WebCGI.CGIContext);
  899. VAR
  900. r : HTTPSupport.HTTPRequest;
  901. var: HTTPSupport.HTTPVariable;
  902. action, forumID, entry : ARRAY 32 OF CHAR;
  903. forum : Forum;
  904. w : Streams.Writer;
  905. chunker : WebHTTP.ChunkedOutStream;
  906. defaultAction : BOOLEAN;
  907. milliTimer : Kernel.MilliTimer;
  908. BEGIN
  909. Kernel.SetTimer(milliTimer, 0);
  910. r := context.request;
  911. defaultAction := TRUE;
  912. var := r.GetVariableByName("action");
  913. IF var # NIL THEN COPY(var.value, action); defaultAction := FALSE END;
  914. var := r.GetVariableByName("forum");
  915. IF var # NIL THEN COPY(var.value, forumID) END;
  916. var := r.GetVariableByName("entry");
  917. IF var # NIL THEN COPY(var.value, entry) END;
  918. forum := GetForum(forumID);
  919. IF forum = NIL THEN
  920. NEW(chunker, w, context.w, context.request.header, context.reply);
  921. context.reply.statuscode := WebHTTP.NotFound;
  922. WebHTTP.SendResponseHeader(context.reply, context.w);
  923. w.String("<html><head><title>Forum</title></head>");
  924. w.String("<body>");
  925. w.String("Forum not found"); w.Ln;
  926. w.String("</body></html>");
  927. w.Ln; w.Update;
  928. chunker.Close
  929. ELSE
  930. IF action = "Show" THEN Show(forum, forumID, entry, context);
  931. ELSIF defaultAction OR (action = "List") THEN List(forum, forumID, context);
  932. ELSIF action = "Reply" THEN QueryPost(forum, forumID, entry, context)
  933. ELSIF action = "Publish" THEN PublishPost(forum, forumID, entry, context)
  934. ELSIF action = "Edit" THEN QueryEditPost(forum, forumID, entry, context)
  935. ELSIF action = "Delete" THEN QueryDeletePost(forum, forumID, entry, context)
  936. ELSIF action = "PublishDelete" THEN DeletePost(forum, forumID, entry, context)
  937. ELSE
  938. NEW(chunker, w, context.w, context.request.header, context.reply);
  939. context.reply.statuscode := WebHTTP.NotFound;
  940. WebHTTP.SendResponseHeader(context.reply, context.w);
  941. w.String("<html><head><title>Forum</title></head>");
  942. w.String("<body>");
  943. w.String("Illegal forum request"); w.Ln;
  944. w.String("</body></html>");
  945. w.Ln; w.Update;
  946. chunker.Close
  947. END
  948. END;
  949. KernelLog.String("Forum request handled in "); KernelLog.Int(Kernel.Elapsed(milliTimer), 0); KernelLog.String("ms"); KernelLog.Ln;
  950. END Access;
  951. PROCEDURE InitURILiterals;
  952. VAR i : LONGINT;
  953. BEGIN
  954. FOR i := 0 TO 255 DO uriLiteral[i] := FALSE END;
  955. FOR i := 61H TO 7AH DO uriLiteral[i] := TRUE END;(* RFC2396 lowalpha *)
  956. FOR i := 41H TO 5AH DO uriLiteral[i] := TRUE END;(* RFC2396 upalpha *)
  957. FOR i := 30H TO 39H DO uriLiteral[i] := TRUE END; (* RFC2396 digit *)
  958. uriLiteral[2DH] := TRUE; (* - *)
  959. uriLiteral[5FH] := TRUE; (* underscore *)
  960. uriLiteral[2EH] := TRUE; (* . *)
  961. uriLiteral[21H] := TRUE; (* ! *)
  962. uriLiteral[7EH] := TRUE; (* ~ *)
  963. uriLiteral[2AH] := TRUE; (* * *)
  964. uriLiteral[27H] := TRUE; (* ' *)
  965. uriLiteral[28H] := TRUE; (* ( *)
  966. uriLiteral[29H] := TRUE; (* ) *)
  967. END InitURILiterals;
  968. PROCEDURE AddForum(CONST id,fileName : ARRAY OF CHAR);
  969. VAR new : ForumList;
  970. i : LONGINT;
  971. BEGIN
  972. IF nofForum >= LEN(forumList) THEN
  973. NEW(new, LEN(forumList) * 2);
  974. FOR i := 0 TO nofForum - 1 DO new[i] := forumList[i] END;
  975. forumList := new
  976. END;
  977. COPY(id, forumList[nofForum].id);
  978. COPY(fileName, forumList[nofForum].fileName);
  979. INC(nofForum)
  980. END AddForum;
  981. PROCEDURE GetForumInternal(CONST id : ARRAY OF CHAR) : Forum;
  982. VAR i : LONGINT; result : Forum;
  983. BEGIN
  984. i := 0;
  985. WHILE (i < nofForum) & (result = NIL) DO
  986. IF forumList[i].id = id THEN
  987. IF forumList[i].content = NIL THEN
  988. NEW(forumList[i].content);
  989. IF forumList[i].content.Load(forumList[i].fileName) THEN
  990. KernelLog.String(forumList[i].id); KernelLog.String(" loaded from "); KernelLog.String(forumList[i].fileName); KernelLog.Ln;
  991. ELSE
  992. KernelLog.String(forumList[i].id); KernelLog.String("FAILED loading from "); KernelLog.String(forumList[i].fileName); KernelLog.Ln;
  993. END;
  994. END;
  995. result := forumList[i].content
  996. END;
  997. INC(i)
  998. END;
  999. RETURN result
  1000. END GetForumInternal;
  1001. PROCEDURE GetForum(CONST id : ARRAY OF CHAR) : Forum;
  1002. BEGIN {EXCLUSIVE}
  1003. RETURN GetForumInternal(id)
  1004. END GetForum;
  1005. PROCEDURE LoadForumList;
  1006. VAR f : Files.File;
  1007. r : Files.Reader;
  1008. id, fileName : ARRAY 128 OF CHAR;
  1009. BEGIN {EXCLUSIVE}
  1010. f := Files.Old(ForumConfigFile);
  1011. IF f # NIL THEN
  1012. Files.OpenReader(r, f, 0);
  1013. WHILE r.res = 0 DO
  1014. r.Token(id); r.SkipWhitespace;
  1015. r.String(fileName);
  1016. IF r.res = 0 THEN AddForum(id, fileName) END;
  1017. r.SkipLn;
  1018. END
  1019. END;
  1020. END LoadForumList;
  1021. PROCEDURE StoreForumList;
  1022. VAR f : Files.File;
  1023. w : Files.Writer;
  1024. i : LONGINT;
  1025. BEGIN {EXCLUSIVE}
  1026. f := Files.New(ForumConfigFile);
  1027. Files.OpenWriter(w, f, 0);
  1028. FOR i := 0 TO nofForum - 1 DO
  1029. w.String(forumList[i].id); w.String(' "'); w.String(forumList[i].fileName); w.String('"'); w.Ln
  1030. END;
  1031. w.Update;
  1032. Files.Register(f)
  1033. END StoreForumList;
  1034. PROCEDURE CreateForum*(context : Commands.Context);
  1035. VAR
  1036. id, fileName, title, user, password : ARRAY 128 OF CHAR;
  1037. forum : Forum;
  1038. BEGIN
  1039. context.arg.Token(id); context.arg.SkipWhitespace(); context.arg.String(fileName); context.arg.SkipWhitespace(); context.arg.String(title);
  1040. context.arg.SkipWhitespace(); context.arg.String(user); context.arg.SkipWhitespace(); context.arg.String(password);
  1041. BEGIN{EXCLUSIVE}
  1042. forum := GetForumInternal(id);
  1043. IF forum # NIL THEN
  1044. context.error.String("Forum already exists"); context.error.Ln; RETURN;
  1045. ELSE
  1046. AddForum(id, fileName);
  1047. forum := GetForumInternal(id);
  1048. forum.SetTitle(title);
  1049. forum.SetEditor(user, password);
  1050. forum.Store(fileName);
  1051. END
  1052. END;
  1053. StoreForumList;
  1054. END CreateForum;
  1055. BEGIN
  1056. empty := Strings.NewString("");
  1057. NEW(forumList, 128); nofForum := 0;
  1058. LoadForumList;
  1059. InitURILiterals;
  1060. END TFWebForum.
  1061. System.Free TFWebForum ~
  1062. TFWebForum.CreateForum RFWde ForumRFWde.XML "Raily for Windows (Deutsch)" rfwuser rfwpassword ~
  1063. TFWebForum.CreateForum RFWfr ForumRFWfr.XML "Raily for Windows (Francais)" rfwuser rfwpassword ~
  1064. TFWebForum.CreateForum RFWen ForumRFWen.XML "Raily for Windows (English)" rfwuser rfwpassword ~
  1065. TFWebForum.CreateForum PCFrey ForumPCFrey.XML "PC - Forum" rfwuser rfwpassword ~
  1066. TFWebForum.CreateForum BluebottleFeatures ForumBluebottle.XML "Forum Bluebottlerum" user password ~
  1067. WebHTTPServerTools.Start ~
  1068. WebCGI.Install ~
  1069. WebCGI.RegisterCGI Forum TFWebForum.Access~
  1070. WebCGI.ListCGI ~