news.c 18 KB

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