// REFERENCE: System.Web using Nemerle.Collections; using Nemerle.Utility; using Nemerle.IO; using Nemerle.Text; using System.Data; using System.IO; using System; module CGI_Comments { blog_url : string = "/blog/"; blog_root : string = "/home/services/nemerle.org/blog/"; mutable output_path : string; mutable output_url : string; /** * Splits a string given a separator character. Checks if the split was * unique -- or, if the separator occured exactly once in the original * string. */ split_unique_at (separator : char, str : string) : string * string * bool { def pos = str.IndexOf (separator); def (l, r) = if (pos != -1) (str.Substring (0, pos), str.Substring (pos + 1)) else (str, ""); def validity = l.IndexOf (separator) == -1 && r.IndexOf (separator) == -1; (l, r, validity) } /** * Parses a string of form: * * param_1=val_1¶m_2=val_2&...¶m_k=val_k * * Checks the validity of such a request and throws exception in * case of error. */ parse_get_params (get_parms : string) : list [string * string] { def decode (str : string) { System.Web.HttpUtility.UrlDecode (str, System.Text.Encoding.UTF8) }; NArray.Fold (get_parms.Split (array ['&']), [], fun (parm, acc) { def (param, value, split_was_unique) = split_unique_at ('=', parm); unless (split_was_unique) throw System.ArgumentException ("invalid query string"); def param = decode (param); def value = decode (value); if (param.Length == 0) acc else (param, value) :: acc }) } /** * Retrieves the post data */ parse_post_data () : list [string * string] { def buf = array (50000); def count = System.Console.In.ReadBlock (buf, 0, buf.Length); parse_get_params (System.String (buf, 0, count)); } hashtable_of_assoc_list (l : list [string * string]) : Hashtable [string, string] { def ht = Hashtable (); List.Iter (l, ht.Set); ht } getenv (name : string) : string { def ret = System.Environment.GetEnvironmentVariable (name); if (ret == null) "" else ret } get_with_default (ht : Hashtable [string, string], key : string) : string { match (ht.Get (key)) { | Some (r) => r | None => "" } } mutable post : Hashtable [string, string]; mutable get : Hashtable [string, string]; mutable comment_rejected : bool; set_output_path () : void { def year = get_with_default (post, "year"); def day = get_with_default (post, "day"); regexp match (year + "/" + day) { | @"^[0-9]+/[A-Z][a-z][a-z]-[0-9]*$" => output_path = blog_root + "archive/" + year + "/" + day; output_url = blog_url + "archive/" + year + "/" + day; if (File.Exists (output_path + ".html")) {} else throw Exception ($"ENOENT: $year/$day ($output_path)") | _ => throw Exception ($"EINVAL: $year/$day") } } post_email () : void { def body = get_with_default (post, "body"); def ip = getenv ("REMOTE_ADDR"); def nick = get_with_default (post, "nick"); def m = System.Net.Mail.MailMessage ("comment notifier ", "feedback@nemerle.org"); m.Subject = if (comment_rejected) $ "Rejected comment by $nick ($ip)"; else $ "New comment by $nick ($ip)"; m.Body = $ "http://$(getenv(\"SERVER_NAME\"))$output_url.html\n\n$body\n"; System.Net.Mail.SmtpClient().Send (m) } quote (s : string) : string { s.Replace ("&", "&") .Replace ("<", "<") .Replace (">", ">") .Replace ("\r", "") .Replace ("\n", "
") } antyspam_pattern (mutable content : string) : bool { content = content.ToLower (); content.IndexOf ("href=") != -1 || content.IndexOf ("[url]http") != -1 || content.IndexOf ("[url=") != -1 || { mutable count = 0; mutable last = -1; while (last < content.Length && {last = content.IndexOf ("http", last + 1); last != -1 }) count++; count > 2 } } store_comment () : void { def body = quote (get_with_default (post, "body")); def ip = getenv ("REMOTE_ADDR"); def nick = quote (get_with_default (post, "nick")); if (antyspam_pattern (nick) || antyspam_pattern (body)) comment_rejected = true; else { def f = File.AppendText (output_path + ".body.html"); f.Write ($ "

From: $nick ($ip)

\n

$body

\n"); f.Close (); } } construct_output () : void { def out_f = File.Create (output_path + ".html"); def buf = array (10000); def copy (name) { def f = File.OpenRead (name); def loop () { def cnt = f.Read (buf, 0, buf.Length); if (cnt == 0) {} else { out_f.Write (buf, 0, cnt); loop () } } loop (); f.Close (); } copy (output_path + ".head.html"); copy (output_path + ".body.html"); copy (output_path + ".foot.html"); out_f.Close (); } public Main () : void { try { def qs = getenv ("QUERY_STRING"); get = hashtable_of_assoc_list (parse_get_params (qs)); post = hashtable_of_assoc_list (parse_post_data ()); set_output_path (); store_comment (); construct_output (); post_email (); if (comment_rejected) { print ("Content-type: text/plain\r\n\r\n" "We are very sorry but your comment have been rejected, because it seemed\r\n" "to contain embedded HTML, which is not allowed. Particularly the string\r\n" "href= is rejected.\r\n" "Please either hit Back and reformulate your comment, or go away if you\r\n" "are a robot.\r\n" "This crude filter was necessary because of the large volume of comment spam."); } else print ("Location: http://$(getenv(\"SERVER_NAME\"))$output_url.html\r\n\r\n"); } catch { e => printf ("Content-type: text/plain; encoding=iso-8859-1\n\n%s\n%s\n", e.ToString (), e.StackTrace) } } }