Milán Major

netscape-revival

1 branch
Code

sun-java/classsrc/sun/net/www/protocol/news/newsFetcher.java

/*
 * @(#)newsFetcher.java	1.16 95/08/22 James Gosling, Jonathan Payne
 * 
 * Copyright (c) 1994 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for NON-COMMERCIAL purposes and without fee is hereby
 * granted provided that this copyright notice appears in all copies. Please
 * refer to the file "copyright.html" for further important copyright and
 * licensing information.
 * 
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY
 * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR
 * ITS DERIVATIVES.
 */

/*-
 *	news stream opener
 */

package sun.net.www.protocol.news;

import java.io.*;
import java.util.*;
import java.lang.SecurityManager;
import sun.net.nntp.*;
import sun.net.www.MessageHeader;
import awt.*;

class newsFetcher extends Thread {
    static Newsgroup Newsgroups[];
    static String newsrcName;

    static NntpClient news;
    String group;
    String article;
    Thread nntpOwner;		/* the nntp connection is inherently single
				 * threaded: we have to lock out multiple
				 * attempts to use it */


    private synchronized void lockServer() {
	while (nntpOwner != null) {
	    if (nntpOwner == Thread.currentThread())
		return;
	    else if (nntpOwner.isAlive())
		wait();
	    else
		break;
	}
	nntpOwner = Thread.currentThread();
    }

    private synchronized void unlockServer() {
	if (nntpOwner == Thread.currentThread())
	    nntpOwner = null;
	notifyAll();
    }

    private synchronized void handoffLock(Thread t) {
	if (nntpOwner == Thread.currentThread())
	    nntpOwner = t;
    }

    static String classLoaderSrc;

    void fireUp(String fn, OutputStream os) {
	try {
	    lockServer();
	    ps = new PrintStream(os);
	    if (news == null) {
		SecurityManager.setScopePermission();
		String serverEnvName = System.getProperty("nntp.server");
		SecurityManager.resetScopePermission();
		if (serverEnvName == null)
		    serverEnvName = "newshost";
		news = new NntpClient(serverEnvName);
	    }
	    article = null;
	    group = null;
	    if (fn != null) {
		while (fn.startsWith("/"))
		    fn = fn.substring(1);
		while (fn.endsWith("/"))
		    fn = fn.substring(0, fn.length() - 1);
		if (fn.length() > 0) {
		    int firstc = fn.charAt(0);
		    int seppos = fn.indexOf('/');
		    int atpos = fn.indexOf('@');
		    if (atpos > 0) {
			article = seppos > 0 && atpos > seppos
			    ? fn.substring(seppos + 1)
			    : fn;
		    } else if (seppos > 0) {
			group = fn.substring(0, seppos);
			article = fn.substring(seppos + 1);
		    } else
			group = fn;
		}
	    }
	    start();
	    handoffLock(this);
	} catch(Object e) {
	    unlockServer();
	    throw e;
	}
    }

    static Newsgroup findGroup(String s) {
	if (s != null && Newsgroups != null)
	    for (int i = Newsgroups.length; --i >= 0;) {
		Newsgroup p = Newsgroups[i];
		if (p.group.name.equals(s))
		    return p;
	    }
	return null;
    }

    static Newsgroup addGroup(String s) {
	Newsgroup r = new Newsgroup(s + "!", news);
	Newsgroup ng[];
	if (Newsgroups != null) {
	    ng = new Newsgroup[Newsgroups.length + 1];
	    System.arraycopy(Newsgroups, 0, ng, 0, Newsgroups.length);
	} else
	    ng = new Newsgroup[1];
	ng[ng.length - 1] = r;
	Newsgroups = ng;
	return r;
    }

    PrintStream ps;

