1 /* Copyright (C) 2000-2004 Thomas Bopp, Thorsten Hampel, Ludger Merkens
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * $Id: nntp.pike,v 1.1 2008/03/31 13:39:57 exodusd Exp $
19 inherit "/kernel/module";
22 #include <attributes.h>
24 #include <exception.h>
25 //! This is a wrapper class for use with nttp. It encapsulates sTeam
26 //! message boards (objects with annotations in general) and supports
27 //! nntp functionality for them.
28 class nntp : public module{
38 #define NNTP_LOG(s, args...) werror(s +"\n", args);
40 #define NNTP_LOG(s, args...)
43 #define HEADER_SEP "\r\n"
44 #define HEADER_SEP_RCV "\r\n\r\n"
55 mapping mReferences = ([ ]);
56 mapping mArticles = ([ ]); //maps the article -> article number
62 if ( !objectp(oSteamObj) )
65 if ( !objectp(_FILEPATH) )
66 return "steam." + oSteamObj->get_identifier();
67 string path = _FILEPATH->object_to_filename(oSteamObj);
68 array tokens = path / "/";
69 if ( sizeof(tokens) < 3 )
70 name = replace(path,"/",".");
72 name = (tokens[2..]*".");
73 name = replace(name, ({ " ", "\t" }), ({ "_", "_" }));
78 * In case of no articles in the group: return 0 ; see RFC 3977 6.1.1.2. the second case
80 int get_last_message() {
83 foreach( values(mArticles), int number ){
91 * In case of no articles in the group: return 0 ; see RFC 3977 6.1.1.2. the second case
93 int get_first_message() {
95 if(sizeof(mArticles)==0)
98 foreach( values(mArticles), int number ){
99 if( (number<min) || (min == -1) ){
107 * In case of no articles in the group: return 0 ; see RFC 3977 6.1.1.2. the second case
108 * @return the total count of messages in the current group
110 int get_num_messages() {
111 array articles = get_articles();
112 return sizeof(articles);
114 bool read_access(object user) {
116 _SECURITY->access_read(0, oSteamObj, user);
120 bool write_access(object user) {
122 _SECURITY->access_write(0, oSteamObj, user);
126 bool newer_than(int date, int t) {
130 void add_article(object article, string ref) {
137 // we take the reference with the highest object-id (the newest post)
138 sscanf(ref, "%{%[ ]<%d%s>%}", iRefs);
139 if( sizeof(iRefs) > 0 ){
141 foreach( iRefs, array iRefsEntry ) {
142 if( iRefsEntry[1] > iRef )
143 iRef = iRefsEntry[1];
145 refobj = find_object(iRef);
148 refobj->add_annotation(article);
149 article->set_acquire(refobj);
153 _SECURITY->access_annotate(0, oSteamObj, this_user(), 0);
156 FATAL("Not allowed to post: " + err[0]);
164 void get_sub_articles(object article) {
165 array subarticles = article->get_annotations_for();
166 foreach( subarticles, object sub) {
167 mReferences[sub] = article;
168 // mArticles[sub] = iEnd++;
169 mArticles[sub] = sub->get_object_id();
170 get_sub_articles(sub);
176 // Fixme: should only fetch articles from time to time
177 array get_articles() {
181 array articles = oSteamObj->get_annotations_for();
182 NNTP_LOG("get_articles() == \n"+sprintf("%O", articles)+
183 "\n for "+oSteamObj->get_identifier());
184 foreach ( articles, object article ) {
186 // mArticles[article] = iEnd;
187 mArticles[article] = article->get_object_id();
188 get_sub_articles(article);
190 return indices(mArticles);
192 object get_article(int id) {
193 NNTP_LOG("get_article(%d)\n", id);
194 NNTP_LOG("Articles = %O\n", mArticles);
195 foreach(indices(mArticles), object article)
196 if ( mArticles[article] == id )
201 int get_article_num(object article) {
202 // return mArticles[article];
203 return article->get_object_id();
206 string get_time(int t) {
207 string tf = ctime(t);
208 sscanf(tf, "%s\n", tf);
213 * used in XOVER for a overview of an article
215 string header(object article) {
216 object creator = article->get_creator();
217 string name = creator->query_attribute(USER_EMAIL);
218 if ( !stringp(name) )
219 name = creator->get_identifier();
221 name = creator->query_attribute(USER_FULLNAME) + " <"+name+">";
223 string res = mArticles[article] + "\t"+
224 article->query_attribute(OBJ_NAME)+"\t"+
226 get_time(article->query_attribute(OBJ_CREATION_TIME))+"\t"+
227 message_id(article)+"\t"+
228 (objectp(mReferences[article]) ? get_references(article)+"\t":"")+
229 article->get_content_size()+"\t"+
230 sizeof((article->get_content()/"\n"))+"\t"+
231 "Xref: "+_Server->get_server_name()+" " + get_name()+ ":"+mArticles[article];
232 NNTP_LOG("Header:\n"+res);
235 string header2(object article)
237 // taken from _Server->get_module("message")->header(object)
238 if ( !objectp(article) ) return "";
239 object creator = article->get_creator();
240 string name = creator->query_attribute(USER_EMAIL);
241 if ( !stringp(name) || name == "" )
242 name = creator->get_identfier() + "@"+sServer;
244 name = creator->query_attribute(USER_FULLNAME) + " <"+name+">";
245 return "From: " + name + HEADER_SEP +
246 "Date: " + timelib.smtp_time(article->query_attribute(OBJ_CREATION_TIME))+
248 "Subject: "+article->query_attribute(OBJ_NAME)+HEADER_SEP+
249 "Message-ID: " + message_id(article) +HEADER_SEP+
250 "Lines: " + (sizeof((article->get_content()/"\n"))) + HEADER_SEP;
254 * Formats the message-id (see RFC 1036, RFC 822)
256 string message_id(object article) {
258 if ( objectp(article) )
259 id = article->get_object_id();
262 return "<" + id + "@" + sServer + ">";
266 string get_references(object article) {
267 if ( !objectp(mReferences[article]) )
269 string ref = get_references(mReferences[article]);
270 if ( strlen(ref) > 0 ) ref += " ";
271 ref += message_id(mReferences[article]);
275 string get_header(object article) {
276 if ( !objectp(article) )
279 // string header = _Server->get_module("message")->header(article);
280 string header = header2(article);
282 header += "Path: not-for-mail"+HEADER_SEP+
283 "User-Agent: sTeam Forum"+HEADER_SEP+
284 "Newsgroups: " + get_name()+HEADER_SEP+
285 "Xref: "+_Server->get_server_name()+" "+get_name()+":"+mArticles[article]+HEADER_SEP+
286 (objectp(mReferences[article]) ?
287 "References:" + get_references(article) + HEADER_SEP : "");
290 string get_body(object article) {
291 return article->get_content();
293 object get_next_article(object article) {
294 array ids = values(mArticles);
296 int nextInIds = ids[1 + search( ids, article->get_object_id() )];
297 return search(mArticles, nextInIds);
300 void create(object o, string|void grp_name) {
302 if ( stringp(grp_name) )
307 int get_object_id() {
308 return (objectp(oSteamObj) ?
309 oSteamObj->get_object_id() :
312 object get_object() { return oSteamObj->get_object(); }
313 string get_identifier() { return "nntp:mailbox"; }
317 * Callback to initialize the module.
323 object objects = _Server->get_module("objects");
324 if ( objectp(objects) )
325 aGroups = ({ Group(objects->lookup("bugs")),
326 Group(objects->lookup("ideas")) });
329 sServer = _Server->get_server_name();
330 set_attribute(OBJ_DESC, "This module functions as a nntp server.");
331 add_data_storage(STORE_NEWSGRP, retrieve_groups, restore_groups);
337 * Restore function to restore group data of NNTP.
339 * @param mapping data - the saved data mapping.
342 final void restore_groups(mapping data)
344 if ( CALLER != _Database )
345 THROW("Invalid call to restore_groups() !", E_ACCESS);
346 array groups = data["groups"];
348 foreach(groups, object g) {
350 aGroups += ({ Group(g) });
357 * Retrieve the group data.
359 * @return Mapping of group data to be saved.
361 * @see restore_groups
364 final mapping retrieve_groups()
366 if ( CALLER != _Database )
367 THROW("Invalid call to retrieve_groups()", E_ACCESS);
369 foreach(aGroups, object g)
370 groups += ({ g->oSteamObj });
372 return ([ "groups": groups, ]);
378 * Register a new group.
380 * @param object grp - the new group to register.
381 * @param string|void name - optional name for the group.
382 * @return newly created Group class for the given group.
384 object register_group(object grp, string|void name)
386 if ( !_Server->is_a_factory(CALLER) )
388 object group = Group(grp, name);
389 aGroups += ({ group });
390 require_save(STORE_NEWSGRP);
395 * List the registered groups.
397 * @return array of registered Groups.
399 array(Group) list_groups()
405 * Get a certain group identified by 'id'.
407 * @param string id - the id of the group to get.
408 * @return the Group or 0.
410 Group get_group(string id)
412 NNTP_LOG("get_group("+id+")");
413 foreach(aGroups, object grp) {
414 if ( grp->get_name() == id )
421 * Get a mapping of article headers for an article with 'content'.
423 * @param string content - the content of the NNTP article.
424 * @return mapping of headers.
426 mapping article_header(string content)
431 // dont know whats this ????!!!! - cutting the header of at HEADER_SEP_RCV
432 int i = search(content, HEADER_SEP_RCV);
433 header = content[..i-1];
434 array settings = header / (HEADER_SEP);
436 foreach(settings, string setting ) {
438 if ( sscanf(setting, "%s: %s", key, val) == 2 )
439 header_map[lower_case(key)] = val;
441 NNTP_LOG("Header-Map="+sprintf("%O",header_map));
446 * Find all group in a string 'id' separated by ','. This subsequently calls
447 * get_group for each group found in 'id'.
449 * @param string id - groups id string.
450 * @return array of found groups.
453 array find_groups(string id)
455 array grps = id / ",";
456 array groups = ({ });
460 NNTP_LOG("find_groups("+id+")");
462 foreach(grps, string grp) {
463 groups += ({ get_group(grp) });
469 * Get the body text of an article which means the
470 * separator is found and the rest of the articles content returned.
472 * @param string content - the content of the article.
474 * @see article_header
476 string get_body(string content)
478 int i = search(content, HEADER_SEP_RCV);
479 string body = content[i+2..];
484 * Post an article with content. This will parse the header information
485 * and create an appropriate object inside sTeam.
487 * @param string content - the posted article.
488 * @return -1 no posting allowed, 0 posting failed, 1 success
490 int post_article(string content)
492 NNTP_LOG("New article :\n"+content);
493 mapping header = article_header(content);
494 object factory = _Server->get_factory(CLASS_DOCUMENT);
498 array groups = find_groups(header["newsgroups"]);
499 foreach ( groups, group ) {
500 NNTP_LOG("Checking group: " + group->get_name() + " for posting...");
501 if ( group->can_post() ) {
502 NNTP_LOG("Posting allowed...");
511 string mimetype = header["content-type"];
512 if ( !stringp(mimetype) )
513 mimetype = "text/html";
515 sscanf(mimetype, "%s;%*s", mimetype);
516 object article = factory->execute( ([ "name": header["subject"],
517 "mimetype": mimetype, ]));
518 article->set_content(get_body(content));
520 foreach(groups, group) {
521 LOG("Adding annotation on group...\n");
525 if ( arrayp(err) && sizeof(err) == 3 && err[2] == E_ACCESS )
535 string get_identifier() { return "nntp"; }