From e4ce000b326121ed185bb403abee3f2acdd5856e Mon Sep 17 00:00:00 2001 From: Gabor Adam Toth Date: Sun, 28 Feb 2010 17:44:02 +0100 Subject: [PATCH 1/4] added /place command for managing place settings like /set usage: /place [] key: e.g. topic, style, twitter, etc. --- world/net/library/signature.c | 19 +++++++++++++------ world/net/library/text.c | 10 ++++++++++ world/net/place/archetype.gen | 29 ++++++++++++++++++++++------- world/net/place/userthreads.c | 28 ++++++++++++++-------------- 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/world/net/library/signature.c b/world/net/library/signature.c index 7e9a709..99ad6e1 100644 --- a/world/net/library/signature.c +++ b/world/net/library/signature.c @@ -51,12 +51,19 @@ private volatile mapping _sigs = ([ "_request_nickname": ({ "_request_nick_local", 0, "_nick_local", "_INTERNAL_stuss" }), // the real thing, maybe? method inheritance could even lead to here // for all of the _request_set_something methods. good? bad? + "_request_place": ({ "_request_set", 0, "_key", "_value" }), "_request_set": ({ "_request_set", 0, "_key", "_value" }), // when called by _request_set(), value might be in _value "_request_set_masquerade": ({ "_request_masquerade", 0, "_flag_masquerade" }), "_request_set_owners": ({ "_request_owners", 0, "_list_owners" }), // _tab "_request_set_public": ({ "_request_public", 0, "_flag_public" }), "_request_set_style": ({ "_request_set_style", 0, "_uniform_style" }), + "_request_set_topic": ({ "_request_set_topic", 0, "_value" }), + "_request_topic": ({ "_request_set_topic", 0, "_value" }), + "_request_topi": ({ "_request_set_topic", 0, "_value" }), + "_request_top": ({ "_request_set_topic", 0, "_value" }), + "_request_to": ({ "_request_set_topic", 0, "_value" }), + "_request_t": ({ "_request_set_topic", 0, "_value" }), // "INTERNAL" METHODS // all of the following "fake" _request methods are just the psyced // way to handle command name variations and shortcuts. never use this @@ -92,15 +99,15 @@ private volatile mapping _sigs = ([ #ifdef _flag_enable_module_microblogging "_request_add": ({ "_request_add", 0, "_person" }), "_request_remove": ({ "_request_remove", 0, "_person" }), - "_request_priv": ({ "_request_privacy", 0, "_privacy" }), - "_request_privacy": ({ "_request_privacy", 0, "_privacy" }), + "_request_set_priv": ({ "_request_set_privacy", 0, "_value" }), + "_request_set_privacy": ({ "_request_set_privacy", 0, "_value" }), #ifdef TWITTER - "_request_tw": ({ "_request_twitter", 0, "_switch" }), - "_request_twitter": ({ "_request_twitter", 0, "_switch" }), + "_request_set_tw": ({ "_request_set_twitter", 0, "_value" }), + "_request_set_twitter": ({ "_request_set_twitter", 0, "_value" }), #endif #ifdef IDENTICA - "_request_ica": ({ "_request_identica", 0, "_switch" }), - "_request_identica": ({ "_request_identica", 0, "_switch" }), + "_request_set_ica": ({ "_request_set_identica", 0, "_value" }), + "_request_set_identica":({ "_request_set_identica", 0, "_value" }), #endif #endif #ifdef EXPERIMENTAL diff --git a/world/net/library/text.c b/world/net/library/text.c index 25e8b04..c1f3411 100644 --- a/world/net/library/text.c +++ b/world/net/library/text.c @@ -179,3 +179,13 @@ string x(string str, int n) { for (i = 0; i < n; i++) res += str; return res; } + +int is_true(string v) { + if (strlen(v) && regmatch(v, "^(?:on|yes|true|enabled|1)$")) return 1; + return 0; +} + +int is_false(string v) { + if (strlen(v) && regmatch(v, "^(?:off|no|false|disabled|0)$")) return 1; + return 0; +} diff --git a/world/net/place/archetype.gen b/world/net/place/archetype.gen index 4bb4935..60efc8d 100644 --- a/world/net/place/archetype.gen +++ b/world/net/place/archetype.gen @@ -462,6 +462,18 @@ showTopic(rcpt, verbose, mc) { ]) ); return 1; } + +_request_set_topic(source, mc, data, vars, b) { + string value = vars["_value"] || vars["_topic"]; + if (strlen(value)) { + if (value == "-") value = 0; + sTopic(value, source, vars["_nick"]); + } else { + showTopic(source, 1, OFFICIAL); + } + return 1; +} + #endif #if HAS_PORT(HTTP_PORT, HTTP_PATH) || HAS_PORT(HTTPS_PORT, HTTP_PATH) @@ -1838,6 +1850,7 @@ cmd(a, args, b, source, vars) { return 1; #endif #ifdef PLACE_TOPIC_COMMAND + // not used anymore, see _request_set_topic() instead case "topic": case "topi": case "top": @@ -2234,9 +2247,11 @@ _request_set(source, mc, data, vars, b) { PT(("set(%O, %O, %O, %O, %O) called\n", source, mc, data, vars, b)) if (k = vars["_key"]) // pass on to a subhandler - if (call_signature(source, "_request_set"+ k, data, vars, b)) + if (call_signature(source, "_request_set_"+ k, data, vars, b)) return 1; - return 0; + + sendmsg(source, "_warning_usage_place", "Usage: /place []"); + return 1; } #ifdef PLACE_OWNED @@ -2331,16 +2346,16 @@ _request_set_style(source, mc, data, vars, b) { vSet("_uniform_style", value); save(); } else if (value) { - sendmsg(source, + sendmsg(source, "_error_illegal_scheme", - "That is not a valid [_scheme] URL for a file.", + "That is not a valid [_scheme] URL for a file.", ([ "_scheme" : "http" ])); - return 1; + return 1; } sendmsg(source, "_status_place_uniform_style", - "Style file [_uniform_style] is in use here.", + "Style file [_uniform_style] is in use here.", ([ "_uniform_style" : - v("_uniform_style") || "-" ])); + v("_uniform_style") || "-" ])); return 1; } return 0; diff --git a/world/net/place/userthreads.c b/world/net/place/userthreads.c index 7c4d3d3..0c091a8 100644 --- a/world/net/place/userthreads.c +++ b/world/net/place/userthreads.c @@ -129,47 +129,47 @@ _request_remove(source, mc, data, vars, b) { // - public: anyone can enter _request_privacy(source, mc, data, vars, b) { P3((">> userthreads:_request_privace(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) - string p = vars["_privacy"]; - if (p == "public" || p == "private") { - vSet("privacy", p); + string value = vars["_value"]; + if (value == "public" || value == "private") { + vSet("privacy", value); save(); } - sendmsg(source, "_status_privacy", "Privacy is: [_privacy].", (["_privacy": v("privacy")])); + sendmsg(source, "_info_set_place_privacy", "Privacy is: [_value].", (["_value": v("privacy")])); return 1; } #ifdef TWITTER -_request_twitter(source, mc, data, vars, b) { - string sw = vars["_switch"]; - if (sw == "on" || sw == "enabled" || sw == "1") { +_request_set_twitter(source, mc, data, vars, b) { + string value = vars["_value"]; + if (is_true(value)) { unless (twitter) twitter = clone_object(NET_PATH "twitter/client")->load(source); vSet("twitter", 1); save(); - } else if (sw == "off" || sw == "disabled" || sw == "0") { + } else if (is_false(value)) { if (twitter) twitter = 0; vSet("twitter", 0); save(); } - sendmsg(source, "_status_twitter", "Twitter submission is [_status].", (["_status": v("twitter") ? "enabled" : "disabled"])); + sendmsg(source, "_info_set_place_twitter", "Twitter submission is [_status].", (["_status": v("twitter") ? "enabled" : "disabled"])); return 1; } #endif #ifdef IDENTICA -_request_identica(source, mc, data, vars, b) { - string sw = vars["_switch"]; - if (sw == "on" || sw == "enabled" || sw == "1") { +_request_set_identica(source, mc, data, vars, b) { + string value = vars["_value"]; + if (is_true(value)) { unless (identica) identica = clone_object(NET_PATH "identica/client")->load(source); vSet("identica", 1); save(); - } else if (sw == "off" || sw == "disabled" || sw == "0") { + } else if (is_false(value)) { if (identica) identica = 0; vSet("identica", 0); save(); } - sendmsg(source, "_status_identica", "Identi.ca submission is [_status].", (["_status": v("identica") ? "enabled" : "disabled"])); + sendmsg(source, "_info_set_place_identica", "Identi.ca submission is [_status].", (["_status": v("identica") ? "enabled" : "disabled"])); return 1; } #endif From 5f15785e9ec6f2f9efc220bbb8c7d013081b493d Mon Sep 17 00:00:00 2001 From: Gabor Adam Toth Date: Sun, 28 Feb 2010 23:38:35 +0100 Subject: [PATCH 2/4] http/server: added post support; http/login: set-cookie path=/ --- world/net/http/library.i | 8 ++++++++ world/net/http/login.c | 2 +- world/net/http/server.c | 36 +++++++++++++++++++++++++++++------- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/world/net/http/library.i b/world/net/http/library.i index 424734f..b3888ed 100644 --- a/world/net/http/library.i +++ b/world/net/http/library.i @@ -215,3 +215,11 @@ varargs string make_query_string(mapping params, int sort) { } return q; } + +checkToken(mapping query) { + string nick; + object user; + if (nick = query["user"]) user = find_person(nick); + if (user && user->validToken(query["token"])) return user; + return 0; +} diff --git a/world/net/http/login.c b/world/net/http/login.c index 6f2e817..cdeae5a 100644 --- a/world/net/http/login.c +++ b/world/net/http/login.c @@ -15,7 +15,7 @@ htget(prot, query, headers, qs) { t = "_error_invalid_authentication_token"; } else { PT(("replacing cookie %O\n", headers["cookie"])) - htok3(prot, 0, "Set-Cookie: psyced=\""+ qs +"\";\n"); + htok3(prot, 0, "Set-Cookie: psyced=\""+ qs +"\"; path=/;\n"); #if 1 // login was supposed to something more than just /surf // but until this is the case, why lose time? diff --git a/world/net/http/server.c b/world/net/http/server.c index dbff168..d9b8754 100644 --- a/world/net/http/server.c +++ b/world/net/http/server.c @@ -9,8 +9,9 @@ #include "header.i" -volatile string url, file, qs, version; +volatile string url, file, qs, version, method, body = ""; volatile mapping headers; +volatile int length; // we're using #'closures to point to the functions we're giving the // next_input_to(). as i don't want to restructure the whole file, i need @@ -19,6 +20,7 @@ volatile mapping headers; // quite stupid indeed, as they don't got any modifiers or whatever :) parse_url(input); parse_header(input); +parse_body(input); devNull(); qScheme() { return "html"; } @@ -67,14 +69,17 @@ parse_wait(null) { // waiting to send my error message here parse_url(input) { P3(("=== SmallHTTP got: %O\n", input)) - unless (sscanf(input, "GET%t%s%tHTTP/%s", url, version)) { - if (sscanf(input, "CONNECT%t%~s")) { + unless (sscanf(input, "%s%t%s%tHTTP/%s", method, url, version)) quit(); + switch (method) { + case "CONNECT": next_input_to(#'parse_wait); return; - } else { + case "GET": + case "POST": + break; + default: quit(); return; - } } version = "HTTP/" + version; @@ -95,11 +100,25 @@ parse_header(input) { next_input_to(#'parse_header); } else { - process(); - next_input_to(#'devNull); + if (method == "POST" && (length = to_int(headers["content-length"])) && + headers["content-type"] == "application/x-www-form-urlencoded") { + input_to(#'parse_body, INPUT_IGNORE_BANG | INPUT_CHARMODE | INPUT_NO_TELNET); + } else { + process(); + next_input_to(#'devNull); + } } } +parse_body(input) { + //P4(("parse_body(%O)\n", input)) + body += input; + if (strlen(body) == length) + process(); + else + input_to(#'parse_body, INPUT_IGNORE_BANG | INPUT_CHARMODE | INPUT_NO_TELNET); +} + process() { string t, ext; mapping query = ([]); @@ -139,6 +158,9 @@ process() { } else { file = url; } + if (method == "POST" && headers["content-type"] == "application/x-www-form-urlencoded") { + query = url_parse_query(query, body); + } P4(("parsed query: %O\n", query)) switch (file) { case "/favicon.ico": From 7ffe715010c6ed51fab227884b0dbbc8468cc3bb Mon Sep 17 00:00:00 2001 From: Gabor Adam Toth Date: Mon, 1 Mar 2010 03:50:58 +0100 Subject: [PATCH 3/4] place/threads: web posting & commenting, entry edit, new settings; place/archetype.gen: _request_set_default_(text|list|bool) place/threads new features: - add/comment forms after /login @ place url (cookie auth) - new settings: /place showform|showcomments|addaction|editaction --- world/net/library/signature.c | 44 ++-- world/net/place/archetype.gen | 73 +++++- world/net/place/threads.c | 409 +++++++++++++++++++--------------- world/net/place/userthreads.c | 39 ++-- world/net/storage.c | 4 + world/net/user.c | 11 +- world/static/examine.css | 46 +++- 7 files changed, 389 insertions(+), 237 deletions(-) diff --git a/world/net/library/signature.c b/world/net/library/signature.c index 99ad6e1..6cd78d8 100644 --- a/world/net/library/signature.c +++ b/world/net/library/signature.c @@ -80,35 +80,47 @@ private volatile mapping _sigs = ([ "_request_ni": ({ "_request_nick_local", 0, "_nick_local", "_INTERNAL_stuss" }), "_request_public": ({ "_request_public", 0, "_flag_public" }), "_request_pub": ({ "_request_public", 0, "_flag_public" }), - + // threads "_request_entries": ({ "_request_entries", 0, "_num" }), "_request_ents": ({ "_request_entries", 0, "_num" }), "_request_entry": ({ "_request_entry", 0, "_id" }), "_request_ent": ({ "_request_entry", 0, "_id" }), - "_request_comment": ({ "_request_comment", 0, "_id", "_text" }), - "_request_com": ({ "_request_comment", 0, "_id", "_text" }), - "_request_title": ({ "_request_title", 0, "_id", "_title" }), - "_request_addentry": ({ "_request_addentry", 0, "_text" }), - "_request_addent": ({ "_request_addentry", 0, "_text" }), - "_request_submit": ({ "_request_addentry", 0, "_text" }), - "_request_blog": ({ "_request_addentry", 0, "_text" }), - "_request_delentry": ({ "_request_delentry", 0, "_id" }), - "_request_delent": ({ "_request_delentry", 0, "_id" }), - "_request_unsubmit": ({ "_request_delentry", 0, "_id" }), - "_request_unblog": ({ "_request_delentry", 0, "_id" }), + "_request_entry_reply": ({ "_request_entry_reply", 0, "_parent", "_text" }), + "_request_comment": ({ "_request_entry_reply", 0, "_parent", "_text" }), + "_request_com": ({ "_request_entry_reply", 0, "_parent", "_text" }), + "_request_entry_add": ({ "_request_entry_add", 0, "_text" }), + "_request_addentry": ({ "_request_entry_add", 0, "_text" }), + "_request_addent": ({ "_request_entry_add", 0, "_text" }), + "_request_submit": ({ "_request_entry_add", 0, "_text" }), + "_request_blog": ({ "_request_entry_add", 0, "_text" }), + "_request_entry_del": ({ "_request_entry_del", 0, "_id" }), + "_request_delentry": ({ "_request_entry_del", 0, "_id" }), + "_request_delent": ({ "_request_entry_del", 0, "_id" }), + "_request_unsubmit": ({ "_request_entry_del", 0, "_id" }), + "_request_unblog": ({ "_request_entry_del", 0, "_id" }), + "_request_entry_edit": ({ "_request_entry_edit", 0, "_id", "_text" }), + "_request_editentry": ({ "_request_entry_edit", 0, "_id", "_text" }), + "_request_edentry": ({ "_request_entry_edit", 0, "_id", "_text" }), + "_request_edent": ({ "_request_entry_edit", 0, "_id", "_text" }), + "_request_set_addact": ({ "_request_set_addaction", 0, "_value" }), + "_request_set_addaction":({ "_request_set_addaction", 0, "_value" }), + "_request_set_editact": ({ "_request_set_editaction", 0, "_value" }), + "_request_set_editaction":({ "_request_set_editaction", 0, "_value" }), + "_request_set_showform":({ "_request_set_showform", 0, "_value" }), + "_request_set_showcomments":({ "_request_set_showcomments", 0, "_value" }), #ifdef _flag_enable_module_microblogging "_request_add": ({ "_request_add", 0, "_person" }), "_request_remove": ({ "_request_remove", 0, "_person" }), "_request_set_priv": ({ "_request_set_privacy", 0, "_value" }), "_request_set_privacy": ({ "_request_set_privacy", 0, "_value" }), -#ifdef TWITTER +# ifdef TWITTER "_request_set_tw": ({ "_request_set_twitter", 0, "_value" }), "_request_set_twitter": ({ "_request_set_twitter", 0, "_value" }), -#endif -#ifdef IDENTICA +# endif +# ifdef IDENTICA "_request_set_ica": ({ "_request_set_identica", 0, "_value" }), "_request_set_identica":({ "_request_set_identica", 0, "_value" }), -#endif +# endif #endif #ifdef EXPERIMENTAL // stuff to play around with diff --git a/world/net/place/archetype.gen b/world/net/place/archetype.gen index 60efc8d..e8b6c14 100644 --- a/world/net/place/archetype.gen +++ b/world/net/place/archetype.gen @@ -216,14 +216,6 @@ int qSaveImmediately() { #endif } -int qHistoryPersistentLimit() { - return _limit_amount_history_persistent; -} - -int qHistoryExportLimit() { - return _limit_amount_history_export; -} - // to be overloaded by place.gen qNewsfeed() { return 0; } // _request_list_feature uses this in *all* place objects, dont ifdef @@ -233,7 +225,9 @@ mayLog(mc) { return abbrev("_message", mc); } qHistory() { return v("log"); } -qHistoryGlimpse() { return DEFAULT_GLIMPSE; } +int qHistoryGlimpse() { return DEFAULT_GLIMPSE; } +int qHistoryPersistentLimit() { return _limit_amount_history_persistent; } +int qHistoryExportLimit() { return _limit_amount_history_export; } histClear(a, b, source, vars) { int l = logSize(); @@ -463,6 +457,7 @@ showTopic(rcpt, verbose, mc) { return 1; } +# ifdef SIGS _request_set_topic(source, mc, data, vars, b) { string value = vars["_value"] || vars["_topic"]; if (strlen(value)) { @@ -473,7 +468,7 @@ _request_set_topic(source, mc, data, vars, b) { } return 1; } - +# endif #endif #if HAS_PORT(HTTP_PORT, HTTP_PATH) || HAS_PORT(HTTPS_PORT, HTTP_PATH) @@ -2254,6 +2249,64 @@ _request_set(source, mc, data, vars, b) { return 1; } +_request_set_default_text(source, mc, data, vars, b) { + unless (qOwner(SNICKER)) return 0; + string key = vars["_key"]; + string value = vars["_value"]; + + if (strlen(value)) { + vSet(key, value); + save(); + } + + sendmsg(source, "_info_set_place_" + key, "Setting \"[_key_set]\" is \"[_value]\".", (["_key_set": key, "_value": v(key)])); + return 1; +} + +_request_set_default_list(source, mc, data, vars, b, list) { + unless (qOwner(SNICKER)) return 0; + string key = vars["_key"]; + string value; + + foreach (string item : list) { + if (item == vars["_value"]) { + value = vars["_value"]; + break; + } + } + + if (strlen(value)) { + vSet(key, value); + save(); + } else if (strlen(vars["_value"])) { + sendmsg(source, "_error_invalid_setting_value", + "Sorry, that is not a valid value for the [_key_set] setting. Valid values are: [_values].", + ([ "_key_set": key, "_values": implode(list, ", ") ]) ); + return 1; + } + + sendmsg(source, "_info_set_place_" + key, "Setting \"[_key_set]\" is \"[_value]\".", (["_key_set": key, "_value": v(key)])); + return 1; +} + +_request_set_default_bool(source, mc, data, vars, b) { + unless (qOwner(SNICKER)) return 0; + string key = vars["_key"]; + string value = vars["_value"]; + + if (is_true(value)) { + vSet(key, 1); + save(); + } else if (is_false(value)) { + vSet(key, 0); + save(); + } + + sendmsg(source, "_info_set_place_" + key, "Setting \"[_key_set]\" is [_value].", (["_key_set": key, "_value": v(key) ? "on" : "off"])); + return 1; +} + + #ifdef PLACE_OWNED _request_owners(source, mc, data, vars, b) { mixed t; diff --git a/world/net/place/threads.c b/world/net/place/threads.c index 22fe4dd..fa58d5b 100644 --- a/world/net/place/threads.c +++ b/world/net/place/threads.c @@ -15,24 +15,22 @@ inherit NET_PATH "place/owned"; -qHistoryPersistentLimit() { +int qHistoryPersistentLimit() { return 0; } -canPost(snicker) { - return qAide(snicker); -} - -canDeleteOwn(snicker) { - return qAide(snicker); -} - -canDeleteEverything(snicker) { - return qOwner(snicker); -} +canPost(snicker) { return qAide(snicker); } +canReply(snicker) { return qAide(snicker); } +canEditOwn(snicker) { return qAide(snicker); } +canEditAll(snicker) { return qOwner(snicker); } +canDeleteOwn(snicker) { return qAide(snicker); } +canDeleteAll(snicker) { return qOwner(snicker); } int mayLog(string mc) { - return abbrev("_notice_thread", mc) || abbrev("_message", mc); + if (abbrev("_notice_thread", mc)) + return regmatch(mc, "_edit\\b") ? 0 : 1; + + return abbrev("_message", mc); } int showWebLog() { @@ -51,6 +49,17 @@ create() { logSet(0, ({0, 0, 0, 0})); } +load(name, keep) { + int ret = ::load(name, keep); + + unless (v("addaction")) vSet("addaction", "adds"); + unless (v("editaction")) vSet("editaction", "edits"); + unless (vExist("showform")) vSet("showform", 1); + unless (vExist("showcomments")) vSet("showcomments", 1); + + return ret; +} + varargs array(mixed) entries(int limit, int offset, int reverse, int parent, int id) { P3((">> entries(%O, %O, %O)\n", limit, offset, parent)) array(mixed) entries = ({}), entry, children, child; @@ -90,54 +99,69 @@ varargs array(mixed) entry(int id) { return entries(0, 0, 0, 0, id); } -varargs int addEntry(mixed source, string snicker, string text, string title, int parent_id) { - P3((">> addEntry(%O, %O, %O, %O, %O)\n", source, snicker, text, title, parent_id)) - int id = logSize(); +varargs int addEntry(mixed source, mapping vars, string _data, string _mc) { + P3((">> addEntry(%O, %O, %O, %O)\n", source, vars, _data, _mc)) string mc = "_notice_thread_entry"; string data = "[_nick] [_action]: "; + vars["_id"] = logSize(); + vars["_action"] ||= v("addaction"); + // this should only be set after a reply + m_delete(vars, "_children"); - mapping vars = ([ - "_id": id, - "_text": text, - "_nick": snicker, - "_action": "adds", //TODO: add a /set'ting for it, or find a better name - ]); - - if (parent_id) { - P3((">>> parent_id: %O\n", parent_id)) + if (vars["_parent"]) { array(mixed) parent; - unless (parent = logPick(parent_id)) return 0; - P3((">>> parent: %O\n", parent)) + vars["_parent"] = to_int(vars["_parent"]); + unless (parent = logPick(vars["_parent"])) return 0; + PT((">>> parent: %O\n", parent)) unless (parent[LOG_VARS]["_children"]) parent[LOG_VARS]["_children"] = ({ }); - parent[LOG_VARS]["_children"] += ({ id }); - save(); + parent[LOG_VARS]["_children"] += ({ vars["_id"] }); mc += "_reply"; data = member(parent[LOG_VARS], "_title") ? "[_nick] [_action] in reply to #[_parent] ([_parent_title]): " : - "[_nick] [_action] in reply to #[_parent]: ", - vars += ([ "_parent": parent_id ]); + "[_nick] [_action] in reply to #[_parent]: "; } - if (title && strlen(title)) { - vars += ([ "_title": title ]); + if (strlen(vars["_title"])) { data += "[_title]\n[_text]"; } else { data += "[_text]"; } - data += " (#[_id] in [_nick_place])"; + if (_mc) mc += _mc; + if (_data) data = _data; + castmsg(source, mc, data, vars); return 1; } +int editEntry(mixed source, mapping vars, string data) { + P3((">> editEntry(%O, %O, %O)\n", source, vars, data)) + array(mixed) entry; + vars["_id"] = to_int(vars["_id"]); + unless (entry = logPick(vars["_id"])) return 0; + + string unick; + unless (canEditAll(SNICKER)) + unless (canEditOwn(SNICKER) && lower_case(psyc_name(source)) == lower_case(entry[LOG_SOURCE][LOG_SOURCE_UNI])) + return 0; + + if (strlen(data)) entry[LOG_DATA] = data; + foreach (string key : vars) + if (key != "_children") entry[LOG_VARS][key] = vars[key]; + + save(); + castmsg(source, entry[LOG_MC] + "_edit", entry[LOG_DATA], vars + ([ "_action": v("editaction") ])); + return 1; +} + int delEntry(int id, mixed source, mapping vars) { array(mixed) entry; unless (entry = logPick(id)) return 0; string unick; - unless (canDeleteEverything(SNICKER)) + unless (canDeleteAll(SNICKER)) unless (canDeleteOwn(SNICKER) && lower_case(psyc_name(source)) == lower_case(entry[LOG_SOURCE][LOG_SOURCE_UNI])) return 0; @@ -155,8 +179,8 @@ sendEntries(mixed source, array(mixed) entries, int level) { PT(("entry: %O\n", entry)) vars = entry[LOG_VARS]; sendmsg(source, regreplace(entry[LOG_MC], "^_notice", "_list", 1), - "[_indent][_nick]: " + (vars["_title"] ? "[_title]\n" : "") + "[_text] (#[_id])", - vars + ([ "_level": level, "_indent": x(" ", level) ])); + "[_indent][_nick]: "+ (vars["_title"] ? "[_title]\n" : "") +"[_text] (#[_id])", + vars + ([ "_level": level, "_indent": x(" ", level), "_postfix_time_log": 1 ])); if (sizeof(entry) >= LOG_CHILDREN + 1) sendEntries(source, entry[LOG_CHILDREN], level + 1); n++; } @@ -181,46 +205,58 @@ _request_entry(source, mc, data, vars, b) { sendmsg(source, "_error_thread_invalid_entry", "#[_id]: no such entry", (["_id": id])); } - return 1; } -_request_addentry(source, mc, data, vars, b) { +_request_entry_add(source, mc, data, vars, b) { P3((">> _request_addentry(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) unless (canPost(SNICKER)) return 0; unless (vars["_text"] && strlen(vars["_text"])) { - sendmsg(source, "_warning_usage_addentry", + sendmsg(source, "_warning_usage_entry_add", "Usage: /addentry ", ([ ])); return 1; } - addEntry(source, SNICKER, vars["_text"], vars["_title"]); + addEntry(source, vars, data); return 1; } -_request_comment(source, mc, data, vars, b) { - P3((">> _request_comment(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) - unless (vars["_id"] && strlen(vars["_id"]) && - vars["_text"] && strlen(vars["_text"])) { - sendmsg(source, "_warning_usage_reply", +_request_entry_reply(source, mc, data, vars, b) { + P3((">> _request_entry_reply(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + unless (canReply(SNICKER)) return 0; + unless (vars["_parent"] && strlen(vars["_text"])) { + sendmsg(source, "_warning_usage_entry_reply", "Usage: /comment ", ([ ])); return 1; } - int id = to_int(vars["_id"]); - string snicker = SNICKER; - P3((">>> id: %O, vars: %O\n", id, vars)); - unless (addEntry(source, snicker, vars["_text"], vars["_title"], id)) + unless (addEntry(source, vars)) sendmsg(source, "_error_thread_invalid_entry", - "#[_id]: no such entry", (["_id": id])); + "#[_id]: no such entry", (["_id": vars["_id"]])); return 1; } -_request_delentry(source, mc, data, vars, b) { - P3((">> _request_delentry(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) - unless (canPost(SNICKER)) return 0; +_request_entry_edit(source, mc, data, vars, b) { + P3((">> _request_title(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + unless (canEditOwn(SNICKER)) return 0; unless (vars["_id"] && strlen(vars["_id"])) { - sendmsg(source, "_warning_usage_delentry", + sendmsg(source, "_warning_usage_entry_edit", + "Usage: /editentry ", ([ ])); + return 1; + } + + unless (editEntry(source, vars)) + sendmsg(source, "_error_thread_invalid_entry", + "#[_id]: no such entry", (["_id": vars["_id"]])); + + return 1; +} + +_request_entry_del(source, mc, data, vars, b) { + P3((">> _request_entry_del(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + unless (canPost(SNICKER)) return 0; + unless (vars["_id"]) { + sendmsg(source, "_warning_usage_entry_del", "Usage: /delentry ", ([ ])); return 1; } @@ -236,23 +272,21 @@ _request_delentry(source, mc, data, vars, b) { return 1; } -#if 0 -_request_title(source, mc, data, vars, b) { - P3((">> _request_title(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) - unless (vars["_id"] && strlen(vars["_id"])) { - sendmsg(source, "_warning_usage_title", - "Usage: /title ", ([ ])); - return 1; - } - - int id = to_int(vars["_id"]); - unless (setTitle(id, vars["_title"])) - sendmsg(source, "_error_thread_invalid_entry", - "#[_id]: no such entry", (["_id": id])); - - return 1; +_request_set_addaction(source, mc, data, vars, b) { + return _request_set_default_text(source, mc, data, vars, b); +} + +_request_set_editaction(source, mc, data, vars, b) { + return _request_set_default_text(source, mc, data, vars, b); +} + +_request_set_showcomments(source, mc, data, vars, b) { + return _request_set_default_bool(source, mc, data, vars, b); +} + +_request_set_showform(source, mc, data, vars, b) { + return _request_set_default_bool(source, mc, data, vars, b); } -#endif msg(source, mc, data, vars){ P3(("thread:msg(%O, %O, %O, %O)", source, mc, data, vars)) @@ -269,28 +303,49 @@ msg(source, mc, data, vars){ return ::msg(source, mc, data, vars); } -varargs string htmlComments(array(mixed) entries, int level) { +varargs array(mixed) htmlComments(array(mixed) entries, int submit, int level, int n) { + array(mixed) ret; mapping entry, vars; string ht = "", style; foreach(entry : entries) { vars = entry[LOG_VARS]; - style = level ? "style='padding-left: " + level + "em'" : ""; - ht += "<div class='comment' title='" + isotime(ctime(vars["_time_place"]), 1) + "' " + style + "><span class='comment-author'>" + vars["_nick"] + "</span>: <span class='comment-text'>" + htquote(vars["_text"], 1) + "</span></div>\n"; - if (sizeof(entry) >= LOG_CHILDREN + 1) ht += htmlComments(entry[LOG_CHILDREN], level + 1); + style = level ? "style='padding-left: "+ level +"em'" : ""; + ht += "<div class='comment' title='"+ isotime(ctime(vars["_time_place"]), 1) +"' "+ style +"><span class='comment-author'>"+ vars["_nick"] +"</span>: <span class='comment-text'>" + (submit ? "<a class='comment-form-toggle' onclick='toggleForm(this.parentNode, "+ vars["_id"] +")'>»</a>" : "")+ htquote(vars["_text"], 1) +"</span></div>\n"; + n++; + if (sizeof(entry) >= LOG_CHILDREN + 1) { + ret = htmlComments(entry[LOG_CHILDREN], submit, level + 1, n); + ht += ret[0]; + n = ret[1]; + } } - return ht; + return ({ ht, n }); } -varargs string htmlEntries(array(mixed) entries, int nojs, string chan, string submit, string url_prefix) { - P3((">> threads:htmlentries(%O, %O, %O, %O, %O)\n", entries, nojs, chan, submit, url_prefix)) +varargs string htmlEntries(array(mixed) entries, int submit, int show_comments, int nojs, string chan, string submit_target, string url_prefix) { + P3((">> threads:htmlentries(%O, %O, %O, %O, %O, %O)\n", entries, submit, nojs, chan, submit_target, url_prefix)) string text, ht = ""; - string id_prefix = chan ? chan + "-" : ""; + string id_prefix = chan ? chan +"-" : ""; unless (url_prefix) url_prefix = ""; unless (nojs) ht += "<script type='text/javascript'>\n" - "function toggle(e) { if (typeof e == 'string') e = document.getElementById(e); e.className = e.className.match('hidden') ? e.className.replace(/ *hidden/, '') : e.className + ' hidden'; }\n" + "function $(id) { return document.getElementById(id); }" + "function toggle(e) { if (!e) return 0; if (typeof e == 'string') e = $(e); e.className = e.className.match('hidden') ? e.className.replace(/ *hidden/, '') : e.className + ' hidden'; return 1; }\n" + "function toggleForm(e, id) { toggle(e.nextSibling) || e.parentNode.appendChild($('entry-form')) && ($('entry-form').className=''); $('form-parent').value = id }" "</script>\n"; + if (submit) ht += + "<form id='entry-form' class='hidden' action='"+ url_prefix +"'"+ + (0 && submit_target + //FIXME: cmd is executed twice, because after a set-cookie it's parsed again + ? "onsubmit=\"cmd('comment '+ $('form-parent').value +' '+ this.previousSibling.value, '"+ submit_target +"')\"" + : "method='post'") + + ">" + "<input type='hidden' name='request' value='post' />" + "<input type='hidden' id='form-parent' name='_parent' value='' />" + "<textarea name='_text' autocomplete='off'></textarea>" + "<input type='submit' value='Send'>" + "</form>"; + mapping entry, vars; foreach (entry : entries) { P3((">>> entry: %O\n", entry)) @@ -298,41 +353,64 @@ varargs string htmlEntries(array(mixed) entries, int nojs, string chan, string s text = htquote(vars["_text"], 1); - string comments = ""; - if (sizeof(entry) >= LOG_CHILDREN + 1) comments = htmlComments(entry[LOG_CHILDREN]); + array(mixed) comments = ({ "", 0 }); + if (sizeof(entry) >= LOG_CHILDREN + 1) comments = htmlComments(entry[LOG_CHILDREN], submit); ht += "<div class='entry'>\n" "<div class='header'>\n" - "<a href=\"" + url_prefix + "?id=" + vars["_id"] + "\">" - "<span class='id'>#" + vars["_id"] + "</span> - \n" - "<span class='author'>" + vars["_nick"] + "</span>\n" + "<a href=\""+ url_prefix +"?id="+ vars["_id"] +"\">" + "<span class='id'>#"+ vars["_id"] +"</span> - \n" + "<span class='author'>"+ vars["_nick"] +"</span>\n" + (vars["_title"] && strlen(vars["_title"]) ? " - " : "") + - "<span class='title'>" + htquote(vars["_title"] || "") + "</span>\n" + "<span class='title'>"+ htquote(vars["_title"] || "") +"</span>\n" "</a>" "</div>\n" "<div class='body'>\n" - "<div class='text'>" + text + "</div>\n" - "<div id='comments-" + id_prefix + vars["_id"] + "' class='comments'>" + comments + - (submit && strlen(submit) ? - "<a onclick=\"toggle(this.nextSibling)\">» reply</a>" - "<div class='comment-submit hidden'>" - "<textarea autocomplete='off'></textarea>" - //FIXME: cmd is executed twice, because after a set-cookie it's parsed again - "<input type='button' value='Send' onclick=\"cmd('comment " + vars["_id"] + " '+ this.previousSibling.value, '" + submit + "')\">" - "</div>" : "") + - "</div>\n" + "<div class='text'>"+ text +"</div>\n"+ + (show_comments ? + "<div id='comments-"+ id_prefix + vars["_id"] +"' class='comments'>"+ comments[0] + + (submit ? "<a onclick=\"toggleForm(this, "+ vars["_id"] +")\">» reply</a>" : "") + + "</div>\n" : "") + "</div>\n" "<div class='footer'>\n" - "<span class='date'>" + isotime(ctime(vars["_time_place"]), 1) + "</span>\n" + "<span class='date'>"+ isotime(ctime(vars["_time_place"]), 1) +"</span>\n" "<span class='comments-link'>" - "<a onclick=\"toggle('comments-" + id_prefix + vars["_id"] + "')\">" + sizeof(vars["_children"]) + " comments</a>" + "<a " + + (show_comments + ? "onclick=\"toggle('comments-"+ id_prefix + vars["_id"] +"')\"" + : "href='"+ url_prefix +"?id="+ vars["_id"] +"''") + + ">"+ comments[1] +" comments</a>" "</span>\n" "</div>\n" "</div>\n"; } P3((">>> ht: %O\n", ht)) - return "<div class='threads'>" + ht + "</div>"; + return "<div class='threads'>"+ ht +"</div>"; +} + +string htmlForm(int link, int title) { + if (link) + return + "<div class='threads'><a class='add-entry' href='?request=form'>Add entry</a></div>\n"; + return + "<div class='threads'>" + "<div class='entry entry-form'>\n" + "<div class='header'>Add entry</div>\n" + "<div class='body'>\n" + "<form method='post'>\n" + "<input type='hidden' name='request' value='post' />\n" + + (title ? + "Title:<br><input type='text' name='_title'><br>\n" + "Text:<br/>" : "") + + "<textarea name='_text'></textarea>\n" + "<input type='submit' value='Send' />\n" + "</form>\n" + "</div>\n" + "<div class='footer'>\n" + "</div>\n" + "</div>\n" + "</div>\n"; } // TODO: fix markup, not displayed correctly (in firefox at least) @@ -354,10 +432,10 @@ string rssEntries(array(mixed) entries) { rss += "\n<item>\n" "\t<title>"+ (vars["_title"] || "no title") +"\n" - "\thttp://" + HTTP_OR_HTTPS_URL + "/" + pathName() + "?id=" + vars["_id"] + "\n" - "\t" + vars["_text"] + "\n" - "\t" + isotime(ctime(vars["_time_place"]), 1) + "\n" - "\t" + vars["_nick"] + "\n" + "\thttp://"+ HTTP_OR_HTTPS_URL +"/"+ pathName() + "?id="+ vars["_id"] +"\n" + "\t"+ vars["_text"] +"\n" + "\t"+ isotime(ctime(vars["_time_place"]), 1) +"\n" + "\t"+ vars["_nick"] +"\n" "\n"; } @@ -379,14 +457,14 @@ string jsEntries(array(mixed) entries) { mapping entry, vars; foreach (entry : entries) { vars = entry[LOG_VARS]; - js += "new Entry(" + vars["_id"] + "," - "\"" + vars["_title"] + "\"," - "\"" + vars["_nick"] + "\"," - + isotime(ctime(vars["_time_place"]), 1) + "," - "\"" + vars["_text"] + "\"),\n"; + js += "new Entry("+ vars["_id"] +"," + "\""+ vars["_title"] +"\"," + "\""+ vars["_nick"] +"\"," + + isotime(ctime(vars["_time_place"]), 1) +"," + "\""+ vars["_text"] +"\"),\n"; } - return js[..<3] + ");"; + return js[..<3] +");"; } varargs string jsonEntries(int limit, int offset) { @@ -405,20 +483,24 @@ varargs void rssExport(int limit, int offset) { write(rssEntries(entries(limit, offset, 1))); } -varargs string htMain(int limit, int offset, string chan) { - return htmlEntries(entries(limit, offset, 1), 0, chan); +varargs string htMain(int limit, int offset, int submit, string chan) { + return htmlEntries(entries(limit, offset, 1), submit, v("showcomments"), 0, chan); } -varargs void displayMain(int limit, int offset) { - write(htMain(limit, offset)); +varargs void displayMain(int limit, int offset, int submit) { + write(htMain(limit, offset, submit)); } -string htEntry(int id) { - return htmlEntries(entry(id)); +void displayForm(int link, int title) { + write(htmlForm(link, title)); } -void displayEntry(int id) { - write(htEntry(id) || "No such entry."); +string htEntry(int id, int submit) { + return htmlEntries(entry(id), submit, 1); +} + +void displayEntry(int id, int submit) { + write(htEntry(id, submit) || "No such entry."); } // wir können zwei strategien fahren.. die technisch einfachere ist es @@ -430,10 +512,14 @@ void displayEntry(int id) { // halten. womöglich kann man auch nachträglich plan A in plan B // umwandeln..... hmmm -lynX // -void displayHeader() { +void displayHeader(string class) { w("_HTML_head_threads", - "\n"+ - "\n\n"); + "" + "" + ""+ MYNICK +"" + "\n"+ + "\n" + "

"+ MYNICK +"

\n"); } void displayFooter() { w("_HTML_tail_threads", ""); @@ -443,6 +529,7 @@ htget(prot, query, headers, qs, data) { mapping entrymap; mixed target; string nick; + object user; int a; int limit = to_int(query["limit"]) || DEFAULT_BACKLOG; int offset = to_int(query["offset"]); @@ -454,14 +541,14 @@ htget(prot, query, headers, qs, data) { if (query["id"]) { htok(prot); // kommentare + urspruengliche Nachricht anzeigen - displayHeader(); - displayEntry(to_int(query["id"])); + displayHeader("entry"); + displayEntry(to_int(query["id"]), checkToken(query) ? 1 : 0); #if 0 // eingabeformular ohne betreff - write("
\n" + write("\n" "\n" "PSYC Uni:
\n" - "\n" + "\n" "
\n" "\n" "
\n"); @@ -472,61 +559,32 @@ htget(prot, query, headers, qs, data) { return 1; } - // formularbehandlung - if (query["request"] == "post" && query["uni"]) { + if (query["request"] == "post") { htok(prot); - /* - sendmsg uni -> _request_authentication mit thread und text drin - dann auf die antwort warten die nen vars mapping mit thread + text hat wieder - */ - if (nick = legal_name(target = query["uni"])) { - target = summon_person(nick); - nick = target->qNick(); - } else { - nick = target; - // write("Hello " + query["uni"] + "
\n"); - // write("Remote auth doesn't work yet. TODO!!!\n"); - // return 1; + + // TODO: remote user auth + unless (user = checkToken(query)) { + write("Not authenticated!\n"); + return 1; } -#ifdef OWNED - if (canPost(nick)) { -#endif -#if 0 - sendmsg(target, "_request_authentication", "please auth me!", - (["_host_IP" : query_ip_number(), - "_blog_thread" : query["thread"], - "_blog_text" : query["text"] ])); - write("your submit is avaiting authentication by " + query["uni"] + "
\n"); -#endif // 0 - if (target->checkAuthentication(ME, ([ "_host_IP" : query_ip_number() ]) ) > 0) { - // check ob reply auf irgendwas ist... - if (query["reply"]) { - addComment(query["text"], query["uni"], to_int(query["reply"])); - } else { - addEntry(query["text"], query["uni"], query["thread"]); - } - write("authentication successful!\n"); - } else { - write("not authenticated!\n"); - } -#ifdef OWNED - } else { + unless (canPost(query["user"])) { write("You are not owner or aide of this place.\n"); + return 1; } -#endif - return 1; + + object vars = ([]); + // add query params beginning with _ as vars + foreach (string key, string value : query) + if (abbrev("_", key)) vars[key] = value; + + vars["_nick"] = user->qName(); + addEntry(user, vars); } // neuen Eintrag verfassen if (query["request"] == "form") { htok(prot); - displayHeader(); - write("
\n" - "\n" - "PSYC Identity:

\n" - "Thread:

\n" - "Text:
\n
\n" - "\n" - "
\n"); + displayHeader("entry-add"); + displayForm(0, 1); displayFooter(); return 1; } @@ -552,12 +610,13 @@ htget(prot, query, headers, qs, data) { } else { // normaler Export //P2(("all entries: %O\n", _thread)) - htok3(prot, "text/html", "Cache-Control: no-cache\n"); - displayHeader(); + htok3(prot, "text/html", "Cache-Control: no-cache\n"); + displayHeader("entries"); + if ((user = checkToken(query)) && canPost(user->qName())) + displayForm(!v("showform")); // display the blog - displayMain(limit, offset); + displayMain(limit, offset, checkToken(query) ? 1 : 0); // display the chatlog - if (showWebLog()) logView(a < 24 ? a : 12, "html", 15); displayFooter(); } diff --git a/world/net/place/userthreads.c b/world/net/place/userthreads.c index 0c091a8..d7907dc 100644 --- a/world/net/place/userthreads.c +++ b/world/net/place/userthreads.c @@ -24,17 +24,19 @@ volatile object identica; load(name, keep) { P3((">> userthreads:load(%O, %O)\n", name, keep)) + int ret = ::load(name, keep); sscanf(name, "~%s#%s", owner, channel); - vSet("owners", ([ lower_case(owner) ])); - vSet("privacy", "private"); - vSet("twitter", 0); - vSet("identica", 0); + + unless (v("owners")) vSet("owners", ([ lower_case(owner) ])); + unless (v("privacy")) vSet("privacy", "private"); + unless (vExist("twitter")) vSet("twitter", 0); + unless (vExist("identica")) vSet("identica", 0); vSet("_restrict_invitation", BLAME); vSet("_filter_conversation", BLAME); - return ::load(name, keep); + return ret; } enter(source, mc, data, vars) { @@ -127,19 +129,13 @@ _request_remove(source, mc, data, vars, b) { // set privacy: private or public // - private: only friends & invited people can enter (default) // - public: anyone can enter -_request_privacy(source, mc, data, vars, b) { - P3((">> userthreads:_request_privace(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) - string value = vars["_value"]; - if (value == "public" || value == "private") { - vSet("privacy", value); - save(); - } - sendmsg(source, "_info_set_place_privacy", "Privacy is: [_value].", (["_value": v("privacy")])); - return 1; +_request_set_privacy(source, mc, data, vars, b) { + return _request_set_default_list(source, mc, data, vars, b, ({"public", "private"})); } #ifdef TWITTER _request_set_twitter(source, mc, data, vars, b) { + unless (qOwner(SNICKER)) return 0; string value = vars["_value"]; if (is_true(value)) { unless (twitter) twitter = clone_object(NET_PATH "twitter/client")->load(source); @@ -158,6 +154,7 @@ _request_set_twitter(source, mc, data, vars, b) { #ifdef IDENTICA _request_set_identica(source, mc, data, vars, b) { + unless (qOwner(SNICKER)) return 0; string value = vars["_value"]; if (is_true(value)) { unless (identica) identica = clone_object(NET_PATH "identica/client")->load(source); @@ -174,21 +171,21 @@ _request_set_identica(source, mc, data, vars, b) { } #endif -varargs int addEntry(mixed source, string snicker, string text, string title, int parent_id) { +varargs int addEntry(mixed source, mapping vars, string _data, string _mc) { int ret; - if (ret = ::addEntry(source, snicker, text, title, parent_id)) { + if (ret = ::addEntry(source, vars, _data, _mc)) { #ifdef TWITTER - if (v("twitter") && twitter) twitter->status_update(text); + if (v("twitter") && twitter) twitter->status_update(vars["_text"]); #endif #ifdef IDENTICA - if (v("identica") && identica) identica->status_update(text); + if (v("identica") && identica) identica->status_update(vars["_text"]); #endif } return ret; } -htMain(int limit, int offset) { - return ::htMain(limit, offset, channel); +htMain(int limit, int offset, int submit) { + return ::htMain(limit, offset, submit, channel); } canPost(snicker) { @@ -207,7 +204,7 @@ qChannel() { return channel; } -qHistoryGlimpse() { +int qHistoryGlimpse() { return HISTORY_GLIMPSE; } diff --git a/world/net/storage.c b/world/net/storage.c index 94ac052..65deb70 100644 --- a/world/net/storage.c +++ b/world/net/storage.c @@ -68,6 +68,10 @@ vInc(k, howmuch) { } // vDec(k) { _v[k]--; } +vExist(k) { + return member(_v, k); +} + vEmpty() { return sizeof(_v) == 0; } vSize() { return sizeof(_v); } diff --git a/world/net/user.c b/world/net/user.c index 6aa7295..e7f2bde 100644 --- a/world/net/user.c +++ b/world/net/user.c @@ -319,7 +319,7 @@ htDescription(anonymous, query, headers, qs, variant, vars) { + (first ? "class='selected'" : "") + ">#" + channel + "
\n"; contents += "
" + threads->htmlEntries(entries, !first, channel, anonymous ? "" : vars["_identification"] + "#" + channel, vars["_profile_url"] + "/" + channel) + "
\n"; + + (first ? "class='selected'" : "") + ">" + threads->htmlEntries(entries, 1, 1, !first, channel, anonymous ? "" : vars["_identification"] + "#" + channel, vars["_profile_url"] + "/" + channel) + "\n"; first = 0; } @@ -1322,7 +1322,7 @@ w(string mc, string data, mapping vars, mixed source, int showingLog) { else t = vars["_time_log"] || vars["_time_place"]; // would be nicer to have _time_log in /log rather than showingLog if (!t && showingLog) t = vars["_time_INTERNAL"]; - if (t && intp(t)) di["_prefix"] = time_or_date(t) +" "; + if (t && intp(t)) di["_prefix"] = time_or_date(t); #if 0 template = T(di["_method"] || mc, 0); #else @@ -1448,7 +1448,12 @@ w(string mc, string data, mapping vars, mixed source, int showingLog) { // who is that output for anyway? output = psyctext(template, vars, data, source); - if (di["_prefix"]) output = di["_prefix"]+output; + if (di["_prefix"]) { + if (vars["_postfix_time_log"]) + output += " " + di["_prefix"]; + else + output = di["_prefix"] +" "+ output; + } if (output) { if (template == "") { diff --git a/world/static/examine.css b/world/static/examine.css index 40e919d..3a0d1e2 100644 --- a/world/static/examine.css +++ b/world/static/examine.css @@ -19,7 +19,7 @@ body.threads, padding: 4; border: 1px solid #f33; } -.comment-submit textarea, .comment-submit input, +.entry textarea, .entry input, .Pe form input,select,textarea { background: black; color: #f33; @@ -74,6 +74,7 @@ body.threads, width: 400; } +a.add-entry, .entry { margin: 22px 44px; } @@ -102,33 +103,54 @@ body.threads, margin: 0 1em; } -.entry .body .comment-submit input, -.entry .body .comment-submit textarea { +.entry .body .comment-form-toggle { + float: right; +} + +.entry input, +.entry textarea { font-size: 12px; } -.entry .body .comment-submit textarea { +.entry input[type=text], +.entry textarea { width: 100%; } +body.entry-add textarea { + height: 40em; +} + +.entry form { + margin: 0.2em 0; + padding: 0; +} + +h1 a, +.entry a { + cursor: pointer; + text-decoration: none; +} +.entry a:hover { + text-decoration: underline; +} + .entry .header a { color: black; } .entry .header .author {} .entry .header .title {} +.entry .body a { + color: #f33; +} + +h1 a, +a.add-entry, .entry .footer a, .entry .footer a:visited { color: white; } -.entry a { - cursor: pointer; - text-decoration: none; -} -.entry a:hover { - text-decoration: underline; -} - .entry .footer .comments-link { float: right; } From 4ff296fff13299c7c7bd5d9edd653a01acc24f2f Mon Sep 17 00:00:00 2001 From: Gabor Adam Toth Date: Mon, 1 Mar 2010 16:37:22 +0100 Subject: [PATCH 4/4] accept.c: moved text.c bit above "%s#%s.c" so it would work for ~user#channels; place/threads: user auth check --- world/drivers/ldmud/master/accept.c | 12 +- world/net/place/#archetype.gen# | 2720 +++++++++++++++++++++++++++ world/net/place/threads.c | 21 +- 3 files changed, 2734 insertions(+), 19 deletions(-) create mode 100644 world/net/place/#archetype.gen# diff --git a/world/drivers/ldmud/master/accept.c b/world/drivers/ldmud/master/accept.c index 880c8ff..523881c 100644 --- a/world/drivers/ldmud/master/accept.c +++ b/world/drivers/ldmud/master/accept.c @@ -384,12 +384,6 @@ object compile_object(string file) { return rob; } # endif - if (sscanf(file, "%s/text.c", path) && path != "") { - rob = clone_object(NET_PATH "text"); - rob -> sPath(path); - D2(if (rob) PP(("DB CLONED: %O becomes %s/text\n", rob, path));) - return rob; - } if (sscanf(file, "place/%s.c", name) && name != "") { #ifdef SANDBOX string t; @@ -441,6 +435,12 @@ object compile_object(string file) { #endif return rob; } + if (sscanf(file, "%s/text.c", path) && path != "") { + rob = clone_object(NET_PATH "text"); + rob -> sPath(path); + D2(if (rob) PP(("DB CLONED: %O becomes %s/text\n", rob, path));) + return rob; + } if (sscanf(file, "%s#%s.c", path, name) && name != "") { unless (name = SIMUL_EFUN_FILE->legal_name(name)) return (object)0; diff --git a/world/net/place/#archetype.gen# b/world/net/place/#archetype.gen# new file mode 100644 index 0000000..1414867 --- /dev/null +++ b/world/net/place/#archetype.gen# @@ -0,0 +1,2720 @@ +// vim:foldmethod=marker:syntax=lpc:noexpandtab +// $Id: archetype.gen,v 1.85 2008/11/29 08:24:10 lynx Exp $ +// +// the meta-generator that contains all sorts of PSYC room code and with +// a few defines and an include generates cloneable room implementations. +// inheritance was not the answer for plugging together the best of room +// behaviours. not even multiple inheritance. + +// psyced rooms store members by object:nick or UNI:nick, nick just being +// an optimization. but don't we really want to have UNI:object or UNI:UNL ...? +// should we use an array as value for the member mapping? this kind of +// discussion belongs into CHANGESTODO anyway, so go read there. + +// the "default" configuration for dynamically instantiated places +#ifdef DEFAULT +// here is an alphabetic list of selectors available for archetype includers. +// some of them commented out when not useful for the default case. +// some simply unfinished/unusable as yet. this file is also used by +// archetype.pl to find out which selectors exist. it uses the letters +// in square brackets for its model filenames. the + was meant to indicate +// things that every model should define, but that's in ESSENTIALS now. +// +//# define PLACE_FILTERS // [+] provide /silence for public rooms +# define PLACE_HISTORY // [h] provide history functionality +# define PLACE_HISTORY_SWITCH // TODO: history off by default +//# define PLACE_HISTORY_EXPORT // [e] export history to websites +# define PLACE_SCRATCHPAD // [s] provide a scratchpad via web +//# define PLACE_LOGGING // provide /logging for public rooms +# define PLACE_MASQUERADE // [m] provide a local /nick command +//# define PLACE_MASQUERADE_COMMAND // provide a /masq admin command +//# define PLACE_MASQUERADE_SECRET -- see FIXME first +//# define PLACE_MAY_HISTORY // provide a MAY_HISTORY hook +//# define PLACE_NEWSFEED -- be a newsfeed.. TODO +//# define PLACE_OWNED // [o] owners and aides logic +# define PLACE_PUBLIC_COMMAND // provide a /public admin command +# define PLACE_STORE_COMMAND // [+] provide a /store command +# define PLACE_STYLE_COMMAND // [+] owners can define /style sheet +# define PLACE_TOPIC // [+] provide a topic function +# define PLACE_TOPIC_COMMAND // [+] provide a /topic command +//# define PLACE_TOPIC_FOR_ALL // allow anybody to set the topic +#endif + +// things that every archetype auto-model gets. +// the rest is #define'able via place.gen +#ifdef ESSENTIALS +# define PLACE_FILTERS // [+] provide /silence for public rooms +# define PLACE_STORE_COMMAND // [+] provide a /store command +# define PLACE_STYLE_COMMAND // [+] owners can define /style sheet +# define PLACE_TOPIC // [+] provide a topic function +# define PLACE_TOPIC_COMMAND // [+] provide a /topic command +#endif + +// the former "basic" place that older place code still inherits +#ifdef BASIC +// this recreates the functionality of the former net/place/basic +// which is still being inherited by some special applications +# define PLACE_TOPIC // provide a topic function +# define PLACE_TOPIC_COMMAND // provide a /topic command +#endif + +// some words about signatures, hooks, method overloading and macros +// are waiting for you to read at.. +// http://about.psyc.eu/Create_place#Message_flow_in_psyced_places +// and a MAY_HISTORY hook is just hanging around in this file waiting +// for you to be seen as an example on how to extract code into its +// own hook. pretty simple really. in fact, before writing these +// words and making this my official plan I considered several other +// architectural plans on how to make efficient extensible places. +// first the one which is in place now. the following paragraphs discuss +// that and are essentially deadly boring and mostly a note to myself. +// so stop reading this. anyway, here goes: +// +// the design that i took uses a single table of known signatures for the +// entire system, since signatures are meant to be constant information +// at all times. yeah it could even be implemented in some preprocessor +// form so that signature unpacking actually happens in the code. but for +// now it's in the call_signature() library function. +// +// behaviour extensions are solved by the handler mentioned in the signature, +// thus the hook calls and inheritance requirement. the function table lookup +// of a string function name is slightly costy - in exchange we don't have +// baggage closure mappings in the objects. one more advantage is, that after +// several other thoughts i figured out that what looks like chaos at first +// glance (this file really does) is actually close to optimal. hooks only +// where necessary, and more than just before/after if necessary (as in a +// complex operation such as _request_enter), and macros as an abstraction +// layer to provide a consistent interface to the place writer. +// +// and these were the alternatives considered: +// +// 1. have before and after hooks (only?) also in the signatures, which +// however need to be called by function table lookup in each object no +// matter if it actually makes use of them: very inefficient. +// +// 2. have all behaviour filters generated by place.gen, even the default +// ones. like every place with a /topic command would be compiled with yet +// another copy of the same bytecode which tells it who is in charge of +// changing the topic. very memory unfriendly. +// +// 3. another option was to put before/after hooks into big switches around +// the signature call. this means several switch lookups additional to the +// signature lookup and you can't access the unpacked variables in the before +// part unless you split signature unpacking from calling the handler. so +// you even have to put the before and after switches into their own functions +// or it will be impossible to extend them. wah. +// +// 4. each place keeps a data structure which describes its signatures +// together with closures for handlers, before and after hooks (ppp +// style). this is cool for extensibility as you don't need inheritance +// to extend functionality (but why would you want to do that in LPC?), +// but the trade-off is larger memory consumption in large installations +// with large amounts of identical place programs, each of them carrying +// a signature table along even though it was supposed to be constant. +// may be worth it anyhow. yeah it could have been an ok alternative. +// but the advantages just don't make sense in the LPC environment. +// also it is limited to before/after hooks unless you make the structure +// complex enough to carry unlimited hooks (ppp doesn't and tries to cope). + +// local debug messages - turn them on by using psyclpc -DDplace= +#ifdef Dplace +# undef DEBUG +# define DEBUG Dplace +#endif + +#include +#include +#include +#include +#include + +#ifdef GAMMA +# define ECHOES +#endif + +#ifdef PLACE_HISTORY +# include // inherits textc + +inherit NET_PATH "lastlog"; + +# ifndef _limit_amount_history_persistent +# define _limit_amount_history_persistent 444 +# endif + +# ifndef _limit_amount_history_export +# define _limit_amount_history_export 24 +# endif + +# define DEFAULT_GLIMPSE 3 +# define MAX_SIMPLE_HISTCLEAR 100 + +volatile int _histChange; +#endif // PLACE_HISTORY + +// internal (was basic.c) +#define MEMBER(UNIFORM) member(_u, UNIFORM) +#define DATA_FILE(name) (DATA_PATH "place/" + lower_case(name)) + +inherit NET_PATH "group/master"; + +#ifdef PERSISTENT_MASTERS +mapping _u; +#endif + +// used by new /reload +members(recover) { + if (recover) { + ASSERT("members() recovering", mappingp(recover), recover) + PT(("%O recovering members %O\n", ME, recover)) + _u = recover; + } + return _u; +} + +#ifdef PLACE_OWNED +volatile mapping banned; + +// sometimes overridden by place.gen +mixed qOwners() { return 0; } +#endif + +#ifdef PLACE_SCRATCHPAD +volatile mapping used, given; // used/given scratchpad IDs +#endif + +#ifdef PLACE_MASQUERADE +// +// OPTIMIZE: the new !DONT_REWRITE_NICKS code serves the purpose of enabling +// local nicks according to jabber MUC. the code may also be used by NICKS +// that is: instead of having an extra snames mapping we could put local +// nicks into our _u[uniform] value field. but this is just a quick thought +// and may not be viable after all, have to look at this closer ... TODO +// +protected volatile mapping snames; +// +# ifdef PLACE_MASQUERADE_COMMAND +qMasquerade() { + ASSERT("qMasquerade called", 1==0, 0) + return 1; +} +# else +qMasquerade() { return v("masquerade"); } +# endif +#endif + +#ifdef PLACE_LOGGING +private volatile string _logfile; + +qLogging() { return v("logging"); } +#endif + +int qSaveImmediately() { +#if defined(SAVE_LOG_IMMEDIATELY) || defined(_flag_save_place_log_immediately) + return 1; +#else + return 0; +#endif +} + +// to be overloaded by place.gen +qNewsfeed() { return 0; } +// _request_list_feature uses this in *all* place objects, dont ifdef + +#ifdef PLACE_HISTORY +mayLog(mc) { return abbrev("_message", mc); } + +qHistory() { return v("log"); } + +int qHistoryGlimpse() { return DEFAULT_GLIMPSE; } +int qHistoryPersistentLimit() { return _limit_amount_history_persistent; } +int qHistoryExportLimit() { return _limit_amount_history_export; } + +histClear(a, b, source, vars) { + int l = logSize(); + + if (a == "") a = 0; + if (!a && l > MAX_SIMPLE_HISTCLEAR) { + sendmsg(source, "_warning_usage_histclear", + "The history is [_amount_history] entries long. " + "Usage: /histclear «amount»", + ([ "_amount_history" : l ])); + return 1; + } + logClear(a); + save(); + sendmsg(source, "_echo_place_history_cleared", + "The history has been reduced from [_amount_history_old] " + "entries to [_amount_history].", + // the "0" is needed for remote delivery by PSYC + // i wonder if i should change psyc/parse instead + // or if this is the correct way to indicate that + // the variable is zero, not removed + ([ "_amount_history" : logSize() || "0", + "_amount_history_old" : l || "0", + "_amount_removed" : a ])); + return 1; +} + +msgView(source, mc, data, vars, showingLog) { + string fmt; + +# ifdef PLACE_HISTORY_EXPORT +# if HAS_PORT(HTTP_PORT, HTTP_PATH) || HAS_PORT(HTTPS_PORT, HTTP_PATH) + // 'ecmascript' output is effectively JSON, but more than that, + // since it is intended for output as html, it uses html quoting + // rather than json quoting, and quotes all html characters on + // top of just quoting javascript's own. also, since we only + // output strings, we can leave out all the other special cases + // of make_json(). all in all this function may belong into json.c + // as a special case - make_htjson(). + // + if (showingLog == "ecmascript" || showingLog == "html") { + string s, *t; +// s = psyctext( T("x"+mc, ""), vars, data, source ); +// first we render the message into a default plain text string + s = psyctext( T(mc, ""), vars, data, source ); + if (strlen(s) < 7) return; + s = s[..<2]; +// htquote ist nicht so gründlich wie für javascript notwendig + s = regreplace(s, "\&", "\\\&", 1); + s = regreplace(s, "<", "\\\<", 1); + s = regreplace(s, ">", "\\\>", 1); + s = regreplace(s, "\"", "\\\"", 1); + s = regreplace(s, "\n|\\\\", " ", 1); +// unless (sscanf(s, "%s: %s", s, tx) == 2) return; +// then we parse the plain text string to seperate nick from +// action and text. this isn't very elegant really. + t = regexplode (s, " [a-z_1-9]*: "); +// under funny circumstances it may match more than once + if (sizeof(t) > 3) t[2] = implode(t[2..], ""); +// and when "en_g" is set as default language, the output may not +// have an action at all + else if (sizeof(t) < 3) { + t = ({ 0, "", 0 }); + unless(sscanf(s, "%s %s", t[0], t[2])) { + P1(("%O could not reparse %O for %s\n", ME, s, + showingLog)) + return; + } + } + P3(("%O becomes %O\n", s, t)) +// t = regreplace(s, ": ", "\", \"", 1); +// if (s == t) return; + if (showingLog == "html") +// fmt = "(%s) %s %s: %s
\n"; + fmt = stringp(t[2]) ? "
%s %s %s %s
\n" : "
%s %s %s
\n"; + else // showingLog == "ecmascript" + fmt = "\t%O, %O, %O, %O,\n"; + + if (vars["_action_possessive"]) + printf(fmt, isotime(vars["_time_place"] + || vars["_time_INTERNAL"] || 4404, 0), + t[0]+"s", vars["_action_possessive"], t[2] + ); + else printf(fmt, isotime(vars["_time_place"] + || vars["_time_INTERNAL"] || 4404, 0), + t[0], vars["_action"] || t[1][1..<3], t[2] + ); + } else +# endif +# endif + { +# if 0 //def BRAIN + // this is just because old storage files on fly + // contain _context's in their history data + // you can delete this after 2004 + m_delete(vars, "_context"); + // showing history is a unicast operation + // and may not contain a _context + // .. then again, isn't _context necessary to calculate + // the message id? okay we don't have msg ids yet.. +# endif + // and we don't wanna fake the origin of the message either.. +# ifdef UNSAFE_LASTLOG + if (source) { +# endif + // stow away the original source, then send + // ME as source, so that clients can figure + // out in which frame to display this + vars["_source_relay"] = source; + // UNIFORM(source) ? + // storing psyc_name for local object looks neat + // but actually messes with history display logic. + // if it's a local object the _nick is enough anyway +# ifdef UNSAFE_LASTLOG + } +# endif + source = ME; +# if 1 + // yes history is not multicast, but when retransmitting + // earlier multicasts you are supposed to provide + // both _target and _context + // see http://about.psyc.eu/Routing#Unicast_Message_for_State_Signaling + // mark this message as a retransmission from this context + vars["_INTERNAL_context"] = ME; // vars["_context"] = ME; +# endif +# ifdef VARS_IS_SACRED + // in this case net/irc takes care of protecting vars + sendmsg(showingLog, mc, data, vars, source, 1); +# else + // since castmsg is not being used we have to protect + // our history database by copying the vars mapping + sendmsg(showingLog, mc, data, copy(vars), source, 1); +# endif + } +} + +# ifdef PLACE_HISTORY_EXPORT +# ifdef TIME_HISTORY_EXPORT_RELOAD +# ifndef TIME_HISTORY_EXPORT_RELOAD_PARANOID +# define TIME_HISTORY_EXPORT_RELOAD_PARANOID 12 +# endif + +volatile int lastTime; +volatile string lastIP; +# endif +# endif +#endif /* PLACE_HISTORY */ + +#ifdef PLACE_TOPIC +// this reminds you of public.c +qTopic() { return v("topic"); } + +#ifdef PLACE_TOPIC_COMMAND +sTopic(text, source, nick) { + unless (nick) return; +#ifndef PLACE_TOPIC_FOR_ALL +# ifdef PLACE_OWNED +# define OFFICIAL 0 + if (!qAide(objectp(source) ? nick : source)) { + sendmsg(source, "_error_unavailable_function_place_topic", + "This function is not available to you here."); + return 1; + } +# else +# ifdef PLACE_PUBLIC_COMMAND +# define OFFICIAL 0 + if (qPublic()) if (!boss(source) && stringp(v("topic-user")) + && lower_case(nick) != lower_case(v("topic-user"))) { + sendmsg(source, "_error_unavailable_function_place_topic", + "This function is not available to you here."); + return; + } +# else +# define OFFICIAL "_official" +//# echo It's official! + unless (boss(source) > 59) return; +# endif +# endif +#else +# define OFFICIAL 0 +#endif + if (stringp(text) && strlen(text)) { + vSet("topic", text); + vSet("topic-user", nick); + vSet("topic-time", time()); + // TODO: no support for _official.. yet + castmsg(ME, "_notice_place_topic", + "New topic in [_nick_place] by [_nick]: [_topic]", + ([ "_nick" : v("topic-user"), + "_topic" : v("topic"), + "_nick_place" : MYNICK ]) ); + } else if (v("topic")) { + // TODO: no support for _official.. again + castmsg(ME, "_notice_place_topic_removed", + "[_nick] removes [_nick_topic]'s topic: [_topic]", + ([ "_nick": nick, + "_nick_topic": v("topic-user"), + "_topic": v("topic"), + "_nick_place" : MYNICK ]) ); + vDel("topic"); + vDel("topic-user"); + } + save(); + return v("topic"); +} +#endif + +showTopic(rcpt, verbose, mc) { + P4(("showTopic to %O (%O)\n", rcpt, mc)) + if (v("topic")) sendmsg(rcpt, mc + ? "_status_place_topic"+ mc + : "_status_place_topic", + "Topic by [_nick]: [_topic]", + ([ "_nick" : v("topic-user"), + // '123456789' is a dummy for old place data + // that has no topic timestamp + "_time_place" : v("topic-time") || 123456789, + // this one is used by IRC + "_INTERNAL_time_topic" : v("topic-time") || 123456789, + "_topic" : v("topic"), + "_nick_place" : MYNICK, + ]) ); + else if (verbose) sendmsg(rcpt, "_status_place_topic_none", + "No topic is set in [_nick_place].", ([ + "_nick_place" : MYNICK, + ]) ); + return 1; +} + +# ifdef SIGS +_request_set_topic(source, mc, data, vars, b) { + string value = vars["_value"] || vars["_topic"]; + if (strlen(value)) { + if (value == "-") value = 0; + sTopic(value, source, vars["_nick"]); + } else { + showTopic(source, 1, OFFICIAL); + } + return 1; +} +# endif +#endif + +#if HAS_PORT(HTTP_PORT, HTTP_PATH) || HAS_PORT(HTTPS_PORT, HTTP_PATH) +htget(prot, query, headers, qs, data, noprocess, authed) { + //P3((">> archetype.gen:htget(%O, %O, %O, %O, %O, %O)\n", prot, query, headers, qs, data, noprocess)) +# ifdef PLACE_SCRATCHPAD + sTextPath(query["layout"] || MYNICK, query["lang"], "html"); + if (!noprocess && (!qs || query["scratchpad"])) { + string rand, buf, len; +// microscopic wiki functionality .. cheap imitation of tobi's save.pike +// +// things one could add: +// 1. advertise URL in /st and change notice +// 2. call it v("notepad") and volatile scratchpad +// so that we can choose if something is persistent or not +// 3. authentication / compare ip with locations of people in room or schergen +// 4. implement locking !? or history !??? +// 5. have multiple scratchpads per key in a mapping. like: +// /place/group?scratchpad&key=goodmood +// 6. autogenerate numeric keys 1,2,3.. +// 7. have a garbage collection for old scratchpads? +// + htok(prot); + //unless (v("key")) vSet("key", ([])); <- next evolutionary step + // if someone pushes send button after 24h, he might block + // someone elses id in the rare case that RANDHEXSTRING + // reprovided the specific id to someone else who didn't send + // his changes first.. well. might not happen. + // TODO: prefix id with epoch? + if (stringp(query["scratchpad"]) + && (len = strlen(query["scratchpad"])) > 1 +// +// id logic serves the purpose of not changing the scratchpad while going +// "back" in the browser history. only a new "post" should change it. +// it could have been an incremental counter, but then you would have to +// recopy the contents of your scratchpad if someone else posted in the +// meantime. it's a question of taste. probably simpler and better. +// + && (!stringp(query["id"]) + || !member(used, query["id"]))) { +#ifdef __LDMUD__ + // problem apparently caused by ldmud input charset logic + buf = replace(query["scratchpad"], "\r", ""); +#else + buf = query["scratchpad"]; +#endif + if (buf != v("scratchpad")) { + vSet("scratchpad", buf); + m_add(used, query["id"]); +// some people bookmark the page after saving, thus they resubmit the used +// id days later.. don't know what to do about that, but this is not helpful: +// call_out(#'m_delete, 86400 /* 24 hours */, +// used, query["id"]); + + castmsg(ME, "_notice_update_scratchpad", 0, ([ + "_size_scratchpad": len, +#ifdef PRO_PATH // __TLS__ + "_page_scratchpad": + ((tls_available() && HTTPS_URL) || HTTP_URL) +#else + // problem with POST over https apparently.. + "_page_scratchpad": (HTTP_URL) +#endif + // TODO: use psycName() here instead + +"/@"+ MYNICK +"?scratchpad" + ])); + } + } + + while (member(given, rand = time() + "z" + RANDHEXSTRING)); + m_add(given, rand); + // notTODO: time() monotonically increases, so .. holding the + // entries for four seconds only might not be sufficient if + // time changes to past. + // anyway, collisions (and timechanges to past) are quite + // unlikely, especially contemporaneous, so we don't need to + // hurry with this. + call_out(#'m_delete, 4, given, rand); + // the whole idea of rands returning the same value while + // two people request this page within the same two seconds + // is a bit paranoid really. this function would probably + // work fine without a "given" mapping. well saga is a + // perfectionist and we usually love him for that ;) + // + // FIXME would be more useful to never delete given tokens so we + // can figure out when a request is coming in with a token + // that has never been given out (older than last restart, + // bookmarked months ago etc). given & used could be + // optimized to be the same mapping with state machine values. + + w("_PAGES_group_scratchpad", 0, + ([ "_scratchpad" : htquote(v("scratchpad") || ""), + "_id" : rand, + //"_group_nick" : MYNICK, -- why? + "_nick_place" : MYNICK ]) ); + //noprocess = 2; + return 1; + } +# endif +# ifdef PLACE_HISTORY_EXPORT + int a; + + PT(("%O inspection %O %O\n", ME, query, headers)) +# ifdef TIME_HISTORY_EXPORT_RELOAD + a = time() - lastTime; + if (a < TIME_HISTORY_EXPORT_RELOAD_PARANOID) return 1; + if (a < TIME_HISTORY_EXPORT_RELOAD && lastIP == query_ip_number()) + return 1; + lastTime = time(); + lastIP = query_ip_number(); +# endif + // limit this to "?format=js", as planned before + unless (noprocess) { + if (query["amount"]) { + sscanf(query["amount"], "%d", a); + a = a < qHistoryExportLimit() ? a : qHistoryExportLimit(); + P4(("%O amount is %O\n", ME, a)) + } + switch(query["format"]) { + case "js": + sTextPath(0, query["lang"], "html"); + htok3(prot, "application/x-javascript", + "Cache-Control: no-cache\n"); + write("\ndocument."+ (query["name"] || MYLOWERNICK) + + " = new Array(\n"); + logView(a, "ecmascript", 15); + write(" \""+ implode(names(), ", ")+"\"" +#if defined(ALPHA) && defined(PLACE_TOPIC) + // this adds current topic to data structure + // but probably breaks all psyclog.js code + ",\n\t\""+ v("topic") +"\"" +#endif + ")\n\n"); + break; +#if 0 + case "json": + sTextPath(0, query["lang"], "html"); + htok3(prot, "application/x-javascript", // probably incorrect + "Cache-Control: no-cache\n"); + logView(a, "json", 15); + // this calls msgView which should probably create a buffer + // data structure that can be output with make_json.. + break; +#endif + default: + sTextPath(query["layout"] || MYNICK, query["lang"], "html"); + htok(prot); + // w("_HTML_head"); + write("\n\n"); + logView(a, "html", 15); + write("
"); + // write("\n\n"); + // w("_HTML_tail"); + break; + } + } +# ifndef _flag_disable_notice_place_examine_web + if (query["from"] == "") query["from"] = 0; + if (query["location"] == "") query["location"] = 0; + + // should be renamed into _notice_examine_web_place + castmsg(ME, "_notice_place_examine_web", "[_nick_place] inspected on [_web_on] coming from [_web_from].", + ([ "_web_referrer" : query["from"] || "bookmark", + "_web_page" : query["location"] || headers["referer"] || "", + "_web_browser" : headers["user-agent"] || "", + "_web_on" : query["location"] || headers["referer"] || + headers["user-agent"] || "", + "_web_from" : query["from"] || + // query_ip_name(this_interactive()) || + headers["user-agent"] || "http", + "_host_name" : query_ip_name(this_interactive()) || "", + "_nick_place" : MYNICK || "This place", + ]) ); +# endif + + // this is usually not very interesting really.. like: + // refer= http://euroclash.com jsloc= http://euroclash.com/ + // + D1( if (query["location"] && headers["referer"] && + headers["referer"] != query["location"]) + D(S("Interesting: refer= %s jsloc= %s\n", + headers["referer"], query["location"])); + ) +# else +# ifdef T + //sTextPath(query["layout"], query["lang"], "html"); + unless (noprocess) { + htok(prot); + w("_HTML_head"); + } + w("_PAGES_place_default", + "You are looking at the [_nick_place] default page.\n", + ([ "_nick_place" : MYNICK ]) ); + unless (noprocess) w("_HTML_tail"); +# else + write("\n\nYou are looking at the "+MYNICK+" default page.\n"); +# endif +# endif + return 1; +} +#endif + +mixed isValidRelay(mixed x) { return x == ME; } + +// not in use anywhere +isMember(source, origin) { return MEMBER(source); } + +#ifndef QUIET_REMOTE_MEMBERS +castPresence(source, mc, data, vars, filterpresence) { + P3(("castpresence vars %O to %O\n", vars, source)) + unless(filterpresence) castmsg(source, mc, data, vars); + // vars["_source"] is the link we are on + // we have to implement that in slave so it will forward the + // _notice_place_enter to _source_relay + // hmmm.. should this be changed to _INTERNAL_origin? TODO + sendmsg(vars["_source"], mc, data, + vars + ([ "_source_relay" : source ])); +} +// #else +// # echo QUIET_REMOTE_MEMBERS is activated. +#endif + +// actually insert the new member into our data structures +// (happens after all checks have passed) +insert_member(source, mc, data, vars, ni, neu, again) { + unless (ni) { + P0(("insert_member w/out nick for %O in %O.\n", source, ME)) + // try to make the ugly three trailing parameters optional + ni = vars["_nick"]; + neu = !MEMBER(source); // don't output the status again + again = 0; + } +#ifdef PLACE_MASQUERADE + if (vars["_nick_local"]) snames[source] = vars["_nick_local"]; +#endif + //mc = "_notice_place_enter"; + // should this be _source_nick and _context_nick instead? +// unless (objectp(source)) +// data = "[_nick] enters [_nick_place]."; + data = "[_nick] enters [_nick_place]."; // TODO + vars["_nick_place"] = MYNICK; + + P4(("%O: %O is %O (%O)\n", ME, source, neu, v("_filter_presence"))) + // We ALWAYS do castmsg() before adding the user to + // our members, otherwise the logic on recipient side + // is much higher + if (neu) { + // TODO: wir wollen eigentlich nicht, dass alle + // vars da durchgehen... _amount_history + // z.b. auch nicht + unless (v("_filter_presence")) { +#ifdef PERSISTENT_SLAVES + revision += 1; +#endif + castmsg(source, mc, data, copy(vars)); + } + // we used to call insert_member() even for people who are + // already in.. i guess it was a bug!! + ::insert_member(source, vars["_INTERNAL_origin"]); + } + // this was before insert_member(), but shouldn't make a difference + // + // makeMembers() used to be at this point, but I changed that + // to simplify the user.c logic +#ifdef DONT_REWRITE_NICKS + _u[ source ] = ni; +#else + // not sure if this can be a security problem. should it + // be so, we need to use an _INTERNAL_nick_local instead. + // then again, if any jabberer can propose his nick here, + // we might aswell allow it for anybody.. + _u[ source ] = vars["_nick_local"] || ni; +#endif +#ifdef ENTER_MEMBERS + // this used to be before adding the new member. if there's a + // reason to put it back there, then we need to teach user.c + // to add itself to _list_members before calling the w()'s + unless(again) vars += makeMembers()[1]; +#endif + // experimental: let the clients know this is a non-speaking room. + // see also conferencing.pml for a more elaborate scheme for + // conference control. this is just something nice to start with. + if (v("_filter_conversation")) vars["_control"] = "_silent"; + // stylesheet support + if (v("_uniform_style")) + vars["_uniform_style"] = v("_uniform_style"); + // we set it on all requests instead +// if (v("topic")) vars["_topic"] = v("topic"); +#ifdef PLACE_OWNED + int nuowna = 0; + + // NEW: if OWNERS have not been provided by place.gen + // we'll make the first guy who walks in our owner. + unless (v("owners")) { + vSet("owners", ([ lower_case(SNICKER): source ])); + // don't send _warning_place_duty_owner + // before acquitting enter operation.. + vars["_duty"] = "owner"; // _owner_new ? + nuowna = 1; + } else { + int level = qAide(SNICKER); + + if (level) vars["_duty"] = + level > 2 ? "owner" : "administrator"; + else vars["_duty"] = "member"; + } +#else +# if 0 //def MODERATED +// this code is ignored, since MODERATED is never defined here +// schoen waers wenn das hier auch funktionieren wuerd +// [x] inheritance aware preprocessor now <- illogical wish ;) +// what you want here is to turn off inheritance and use code +// generation by include. that's what we do in place.gen, but +// to avoid code explosion we use inheritance here. simply put +// this code into place.gen and it works as you intend it. ok, not +// *that* simple - you have to put it into the appropriate hook. +// which in this case is enter() probably. TODO + vars["_duty"] = "none"; + // role of "moderator" upon entering? +# else + // only this code runs + vars["_duty"] = "member"; + // why not _role instead of _duty? +# endif +#endif // OWNED + // we are also echoing his vars back to the requestor + // that should normally be harmless and even useful + // yes.. in fact we handle _nick_local like that + P4(("sending _echo for %O with vars %O to %O\n", + mc, vars, source)) + // is it okay to remap mc here, or should i use a temp var? + // semantically it looks ok + mc = "_echo" + mc[7..]; + //if (v("_filter_conversation")) mc += "_filtered"; + m_delete(vars, "_group"); // why are we merging vars from a _do anyway? + sendmsg(source, mc, data, vars); + // ensure that a client knows this room has been entered + // before any room details like topic etc. are sent + // + // this used to have an unless(quiet), but why not show + // the status in public rooms with _filter_presence on? + // but what we still can't do is output this twice for + // repeating joins, in particular because jabber cannot + // distinguish a new join from just a change in presence + // (going away or returning from idle looks just like a join) + // so we now make sure the user is already in. should we + // need further distinction, then we will have to append + // a _jabber to the mc just to take care of this jabberish + // shortcoming. + // + // wait.. THIS is the new 'quiet' + // newsfeeds and other non-talking rooms do not need to be + // visible as channels in any user interface + if (neu &&! v("_filter_conversation")) + onEnter(source, mc, data, vars); +#ifdef PLACE_OWNED + if (nuowna) + sendmsg(source, "_warning_place_duty_owner", + "You are the new owner of [_nick_place].", + ([ "_nick_place" : MYNICK ])); +#endif // OWNED +} + +private leaveMsg(ni, isError, source, mc, data, vars) { + mixed origin; + // a direct psyc connection can be its own origin + if (isError && vars && (origin = vars["_INTERNAL_origin"] || source)) { + mixed m, n, server, t; + mapping abandon = ([]); + + // member list clean up logic. + // + // this stuff mentally belongs to group/master, but + // since it doesn't apply for persons, we leave it here. + + if (objectp(origin)) + server = origin->qOrigin() || source; + else + server = origin; + + P3(("%O: errleave(%O, %O, %O, %O, %O, %O) origin %O\n", + ME, ni, isError, source, mc, data, vars, origin)) + + // a previous message from the pushback may already + // have removed the subtree + unless (t = _routes[server]) return 0; + +#ifdef MEMBERS_BY_SOURCE +# ifdef TIDILY + if (mappingp(t)) { + P2(("Tidily looking for netburps for %O via %O in %O\n", + server, origin, ME)) + // does foreach really need a copy() here? + each (m, copy(t)) { + abandon[m] = _u[m]; + remove_member(m, server); + } + } + else +# endif + if (intp(t)) { + P2(("Messily looking for netburps for %O via %O in %O\n", + server, origin, ME)) + // does foreach really need a copy() here? + mapeach(m, n, copy(_u)) if (stringp(m) +# if 1 //def we_dont_care_about_virtual_hosts + && abbrev(server, m) +# else // could run both for maximum paranoia + && find_target_handler(m) == origin +# endif + ) { + abandon[m] = n; + remove_member(m, server); + } + } + if (sizeof(abandon)) { +# if DEBUG == 1 + if (t && t != sizeof(abandon) && intp(t)) { + P1(("Netburp in %O: supposed to delete %O people coming from %O, but found %O\n", + ME, t, server, abandon)) + } +# else + P2(("Netburp in %O: %O people coming from %O, found %O\n", + ME, t, server, abandon)) +# endif + mapeach (m, n, abandon) { + castmsg(m, "_notice_place_leave_netburp", + "Lost route to [_nick].", + ([ "_nick" : n ])); + } + } else +#endif + monitor_report("_notice_forward"+mc, + "could not handle the error coming from "+ + to_string(source)); + return 1; + } + // should use object, not nickname... old story +#ifndef QUIET_REMOTE_MEMBERS + if (isValidRelay(source) + || isValidRelay(vars["_source_relay"])) { + // we have to do sendmsg first... daher kein castPresence().. doof! + sendmsg(vars["_source"], mc, data, vars + ([ "_source_relay" : source ])); + unless(v("_filter_presence")) castmsg(source, mc, data, vars); + } else unless (objectp(source) || is_formal(source)) { + return; + } +#endif + unless (MEMBER(source)) { + if (isError) error(source, mc, data, vars); + // probably room got reloaded .. or person first left the + // place, then unsubscribed.. just let them leave + // _notice not _echo? was: _failure_invalid_place_membership + else if (mc == "_notice_place_leave_subscribe") +#ifndef ECHOES + sendmsg(source, mc, +#else /* ECHOES */ + sendmsg(source, "_echo_place_leave_subscribe", +#endif /* ECHOES */ + "You unsubscribe [_nick_place]. Bye bye.", + ([ "_nick_place" : MYNICK, + "_nick" : vars["_nick"] ]) ); + // careful: detected some recursions here.. + // and.. _echo shouldnt be used for enter/leave meguesses + //else if (mc != "_echo_place_leave_invalid") + else if (mc != "_notice_place_leave_invalid") + // couldn't it be a real _warning ? +#ifndef ECHOES + sendmsg(source, "_notice_place_leave_invalid", +#else /* ECHOES */ + sendmsg(source, "_echo_place_leave_invalid", +#endif /* ECHOES */ + "[_nick_place] doesn't remember seeing you anyway.", + ([ "_nick_place" : MYNICK, + "_nick_local" : vars["_nick_local"], + "_nick" : vars["_nick"] ]) ); + return 1; + } + // unless (member(_u, vars["_nick"])) return 1; + // unless (_u[who] == previous_object() || + // find_person(who) == previous_object()) return 0; + + //mc = "_notice_place_leave"; + data = "[_nick] leaves [_nick_place]."; // TODO + vars["_nick_place"] = MYNICK; +#ifdef PERSISTENT_SLAVES + revision += 1; +#endif + // this code doesn't really consider the presence of _tag. + // a _tag should make it unicast an _echo, remove the member + // from the tree, then multicast the notice to other people. + // and since we don't leave without a _tag in most cases, we + // don't need any other behaviour really. the idea that we can + // optimize away the echo isn't really pragmatic.. TODO. + // we may also simplify the context specification then, huh? + // +#ifdef EXPERIMENTAL + // first send tagged echo to the person leaving + unless(isError) sendmsg(source, mc, data, vars+ ([ "_source_relay" : source ])); + // remove her from the context + remove_member(source, vars["_INTERNAL_origin"]); + // let everybody else know about the loss + unless(isError || v("_filter_presence")) castmsg(source, mc, data, vars); +#else + unless(isError) { + if (v("_filter_presence")) { + // arent you a little heavy on the relays here? i dont get it + sendmsg(source, mc, data, vars+ ([ "_source_relay" : source ])); + } + else castmsg(source, mc, data, vars); + } + remove_member(source, vars["_INTERNAL_origin"]); +#endif + // it's probably safer to call this hook _after_ removing + // the candidate to avoid risking loops + leave(source, mc, data, vars); // hook here.. hardly ever used + // in fact it should no longer be necessary as you can extend leaveMsg() + // from place.gen (need to add a #define for it.. TODO) + return 1; +} + +// extending this to hook up operations is more complete, as it also sees +// people dropping out of the place by errors, but it is also a bit more +// dangerous: if you castmsg() from here, you may be recursively triggering +// more errors, since there may be more then one person dropping out of +// the place from the same origin. add a #define to place.gen anyway.. TODO +remove_member(source, origin) { + m_delete(_u, source); +#ifdef PLACE_MASQUERADE + m_delete(snames, source); +#endif + ::remove_member(source, origin); +} + +msg(source, mc, data, mapping vars) { + string t; + string ni; + + P4(("PRE %O msg(%O,%O,%O,%O) from %O\n", ME, source, mc, data, vars, + previous_object())) + + unless (source) { + source = vars["_INTERNAL_source"]; + ASSERT("source", source, source) + } + // this can indeed happen when the object has already been terminated + // during shutdown procedure, but something comes back to talk to it. + // in fact it's enough to type something (^L) in telnet after + // officially having left all places. should set place=0 in user.c... + // ASSERT() is no good here, as this is not a debug case + if (!_u) { + //raise_error("place not initialized!?\n"); + P1(("Warn: Unitialized %O got %O from %O\n", ME, mc, source)) + return; + } + +#ifdef PLACE_OWNED + // belongs into the enter() hook + if (abbrev("_request_enter", mc) || abbrev("_request_context_enter", mc)) { + // let NICKLESS pass + if (member(banned, lower_case(SNICKER))) { + sendmsg(source, "_error_place_enter_necessary_invitation", + // let's pretend this is an invite-only room + "[_nick_place] can only be entered upon invitation.", + // "You are not allowed to join [_nick_place].", + ([ "_nick_place" : MYNICK ])); + return; + } + } + // new hook necessary here + else if (mc == "_request_invitation" && v("_restrict_invitation")) { + mixed supplicant; + unless (supplicant = vars["_invitation"]) return; + if (qOwner(SNICKER)) { + if (objectp(supplicant)) + supplicant = supplicant->qName(); + sAide(supplicant, source); + PT(("implied mandate for %O with invitation into " + "restricted room: %O\n", supplicant, ME)) + } else { + castmsg(ME, + "_failure_place_invitation_necessary_ownership", + "[_nick] tries to invite [_nick_target] but fails.", + vars); + // be kind and also inform the supplicant + sendmsg(supplicant, + "_failure_place_invitation_necessary_ownership", + "[_nick] tries to invite [_nick_target] but fails.", + vars); + return; + } + } +#endif +#if 0 + // zero source means it's a system message + // currently only used by monitor_report() + // but we should work on that + unless(source) { + //D2( raise_error("caught zero source\n"); ) + // server warn gets here! + P1(("%O caught zero source (%O,%O,%O)\n", + ME, mc, data, vars)) + return; + } +#endif + // if (data && member(data, '\n') != -1) + // data = replace(data, "\n", " "); + + //t = lookup_identification(source, (qExists(source) ? 0 : vars["_source_identification"])); + t = source; + // should this happen for string sources only? as in person.c? TODO + // + // again, entity.c replaces UNLs with UNIs here and later patches things + // back in uni::sendmsg so on the way out stuff goes to the UNLs + unless (::msg(&source, mc, data, vars)) return 0; + P4(("POST %O msg(%O,%O,%O,%O) from %O in %O\n", ME, source, mc, data, vars, + t, previous_object())) +// ASSERT("seen local uniform", +// objectp(source) || ... is_formal(source), source) +#ifdef SIGS + if (call_signature(source, mc, data, vars)) return 1; +#endif +// TODO: drin weil das unten nicht geht + if (abbrev("_status", mc)) { + P1(("%O rejecting status(%O,%O..)\n", + ME,source,mc)) + return; + } + // uni and unl differ + if (t != source) { + P2(("@%s knows: %O is really %O\n", MYNICK, t, source)) + //source = t; + // if (!member(vars, "_nick")) -- dont trust the UNL + if (objectp(source)) { + if (objectp(t)) // location is local, we can trust him + ni = vars["_nick"] || source->qName(); + else // uni is here but msg comes from client + ni = vars["_nick"] = source->qName(); + } else { +#if 1 +// hier folgt ein block mit der komplexen extraktion des nicknames aus +// der url der UNI. was mich dabei verwundert ist, wenn wir schon nicht +// der _nick variable vertrauen wollen, warum machen wir das nur bei der UNI +// und nicht bei jeder remote suorce generell? TODO + mixed *u = parse_uniform(source); + if (u) { + string n = u[UNick]; + unless (n) return 0; // not n? hm... + + unless (vars["_nick"] + && (lower_case(n) + == lower_case(ni = vars["_nick"]))) + ni = vars["_nick"] = n; + } else //return 0; // uhm. + // just think of non-chat apps where a host may + // have a good reason to enter a group.. + ni = source; +#else + vars["_nick"] = source; +#endif + } + } else { + // uni and unl are the same + if (vars["_nick"]) ni = vars["_nick"]; + else + ni = vars["_nick"] = UNIFORM(source); + } + + // to avoid recursions, we catch this right from the start + if (abbrev("_warning", mc)) { + t = objectp(source) ? vars["_source_relay"] : source; + P1(("Ignored: %O got %s from %O via %O\n", + ME, mc, t, previous_object())) + return; + } + if (abbrev("_error", mc) || abbrev("_failure", mc)) { + t = objectp(source) ? vars["_source_relay"] : source; + P1(("Netburp: %O got %s from %O via %O\n", + ME, mc, t, previous_object())) + ni = vars["_nick_target"] || t; + if (leaveMsg(ni, 1, t, mc, data, vars)) { +#if 0 + // klappt immernoch nicht zuverlässig! + castmsg(t, "_notice_place_leave_netburp", + "Error delivering to [_nick].", + ([ "_nick" : ni ])); + } else { +#endif +// monitor_report("_notice_forward"+mc, +// S("%O got %O from %O\n", +// ME, psyctext(data, vars, mc, source), source)); + log_file("PLACE_ERROR", "%O « (%O,%s,%O,%O)\n", + ME, source, mc, data, vars); +// monitor_report(mc, psyctext(source+": "+data,vars,"",source)); + } + return; + } + if (abbrev("_request", mc)) { + // needs a rewrite into a try-and-slice switch() + // or shouldn't this stuff just move into the signature table? + if (abbrev("_request_enter", mc) + || abbrev("_request_leave", mc) + || abbrev("_request_invitation", mc)) { + mc = "_notice_place"+mc[8..]; +#ifdef PLACE_TOPIC + // hm, this doesn't just go back to the new + // person.. it is resent to all.. + if (v("topic") &&! abbrev("_request_leave", mc)) + vars["_topic"] = v("topic"); +#endif + // should become the common family for all three of the above + } else if (abbrev("_request_context", mc)) { // SPEC + // we're not ready for this: + //mc = "_notice"+mc[8..]; // keep the _context + // instead: + mc = "_notice_place"+mc[16..]; +#ifdef PLACE_TOPIC + // we even set it on leaves.. so what. + if (v("topic")) vars["_topic"] = v("topic"); +#endif + } else if (abbrev("_request_description", mc)) { + unless (mappingp(t = qDescription(source, vars, ni))) { + // should we issue a _failure_unavailable..? + // then again person.c says: be quiet do not reply + return 1; + } + t["_nick_place"] = MYNICK; + return sendmsg(source, "_status_description_place", 0, t); +#ifdef JABBER_PATH + } else if (abbrev("_request_list_feature", mc)) { + mixed isfeed = qNewsfeed(); + mixed rv = ([ "_nick_place" : MYNICK, + "_name" : MYNICK, + "_list_feature" : ({ "list_feature" }), // _tab + ]); + if (isfeed) { + rv["_identity"] = "news"; + } else { + rv["_identity"] = "place"; + rv["_list_feature"] += ({ "jabber-gc-1.0", "jabber-muc", "nonanonymous", v("public") ? "public" : "private", "persistent" }); + } + // _notice??? this is a _status! FIXME + sendmsg(source, "_notice_list_feature" + + (isfeed ? "_newsfeed" : "_place"), + "[_nick] is a [_identity] called [_name] offering features [_list_feature].", + rv); + return 1; + } else if (abbrev("_request_list_item", mc)) { + // after the cmd parser rewrite we may offer so-called + // ad-hoc commands + mixed rv = ([ "_nick_place" : MYNICK, + "_name" : MYNICK, + ]); + unless(MEMBER(source)) { + // TODO: send an appropriate error + // for now, just continue as it doesnt make + // a difference + } + PT(("_target_fragment: %O\n", vars["_target_fragment"])) + switch(vars["_target_fragment"]) { + case 0: + rv["_list_item"] = ({ }), // _tab + rv["_list_item_description"] = ({ }); // _tab + } + // _notice??? this is a _status! FIXME + sendmsg(source, "_notice_list_item", + "[_nick] has lots of items: [_list_item_description].", + rv); + return 1; +#endif + } else if (!MEMBER(source)) { +/* + * everything below this check is member-only + * so when rewriting commands and functions into + * signature handlers, the checks need to be + * transplanted accordingly. this makes the + * blueprints a bit larger, as you have the same + * if-member checks in each handler, but that is no + * problem. nothing gets less efficient by that. + * in fact the signature call should be a lot + * faster than going through a series of switches + * or -gasp- if/else trees of abbrev checks. + */ + P1(("ARCHETYPE (%O,%O,%O,%O..) needs to enter %O first" + " (members: %O).\n", + source,mc,data,vars, ME, _u)) + // do not allow _request_history etc. + return sendmsg(source, "_error_necessary_membership", + "You need to enter [_nick_place] first.", + ([ "_nick_place" : MYNICK ]) ); + } else if (abbrev("_request_members", mc)) { + P4(("%O from %O.\n", mc, ME)) + return showMembers(VERBOSITY_REQUEST_MEMBERS, source); + } else if (abbrev("_request_status", mc)) { + mixed b = 0, vy = VERBOSITY_STATUS_TERSE; + + if (!abbrev("_request_status_terse", mc)) { + b = boss(source); + vy = VERBOSITY_STATUS; + } + // irc clients use extra requests for topic + if (trail("_IRC", mc)) + vy -= VERBOSITY_TOPIC; + // irc clients don't like unexpected member lists + else if (vy & VERBOSITY_MEMBERS) + showMembers(vy, source); + return showStatus(vy, b, source, mc, data, vars); + } else if (abbrev("_request_execute", mc)) { + string *args = explode(data, " "); + data = lower_case(args[0]); + unless(vars["_nick"]) vars["_nick"] = objectp(source) ? + source->qName() : to_string(source); + unless(cmd(data, args, boss(source), source, vars, mc)) + sendmsg(source, + "_failure_unsupported_execute_place_command", + "Sorry, no such command here."); + return 1; +#ifdef PLACE_HISTORY +# ifndef SIGS + } else if (abbrev("_request_history", mc)) { + return _request_history(source, mc, data, vars, 0); +# endif +#endif +#ifdef PLACE_TOPIC_COMMAND + } else if (abbrev("_request_set_topic", mc)) { + sTopic(vars["_topic"], source, vars["_nick"]); + return 0; +#endif + } else return; + } + // it is correct to leave a place with _notice_place_leave instead + // of _request_leave because a room is not entitled to keep a + // member in, when he doesn't want to. the place also accepts + // _notice_place_enter as a _request_enter, but you shouldn't use that + if (abbrev("_notice_place_enter", mc) + || abbrev("_notice_context_enter", mc)) { // actually a _request + int neu, again; + +#ifdef PLACE_MASQUERADE + // könnte ungut enden.. lieber nicht + // if (vars["_nick_local"]) ni = vars["_nick_local"]; +#endif + P3(("ENTER (%O, %O)\n", source, vars)) + unless (stringp(vars["_nick"]) && strlen(ni)) { + sendmsg(source, "_error_place_enter_necessary_nick", + "Sorry, you can't just walk into here without a proper nickname."); + return; + } + // TODO: double usage of this flag is bad: + // /1/ send history, memberlist, ... + // /2/ cast new presence to room + // i would like to differentiate this and do /1/ always + // because the only st00pid clients that have problems + // with that are jabber clients which may send a + // different mc (_request_enter_this_may_be_a_presence_change) + neu = !MEMBER(source); // don't output the status again + if (trail("_again", mc)) { + mc = mc[..<7]; + again = !neu; + } + + // zero return value means user is not allowed to enter! +#ifndef QUIET_REMOTE_MEMBERS + // TODO: haeh? das is doch fuer den basic vollkommen wurst + // das gehoert wenn schon in den master und dort + // unter SILENT oder so gehandelt + // der basic koennte es generell einfach rejecten + // wenn source relay oder context gesetzt ist. + // aber die Nachrichten erzeugen die der master machen + // sollte... nene. + P1(("enter! %O relay? ", source)) + // simplify for all objects that have no isValidRelay() + if (isValidRelay(source) + || isValidRelay(vars["_source_relay"]) + || isValidRelay(vars["_context"])) { + P1(("yes. cast & out: %O in %O\n", source, ME)) + return castPresence(source, mc, data, vars, + // don't broadcast the user's presence + !neu || v("_filter_presence")); + } else unless (objectp(source) || is_formal(source)) { + P1(("no, but what is it? returning.\n")) + return; + } + if (vars["_source_relay"]) { + P1(("no, he forgot to link. returning.\n")) + sendmsg(source, "_error_necessary_link_place", + "Sorry, you can't just walk into here without a proper link."); + return; + } + P1(("no. he's local.\n")) +#endif +#ifdef FORK // {{{ + if (member(vars, "_INTERNAL_origin") + && !vars["_INTERNAL_origin"]) return; +#endif // }}} + if (neu) // mayEnter hook + unless (enter(source, mc, data, vars)) return; + insert_member(source, mc, data, vars, ni, neu, again); + return 1; + } else if (abbrev("_notice_place_leave", mc) +#if 0 + || (t = abbrev("_failure", mc)) + || (t = abbrev("_warning", mc)) + || (t = abbrev("_error", mc)) +#endif + || (abbrev("_notice_context_leave", mc)) ) { + // this is not a hook, this is behaviour! + leaveMsg(ni, 0, source, mc, data, vars); + return 1; + } +#ifdef PLACE_TOPIC + else if (abbrev("_query_topic", mc)) return showTopic(source, 1, OFFICIAL); +#endif + // would be nice to lighten up this orgy of ors + else unless (!source + || qAllowExternal(&source, mc, vars) // mayExternal hook + || MEMBER(source) + || source == "/" // allow root to send everywhere + || isValidRelay(source) + || isValidRelay(vars["_source_relay"]) + || isValidRelay(vars["_context"])) { + P1(("(%O,%O,%O..) needs to enter %O first (members: %O).\n", + source,mc,data, ME, _u)) +#if !( defined(PRO_PATH) && DEBUG < 2 ) + sendmsg(source, "_error_necessary_membership", 0, + ([ "_nick_place" : MYNICK ]) ); + //P1(("==> source, relay: %O, %O\n", source, vars["_source_relay"])) +#endif + return 1; + } else if (v("_filter_presence") && abbrev("_notice_place", mc) ) { +#ifdef PLACE_TOPIC + if (strstr(mc, "_topic") == -1) return 1; +#endif + D2(D(mc+" has passed the silence check!\n");) + return castmsg(source, mc, data, vars); +#if 0 // TODO: this causes bugs + } else unless (objectp(source) || abbrev("_message", mc)) { + // fängt auch _notice_link ab!? + P1(("%O rejecting %O from %O\n", ME,mc,source)) + return; +#endif + } +#if 0 + // and here it belongs, the new flood protection + // store lasttime per user + + if (time() - lasttime1 < MIN_INPUT_TIME && !boss(ME)) { + pr("_error_invalid_input_speed", + "Too much input! %O rejected.\n", a); + return 1; + } else { + lasttime1 = lasttime2; + lasttime2 = time(); + } +#endif + if (abbrev("_message", mc) +#ifdef JABBER_PATH + // specially requested by fippo for MUC + // still wondering whether this is generally + // a good idea or not.. + || mc == "_request_message_public" +#endif + ) { + if (v("_filter_conversation")) { + // add hook here? +#ifdef PLACE_TOPIC + sendmsg(source, + "_error_place_silent_configuration_topic", + "This room is not for talking. [_topic]", + ([ "_topic" : (v("topic") || "") ]) ); +#else + sendmsg(source, + "_error_place_silent_configuration", + "This room is not for talking."); +#endif + return 0; + } + // ist das die nette antwort auf /m channel ? + if (mc == "_message_private") mc = "_message_public"; + // vorsicht mit _message_public_question und _message_video + } + /* if the source is not a member of the room and was allowed + * because of qAllowExternal* we probably should use the rooms + * psycName instead of the source + * yes, I admit it, afaik this only fixes bugs in net/jabber! + */ + castmsg(source, mc, data, vars); +} + +// distribution being done by place object.. +// this is not the final PSYC plan, but at least +// it is protocol-conformant! +// +// this function is here to be enhanced by net/place/public and master +// +castmsg(source, mc, data, vars) { + mixed rc; +#ifdef PLACE_MASQUERADE + string nick; + + // we could also overwrite _nick, but then we end up with some + // identity headaches in user.c which probably need to be solved 1st + if (nick = snames[source]) vars["_nick_local"] = nick; +#endif +#ifdef PLACE_LOGGING + if (v("logging")) { + unless (_logfile) + _logfile = "place/"+ MYLOWERNICK +".plog"; + log_file(_logfile, "%O %O %O\n", mc, + vars + ([ "_time" : time() ]), data); + } +#endif +#ifndef DONT_REWRITE_NICKS + if (vars["_nick"] && _u[source] && vars["_nick"] != _u[source]) + vars["_nick"] = _u[source]; +#endif + vars["_nick_place"] = MYNICK; +#ifdef IRC_PATH + // a clear case of _state here.. this is being used by irc/user:w() + // only, currently, so we might as well make it _INTERNAL. + if (v("_filter_conversation")) vars["_INTERNAL_control"] = "_silent"; +#endif +#ifndef HISTORY_COUNT + // here we create a copy(vars) by subtracting the _tag + // later mods like adding _context will not arrive at the upper + // layer but the _nick changes just above are returned. + // we could change that by creating our own vars right from the + // start of this function, but it will probably not do the right thing + // (all apps and in particular history may not depend on _nick_place + // and _nick having been set by this function) + rc = ::castmsg(source, mc, data, vars - ([ "_tag" ])); // == copy(vars) +#else + // in this case, calling castmsg() removes tags and makes _context + // and _count show up in vars and go into history. all apps have to + // ensure they no longer need the _tag before going into here. :( + m_delete(vars, "_tag"); + rc = ::castmsg(source, mc, data, vars); + // restore the _tag after the cast? +#endif +#ifdef PLACE_HISTORY + if (mayLog(mc)) { // mayLog hook! + // _context should not go into history + // because history is never multicast + // to its viewers.. we first need to make + // a copy of the vars as inheriters may want + // to keep on using vars after we were here +# if 1 + // all of the above is incorrect. yes history + // is not multicast, but when retransmitting + // earlier multicasts you are supposed to provide + // both _target and _context + // see http://about.psyc.eu/Routing#Unicast_Message_for_State_Signaling + // but since object pointers get lost in persistence + // we re-add the _context at sending time + if (member(vars, "_context")) { + // we normally don't get here anyway, because castmsg() + // created a copy of the vars before setting _context + // so only a _context mistakenly set by the caller + // can appear here. + P1(("Archetype Warning: _context provided to castmsg(%O)\nin %O. Traceback (this is NOT an error):\n%s\n", mc, ME, DEBUG_TRACE)) +# ifndef VARS_IS_SACRED + vars = copy(vars); +# endif + m_delete(vars, "_context"); + } +# endif + logAppend(source, mc, data, vars, 0, "_time_place"); + _histChange++; + if (qSaveImmediately()) save(); + // cannot just call ::castmsg after logAppend because + // logAppend adds the _time_place var so i have to + // patch around one way or the other + } +#endif /* PLACE_HISTORY */ + return rc; +} + +reboot(reason, restart) { + // some place objects are clones, some are their blueprints + // but some are originals. this weeds out the blueprints: + unless (MYNICK) { + P3(("reboot: %O has no MYNICK. good\n", ME)) + return; + } + // if _u were persistent we could recover the (remote) member list + // after reboot, we should try if that makes sense.. not sure + save(); + if (mappingp(_u) && sizeof(_u)) { + P2(("%O shutting down\n", ME)) + if (restart) + castmsg(ME, +#ifdef PERSISTENT_MASTERS + // not so sure about this mc ;) + "_warning_unavailable_temporary_shutdown", +#else + "_notice_place_leave_temporary_shutdown", +#endif + // used to output _source here, but when a UNI relays + // the message to a UNL, the UNI is shown instead of ME + "Server for [_nick_place] is being restarted: [_reason]", + ([ "_reason": reason ]) ); + else castmsg(ME, "_notice_place_leave_shutdown", // _permanent ? + "Server for [_nick_place] is shutting down: [_reason]", + ([ "_reason": reason ]) ); +#ifndef PERSISTENT_MASTERS + _u = 0; +#endif + // circuits still need our object id to process the + // unlink request from the queue. also this part gets + // called before slave::reboot has run. so lets not destruct. + //destruct(ME); + } +} + +// administrative call to reload the object +quit() { + save(); +#ifndef PERSISTENT_MASTERS + castmsg(ME, "_notice_place_leave_temporary", + "[_nick_place] is temporarily being shut down for reload.", + ([ "_nick_place" : MYNICK ]) ); +#endif + destruct(ME); +} + +// called from prepare_destruct() in modern MUDs and now also in psyced +remove() { + PT(("%O getting destructed...\n", ME)) +#ifdef MUD + quit(); +#else + save(); +#endif +} + +// create() gets called in clones (of standard), in inheriters +// (like place/europe.c) and inheritees (so-called "super" objects) +// alike.. so you have to be somewhat careful. we could split that, +// but that would worsen the chances of MUD compatibility. +void create() { + mixed t; + + P2((" [@ %O] ", ME)) +#ifdef PLACE_SCRATCHPAD + used = m_allocate(0, 1); + given = m_allocate(0, 1); +#endif +#ifdef PLACE_MASQUERADE + snames = ([]); +#endif +#ifdef PLACE_HISTORY + _histChange = 0; + logInit(); + P4(("logInit() in %O\n", ME)) +#endif + // this will allocate empty mappings even in "super" objects + // (classes which are only used for inheritance) + // they are useless, but also harmless + vInit(); + ::create(); +#ifdef T + sTextPath(); +#endif + // this will optimize one or two instances of ([]) away + //if (!_u || clonep(ME)) _u = ([]); + // not really worth the hassle.. thanks however + _u = ([]); + // geht nicht: if (!a) load(); + // wir müssen den richtigen namen abwarten +#ifdef PLACE_OWNED + banned = ([]); + if (t = qOwners()) vSet("owners", (mapping) t); +#endif +} + +#ifdef PLACE_HISTORY +void reset(int again) { +// ::reset(again); + if (_histChange) { + if (qHistoryPersistentLimit()) + logClip(2 * qHistoryPersistentLimit(), qHistoryPersistentLimit()); + save(); + P2(("RESET: %O stores its history (+%O)\n", ME, _histChange)) + } + + _histChange = 0; +#if 0 //ndef NOCLEANUP + // keep the server clean. unused places may exit. + // shall this peace of code resist here ... or better in leave()? + if (clonep() && ! sizeof(_u)) { + P2(("%O is empty, shutting down.\n", ME)) + reboot("Self-destructing"); // msg should never be seen + } else + P2(("%O is not empty (or is booting up), staying up.\n", ME)) +#endif +} +#endif + +// see also doc/applied/clean_up: A room defines clean_up() to +// self-destruct if it is neither inherited nor used as a +// blueprint, is empty and was not entered for a long time. +// +clean_up(ref) { + P3(("REFCOUNT for %O is %O.\n", ME, ref)) + if (ref < 1 && clonep(ME)) { + unless (sizeof(_u)) { + P2(("%O is empty, shutting down.\n", ME)) + reboot("Self-destructing"); // msg should never be seen + } else { + P2(("%O is not empty. staying up.\n", ME)) + return 1; // clean_up wouldn't (under some circumstances?) be + // called again afterwards. + } + } + D3(else D(S("%O is a blueprint or special crafted. staying up. " + "won't be clean_up()ed again.\n", ME));) +} + +load(name, keep) { + unless (v("name")) { + unless (name) { + sscanf(o2s(ME), PLACE_PATH "%s", name); + if (!name) return; // the blueprint? + + name = capitalize(name); + keep = 0; + } + ::load(DATA_FILE(name)); + if (keep || !v("name") || stricmp(v("name"), name)) + vSet("name", name); + else + name = v("name"); + sName(name); + unless (identification) + identification = psyc_name(ME, psycName()); +#ifdef PERSISTENT_MASTERS + if (!mappingp(_u)) _u = ([]); + D2( else if (sizeof(_u)) + PP(("members in %O: %O\n", ME, _u)); ) + if (!mappingp(_routes)) _routes = ([]); + D2( else if (sizeof(_routes)) + PP(("_routes in %O: %O\n", ME, _routes)); ) +#endif +#ifdef PLACE_OWNED + // override storage data + if (qOwners()) vSet("owners", (mapping) qOwners()); +#endif + } + //return MYNICK; + return ME; +} + +save() { + // the ldmud way to check this probably is + // clonep() || object_info(ME)[OIB_REF] + unless (MYNICK) { + PT(("Should not save() %O !!\n", ME)) + return; // blueprint? skip! + } + P4(("save() %O\n", ME)) +#ifndef VOLATILE + if (index(MYNICK, '/') >= 0) { + D("Will not save() a place called "+MYNICK+"\n"); + return; + } +#endif + return ::save(DATA_FILE(MYNICK)); +} + +// called by user object when unknown commands have been entered +// with the advent of signatures, which allow for a proper "psyc" +// way to do commands, this strategy is slowly being evaporated. +cmd(a, args, b, source, vars) { + P2(("cmd(%O, %O, %O)\n", a, b, source)) + P4(("cmd(%O %O %O %O %O)\n", a, args, b, source, vars)) +#ifdef SIGS + if (call_signature(source, "_request_"+a, args, vars, b)) return 1; +#endif + switch(a) { +#ifndef SIGS +#ifdef PLACE_MASQUERADE + case "nickname": + case "nick": + case "ni": +#ifdef PLACE_MASQUERADE_COMMAND + unless (qMasquerade()) break; +#endif + if (sizeof(args) >= 2 && args[1]) { + if(args[1] == "-") { + snames = m_delete(snames, source); + previous_object()->w("_echo_place_nick_removed", + "You have removed your mask."); + } else { + snames[source] = ARGS(1); +#ifndef PLACE_MASQUERADE_SECRET + castmsg(ME, "_notice_place_masquerade", + "[_nick] now masquerades as [_nick_local].", + ([ "_nick" : previous_object()->qName(), + "_nick_local" : snames[source] ])); +#else + // FIXME + previous_object()->w("_echo_place_nick", + "You are now masquerading as [_nick_local].", + ([ "_nick_local" : snames[source] ])); +#endif + } + } + else if (snames[source]) + previous_object()->w("_status_place_nick", + "You are masquerading as [_nick_local].", + ([ "_nick_local" : snames[source] ])); + else + previous_object()->w("_warning_usage_nick", + "Usage: /nick (Mask)"); + return 1; +#ifdef PLACE_MASQUERADE_COMMAND + case "masquerade": + case "masq": +// if (!b && stringp(v("topic-user")) +// && lower_case(previous_object()->qName()) != +// lower_case(v("topic-user"))) { +// previous_object()->pr("_error_unavailable_function_place_masquerade", +// "This function is not available to you here.\n"); +// return 1; +// } + if (b > 0) { + if (sizeof(args) == 2) sMasquerade(args[1]); + else previous_object()->pr("_warning_usage_masquerade", + "Usage: /masquerade [yes|no]\n"); + return 1; + } + break; +#endif +#endif +#ifdef PLACE_PUBLIC_COMMAND + case "public": + if (b > 20) { + if (sizeof(args) == 2) sPublic(args[1]); + else previous_object()->pr("_warning_usage_public", + "Usage: /public [yes|no]\n"); + return 1; + } + break; +#endif +#endif /* !SIGS */ +#ifdef PLACE_HISTORY +# ifndef SIGS + case "hist": + case "history": + if (sizeof(args)>1) vars["_parameter"] = ARGS(1); + _request_history(source, 0, 0, vars, b); + return 1; +# endif + case "hc": + case "histclean": // in case you don't remember exactly.. it's ok + case "histclear": // wieso um alles in der welt soll das jeder dürfen? +// liest denn keiner die cvs kommentare? da stands +// drin.. ok wir sind realistisch und schreiben es +// gleich in den source... ja also manche menschen +// merken erst nachtraeglich dass sie zu private +// dinge oeffentlich gesagt haben.. dann sollen sie +// nicht betteln muessen.. jeder hat das recht die +// history zu killen.. mal schaun ob das mal ein +// problem ist, aber bisher hatte ich nicht den +// eindruck als sei das der fall +// if (b > 49) return histClear(); + return histClear(sizeof(args)>1? args[1]: 0, b, source, vars); + break; +#endif +#ifdef PLACE_STORE_COMMAND + case "store": + case "save": + save(); + return 1; +#endif +#ifdef PLACE_TOPIC_COMMAND + // not used anymore, see _request_set_topic() instead + case "topic": + case "topi": + case "top": + case "to": + case "t": + sTopic(ARGS(1), source, vars["_nick"]); + return 1; +#endif +#ifdef PLACE_OWNED + case "mandates": + case "listaides": + case "listschergen": + // warum nicht einfach bei /st ausgeben? + if (qOwner(SNICKER)) return listAides(source); + return; + case "uninvite": + unless (v("_restrict_invitation")) return; + // fall thru + case "unmandate": + case "unmand": + case "unmd": + case "unchop": + // fall thru. i think we should provide real /unmandate + case "mandate": + case "mand": + case "md": + case "schergify": + case "chop": + if (qOwner(SNICKER)) { + unless (sizeof(args) == 2) { +#if 0 + sendmsg(source, "_warning_usage_mandate", + "Usage: /mandate «aide»"); + return 1; +#else + return listAides(source); +#endif + } + switch(sAide(args[1], source)) { + case 1: + sendmsg(source, "_notice_place_aide_removed", + "[_aide] was removed from the list of " + "aides.", ([ "_aide" : args[1] ]) ); + return 1; + case 2: + sendmsg(source, "_notice_place_aide_added", + "[_aide] was added to the list of " + "aides.", ([ "_aide" : args[1] ]) ); + return 1; + } + } + return; +#endif // PLACE_OWNED + } +#if defined(PLACE_LOGGING) || defined(PLACE_FILTERS) + if (b) switch(a) { +#ifdef PLACE_LOGGING + case "logging": + if (sizeof(args) == 2 && stringp(a = args[1])) { + string state; + + if (a == "on") vSet("logging", SNICKER); + else vDel("logging"); + save(); + state = v("logging") ? "on" : "off"; + sendmsg(source, "_echo_place_set_logging_"+state, + "You switch logging "+ state +"."); + } + else sendmsg(source, "_warning_usage_logging", + "Usage: /logging (on|off)"); + return 1; +#endif +#ifdef PLACE_FILTERS + case "quiet": + case "filter": + case "silent": + sendmsg(source, "_error_usage_quiet", + "The /quiet and /silent functions have been replaced by /silence.\n\ +See the manual for details."); + return 1; + case "silence": + // thought about calling this /filter, but maybe we need that + // in the user as /filter someday.. + if (sizeof(args) == 2 && a = lower_case(args[1])) { + string who = SNICKER; + + if (a == "presence") { + vSet("_filter_presence", who); + vDel("_filter_conversation"); + } else if (a == "conversation" || a == "talk") { + vSet("_filter_conversation", who); + vDel("_filter_presence"); + } else if (a=="all" || a=="on" || a=="yes") { + vSet("_filter_presence", who); + vSet("_filter_conversation", who); + } else { + vDel("_filter_conversation"); + vDel("_filter_presence"); + } + save(); + } + else sendmsg(source, "_warning_usage_silence", + "Usage: /silence [presence|conversation|all|none]"); + sendmsg(source, "_status_place_filter_presence_"+ + (v("_filter_presence") ? "on" : "off"), + "Presence (enter/leave) messages are " + +( v("_filter_presence") ? "disabled" : "enabled" )+ + " in this place."); + sendmsg(source, "_status_place_filter_conversation_"+ + (v("_filter_conversation") ? "on" : "off"), + "Conversational messages are " + +( v("_filter_conversation") ? "disabled" : "enabled" )+ + " in this place."); + return 1; +#endif +#if 0 + case "banhost": + case "tiehost": + sendmsg(source, "_warning_usage_tiehost", + "Usage: /tiehost "); + return 1; +#endif + case "rou": + case "routes": + sendmsg(source, "_status_place_routes", + sprintf("The route data structure for %O is: %O", + ME, routes())); + return 1; + case "obj": + case "objects": + sendmsg(source, "_status_place_members_technical", + sprintf("The member data structure for %O is: %O", + ME, _u)); + return 1; + case "remove": + if (sizeof(args) > 1 + // we disable this check so we can also + // remove routes, not just single users. + //&& member(_u, args[1]) + ) { + // this admin command quietly removes a user + log_file("BEHAVIOUR", "[%s] %O removes %s from %O\n", + ctime(), previous_object(), args[1], ME); + // this can trigger a _failure_destruct_circuit + // should provide origin here, huh? don't have it! + // let the user make it up.. + remove_member(args[1], sizeof(args) > 2 && args[2]); + // apparently it's pointless if we don't save it + save(); + return 1; + } + sendmsg(source, "_warning_usage_remove", + "Usage: /remove []"); + return 1; + } +#endif + return 0; +} + +// amountOnly used by news.c by inheritance +makeMembers(amountOnly) { + if (sizeof(_u) == 0) + return ({ "_none", ([ "_amount_members" : 0 ]) }); + else + if (amountOnly || sizeof(_u) > MAX_VISIBLE_USERS) + return ({ "_amount", ([ "_amount_members" : sizeof(_u) ]) }); + else + return ({ "", ([ + "_list_members" : objects(), // _tab + "_list_members_nicks" : names(), // _tab + ]) }); +} + +// also called from net/usercmd.i and tn/user +showMembers(verbosity, person) { + array(mixed) a = makeMembers(); + string auto = verbosity & VERBOSITY_AUTOMATIC ? "_automatic" : ""; + + P4(("showMembers for %O. verbosity %O.\n", person, verbosity)) + sendmsg(person, "_status_place_members"+ a[0] + auto, + 0, ([ "_nick_place" : MYNICK, ]) + a[1] ); + return 1; +} + +showStatus(verbosity, al, person, mc, data, vars) { + if (verbosity & VERBOSITY_UNIFORMS) { + // should room status be compatible to person description? + sendmsg(person, +// similar code also in net/person.c +#ifdef IRC_PATH + "_status_place_identification_aliases", + "PSYC Identification of [_nick_place]: [_identification]" + "\nAliases: [_identification_alias]", +#else + "_status_place_identification", + "PSYC Identification of [_nick_place]: [_identification]", +#endif + ([ "_identification": identification, +#ifdef IRC_PATH + "_identification_alias": +# if HAS_PORT(HTTP_PORT, HTTP_PATH) || HAS_PORT(HTTPS_PORT, HTTP_PATH) +# ifdef PRO_PATH + ((tls_available() && HTTPS_URL) || HTTP_URL) +# else + // problem with POST over https apparently.. + (HTTP_URL) +# endif + + "/"+ pathName() +" "+ +# endif +# ifdef SIP_PATH + // "sip:#"+ MYLOWERNICK +"@"+ SERVER_HOST +" "+ +# endif +# ifdef _host_XMPP + "xmpp:*"+ MYLOWERNICK +"@"+ _host_XMPP +" "+ +# else +# ifdef JABBER_PATH + "xmpp:*"+ MYLOWERNICK +"@"+ SERVER_HOST +" "+ +# endif +# endif + "irc://"+ SERVER_HOST +"/"+ MYNICK, +#endif +#ifdef JABBER_PATH + "_identification_scheme_XMPP": +# ifdef _host_XMPP + "*"+ MYLOWERNICK +"@" _host_XMPP, +# else + "*"+ MYLOWERNICK +"@" SERVER_HOST, +# endif +#endif + "_nick_place": MYNICK ]) ); + } +#ifdef PLACE_TOPIC + if (verbosity & VERBOSITY_TOPIC) showTopic(person, 0, OFFICIAL); +#endif + if (al) { +#ifdef PLACE_PUBLIC_COMMAND + if (verbosity & VERBOSITY_PUBLICIZED && v("public")) { + sendmsg(person, "_status_place_public", + "[_nick_place] publicized by [_nick].", + ([ "_nick" : v("public"), + "_nick_place" : MYNICK ]) ); + } +#endif + // tiehosts ? +#ifdef PLACE_LOGGING + if (verbosity & VERBOSITY_LOGGING && v("logging")) { + sendmsg(person, "_status_place_logging", + "Logging activated by [_nick].", + ([ "_nick" : v("logging"), + "_nick_place" : MYNICK ]) ); + } +#endif + } +#ifdef PLACE_MASQUERADE + if (verbosity & VERBOSITY_MASQUERADE +#ifdef PLACE_MASQUERADE_COMMAND + && qMasquerade() +#endif + ) { + string ni = snames[person]; + + if (ni) sendmsg(person, "_status_place_nick_local", + 0, ([ "_nick_local" : ni, "_nick_place" : MYNICK ])); + else sendmsg(person, "_status_place_masquerade", + "Masquerading in [_nick_place] is permitted. Try out the /nick command.", + ([ "_nick_place" : MYNICK ])); + } +#endif +#ifdef PLACE_FILTERS +# ifndef VOLATILE + if (v("_filter_presence") && verbosity & VERBOSITY_FILTER) { + sendmsg(person, "_status_place_filter_presence", + "Presence noise silenced by [_nick].", + ([ "_nick" : v("_filter_presence"), + "_nick_place" : MYNICK ]) ); + } +# endif +#endif +#ifdef PLACE_HISTORY + if (verbosity & VERBOSITY_HISTORY && qHistoryGlimpse() && mc +// && !abbrev("_echo_place_enter_automatic", mc) + && !abbrev("_echo_place_enter_other", mc) // TODO +// && !abbrev("_request_status", mc) + ) { + logView(qHistoryGlimpse(), person); + } +#endif + return 1; +} + +names() { return m_values(_u); } // used by irc/user, applet/user + // place/owned and place/storic +objects() { return m_indices(_u); } // used by place/owned + +size() { return sizeof(_u); } // who needs this!? + +// this is overridden in more complex rooms to provide continous status output +memberInfo() { return _u; } // used by showRoom() in http/user + +psycName() { + ASSERT("psycName", stringp(MYNICK), MYNICK) + return "@"+MYLOWERNICK; +} + +pathName() { + ASSERT("pathName", stringp(MYNICK), MYNICK) + return "@"+MYNICK; +} + +// inheritance hooks +leave(source, mc, data, vars) { return 1; } +enter(source, mc, data, vars) { // mayEnter? + string t, ni; + + // muss der _nick_local auch legal sein? TODO + if (mappingp(vars)) ni = vars["_nick"]; + if ( !(t = legal_name(ni)) || stricmp(t, ni) ) { + P2(("illegal name %O vs %O in %O\n", ni, t, ME)) + sendmsg(source, "_error_illegal_name_person_place", 0, + ([ "_nick_place" : MYNICK, "_nick" : ni ])); + return 0; + } + return 1; +} + +// we shouldn't get here for _filter_conversation rooms (news etc) +// btw. should all "after" hooks be named onSomething() or hasSomethinged() ? +onEnter(source, mc, data, vars) { + // this does not include _other which is issued on /c - that's good + int verbosity = abbrev("_echo_place_enter_automatic", mc) + ? VERBOSITY_ENTER_AUTOMATIC + VERBOSITY_AUTOMATIC + : VERBOSITY_ENTER; +#ifndef ENTER_MEMBERS + if (verbosity & VERBOSITY_MEMBERS) showMembers(verbosity, source); +#endif +#ifdef PLACE_HISTORY + if (vars["_amount_history"]) { + int max = qHistoryGlimpse(); + int amount = to_int(vars["_amount_history"]); + logView(amount > max ? max : amount, source); + } +#endif + showStatus(verbosity, boss(source), source, mc, data, vars); +} + +// onError hook +error(source, mc, data, vars) { + P2 (("%O « unexpected msg(%O,%s,%O,%O)\n", + ME, source, mc, data, vars)) + return 1; +} + +qAllowExternal() { return 0; } // mayExternal hook + +qInstance() { return 0; } + +qDescription(source, vars, nick) { + // should thus be renamed + mapping dv = ([ "_topic": v("topic") || "", "_nick": MYNICK ]); + mixed k, l; + + // should distinguish members from strangers..? + foreach (k, l: vMapping()) { + // st00pid simple way of distinguishing exportable vars + if (k[0] == '_') dv[k] = l; + } + P3(("%O qDesc returns %O\n", ME, dv)) + return dv; +} + +// this is usually done using sName() or load() +//object compile() { return ME; } + +#ifndef DONT_REWRITE_NICKS +sendmsg(target, mc, data, vars, source, showingLog, callback) { + // some usage: sendmsg calls do not provide vars. + // change it there, or change it here? + // actually here we get to 'else' which is nice too + if (!vars) vars = ([]); + else if (vars["_nick"] && _u[source] && vars["_nick"] != _u[source]) { + P1(("rewriting %O into %O\n", vars["_nick"], _u[source])) + vars["_nick"] = _u[source]; + } + return ::sendmsg(target, mc, data, vars, source, showingLog, callback); +} +#else +# echo OOPS... DONT_REWRITE_NICKS is on! +#endif + +#ifdef SIGS +_request_set(source, mc, data, vars, b) { + string k; + + PT(("set(%O, %O, %O, %O, %O) called\n", source, mc, data, vars, b)) + if (k = vars["_key"]) + // pass on to a subhandler + if (call_signature(source, "_request_set_"+ k, data, vars, b)) + return 1; + + sendmsg(source, "_warning_usage_place", "Usage: /place []"); + return 1; +} + +_request_set_default_text(source, mc, data, vars, b) { + unless (qOwner(SNICKER)) return 0; + string key = vars["_key"]; + string value = vars["_value"]; + + if (strlen(value)) { + vSet(key, value); + save(); + } + + sendmsg(source, "_info_set_place_" + key, "Setting \"[_key_set]\" is \"[_value]\".", (["_key_set": key, "_value": v(key)])); + return 1; +} + +_request_set_default_list(source, mc, data, vars, b, list) { + unless (qOwner(SNICKER)) return 0; + string key = vars["_key"]; + string value; + + foreach (string item : list) { + if (item == vars["_value"]) { + value = vars["_value"]; + break; + } + } + + if (strlen(value)) { + vSet(key, value); + save(); + } else if (strlen(vars["_value"])) { + sendmsg(source, "_error_invalid_setting_value", + "Sorry, that is not a valid value for the [_key_set] setting. Valid values are: [_values].", + ([ "_key_set": key, "_values": implode(list, ", ") ]) ); + return 1; + } + + sendmsg(source, "_info_set_place_" + key, "Setting \"[_key_set]\" is \"[_value]\".", (["_key_set": key, "_value": v(key)])); + return 1; +} + +_request_set_default_bool(source, mc, data, vars, b) { + unless (qOwner(SNICKER)) return 0; + string key = vars["_key"]; + string value = vars["_value"]; + + if (is_true(value)) { + vSet(key, 1); + save(); + } else if (is_false(value)) { + vSet(key, 0); + save(); + } + + sendmsg(source, "_info_set_place_" + key, "Setting \"[_key_set]\" is [_value].", (["_key_set": key, "_value": v(key) ? "on" : "off"])); + return 1; +} + + +#ifdef PLACE_OWNED +_request_owners(source, mc, data, vars, b) { + mixed t; + P1(("_request_owners(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + // this kind of 3 different policies logic needs to be integrated TODO + if (v("owners") &&! (b || vars["_INTERNAL_trust"] > 5 +#ifdef _flag_trust_place_owners + || qOwner(SNICKER) +#endif + )) return; + // not sure if _value is needed here + t = vars["_list_owners"] || vars["_value"]; // _tab + // should check for legal nicks & uniforms + // should provide for a way to make a place unowned again TODO + if (pointerp(t)) t = mkmapping(t); + else if (stringp(t)) t = mkmapping(explode(t, " ")); + if (mappingp(t) && sizeof(t)) vSet("owners", t); + sendmsg(source, "_echo_place_owners", + "The owner list of [_nick_place] is [_list_owners].", + ([ "_list_owners" : m_indices(v("owners")), + "_nick_place" : MYNICK ]) ); + return 1; +} + +_request_kick(source, mc, data, vars, b) { + mixed target, t, handler; + + P1(("_request_kick(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + //unless (source) source = previous_object(); + unless (qOwner(SNICKER)) return; + if (vars["_person"]) { + target = find_person(vars["_person"]) + || lower_case(vars["_person"]); + if (objectp(target)) { +// if (member(objects(), target) == -1) { +// return; +// } + t = target->qName(); + handler = 0; + } else { +// if (member(names(), target) == -1) { +// return; +// } + t = target; + // lower_case should be unnecessary here.. but who cares + handler = find_target_handler(lower_case( +#ifdef UNL_ROUTING + uni2unl[t] || +#endif + t)); + } + PT(("%O KICK %O in %O? banned: %O\n", + ME, target, names(), banned)) + // allow to elrid someone even if he isn't here right now + banned[lower_case(t)] = vars["_person"]; + // but if he is, kick him, too + if (member(objects(), target) != -1) + // TODO: irc fehlt das format hierfuer + // und.. oops.. der betroffene empfängt das nicht!! + sendmsg(ME, +#ifdef SPEC + "_request_context_leave" +#else + "_request_leave" +#endif + "_banned", + "[_nick] got banned from [_nick_place]", ([ + "_nick" : t, + "_nick_place" : MYNICK, + "_INTERNAL_origin" : handler + ]), target); + } + sendmsg(source, "_status_place_banned", + "These bozos are banned: [_list_banned]", // _tab + ([ "_list_banned" : m_indices(banned) ]) ); + return 1; +} +#endif + +# ifdef PLACE_STYLE_COMMAND +// this could turn into a generic _request_set*** as it does the +// default thing to do: store the setting. +_request_set_style(source, mc, data, vars, b) { + if (qOwner(SNICKER)) { + string value = vars["_uniform_style"] || vars["_value"]; + if (value == "-") { + vDel("_uniform_style", value); + save(); + } else if (value && (value = legal_url(value, "http"))) { + vSet("_uniform_style", value); + save(); + } else if (value) { + sendmsg(source, + "_error_illegal_scheme", + "That is not a valid [_scheme] URL for a file.", + ([ "_scheme" : "http" ])); + return 1; + } + sendmsg(source, "_status_place_uniform_style", + "Style file [_uniform_style] is in use here.", + ([ "_uniform_style" : + v("_uniform_style") || "-" ])); + return 1; + } + return 0; +} +# endif +#endif + +#define complainMembership(source) \ + sendmsg(source, "_error_necessary_membership", \ + "You need to enter [_nick_place] first.", \ + ([ "_nick_place" : MYNICK ]) ) + +#ifdef PLACE_MAY_HISTORY +// only provide this hook if PLACE_MAY_HISTORY was defined above. +// this hook can then be overridden using #define MAY_HISTORY in +// place.gen should you ever need it.. otherwise MAY_HISTORY will +// have no effect. since that is my current default, i will not +// document MAY_HISTORY. +// +int mayHistory(source, mc, data, vars, b) { + if (MEMBER(source)) return 1; + complainMembership(source); + return 0; +} +#endif + +#ifdef PLACE_HISTORY +_request_history(source, mc, data, vars, b) { + // it's good to have separate vars in the protocol + // but logView() doesn't want to know.. + P3(("_request_history(%O,%O,%O,%O,%O)\n", + source, mc, data, vars, b)) +# ifdef SIGS +# ifdef PLACE_MAY_HISTORY + unless (mayHistory(source, mc, data, vars, b)) return; +# else + unless (MEMBER(source)) return complainMembership(source); +# endif +# endif + logView(vars["_match"] || vars["_parameter"] || vars["_amount"], + source); + sendmsg(source, "_echo_history"); // indicate end of history + return 1; +} +#endif + +#ifdef PLACE_PUBLIC_COMMAND +// should we use v("public") and ->vQuery("public") only? no, bad idea. +// then an operator can change a #define PUBLIC room into non-public. +// let's keep this mixed behaviour where v("public") can be overridden +// by place.gen. +qPublic() { return v("public"); } +qPublicName() { return qPublic() ? MYNICK : 0; } + +#ifdef SIGS +_request_public(source, mc, data, vars, b) { + // API to be decided.. + string a = vars["_flag_public"] || vars["_value"]; +#else +sPublic(a) { + object source = previous_object(); +#endif + string state; + + unless (stringp(a) && strlen(a)) { +#ifdef SIGS + showStatus(VERBOSITY_PUBLICIZED, b, source, 0, data, vars); +#endif + return 1; + } +#ifdef SIGS + // this *could* go into a mayPublic() if anyone cares.. + if (b <= 20) { + sendmsg(source, "_error_necessary_privilege", 0, + ([ "_command": "public" ]) ); + return 1; + } +#endif + if (a == "on" || a == "yes") { + if (qPublic()) { + sendmsg(source, "_warning_place_set_public_on", + "This room is already public."); + return 1; + } + vSet("public", UNIFORM(source)); + } else if (a == "off" || a == "no") { + unless (qPublic()) { + sendmsg(source, "_warning_place_set_public_off", + "This room is already private."); + return 1; + } + vDel("public"); + } else { + sendmsg(source, "_warning_usage_public", + "Usage: /public [yes|no]"); + return 1; + } + save(); + state = v("public") ? "on" : "off"; + log_file("PUBLIC", "[%s] %O %s %O (%O)\n", + ctime(), source, state, MYNICK, ME); + sendmsg(source, "_echo_place_set_public_"+state, + "You make this room "+ + (v("public") ? "public" : "private") +"."); + return 1; +} +#endif + +#ifdef PLACE_MASQUERADE +# ifdef SIGS +_request_masquerade(source, mc, data, vars, b) { + // API to be decided.. + string a = vars["_flag_masquerade"] || vars["_value"]; +# else +sMasquerade(a) { + object source = previous_object(); +# endif + string state; + + unless (stringp(a) && strlen(a)) { +# ifdef SIGS + showStatus(VERBOSITY_MASQUERADE, b, source, 0, data, vars); +# endif + return 1; + } +# ifdef SIGS + if (b < 1) { + // this *could* go into a mayMasquerade() if anyone cares.. + sendmsg(source, "_error_necessary_privilege", 0, + ([ "_command": "masquerade" ]) ); + return 1; + } +# endif +# ifdef PLACE_MASQUERADE_COMMAND + if (a == "on" || a == "yes") { + if (qMasquerade()) { + sendmsg(source, "_warning_place_set_masquerade_on", + "This room is already in a masquerade."); + return 1; + } + vSet("masquerade", UNIFORM(source)); + } else if (a == "off" || a == "no") { + unless (qMasquerade()) { + sendmsg(source, "_warning_place_set_masquerade_off", + "This room already isn't in a masquerade."); + return 1; + } + vDel("masquerade"); + snames = ([ ]); // important detail ;) + } else { + sendmsg(source, "_warning_usage_masquerade", + "Usage: /masquerade [yes|no]"); + return 1; + } + save(); + state = v("masquerade") ? "on" : "off"; + log_file("MASQUERADE", "[%s] %O %s %O (%O)\n", + ctime(), source, state, MYNICK, ME); + sendmsg(source, "_echo_place_set_masquerade_"+state, + "You "+ (v("masquerade") ? "en" : "dis") + +"able masquerading."); +# else + sendmsg(source, "_warning_place_set_masquerade_on_always", + "This room is always in a masquerade."); +# endif // PLACE_MASQUERADE_COMMAND + return 1; +} + +# ifdef SIGS +_request_nick_local(source, mc, data, vars, b) { + string a; + string ni; + +# ifdef PLACE_MASQUERADE_COMMAND + unless (qMasquerade()) { + sendmsg(source, "_error_disabled_masquerade"); + return 1; + } +# endif + ni = vars["_nick"]; + ASSERT("_request_nick_local (source has nick)", + stringp(ni) && strlen(ni), ni) + a = vars["_nick_local"] || vars["_value"]; + unless (stringp(a) && strlen(a) && stricmp(a, ni)) { + ni = snames[source]; + if (ni) { + sendmsg(source, "_echo_place_nick_removed", + 0, ([ "_nick_local" : ni ])); + snames = m_delete(snames, source); + } else sendmsg(source, "_status_place_nick_local_none"); + // w("_warning_usage_nick", "Usage: /nick (Mask)"); + return 1; + } + if (vars["_INTERNAL_stuss"]) { + sendmsg(source, "_warning_usage_nick", "Usage: /nick (Mask)"); + return 1; + } + snames[source] = a; +# ifndef PLACE_MASQUERADE_SECRET + castmsg(source, "_notice_place_masquerade", + "[_nick] now masquerades as [_nick_local].", + ([ "_nick" : ni, "_nick_local" : a ])); +# else + // FIXME + previous_object()->w("_echo_place_nick", + "You are now masquerading as [_nick_local].", + ([ "_nick_local" : snames[source] ])); +# endif + return 1; +} +# endif // SIGS +#endif // PLACE_MASQUERADE + +#ifdef PLACE_OWNED +/* SOME NOTES ON AIDES + * + * Aides are (in the context of owned rooms) privileged users, which the + * room owners (or at least one of them) put some special trust in. + * Therefore aides have more rights than ordinary users, but less than + * the owners. In a default owned room, aides have no more rights than + * the one to set the topic. This is for the following reasons: + * + * * Even owned rooms have few privileged commands, most of them deal with + * aides - room owners can /mandate someone (make him an aide), + * request a list of aides and such. + * * Elrid (which gets you rid of a malicious user) is, beside of the + * aides-related commands the only privileged command, which aides + * cannot use, as elrid is a really powerful command. Once you elrided + * someone, he cannot join the room again (with his current identity). A + * server restart is needed to clear the list of elridded identities. So + * elrid is in no case a fun command like KICK on IRC, but the last resort + * against really malicious users. Ignoring bad behaving users, or talking + * to them is highly preferred to an elrid. + * So as an elrid cannot be undone by simple means, it should be up to the + * room owner(s) to decide what people will be denied to access the room + * + * You might ask: What are aides good for then? Setting topics isn't a + * quite fulfilling task after all, is it? + * No of course it is not. Aides were originally designed for special + * crafted places, like psyc://psyced.org/@GoodAdvice, which is quite + * similar to fortune, except for it is better and generates webpages + * containing the fortunes and such. As it wasn't acceptable to let anybody + * add fortunes and neither that only owners add fortunes a special rank + * was needed: Aides. In that special environment of GoodAdvice, aides + * are allowed to add fortunes, to go through the list of all existing + * fortunes, to search for existing ones and to delete fortunes they added + * themselves. Owners can delete all fortunes, no matter who added them, + * which was not a power the usual team members should be able to use. + * + * So, as the example describes, aides are a rank for special-crafted + * rooms building up on owned rooms, which do need more levels than owner + * and user. Should you want to write your own special crafted room with + * extra-features nobody but you ever imagined, consider using qAide() + * and such to give your friends privileged access to all the extra- + * functions you invented. + */ + +sAide(whom) { + string t; + int ret; + mapping aides = v("aides") || ([]); + + // change local uniform to nick + array(mixed) u = parse_uniform(whom); + if (u && is_localhost(lower_case(u[UHost]))) whom = u[UResource]; + + t = lower_case(whom); + if (aides[t]) { + aides -= ([ t ]); + ret = 1; + } else { + aides[t] = 1; + ret = 2; + } + + vSet("aides", aides); + save(); + return ret; +} + +listAides(source) { + if (mappingp(v("aides"))) + sendmsg(source, "_status_place_aides", + "This room knows the following aides: [_aides]", ([ + "_aides" : v("aides") + ]) ); + else + sendmsg(source, "_status_place_aides_none", + "No aides defined in this owned room."); + return 1; +} + +qAide(snicker, aidesonly) { +// never call with objectp.. use SNICKER +// if (objectp(whom)) whom = whom->qName(); + + snicker = lower_case(snicker); // should we enforce SNICKER to be lc? yes! + if (!aidesonly && sizeof(v("owners")) && member(v("owners"), snicker)) return 4; + unless (mappingp(v("aides"))) return 0; + return v("aides")[snicker]; +} + +qOwner(snicker) { return member(v("owners"), lower_case(snicker)); } +#endif + +qMember(snicker) { + P3((">> qMember(%O) in _u: %O\n", snicker, _u)) + return member(_u, snicker); +} diff --git a/world/net/place/threads.c b/world/net/place/threads.c index fa58d5b..52b05eb 100644 --- a/world/net/place/threads.c +++ b/world/net/place/threads.c @@ -533,6 +533,12 @@ htget(prot, query, headers, qs, data) { int a; int limit = to_int(query["limit"]) || DEFAULT_BACKLOG; int offset = to_int(query["offset"]); + int authed = checkToken(query) ? 1 : 0; + unless (isPublic() || authed) { + write("

404

"); + return 1; + } + string webact = PLACE_PATH + MYLOWERNICK; // shouldnt it be "html" here? sTextPath(query["layout"] || MYNICK, query["lang"], "ht"); @@ -542,18 +548,7 @@ htget(prot, query, headers, qs, data) { htok(prot); // kommentare + urspruengliche Nachricht anzeigen displayHeader("entry"); - displayEntry(to_int(query["id"]), checkToken(query) ? 1 : 0); -#if 0 - // eingabeformular ohne betreff - write("
\n" - "\n" - "PSYC Uni:
\n" - "\n" - "
\n" - "\n" - "
\n"); - write("


"); -#endif + displayEntry(to_int(query["id"]), authed); //logView(a < 24 ? a : 12, "html", 15); displayFooter(); return 1; @@ -589,7 +584,7 @@ htget(prot, query, headers, qs, data) { return 1; } - //::htget(prot, query, headers, qs, data, 1); // no processing, just info + ::htget(prot, query, headers, qs, data, 1); // no processing, just info string export = query["export"] || query["format"]; if (export == "js") {