news.c 18 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  1. /*
  2. * This file is part of the UCB release of Plan 9. It is subject to the license
  3. * terms in the LICENSE file found in the top-level directory of this
  4. * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
  5. * part of the UCB release of Plan 9, including this file, may be copied,
  6. * modified, propagated, or distributed except according to the terms contained
  7. * in the LICENSE file.
  8. */
  9. /*
  10. * Acme interface to nntpfs.
  11. */
  12. #include <u.h>
  13. #include <libc.h>
  14. #include <bio.h>
  15. #include <thread.h>
  16. #include "win.h"
  17. #include <ctype.h>
  18. int canpost;
  19. int debug;
  20. int nshow = 20;
  21. int lo; /* next message to look at in dir */
  22. int hi; /* current hi message in dir */
  23. char *dir = "/mnt/news";
  24. char *group;
  25. char *from;
  26. typedef struct Article Article;
  27. struct Article {
  28. Ref;
  29. Article *prev;
  30. Article *next;
  31. Window *w;
  32. int n;
  33. int dead;
  34. int dirtied;
  35. int sayspost;
  36. int headers;
  37. int ispost;
  38. };
  39. Article *mlist;
  40. Window *root;
  41. int
  42. cistrncmp(char *a, char *b, int n)
  43. {
  44. while(n-- > 0){
  45. if(tolower(*a++) != tolower(*b++))
  46. return -1;
  47. }
  48. return 0;
  49. }
  50. int
  51. cistrcmp(char *a, char *b)
  52. {
  53. for(;;){
  54. if(tolower(*a) != tolower(*b++))
  55. return -1;
  56. if(*a++ == 0)
  57. break;
  58. }
  59. return 0;
  60. }
  61. char*
  62. skipwhite(char *p)
  63. {
  64. while(isspace(*p))
  65. p++;
  66. return p;
  67. }
  68. int
  69. gethi(void)
  70. {
  71. Dir *d;
  72. int hi;
  73. if((d = dirstat(dir)) == nil)
  74. return -1;
  75. hi = d->qid.vers;
  76. free(d);
  77. return hi;
  78. }
  79. char*
  80. fixfrom(char *s)
  81. {
  82. char *r, *w;
  83. s = estrdup(s);
  84. /* remove quotes */
  85. for(r=w=s; *r; r++)
  86. if(*r != '"')
  87. *w++ = *r;
  88. *w = '\0';
  89. return s;
  90. }
  91. char *day[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", };
  92. char *mon[] = {
  93. "Jan", "Feb", "Mar", "Apr",
  94. "May", "Jun", "Jul", "Aug",
  95. "Sep", "Oct", "Nov", "Dec",
  96. };
  97. char*
  98. fixdate(char *s)
  99. {
  100. char *f[10], *m, *t, *wd, tmp[40];
  101. int d, i, j, nf, hh, mm;
  102. nf = tokenize(s, f, nelem(f));
  103. wd = nil;
  104. d = 0;
  105. m = nil;
  106. t = nil;
  107. for(i=0; i<nf; i++){
  108. for(j=0; j<7; j++)
  109. if(cistrncmp(day[j], f[i], 3)==0)
  110. wd = day[j];
  111. for(j=0; j<12; j++)
  112. if(cistrncmp(mon[j], f[i], 3)==0)
  113. m = mon[j];
  114. j = atoi(f[i]);
  115. if(1 <= j && j <= 31 && d != 0)
  116. d = j;
  117. if(strchr(f[i], ':'))
  118. t = f[i];
  119. }
  120. if(d==0 || wd==nil || m==nil || t==nil)
  121. return nil;
  122. hh = strtol(t, 0, 10);
  123. mm = strtol(strchr(t, ':')+1, 0, 10);
  124. sprint(tmp, "%s %d %s %d:%.2d", wd, d, m, hh, mm);
  125. return estrdup(tmp);
  126. }
  127. void
  128. msgheadline(Biobuf *bin, int n, Biobuf *bout)
  129. {
  130. char *p, *q;
  131. char *date;
  132. char *from;
  133. char *subject;
  134. date = nil;
  135. from = nil;
  136. subject = nil;
  137. while(p = Brdline(bin, '\n')){
  138. p[Blinelen(bin)-1] = '\0';
  139. if((q = strchr(p, ':')) == nil)
  140. continue;
  141. *q++ = '\0';
  142. if(cistrcmp(p, "from")==0)
  143. from = fixfrom(skipwhite(q));
  144. else if(cistrcmp(p, "subject")==0)
  145. subject = estrdup(skipwhite(q));
  146. else if(cistrcmp(p, "date")==0)
  147. date = fixdate(skipwhite(q));
  148. }
  149. Bprint(bout, "%d/\t%s", n, from ? from : "");
  150. if(date)
  151. Bprint(bout, "\t%s", date);
  152. if(subject)
  153. Bprint(bout, "\n\t%s", subject);
  154. Bprint(bout, "\n");
  155. free(date);
  156. free(from);
  157. free(subject);
  158. }
  159. /*
  160. * Write the headers for at most nshow messages to b,
  161. * starting with hi and working down to lo.
  162. * Return number of first message not scanned.
  163. */
  164. int
  165. adddir(Biobuf *body, int hi, int lo, int nshow)
  166. {
  167. char *p, *q, tmp[40];
  168. int i, n;
  169. Biobuf *b;
  170. n = 0;
  171. for(i=hi; i>=lo && n<nshow; i--){
  172. sprint(tmp, "%d", i);
  173. p = estrstrdup(dir, tmp);
  174. if(access(p, OREAD) < 0){
  175. free(p);
  176. break;
  177. }
  178. q = estrstrdup(p, "/header");
  179. free(p);
  180. b = Bopen(q, OREAD);
  181. free(q);
  182. if(b == nil)
  183. continue;
  184. msgheadline(b, i, body);
  185. Bterm(b);
  186. n++;
  187. }
  188. return i;
  189. }
  190. /*
  191. * Show the first nshow messages in the window.
  192. * This depends on nntpfs presenting contiguously
  193. * numbered directories, and on the qid version being
  194. * the topmost numbered directory.
  195. */
  196. void
  197. dirwindow(Window *w)
  198. {
  199. if((hi=gethi()) < 0)
  200. return;
  201. if(w->data < 0)
  202. w->data = winopenfile(w, "data");
  203. fprint(w->ctl, "dirty\n");
  204. winopenbody(w, OWRITE);
  205. lo = adddir(w->body, hi, 0, nshow);
  206. winclean(w);
  207. }
  208. /* return 1 if handled, 0 otherwise */
  209. static int
  210. iscmd(char *s, char *cmd)
  211. {
  212. int len;
  213. len = strlen(cmd);
  214. return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
  215. }
  216. static char*
  217. skip(char *s, char *cmd)
  218. {
  219. s += strlen(cmd);
  220. while(*s==' ' || *s=='\t' || *s=='\n')
  221. s++;
  222. return s;
  223. }
  224. void
  225. unlink(Article *m)
  226. {
  227. if(mlist == m)
  228. mlist = m->next;
  229. if(m->next)
  230. m->next->prev = m->prev;
  231. if(m->prev)
  232. m->prev->next = m->next;
  233. m->next = m->prev = nil;
  234. }
  235. int mesgopen(char*);
  236. int fillmesgwindow(int, Article*);
  237. Article *newpost(void);
  238. void replywindow(Article*);
  239. void mesgpost(Article*);
  240. /*
  241. * Depends on d.qid.vers being highest numbered message in dir.
  242. */
  243. void
  244. acmetimer(Article *m, Window *w)
  245. {
  246. Biobuf *b;
  247. Dir *d;
  248. assert(m==nil && w==root);
  249. if((d = dirstat(dir))==nil | hi==d->qid.vers){
  250. free(d);
  251. return;
  252. }
  253. if(w->data < 0)
  254. w->data = winopenfile(w, "data");
  255. if(winsetaddr(w, "0", 0))
  256. write(w->data, "", 0);
  257. b = emalloc(sizeof(*b));
  258. Binit(b, w->data, OWRITE);
  259. adddir(b, d->qid.vers, hi+1, d->qid.vers);
  260. hi = d->qid.vers;
  261. Bterm(b);
  262. free(b);
  263. free(d);
  264. winselect(w, "0,.", 0);
  265. }
  266. int
  267. acmeload(Article*, Window*, char *s)
  268. {
  269. int nopen;
  270. //fprint(2, "load %s\n", s);
  271. nopen = 0;
  272. while(*s){
  273. /* skip directory name */
  274. if(strncmp(s, dir, strlen(dir))==0)
  275. s += strlen(dir);
  276. nopen += mesgopen(s);
  277. if((s = strchr(s, '\n')) == nil)
  278. break;
  279. s = skip(s, "");
  280. }
  281. return nopen;
  282. }
  283. int
  284. acmecmd(Article *m, Window *w, char *s)
  285. {
  286. int n;
  287. Biobuf *b;
  288. //fprint(2, "cmd %s\n", s);
  289. s = skip(s, "");
  290. if(iscmd(s, "Del")){
  291. if(m == nil){ /* don't close dir until messages close */
  292. if(mlist != nil){
  293. ctlprint(mlist->w->ctl, "show\n");
  294. return 1;
  295. }
  296. if(windel(w, 0))
  297. threadexitsall(nil);
  298. return 1;
  299. }else{
  300. if(windel(w, 0))
  301. m->dead = 1;
  302. return 1;
  303. }
  304. }
  305. if(m==nil && iscmd(s, "More")){
  306. s = skip(s, "More");
  307. if(n = atoi(s))
  308. nshow = n;
  309. if(w->data < 0)
  310. w->data = winopenfile(w, "data");
  311. winsetaddr(w, "$", 1);
  312. fprint(w->ctl, "dirty\n");
  313. b = emalloc(sizeof(*b));
  314. Binit(b, w->data, OWRITE);
  315. lo = adddir(b, lo, 0, nshow);
  316. Bterm(b);
  317. free(b);
  318. winclean(w);
  319. winsetaddr(w, ".,", 0);
  320. }
  321. if(m!=nil && !m->ispost && iscmd(s, "Headers")){
  322. m->headers = !m->headers;
  323. fillmesgwindow(-1, m);
  324. return 1;
  325. }
  326. if(iscmd(s, "Newpost")){
  327. m = newpost();
  328. winopenbody(m->w, OWRITE);
  329. Bprint(m->w->body, "%s\nsubject: \n\n", group);
  330. winclean(m->w);
  331. winselect(m->w, "$", 0);
  332. return 1;
  333. }
  334. if(m!=nil && !m->ispost && iscmd(s, "Reply")){
  335. replywindow(m);
  336. return 1;
  337. }
  338. // if(m!=nil && iscmd(s, "Replymail")){
  339. // fprint(2, "no replymail yet\n");
  340. // return 1;
  341. // }
  342. if(iscmd(s, "Post")){
  343. mesgpost(m);
  344. return 1;
  345. }
  346. return 0;
  347. }
  348. void
  349. acmeevent(Article *m, Window *w, Event *e)
  350. {
  351. Event *ea, *e2, *eq;
  352. char *s, *t, *buf;
  353. int na;
  354. //int n;
  355. //ulong q0, q1;
  356. switch(e->c1){ /* origin of action */
  357. default:
  358. Unknown:
  359. fprint(2, "unknown message %c%c\n", e->c1, e->c2);
  360. break;
  361. case 'T': /* bogus timer event! */
  362. acmetimer(m, w);
  363. break;
  364. case 'F': /* generated by our actions; ignore */
  365. break;
  366. case 'E': /* write to body or tag; can't affect us */
  367. break;
  368. case 'K': /* type away; we don't care */
  369. if(m && (e->c2 == 'I' || e->c2 == 'D')){
  370. m->dirtied = 1;
  371. if(!m->sayspost){
  372. wintagwrite(w, "Post ", 5);
  373. m->sayspost = 1;
  374. }
  375. }
  376. break;
  377. case 'M': /* mouse event */
  378. switch(e->c2){ /* type of action */
  379. case 'x': /* mouse: button 2 in tag */
  380. case 'X': /* mouse: button 2 in body */
  381. ea = nil;
  382. //e2 = nil;
  383. s = e->b;
  384. if(e->flag & 2){ /* null string with non-null expansion */
  385. e2 = recvp(w->cevent);
  386. if(e->nb==0)
  387. s = e2->b;
  388. }
  389. if(e->flag & 8){ /* chorded argument */
  390. ea = recvp(w->cevent); /* argument */
  391. na = ea->nb;
  392. recvp(w->cevent); /* ignore origin */
  393. }else
  394. na = 0;
  395. /* append chorded arguments */
  396. if(na){
  397. t = emalloc(strlen(s)+1+na+1);
  398. sprint(t, "%s %s", s, ea->b);
  399. s = t;
  400. }
  401. /* if it's a known command, do it */
  402. /* if it's a long message, it can't be for us anyway */
  403. // DPRINT(2, "exec: %s\n", s);
  404. if(!acmecmd(m, w, s)) /* send it back */
  405. winwriteevent(w, e);
  406. if(na)
  407. free(s);
  408. break;
  409. case 'l': /* mouse: button 3 in tag */
  410. case 'L': /* mouse: button 3 in body */
  411. //buf = nil;
  412. eq = e;
  413. if(e->flag & 2){
  414. e2 = recvp(w->cevent);
  415. eq = e2;
  416. }
  417. s = eq->b;
  418. if(eq->q1>eq->q0 && eq->nb==0){
  419. buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
  420. winread(w, eq->q0, eq->q1, buf);
  421. s = buf;
  422. }
  423. if(!acmeload(m, w, s))
  424. winwriteevent(w, e);
  425. break;
  426. case 'i': /* mouse: text inserted in tag */
  427. case 'd': /* mouse: text deleted from tag */
  428. break;
  429. case 'I': /* mouse: text inserted in body */
  430. case 'D': /* mouse: text deleted from body */
  431. if(m == nil)
  432. break;
  433. m->dirtied = 1;
  434. if(!m->sayspost){
  435. wintagwrite(w, "Post ", 5);
  436. m->sayspost = 1;
  437. }
  438. break;
  439. default:
  440. goto Unknown;
  441. }
  442. }
  443. }
  444. void
  445. dirthread(void *v)
  446. {
  447. Event *e;
  448. Window *w;
  449. w = v;
  450. while(e = recvp(w->cevent))
  451. acmeevent(nil, w, e);
  452. threadexitsall(nil);
  453. }
  454. void
  455. mesgthread(void *v)
  456. {
  457. Event *e;
  458. Article *m;
  459. m = v;
  460. while(!m->dead && (e = recvp(m->w->cevent)))
  461. acmeevent(m, m->w, e);
  462. //fprint(2, "msg %p exits\n", m);
  463. unlink(m);
  464. free(m->w);
  465. free(m);
  466. threadexits(nil);
  467. }
  468. /*
  469. Xref: news.research.att.com comp.os.plan9:7360
  470. Newsgroups: comp.os.plan9
  471. Path: news.research.att.com!batch0!uunet!ffx.uu.net!finch!news.mindspring.net!newsfeed.mathworks.com!fu-berlin.de!server1.netnews.ja.net!hgmp.mrc.ac.uk!pegasus.csx.cam.ac.uk!bath.ac.uk!ccsdhd
  472. From: Stephen Adam <saadam@bigpond.com>
  473. Subject: Future of Plan9
  474. Approved: plan9mod@bath.ac.uk
  475. X-Newsreader: Microsoft Outlook Express 5.00.2014.211
  476. X-Mimeole: Produced By Microsoft MimeOLE V5.00.2014.211
  477. Sender: ccsdhd@bath.ac.uk (Dennis Davis)
  478. Nntp-Posting-Date: Wed, 13 Dec 2000 21:28:45 EST
  479. NNTP-Posting-Host: 203.54.121.233
  480. Organization: Telstra BigPond Internet Services (http://www.bigpond.com)
  481. X-Date: Wed, 13 Dec 2000 20:43:37 +1000
  482. Lines: 12
  483. Message-ID: <xbIZ5.157945$e5.114349@newsfeeds.bigpond.com>
  484. References: <95pghu$3lf$1@news.fas.harvard.edu> <95ph36$3m9$1@news.fas.harvard.edu> <slrn980iic.u5q.mperrin@hcs.harvard.edu> <95png6$4ln$1@news.fas.harvard.edu> <95poqg$4rq$1@news.fas.harvard.edu> <slrn980vh8.2gb.myLastName@is07.fas.harvard.edu> <95q40h$66c$2@news.fas.harvard.edu> <95qjhu$8ke$1@news.fas.harvard.edu> <95riue$bu2$1@news.fas.harvard.edu> <95rnar$cbu$1@news.fas.harvard.edu>
  485. X-Msmail-Priority: Normal
  486. X-Trace: newsfeeds.bigpond.com 976703325 203.54.121.233 (Wed, 13 Dec 2000 21:28:45 EST)
  487. X-Priority: 3
  488. Date: Wed, 13 Dec 2000 10:49:50 GMT
  489. */
  490. char *skipheader[] =
  491. {
  492. "x-",
  493. "path:",
  494. "xref:",
  495. "approved:",
  496. "sender:",
  497. "nntp-",
  498. "organization:",
  499. "lines:",
  500. "message-id:",
  501. "references:",
  502. "reply-to:",
  503. "mime-",
  504. "content-",
  505. };
  506. int
  507. fillmesgwindow(int fd, Article *m)
  508. {
  509. Biobuf *b;
  510. char *p, tmp[40];
  511. int i, inhdr, copy, xfd;
  512. Window *w;
  513. xfd = -1;
  514. if(fd == -1){
  515. sprint(tmp, "%d/article", m->n);
  516. p = estrstrdup(dir, tmp);
  517. if((xfd = open(p, OREAD)) < 0){
  518. free(p);
  519. return 0;
  520. }
  521. free(p);
  522. fd = xfd;
  523. }
  524. w = m->w;
  525. if(w->data < 0)
  526. w->data = winopenfile(w, "data");
  527. if(winsetaddr(w, ",", 0))
  528. write(w->data, "", 0);
  529. winopenbody(m->w, OWRITE);
  530. b = emalloc(sizeof(*b));
  531. Binit(b, fd, OREAD);
  532. inhdr = 1;
  533. copy = 1;
  534. while(p = Brdline(b, '\n')){
  535. if(Blinelen(b)==1)
  536. inhdr = 0, copy=1;
  537. if(inhdr && !isspace(p[0])){
  538. copy = 1;
  539. if(!m->headers){
  540. if(cistrncmp(p, "from:", 5)==0){
  541. p[Blinelen(b)-1] = '\0';
  542. p = fixfrom(skip(p, "from:"));
  543. Bprint(m->w->body, "From: %s\n", p);
  544. free(p);
  545. copy = 0;
  546. continue;
  547. }
  548. for(i=0; i<nelem(skipheader); i++)
  549. if(cistrncmp(p, skipheader[i], strlen(skipheader[i]))==0)
  550. copy=0;
  551. }
  552. }
  553. if(copy)
  554. Bwrite(m->w->body, p, Blinelen(b));
  555. }
  556. Bterm(b);
  557. free(b);
  558. winclean(m->w);
  559. if(xfd != -1)
  560. close(xfd);
  561. return 1;
  562. }
  563. Article*
  564. newpost(void)
  565. {
  566. Article *m;
  567. char *p, tmp[40];
  568. static int nnew;
  569. m = emalloc(sizeof *m);
  570. sprint(tmp, "Post%d", ++nnew);
  571. p = estrstrdup(dir, tmp);
  572. m->w = newwindow();
  573. proccreate(wineventproc, m->w, STACK);
  574. winname(m->w, p);
  575. wintagwrite(m->w, "Post ", 5);
  576. m->sayspost = 1;
  577. m->ispost = 1;
  578. threadcreate(mesgthread, m, STACK);
  579. if(mlist){
  580. m->next = mlist;
  581. mlist->prev = m;
  582. }
  583. mlist = m;
  584. return m;
  585. }
  586. void
  587. replywindow(Article *m)
  588. {
  589. Biobuf *b;
  590. char *p, *ep, *q, tmp[40];
  591. int fd, copy;
  592. Article *reply;
  593. sprint(tmp, "%d/article", m->n);
  594. p = estrstrdup(dir, tmp);
  595. if((fd = open(p, OREAD)) < 0){
  596. free(p);
  597. return;
  598. }
  599. free(p);
  600. reply = newpost();
  601. winopenbody(reply->w, OWRITE);
  602. b = emalloc(sizeof(*b));
  603. Binit(b, fd, OREAD);
  604. copy = 0;
  605. while(p = Brdline(b, '\n')){
  606. if(Blinelen(b)==1)
  607. break;
  608. ep = p+Blinelen(b);
  609. if(!isspace(*p)){
  610. copy = 0;
  611. if(cistrncmp(p, "newsgroups:", 11)==0){
  612. for(q=p+11; *q!='\n'; q++)
  613. if(*q==',')
  614. *q = ' ';
  615. copy = 1;
  616. }else if(cistrncmp(p, "subject:", 8)==0){
  617. if(!strstr(p, " Re:") && !strstr(p, " RE:") && !strstr(p, " re:")){
  618. p = skip(p, "subject:");
  619. ep[-1] = '\0';
  620. Bprint(reply->w->body, "Subject: Re: %s\n", p);
  621. }else
  622. copy = 1;
  623. }else if(cistrncmp(p, "message-id:", 11)==0){
  624. Bprint(reply->w->body, "References: ");
  625. p += 11;
  626. copy = 1;
  627. }
  628. }
  629. if(copy)
  630. Bwrite(reply->w->body, p, ep-p);
  631. }
  632. Bterm(b);
  633. close(fd);
  634. free(b);
  635. Bprint(reply->w->body, "\n");
  636. winclean(reply->w);
  637. winselect(reply->w, "$", 0);
  638. }
  639. char*
  640. skipbl(char *s, char *e)
  641. {
  642. while(s < e){
  643. if(*s!=' ' && *s!='\t' && *s!=',')
  644. break;
  645. s++;
  646. }
  647. return s;
  648. }
  649. char*
  650. findbl(char *s, char *e)
  651. {
  652. while(s < e){
  653. if(*s==' ' || *s=='\t' || *s==',')
  654. break;
  655. s++;
  656. }
  657. return s;
  658. }
  659. /*
  660. * comma-separate possibly blank-separated strings in line; e points before newline
  661. */
  662. void
  663. commas(char *s, char *e)
  664. {
  665. char *t;
  666. /* may have initial blanks */
  667. s = skipbl(s, e);
  668. while(s < e){
  669. s = findbl(s, e);
  670. if(s == e)
  671. break;
  672. t = skipbl(s, e);
  673. if(t == e) /* no more words */
  674. break;
  675. /* patch comma */
  676. *s++ = ',';
  677. while(s < t)
  678. *s++ = ' ';
  679. }
  680. }
  681. void
  682. mesgpost(Article *m)
  683. {
  684. Biobuf *b;
  685. char *p, *ep;
  686. int isfirst, ishdr, havegroup, havefrom;
  687. p = estrstrdup(dir, "post");
  688. if((b = Bopen(p, OWRITE)) == nil){
  689. fprint(2, "cannot open %s: %r\n", p);
  690. free(p);
  691. return;
  692. }
  693. free(p);
  694. winopenbody(m->w, OREAD);
  695. ishdr = 1;
  696. isfirst = 1;
  697. havegroup = havefrom = 0;
  698. while(p = Brdline(m->w->body, '\n')){
  699. ep = p+Blinelen(m->w->body);
  700. if(ishdr && p+1==ep){
  701. if(!havegroup)
  702. Bprint(b, "Newsgroups: %s\n", group);
  703. if(!havefrom)
  704. Bprint(b, "From: %s\n", from);
  705. ishdr = 0;
  706. }
  707. if(ishdr){
  708. ep[-1] = '\0';
  709. if(isfirst && strchr(p, ':')==0){ /* group list */
  710. commas(p, ep);
  711. Bprint(b, "newsgroups: %s\n", p);
  712. havegroup = 1;
  713. isfirst = 0;
  714. continue;
  715. }
  716. if(cistrncmp(p, "newsgroup:", 10)==0){
  717. commas(skip(p, "newsgroup:"), ep);
  718. Bprint(b, "newsgroups: %s\n", skip(p, "newsgroup:"));
  719. havegroup = 1;
  720. continue;
  721. }
  722. if(cistrncmp(p, "newsgroups:", 11)==0){
  723. commas(skip(p, "newsgroups:"), ep);
  724. Bprint(b, "newsgroups: %s\n", skip(p, "newsgroups:"));
  725. havegroup = 1;
  726. continue;
  727. }
  728. if(cistrncmp(p, "from:", 5)==0)
  729. havefrom = 1;
  730. ep[-1] = '\n';
  731. }
  732. Bwrite(b, p, ep-p);
  733. }
  734. winclosebody(m->w);
  735. Bflush(b);
  736. if(write(Bfildes(b), "", 0) == 0)
  737. winclean(m->w);
  738. else
  739. fprint(2, "post: %r\n");
  740. Bterm(b);
  741. }
  742. int
  743. mesgopen(char *s)
  744. {
  745. char *p, tmp[40];
  746. int fd, n;
  747. Article *m;
  748. n = atoi(s);
  749. if(n==0)
  750. return 0;
  751. for(m=mlist; m; m=m->next){
  752. if(m->n == n){
  753. ctlprint(m->w->ctl, "show\n");
  754. return 1;
  755. }
  756. }
  757. sprint(tmp, "%d/article", n);
  758. p = estrstrdup(dir, tmp);
  759. if((fd = open(p, OREAD)) < 0){
  760. free(p);
  761. return 0;
  762. }
  763. m = emalloc(sizeof(*m));
  764. m->w = newwindow();
  765. m->n = n;
  766. proccreate(wineventproc, m->w, STACK);
  767. p[strlen(p)-strlen("article")] = '\0';
  768. winname(m->w, p);
  769. if(canpost)
  770. wintagwrite(m->w, "Reply ", 6);
  771. wintagwrite(m->w, "Headers ", 8);
  772. free(p);
  773. if(mlist){
  774. m->next = mlist;
  775. mlist->prev = m;
  776. }
  777. mlist = m;
  778. threadcreate(mesgthread, m, STACK);
  779. fillmesgwindow(fd, m);
  780. close(fd);
  781. windormant(m->w);
  782. return 1;
  783. }
  784. void
  785. usage(void)
  786. {
  787. fprint(2, "usage: News [-d /mnt/news] comp.os.plan9\n");
  788. exits("usage");
  789. }
  790. void
  791. timerproc(void *v)
  792. {
  793. Event e;
  794. Window *w;
  795. memset(&e, 0, sizeof e);
  796. e.c1 = 'T';
  797. w = v;
  798. for(;;){
  799. sleep(60*1000);
  800. sendp(w->cevent, &e);
  801. }
  802. }
  803. char*
  804. findfrom(void)
  805. {
  806. char *p, *u;
  807. Biobuf *b;
  808. u = getuser();
  809. if(u==nil)
  810. return "glenda";
  811. p = estrstrstrdup("/usr/", u, "/lib/newsfrom");
  812. b = Bopen(p, OREAD);
  813. free(p);
  814. if(b){
  815. p = Brdline(b, '\n');
  816. if(p){
  817. p[Blinelen(b)-1] = '\0';
  818. p = estrdup(p);
  819. Bterm(b);
  820. return p;
  821. }
  822. Bterm(b);
  823. }
  824. p = estrstrstrdup("/mail/box/", u, "/headers");
  825. b = Bopen(p, OREAD);
  826. free(p);
  827. if(b){
  828. while(p = Brdline(b, '\n')){
  829. p[Blinelen(b)-1] = '\0';
  830. if(cistrncmp(p, "from:", 5)==0){
  831. p = estrdup(skip(p, "from:"));
  832. Bterm(b);
  833. return p;
  834. }
  835. }
  836. Bterm(b);
  837. }
  838. return u;
  839. }
  840. void
  841. threadmain(int argc, char **argv)
  842. {
  843. char *p, *q;
  844. Dir *d;
  845. Window *w;
  846. ARGBEGIN{
  847. case 'D':
  848. debug++;
  849. break;
  850. case 'd':
  851. dir = EARGF(usage());
  852. break;
  853. default:
  854. usage();
  855. break;
  856. }ARGEND
  857. if(argc != 1)
  858. usage();
  859. from = findfrom();
  860. group = estrdup(argv[0]); /* someone will be cute */
  861. while(q=strchr(group, '/'))
  862. *q = '.';
  863. p = estrdup(argv[0]);
  864. while(q=strchr(p, '.'))
  865. *q = '/';
  866. p = estrstrstrdup(dir, "/", p);
  867. cleanname(p);
  868. if((d = dirstat(p)) == nil){ /* maybe it is a new group */
  869. if((d = dirstat(dir)) == nil){
  870. fprint(2, "dirstat(%s) fails: %r\n", dir);
  871. threadexitsall(nil);
  872. }
  873. if((d->mode&DMDIR)==0){
  874. fprint(2, "%s not a directory\n", dir);
  875. threadexitsall(nil);
  876. }
  877. free(d);
  878. if((d = dirstat(p)) == nil){
  879. fprint(2, "stat %s: %r\n", p);
  880. threadexitsall(nil);
  881. }
  882. }
  883. if((d->mode&DMDIR)==0){
  884. fprint(2, "%s not a directory\n", dir);
  885. threadexitsall(nil);
  886. }
  887. free(d);
  888. dir = estrstrdup(p, "/");
  889. q = estrstrdup(dir, "post");
  890. canpost = access(q, AWRITE)==0;
  891. w = newwindow();
  892. root = w;
  893. proccreate(wineventproc, w, STACK);
  894. proccreate(timerproc, w, STACK);
  895. winname(w, dir);
  896. if(canpost)
  897. wintagwrite(w, "Newpost ", 8);
  898. wintagwrite(w, "More ", 5);
  899. dirwindow(w);
  900. threadcreate(dirthread, w, STACK);
  901. threadexits(nil);
  902. }