    private String makeSafe(String s) {
	int start = 0;
	int pos;
	while ((pos = s.indexOf("&", start)) >= 0) {
	    start = pos + 3;
	    s = s.substring(0, pos) + "&" + s.substring(pos + 1);
	}
	start = 0;
	while ((pos = s.indexOf("<", start)) >= 0) {
	    start = pos + 3;
	    s = s.substring(0, pos) + "&lt;" + s.substring(pos + 1);
	}
	return s;
    }

    private void title(String s) {
	s = makeSafe(s);
	ps.print("<html><title>" + s + "</title>\n<body><H1>" + s + "</H1>\n");
    }

    private void button(String label, String target) {
	ps.print("<app class=sun.net.www.protocol.news.InlineButton label=\""
		 + label + "\" href=" + target + classLoaderSrc + ">\n");
    }

    private void StandardButtons(boolean allgroups) {
	if (allgroups) {
	    button("All Groups", "news:/");
	    button("Catch up", "catchup");
	}
	button("Regenerate page", "regen");
	button("Post", "post");
	button("Mail", "mail");
	button("Save", "saverc");
	button("Rescan", "rescan");
    }

    private void outStr(char s[], int i, int lim, boolean html) {
	while (i < lim) {
	    int c = s[i++];
	    switch (c) {
	      case '<':
		ps.print(html ? "<" : "&lt;");
		break;
	      case '&':
		ps.print(html ? "&" : "&amp;");
		break;
	      default:
		ps.write(c);
		break;
	    }
	}
    }

    private void FormatArticle(InputStream is, int articlenum) {
	boolean html = false;
	Newsgroup thisNG = null;
	MessageHeader mh = new MessageHeader(is);
	String gp = mh.findValue("newsgroups");
	if (gp == null || (gp.indexOf(",") >= 0 && group != null))
	    gp = group;
	String Subject = mh.findValue("subject");
	if (Subject == null) {
	    if (gp == null)
		Subject = "News message";
	    else
		Subject = "Message from " + gp;
	}
	thisNG = findGroup(gp);
	title(Subject);
	ps.print("<blockquote>\n");
	String s;
	if ((s = mh.findValue("from")) != null)
	    ps.print("<i>" + makeSafe(s) + "</i><br>\n");
	if ((s = mh.findValue("organization")) != null)
	    ps.print(makeSafe(s) + "<br>\n");
	if ((s = mh.findValue("date")) != null)
	    ps.print(makeSafe(s) + "<br>\n");
	ps.print("<p>\n");
	if (gp != null)
	    button("Group", "news:/" + gp);
	if (thisNG != null && articlenum > 0) {
	    thisNG.markAsRead(articlenum);
	    if (articlenum > thisNG.group.firstArticle)
		button("Previous", "prev");
	    if (articlenum < thisNG.group.lastArticle)
		button("Next", "next");
	}
	if ((s = mh.findValue("content-type")) != null &&
		s.startsWith("text/html"))
	    html = true;
	StandardButtons(true);
	ps.print("</blockquote>\n");
	ps.print("<hr><pre>");
	int c;
	char line[] = new char[40];
	int len = 0;
	while ((c = is.read()) >= 0) {
	    if (c == '\n') {
		int outed = 0;
		int limit = len - 1;
		if (!html) {
		    for (int i = 3; i < limit; i++) {
			if (line[i] == ':') {
			    int st = -1;
			    if (line[i + 1] == '/' && line[i - 1] == 'p'
				&& line[i - 2] == 't')
			    {
				if (line[i - 3] == 't'
				    && line[i - 4] == 'h')
				{
				    st = 4;	/* seen http: */
				} else if (line[i - 3] == 'f') {
				    st = 3;	/* seen ftp: */
				}
			    } else if (line[i - 1] == 's'
				       && line[i - 2] == 'w'
				       && line[i - 3] == 'e'
				       && line[i - 4] == 'n')
			    {
				st = 4;	/* seen news: */
			    }
			    if (st > 0) {
				outStr(line, outed, i - st, false);
				outed = i - st;
				while (i < len
				       && (c = line[i]) > ' ' && c != '>'
				       && c != '"' && c != '\'' && c != ';'
				       && c != ')' && c != ']' && c != '}')
				{
				    i++;
				}
				while (i > outed && (line[i - 1] == ','
						     || line[i - 1] == '.'))
				{
				    i--;
				}
				ps.print("<a href=\""
					 + new String(line, outed, i - outed)
					 + "\">");
				outStr(line, outed, i, false);
				ps.print("</a>");
				outed = i;
			    }
			}
		    }
		}
		outStr(line, outed, len, html);
		ps.write('\n');
		len = 0;
	    } else {
		if (len >= line.length) {
		    char nln[] = new char[line.length * 2];
		    System.arraycopy(line, 0, nln, 0, line.length);
		    line = nln;
		}
		line[len++] = (char) c;
	    }
	}
	ps.print("</pre>\n");
    }

    private NewsDirectoryEntry dumpRange(Newsgroup ng, NewsDirectoryEntry root,
					 int first, int last)
    {
	for (int j = first; j <= last; j++) {
	    try {
		InputStream is = news.getHeader(j);
		MessageHeader mh = new MessageHeader(is);
		if (is == null)
		    ps.print("Null stream pointer\n");
		else {
		    is.close();
		    String From = mh.findValue("from");
		    String mid = mh.findValue("message-id");
		    if (root != null && root.find(mid))
			continue;
		    if (From != null) {
			int lparen = From.indexOf('(');
			if (lparen >= 0) {
			    int rparen = From.indexOf(')', lparen);
			    if (rparen >= 0)
				From = From.substring(lparen + 1, rparen);
			}
		    }
		    NewsDirectoryEntry n = new NewsDirectoryEntry(
					   mh.findValue("subject"), From, j,
					   mid, mh.findValue("references"));
		    root = n.insert(root, true);
		}
	    } catch(Exception e) {
		if (ng != null)
		    ng.markAsRead(j);
	    }
	}
	return root;
    }

    public void run() {
	try {
	    if (classLoaderSrc == null) {
		classLoaderSrc = " src=news:///";
		/* AVH: I have no idea how to fix this
		try {
		    classLoaderSrc = " src=" + ((WWWClassLoader) this.getClass().getClassLoader()).ctx.toExternalForm();
		} catch(Exception e) {
		}
		*/
	    }
	    Newsgroup p = null;
	    if (Newsgroups == null) {
		SecurityManager.setScopePermission();
		newsrcName = System.getProperty("user.home") + File.separator + ".newsrc";
		Newsgroups = Newsgroup.readNewsrcFile(newsrcName, news);
		if (Newsgroups == null) {
		    newsrcName = System.getProperty("user.home") + File.separator + ".hotjava"
			+ File.separator + "newsrc";
		    Newsgroups = Newsgroup.readNewsrcFile(newsrcName, news);
		}
		SecurityManager.resetScopePermission();
		if (Newsgroups == null)
		    Newsgroups = new Newsgroup[0];
	    }
	    int articlenum = -1;
	    try {
		articlenum = Integer.parseInt(article);
	    } catch(Exception e) {
	    }
	    if (article == null && group == null) {
		title("Newsgroup Directory");
		StandardButtons(false);
		ps.print("<p>\n");
		if (Newsgroups == null || Newsgroups.length == 0) {
		    ps.print("No subscribed newsgroups or .newsrc is unreadable.  If the news: protocol handler was dynamically loaded, it probably doesn't have the necessary authority to read .newsrc.\n<br>Goto the url news:<i>groupname</i> to read a new group");
		} else
		    for (int i = 0; i < Newsgroups.length; i++) {
			p = Newsgroups[i];
			if (p.subscribed) {
			    int unread = p.articles != null ? p.articles.size() : 0;
			    if (unread > 0)
				ps.print("<br><app class=sun.net.www.protocol.news.TotalUnReadIndicator group=" +
					 p.group.name +
					 classLoaderSrc +
					 "><a href=news:/" + p.group.name +
					 "> " + p.group.name
					 + "</a>\n");
			}
		    }
	    } else if (group != null) {
		if (articlenum <= 0) {
		    int i;
		    boolean inConversation = false;
		    title("Newsgroup " + group);
		    StandardButtons(true);
		    boolean groupExists = false;
		    try {
			news.setGroup(group);
			groupExists = true;
		    } catch(Exception e) {
		    }
		    p = findGroup(group);
		    if (p == null) {
			try {
			    p = addGroup(group);
			} catch(Exception e) {
			    p = null;
			}
		    }
		    NumberSet ns = p == null ? null : p.articles;
		    NewsDirectoryEntry root = null;
		    if (p != null && p.subscribed)
			button("Unsubscribe", "unsubscribe");
		    else
			button("Subscribe", "subscribe");
		    boolean readingUnread = true;
		    if (article != null && article.startsWith("upto")) {
			int min = p.group.firstArticle;
			if (min <= 0) {
			    p.group = news.getGroup(p.group.name);
			    min = p.group.firstArticle;
			}
			int max = p.group.lastArticle;
			int hi = max;
			readingUnread = false;
			try {
			    hi = Integer.parseInt(article.substring(4));
			} catch(Exception e) {
			}
			if (hi > max)
			    hi = max;
			int lo = hi - 40;
			if (lo < min)
			    lo = min;
			if (lo > min)
			    button("Earlier",
				   "news:/" + group + "/upto" + (lo + 5));
			if (hi < max)
			    button("Later",
				   "news:/" + group + "/upto" + (hi + 35));
			button("New or unread", "news:/" + group);
			root = dumpRange(p, root, lo, hi);
		    } else {
			button("View All", "news:/" + group + "/upto");
			if (ns != null) {
			    for (i = 0; i < ns.nranges; i++) {
				root = dumpRange(p, root,
						 ns.starts[i], ns.ends[i]);
			    }
			}
		    }
		    // button("Catch up", "catchup");
		    ps.print("<hr>\n");
		    if (root == null) {
			ps.print("No articles found.\n");
			if (readingUnread) {
			    ps.print("Use ");
			    button("View All", "news:/" + group + "/upto");
			    ps.print("to read all messages.\n");
			}
		    }
		    addTree(root, true);
		} else {
		    try {
			news.setGroup(group);
			FormatArticle(news.getArticle(articlenum), articlenum);
		    } catch(Exception e) {
			ps.print("<hr>Error fetching article:<br> <i> " + e + "</i>\n");
		    }
		}
	    } else {
		/* group == null */
		try {
		    FormatArticle(news.getArticle(article), -1);
		} catch(Exception e) {
		    ps.print("<hr><H1>Error fetching article: " + e + "</H1>\n");
		}
	    }
	    ps.print("</body></html>\n");
	    ps.close();
	    unlockServer();
	} catch(Exception e) {
	    try {
		ps.close();
	    } catch(Exception e2) {
	    }
	    try {
		unlockServer();
	    } catch(Exception e3) {
	    }
	}
    }

    void addTree(NewsDirectoryEntry n, boolean toplevel) {
	ps.print("<dl compact>\n");
	while (n != null) {
	    ps.print("<dt><app class=sun.net.www.protocol.news.ReadIndicator"
		     + " group=" + group
		     + " article=" + n.anum
		     + classLoaderSrc
		     + ">\n");
	    ps.print("<dd><a href=news:/" + group + "/" + n.anum + ">\n");
	    if (toplevel) {
		ps.print(makeSafe(n.subject));
	    }
	    if (n.author != null) {
		if (toplevel)
		    ps.print(", ");
		ps.print("<i>" + makeSafe(n.author) + "</i>");
	    }
	    ps.print("</a>\n");
	    if (n.child != null) {
		addTree(n.child, false);
	    }
	    n = n.next;
	}
	ps.print("</dl>\n");
    }
}