0001: package org.methodize.nntprss.nntp;
0002:
0003: /* -----------------------------------------------------------
0004: * nntp//rss - a bridge between the RSS world and NNTP clients
0005: * Copyright (c) 2002, 2003 Jason Brome. All Rights Reserved.
0006: *
0007: * email: nntprss@methodize.org
0008: * mail: Methodize Solutions
0009: * PO Box 3865
0010: * Grand Central Station
0011: * New York NY 10163
0012: *
0013: * This file is part of nntp//rss
0014: *
0015: * nntp//rss is free software; you can redistribute it
0016: * and/or modify it under the terms of the GNU General
0017: * Public License as published by the Free Software Foundation;
0018: * either version 2 of the License, or (at your option) any
0019: * later version.
0020: *
0021: * This program is distributed in the hope that it will be
0022: * useful, but WITHOUT ANY WARRANTY; without even the implied
0023: * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
0024: * PURPOSE. See the GNU General Public License for more
0025: * details.
0026: *
0027: * You should have received a copy of the GNU General Public
0028: * License along with this program; if not, write to the
0029: * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
0030: * Boston, MA 02111-1307 USA
0031: * ----------------------------------------------------- */
0032:
0033: import java.io.BufferedReader;
0034: import java.io.ByteArrayInputStream;
0035: import java.io.IOException;
0036: import java.io.InputStreamReader;
0037: import java.io.OutputStream;
0038: import java.io.OutputStreamWriter;
0039: import java.io.PrintWriter;
0040: import java.io.StringWriter;
0041: import java.net.Socket;
0042: import java.text.DateFormat;
0043: import java.text.ParseException;
0044: import java.text.SimpleDateFormat;
0045: import java.util.ArrayList;
0046: import java.util.Date;
0047: import java.util.Iterator;
0048: import java.util.List;
0049: import java.util.Locale;
0050: import java.util.Properties;
0051: import java.util.StringTokenizer;
0052: import java.util.TimeZone;
0053:
0054: import javax.mail.MessagingException;
0055: import javax.mail.Multipart;
0056: import javax.mail.Session;
0057: import javax.mail.internet.MimeBodyPart;
0058: import javax.mail.internet.MimeMessage;
0059:
0060: import org.apache.log4j.Logger;
0061: import org.apache.log4j.Priority;
0062: import org.methodize.nntprss.rss.Channel;
0063: import org.methodize.nntprss.rss.ChannelManager;
0064: import org.methodize.nntprss.rss.Item;
0065: import org.methodize.nntprss.rss.publish.BloggerPublisher;
0066: import org.methodize.nntprss.rss.publish.LiveJournalPublisher;
0067: import org.methodize.nntprss.rss.publish.MetaWeblogPublisher;
0068: import org.methodize.nntprss.rss.publish.Publisher;
0069: import org.methodize.nntprss.rss.publish.PublisherException;
0070: import org.methodize.nntprss.util.AppConstants;
0071: import org.methodize.nntprss.util.CRLFPrintWriter;
0072: import org.methodize.nntprss.util.HTMLHelper;
0073: import org.methodize.nntprss.util.RSSHelper;
0074: import org.methodize.nntprss.util.XMLHelper;
0075:
0076: /**
0077: * @author Jason Brome <jason@methodize.org>
0078: * @version $Id: ClientHandler.java,v 1.7 2003/03/22 16:28:52 jasonbrome Exp $
0079: */
0080: public class ClientHandler implements Runnable {
0081:
0082: private Logger log = Logger.getLogger(ClientHandler.class);
0083:
0084: private Socket client = null;
0085: private ChannelManager channelManager = ChannelManager
0086: .getChannelManager();
0087: private DateFormat df;
0088: private DateFormat nntpDateFormat;
0089: private NNTPServer nntpServer;
0090:
0091: // Required
0092: private static final int NNTP_HEADER_UNKNOWN = -1;
0093: private static final int NNTP_HEADER_FROM = 1;
0094: private static final int NNTP_HEADER_DATE = 2;
0095: private static final int NNTP_HEADER_NEWSGROUP = 3;
0096: private static final int NNTP_HEADER_SUBJECT = 4;
0097: private static final int NNTP_HEADER_MESSAGE_ID = 5;
0098: private static final int NNTP_HEADER_PATH = 6;
0099:
0100: // Optional
0101: private static final int NNTP_HEADER_FOLLOWUP_TO = 7;
0102: private static final int NNTP_HEADER_EXPIRES = 8;
0103: private static final int NNTP_HEADER_REPLY_TO = 9;
0104: private static final int NNTP_HEADER_SENDER = 10;
0105: private static final int NNTP_HEADER_REFERENCES = 11;
0106: private static final int NNTP_HEADER_CONTROL = 12;
0107: private static final int NNTP_HEADER_DISTRIBUTION = 13;
0108: private static final int NNTP_HEADER_KEYWORDS = 14;
0109: private static final int NNTP_HEADER_SUMMARY = 15;
0110: private static final int NNTP_HEADER_APPROVED = 16;
0111: private static final int NNTP_HEADER_LINES = 17;
0112: private static final int NNTP_HEADER_XREF = 18;
0113: private static final int NNTP_HEADER_ORGANIZATION = 19;
0114:
0115: private static final int NO_CURRENT_ARTICLE = -1;
0116:
0117: public ClientHandler(NNTPServer nntpServer, Socket client) {
0118: this .nntpServer = nntpServer;
0119: this .client = client;
0120:
0121: df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",
0122: Locale.US);
0123: df.setTimeZone(TimeZone.getTimeZone("GMT"));
0124: nntpDateFormat = new SimpleDateFormat("yyMMdd HHmmss");
0125: }
0126:
0127: private int parseHeaderName(String headerName) {
0128: int header = NNTP_HEADER_UNKNOWN;
0129: if (headerName.equalsIgnoreCase("from")) {
0130: header = NNTP_HEADER_FROM;
0131: } else if (headerName.equalsIgnoreCase("date")) {
0132: header = NNTP_HEADER_DATE;
0133: } else if (headerName.equalsIgnoreCase("newsgroup")) {
0134: header = NNTP_HEADER_NEWSGROUP;
0135: } else if (headerName.equalsIgnoreCase("subject")) {
0136: header = NNTP_HEADER_SUBJECT;
0137: } else if (headerName.equalsIgnoreCase("message-id")) {
0138: header = NNTP_HEADER_MESSAGE_ID;
0139: } else if (headerName.equalsIgnoreCase("path")) {
0140: header = NNTP_HEADER_PATH;
0141: } else if (headerName.equalsIgnoreCase("followup-to")) {
0142: header = NNTP_HEADER_FOLLOWUP_TO;
0143: } else if (headerName.equalsIgnoreCase("expires")) {
0144: header = NNTP_HEADER_EXPIRES;
0145: } else if (headerName.equalsIgnoreCase("reply-to")) {
0146: header = NNTP_HEADER_REPLY_TO;
0147: } else if (headerName.equalsIgnoreCase("sender")) {
0148: header = NNTP_HEADER_SENDER;
0149: } else if (headerName.equalsIgnoreCase("references")) {
0150: header = NNTP_HEADER_REFERENCES;
0151: } else if (headerName.equalsIgnoreCase("control")) {
0152: header = NNTP_HEADER_CONTROL;
0153: } else if (headerName.equalsIgnoreCase("distribution")) {
0154: header = NNTP_HEADER_DISTRIBUTION;
0155: } else if (headerName.equalsIgnoreCase("keywords")) {
0156: header = NNTP_HEADER_KEYWORDS;
0157: } else if (headerName.equalsIgnoreCase("summary")) {
0158: header = NNTP_HEADER_SUMMARY;
0159: } else if (headerName.equalsIgnoreCase("approved")) {
0160: header = NNTP_HEADER_APPROVED;
0161: } else if (headerName.equalsIgnoreCase("lines")) {
0162: header = NNTP_HEADER_LINES;
0163: } else if (headerName.equalsIgnoreCase("xref")) {
0164: header = NNTP_HEADER_XREF;
0165: } else if (headerName.equalsIgnoreCase("organization")) {
0166: header = NNTP_HEADER_ORGANIZATION;
0167: }
0168:
0169: return header;
0170: }
0171:
0172: private String[] parseParameters(String commandString) {
0173:
0174: StringTokenizer strTok = new StringTokenizer(commandString);
0175: int tokens = strTok.countTokens();
0176: String[] parameters = new String[tokens];
0177: int paramPos = 0;
0178: while (strTok.hasMoreTokens()) {
0179: parameters[paramPos++] = strTok.nextToken();
0180: }
0181: return parameters;
0182: }
0183:
0184: private int[] getIntRange(String rangeStr) {
0185: int[] range = new int[2];
0186: if (rangeStr != null) {
0187: int rangePos = rangeStr.indexOf('-');
0188: if (rangePos > -1 && rangePos > 0
0189: && rangePos < rangeStr.length() - 1) {
0190: String startStr = rangeStr.substring(0, rangePos);
0191: String endStr = rangeStr.substring(rangePos + 1);
0192: range[0] = Integer.parseInt(startStr);
0193: range[1] = Integer.parseInt(endStr);
0194: } else if (rangePos > 0 && rangeStr.length() > 0) {
0195: range[0] = Integer.parseInt(rangeStr.substring(0,
0196: rangePos));
0197: range[1] = AppConstants.OPEN_ENDED_RANGE;
0198: } else if (rangePos == 0) {
0199: range[0] = AppConstants.OPEN_ENDED_RANGE;
0200: range[1] = Integer.parseInt(rangeStr.substring(1));
0201: } else {
0202: range[0] = Integer.parseInt(rangeStr);
0203: range[1] = range[0];
0204: }
0205: }
0206: return range;
0207: }
0208:
0209: private String createMessageId(Item item) {
0210: StringBuffer messageId = new StringBuffer();
0211: messageId.append('<');
0212: messageId.append(item.getSignature());
0213: messageId.append('@');
0214: messageId.append(item.getChannel().getName());
0215: messageId.append('>');
0216: return messageId.toString();
0217: }
0218:
0219: /**
0220: *
0221: * Very basic article writer
0222: *
0223: * @todo Consider char encoding issues
0224: * @todo Support additional links from article to nntp//rss services
0225: *
0226: */
0227:
0228: private void writeArticle(PrintWriter pw, PrintWriter bodyPw,
0229: Channel channel, Item item) throws IOException {
0230:
0231: writeHead(pw, channel, item);
0232: pw.println();
0233: pw.flush();
0234:
0235: writeBody(bodyPw, channel, item);
0236:
0237: }
0238:
0239: private void writeHead(PrintWriter pw, Channel channel, Item item)
0240: throws IOException {
0241:
0242: String boundary = "----=_Part_" + item.getDate().getTime();
0243:
0244: pw.print("From: ");
0245: if (channel.getManagingEditor() == null) {
0246: pw.println(channel.getAuthor());
0247: } else {
0248: pw.print(channel.getAuthor());
0249: pw.print(" <");
0250: pw.print(RSSHelper.parseEmail(channel.getManagingEditor()));
0251: pw.println(">");
0252: }
0253:
0254: pw.println("Newsgroups: " + channel.getName());
0255: pw.println("Date: " + df.format(item.getDate()));
0256: pw.println("Subject: " + processSubject(item.getTitle()));
0257: pw.println("Message-ID: " + "<" + item.getSignature() + "@"
0258: + channel.getName() + ">");
0259: pw.println("Path: nntprss");
0260: pw.println("MIME-Version: 1.0");
0261:
0262: switch (nntpServer.getContentType()) {
0263: case AppConstants.CONTENT_TYPE_TEXT:
0264: pw
0265: .println("Content-Type: text/plain; charset=utf-8; format=flowed");
0266: break;
0267: case AppConstants.CONTENT_TYPE_HTML:
0268: pw.println("Content-Type: text/html; charset=utf-8");
0269: break;
0270: default:
0271: // Mixed
0272: pw.println("Content-Type: multipart/alternative;");
0273: pw.println(" boundary=\"" + boundary + "\"");
0274: }
0275:
0276: pw.flush();
0277: }
0278:
0279: private void writeBody(PrintWriter pw, Channel channel, Item item)
0280: throws IOException {
0281:
0282: String boundary = null;
0283:
0284: if (nntpServer.getContentType() == AppConstants.CONTENT_TYPE_MIXED) {
0285: boundary = "----=_Part_" + item.getDate().getTime();
0286: pw.println("--" + boundary);
0287: pw
0288: .println("Content-Type: text/plain; charset=utf-8; format=flowed");
0289: pw.println();
0290: }
0291:
0292: // Plain text content
0293: if (nntpServer.getContentType() == AppConstants.CONTENT_TYPE_MIXED
0294: || nntpServer.getContentType() == AppConstants.CONTENT_TYPE_TEXT) {
0295:
0296: String description = HTMLHelper.unescapeString(XMLHelper
0297: .stripHtmlTags(item.getDescription()));
0298:
0299: if (description.length() > 0) {
0300: pw.println(description);
0301: pw.println();
0302: }
0303:
0304: if (item.getComments() != null
0305: && item.getComments().length() > 0) {
0306: pw.print("Comments: ");
0307: pw.println(item.getLink());
0308: }
0309:
0310: if (item.getLink() != null && item.getLink().length() > 0) {
0311: pw.print("Link: ");
0312: pw.println(item.getLink());
0313: }
0314:
0315: pw.println();
0316:
0317: if (channel.getTitle() != null
0318: || channel.getDescription() != null
0319: || channel.getLink() != null) {
0320:
0321: // RFC compliant sig delimiter
0322: pw.println("-- ");
0323:
0324: StringBuffer header = new StringBuffer();
0325:
0326: if (channel.getTitle() != null) {
0327: header.append(channel.getTitle());
0328: }
0329:
0330: if (channel.getLink() != null) {
0331: if (header.length() > 0) {
0332: header.append(' ');
0333: }
0334: header.append(channel.getLink());
0335: }
0336:
0337: if (channel.getDescription() != null) {
0338: if (header.length() > 0) {
0339: header.append("\r\n");
0340: }
0341: header.append(channel.getDescription());
0342: }
0343:
0344: pw.println(header.toString());
0345: }
0346:
0347: pw.println();
0348: pw.println("Served by nntp//rss v" + AppConstants.VERSION
0349: + " ( http://www.methodize.org/nntprss )");
0350: pw.println();
0351: }
0352:
0353: if (nntpServer.getContentType() == AppConstants.CONTENT_TYPE_MIXED) {
0354: pw.println("--" + boundary);
0355: pw.println("Content-Type: text/html; charset=utf-8");
0356: pw.println();
0357: }
0358:
0359: // HTML Content
0360: if (nntpServer.getContentType() == AppConstants.CONTENT_TYPE_MIXED
0361: || nntpServer.getContentType() == AppConstants.CONTENT_TYPE_HTML) {
0362: pw.println("<html>");
0363:
0364: // Handle relative links by inserting base
0365: if (channel.getLink() != null
0366: && channel.getLink().length() > 0) {
0367: pw.println("<head><base href='" + channel.getLink()
0368: + "'>");
0369: }
0370: pw.println("<body>");
0371:
0372: pw.println(item.getDescription());
0373:
0374: pw.println("<p>");
0375:
0376: boolean hasLinks = false;
0377: if (item.getComments() != null
0378: && item.getComments().length() > 0) {
0379: pw.print("<a href=\"");
0380: pw.print(item.getComments());
0381: pw.print("\">Comments</a> ");
0382: hasLinks = true;
0383: }
0384:
0385: // Output link
0386: if (item.getLink() != null && item.getLink().length() > 0) {
0387: if (hasLinks) {
0388: pw.println("| ");
0389: }
0390: pw.print("Link: <a href=\"");
0391: pw.print(item.getLink());
0392: pw.print("\">");
0393: pw.print(item.getLink());
0394: pw.println("</a>");
0395: hasLinks = true;
0396: }
0397:
0398: if (hasLinks) {
0399: pw.println("<br>");
0400: }
0401:
0402: pw.println("<hr>");
0403:
0404: if (channel.getTitle() != null
0405: || channel.getDescription() != null
0406: || channel.getLink() != null) {
0407:
0408: pw
0409: .println("<table width='100%' border='0'><tr><td align='left' valign='top'>");
0410:
0411: StringBuffer header = new StringBuffer();
0412:
0413: pw.println("<font size='-1'>");
0414:
0415: if (channel.getLink() == null) {
0416: if (channel.getTitle() != null) {
0417: header.append(channel.getTitle());
0418: }
0419: } else {
0420: header.append("<a href='");
0421: header.append(channel.getLink());
0422: header.append("'>");
0423: if (channel.getTitle() != null) {
0424: header.append(channel.getTitle());
0425: } else {
0426: header.append(channel.getLink());
0427: }
0428: header.append("</a>");
0429: }
0430:
0431: if (channel.getDescription() != null) {
0432: if (header.length() > 0) {
0433: header.append("<br>");
0434: }
0435: header.append(channel.getDescription());
0436: }
0437:
0438: pw.println(header.toString());
0439:
0440: pw.println("</font>");
0441: pw.println("</td><td align='right' valign='top'>");
0442: pw
0443: .println("<font size='-1'>Served by <a href=\"http://www.methodize.org/nntprss\">nntp//rss</a> v"
0444: + AppConstants.VERSION
0445: + "</font></td></tr></table>");
0446: } else {
0447: pw
0448: .println("<div align='right'><font size='-1'>Served by <a href=\"http://www.methodize.org/nntprss\">nntp//rss</a> v"
0449: + AppConstants.VERSION
0450: + "</font></div>");
0451: }
0452:
0453: pw.println("</body></html>");
0454: }
0455:
0456: if (nntpServer.getContentType() == AppConstants.CONTENT_TYPE_MIXED) {
0457: pw.println("--" + boundary + "--");
0458: }
0459:
0460: pw.flush();
0461:
0462: }
0463:
0464: /**
0465: * Main client request processing loop
0466: *
0467: * Note - for those reviewing this code - this is
0468: * not meant to be a full NNTP-server implementation.
0469: * It has been implemented to support the bare-minimum
0470: * required to support interaction from the popular
0471: * NNTP clients. Functionality is restricted to group
0472: * listing, and direct article retrieval (by article number
0473: * or message id)
0474: *
0475: */
0476:
0477: private void processRequestLoop(BufferedReader br, PrintWriter pw,
0478: PrintWriter bodyPw) throws IOException {
0479:
0480: boolean quitRequested = false;
0481: String currentGroupName = null;
0482: int currentArticle = NO_CURRENT_ARTICLE;
0483: String user = null;
0484: boolean userAuthenticated = false;
0485:
0486: while (quitRequested == false) {
0487: String requestString = br.readLine();
0488:
0489: String command = null;
0490:
0491: String[] parameters = null;
0492: if (requestString != null) {
0493: parameters = parseParameters(requestString);
0494: if (parameters.length > 0) {
0495: command = parameters[0];
0496: }
0497: } else {
0498: // If requestString == null - end of stream, indicate that client
0499: // wants to quit/has quit (result of OE testing)
0500: command = "QUIT";
0501: }
0502:
0503: if (command != null) {
0504: if (nntpServer.isSecure()
0505: && !userAuthenticated
0506: && (!command.equalsIgnoreCase("AUTHINFO") && !command
0507: .equalsIgnoreCase("QUIT"))) {
0508: pw.println("480 Authentication Required");
0509: } else if (command.equalsIgnoreCase("ARTICLE")
0510: || command.equalsIgnoreCase("HEAD")
0511: || command.equalsIgnoreCase("BODY")
0512: || command.equalsIgnoreCase("STAT")) {
0513: // pw.println("430 no such article found");
0514: Item item = null;
0515: Channel channel = null;
0516:
0517: // TODO resolve no current group scenario
0518:
0519: if (parameters.length == 1
0520: && currentArticle != NO_CURRENT_ARTICLE) {
0521: // Get current article
0522: channel = channelManager
0523: .channelByName(currentGroupName);
0524:
0525: item = channelManager.getChannelManagerDAO()
0526: .loadItem(channel, currentArticle);
0527:
0528: } else {
0529: String artNumOrMsgId = parameters[1];
0530:
0531: if (artNumOrMsgId.indexOf('<') == -1) {
0532: // Article number
0533: // item = channel.getItemByArticleNumber(Long.parseLong(artNumOrMsgId));
0534: channel = channelManager
0535: .channelByName(currentGroupName);
0536:
0537: item = channelManager
0538: .getChannelManagerDAO()
0539: .loadItem(
0540: channel,
0541: Integer
0542: .parseInt(artNumOrMsgId));
0543: } else {
0544: // Message IDs are in the form
0545: // <itemsignature@channelname>
0546: int sepPos = artNumOrMsgId.indexOf('@');
0547: if (sepPos > -1) {
0548: String itemSignature = artNumOrMsgId
0549: .substring(1, sepPos);
0550: String artChannelName = artNumOrMsgId
0551: .substring(
0552: sepPos + 1,
0553: artNumOrMsgId.length() - 1);
0554:
0555: channel = channelManager
0556: .channelByName(artChannelName);
0557: if (channel != null) {
0558: item = channelManager
0559: .getChannelManagerDAO()
0560: .loadItem(channel,
0561: itemSignature);
0562: }
0563:
0564: }
0565: }
0566: }
0567:
0568: if (item == null) {
0569: if (parameters.length == 1
0570: && currentArticle == NO_CURRENT_ARTICLE) {
0571: pw
0572: .println("420 no current article has been selected");
0573: } else {
0574: pw.println("430 no such article found");
0575: }
0576: } else {
0577: if (command.equalsIgnoreCase("ARTICLE")) {
0578: pw
0579: .println("220 "
0580: + item.getArticleNumber()
0581: + " <"
0582: + item.getSignature()
0583: + "@"
0584: + channel.getName()
0585: + "> article retrieved - head and body follow");
0586:
0587: writeArticle(pw, bodyPw, channel, item);
0588: } else if (command.equalsIgnoreCase("HEAD")) {
0589: pw
0590: .println("221 "
0591: + item.getArticleNumber()
0592: + " <"
0593: + item.getSignature()
0594: + "@"
0595: + channel.getName()
0596: + "> article retrieved - head follows");
0597:
0598: writeHead(pw, channel, item);
0599: } else if (command.equalsIgnoreCase("BODY")) {
0600: pw
0601: .println("222 "
0602: + item.getArticleNumber()
0603: + " <"
0604: + item.getSignature()
0605: + "@"
0606: + channel.getName()
0607: + "> article retrieved - body follows");
0608: writeBody(bodyPw, channel, item);
0609: } else if (command.equalsIgnoreCase("STAT")) {
0610: pw
0611: .println("223 "
0612: + item.getArticleNumber()
0613: + " <"
0614: + item.getSignature()
0615: + "@"
0616: + channel.getName()
0617: + "> article retrieved - request text separately");
0618: }
0619:
0620: currentArticle = item.getArticleNumber();
0621:
0622: pw.println(".");
0623: }
0624: } else if (command.equalsIgnoreCase("GROUP")) {
0625: currentGroupName = parameters[1];
0626: Channel channel = channelManager
0627: .channelByName(currentGroupName);
0628: if (channel != null) {
0629: pw.println("211 " + channel.getTotalArticles()
0630: + " " + channel.getFirstArticleNumber()
0631: + " " + channel.getLastArticleNumber()
0632: + " " + currentGroupName);
0633: currentArticle = channel
0634: .getFirstArticleNumber();
0635: } else {
0636: pw.println("411 no such news group");
0637: }
0638: } else if (command.equalsIgnoreCase("HELP")) {
0639: pw.println("100 help text follows");
0640: pw.println(".");
0641: } else if (command.equalsIgnoreCase("IHAVE")) {
0642: pw
0643: .println("435 article not wanted - do not send it");
0644: } else if (command.equalsIgnoreCase("LAST")) {
0645: if (currentGroupName == null) {
0646: pw
0647: .println("412 No news group currently selected");
0648: } else {
0649: Channel channel = channelManager
0650: .channelByName(currentGroupName);
0651: if (currentArticle == NO_CURRENT_ARTICLE) {
0652: pw
0653: .println("420 no current article has been selected");
0654: } else if (currentArticle > channel
0655: .getFirstArticleNumber()) {
0656: Item item = channelManager
0657: .getChannelManagerDAO()
0658: .loadPreviousItem(channel,
0659: currentArticle);
0660: currentArticle = item.getArticleNumber();
0661: pw
0662: .println("223 "
0663: + item.getArticleNumber()
0664: + " "
0665: + createMessageId(item)
0666: + " article retrieved - request text separately");
0667: } else {
0668: pw
0669: .println("422 no previous article in thie group");
0670: }
0671: }
0672: } else if (command.equalsIgnoreCase("LIST")) {
0673: if (parameters.length > 1
0674: && !parameters[1]
0675: .equalsIgnoreCase("ACTIVE")) {
0676: if (parameters[1]
0677: .equalsIgnoreCase("ACTIVE.TIMES")) {
0678: // pw.println("503 program error, function not performed");
0679: // Added for nn
0680: pw.println("215 information follows");
0681: pw.println(".");
0682: } else if (parameters[1]
0683: .equalsIgnoreCase("DISTRIBUTIONS")) {
0684: pw
0685: .println("503 program error, function not performed");
0686: } else if (parameters[1]
0687: .equalsIgnoreCase("DISTRIB.PATS")) {
0688: pw
0689: .println("503 program error, function not performed");
0690: } else if (parameters[1]
0691: .equalsIgnoreCase("NEWSGROUPS")) {
0692: // pw.println("503 program error, function not performed");
0693: pw
0694: .println("215 list of newsgroups follows");
0695: Iterator channelIter = channelManager
0696: .channels();
0697: while (channelIter.hasNext()) {
0698: Channel channel = (Channel) channelIter
0699: .next();
0700: pw.println(channel.getName() + " ");
0701: }
0702: pw.println(".");
0703: } else if (parameters[1]
0704: .equalsIgnoreCase("OVERVIEW.FMT")) {
0705: pw.println("215 information follows");
0706: pw.println("Subject:");
0707: pw.println("From:");
0708: pw.println("Date:");
0709: pw.println("Message-ID:");
0710: pw.println("References:");
0711: pw.println("Bytes:");
0712: pw.println("Lines:");
0713: // pw.println("Xref:full");
0714: pw.println(".");
0715: } else if (parameters[1]
0716: .equalsIgnoreCase("SUBSCRIPTIONS")) {
0717: // Empty default subscription list
0718: pw.println("215 information follows");
0719: pw.println(".");
0720: // pw.println("503 program error, function not performed");
0721: } else {
0722: pw
0723: .println("503 program error, function not performed");
0724: }
0725:
0726: } else {
0727: pw.println("215 list of newsgroups follows");
0728: Iterator channelIter = channelManager
0729: .channels();
0730: while (channelIter.hasNext()) {
0731: Channel channel = (Channel) channelIter
0732: .next();
0733: // group list first p
0734: pw
0735: .println(channel.getName()
0736: + " "
0737: + (channel
0738: .getLastArticleNumber() - 1)
0739: + " "
0740: + channel
0741: .getFirstArticleNumber()
0742: + " "
0743: + (channel
0744: .isPostingEnabled() ? "y"
0745: : "n"));
0746: }
0747: pw.println(".");
0748: }
0749: } else if (command.equalsIgnoreCase("LISTGROUP")) {
0750: Channel channel = null;
0751: if (parameters.length > 1) {
0752: channel = channelManager
0753: .channelByName(parameters[1]);
0754: } else {
0755: if (currentGroupName != null) {
0756: channel = channelManager
0757: .channelByName(currentGroupName);
0758: }
0759: }
0760:
0761: if (channel != null) {
0762: pw
0763: .println("211 list of article numbers follow");
0764:
0765: List items = channelManager
0766: .getChannelManagerDAO()
0767: .loadArticleNumbers(channel);
0768:
0769: Iterator itemIter = items.iterator();
0770: while (itemIter.hasNext()) {
0771: pw.println(itemIter.next());
0772: }
0773: pw.println(".");
0774:
0775: } else {
0776: pw.println("412 Not currently in newsgroup");
0777: }
0778: } else if (command.equalsIgnoreCase("MODE")) {
0779: pw.println("201 Hello, you can't post");
0780: } else if (command.equalsIgnoreCase("NEWGROUPS")) {
0781: if (parameters.length < 3) {
0782: pw.println("500 command not recognized");
0783: } else {
0784: if (parameters.length > 3
0785: && parameters[3]
0786: .equalsIgnoreCase("GMT")) {
0787: nntpDateFormat.setTimeZone(TimeZone
0788: .getTimeZone("GMT"));
0789: } else {
0790: nntpDateFormat.setTimeZone(TimeZone
0791: .getDefault());
0792: }
0793:
0794: Date startDate = null;
0795: String startDateStr = parameters[1] + " "
0796: + parameters[2];
0797: try {
0798: startDate = nntpDateFormat
0799: .parse(startDateStr);
0800: } catch (ParseException pe) {
0801: if (log.isDebugEnabled()) {
0802: log
0803: .debug("Invalid date received in NEWGROUPS request - "
0804: + startDateStr);
0805: }
0806: }
0807:
0808: if (startDate != null) {
0809: pw
0810: .println("231 list of new newsgroups follows");
0811: Iterator channelIter = channelManager
0812: .channels();
0813: while (channelIter.hasNext()) {
0814: Channel channel = (Channel) channelIter
0815: .next();
0816: // Only list channels created after the
0817: // start date provided by the nntp client
0818: if (channel.getCreated().after(
0819: startDate)) {
0820: pw
0821: .println(channel.getName()
0822: + " "
0823: + channel
0824: .getFirstArticleNumber()
0825: + " "
0826: + (channel
0827: .getLastArticleNumber() - 1)
0828: + " n");
0829: }
0830: }
0831: pw.println(".");
0832: } else {
0833: pw.println("500 command not recognized");
0834: }
0835: }
0836: } else if (command.equalsIgnoreCase("NEWNEWS")) {
0837: pw
0838: .println("230 list of new articles by message-id follows");
0839: pw.println(".");
0840: } else if (command.equalsIgnoreCase("NEXT")) {
0841: if (currentGroupName == null) {
0842: pw
0843: .println("412 No news group currently selected");
0844: } else {
0845: Channel channel = channelManager
0846: .channelByName(currentGroupName);
0847: if (currentArticle == NO_CURRENT_ARTICLE) {
0848: pw
0849: .println("420 no current article has been selected");
0850: } else if (currentArticle < channel
0851: .getLastArticleNumber()) {
0852: Item item = channelManager
0853: .getChannelManagerDAO()
0854: .loadNextItem(channel,
0855: currentArticle);
0856: currentArticle = item.getArticleNumber();
0857: pw
0858: .println("223 "
0859: + item.getArticleNumber()
0860: + " "
0861: + createMessageId(item)
0862: + " article retrieved - request text separately");
0863: } else {
0864: pw
0865: .println("421 no next article in this group");
0866: }
0867: }
0868: } else if (command.equalsIgnoreCase("POST")) {
0869: // pw.println("440 posting not allowed");
0870: cmdPost(br, pw);
0871: } else if (command.equalsIgnoreCase("QUIT")) {
0872: pw.println("205 closing connection - goodbye!");
0873: quitRequested = true;
0874: } else if (command.equalsIgnoreCase("SLAVE")) {
0875: pw.println("202 slave status noted");
0876: } else if (command.equalsIgnoreCase("XHDR")) {
0877: List items = null;
0878: int header = NNTP_HEADER_UNKNOWN;
0879: boolean useMessageId = false;
0880: if (parameters.length == 2) {
0881: header = parseHeaderName(parameters[1]);
0882: Channel channel = channelManager
0883: .channelByName(currentGroupName);
0884: Item item = channelManager
0885: .getChannelManagerDAO().loadItem(
0886: channel, currentArticle);
0887: if (item != null) {
0888: items = new ArrayList();
0889: items.add(item);
0890: }
0891: } else if (parameters.length == 3) {
0892: header = parseHeaderName(parameters[1]);
0893: // Check parameter for message id...
0894: if (parameters[2].charAt(0) == '<') {
0895: useMessageId = true;
0896:
0897: int sepPos = parameters[2].indexOf('@');
0898: if (sepPos > -1) {
0899: String itemSignature = parameters[2]
0900: .substring(1, sepPos);
0901: String artChannelName = parameters[2]
0902: .substring(
0903: sepPos + 1,
0904: parameters[2].length() - 1);
0905:
0906: Channel channel = channelManager
0907: .channelByName(artChannelName);
0908: if (channel != null) {
0909: Item item = channelManager
0910: .getChannelManagerDAO()
0911: .loadItem(channel,
0912: itemSignature);
0913:
0914: if (item != null) {
0915: items = new ArrayList();
0916: items.add(item);
0917: }
0918:
0919: }
0920:
0921: }
0922:
0923: } else {
0924: int[] range = getIntRange(parameters[2]);
0925: Channel channel = channelManager
0926: .channelByName(currentGroupName);
0927: items = channelManager
0928: .getChannelManagerDAO().loadItems(
0929: channel, range, false);
0930: }
0931: } else {
0932: // Invalid request...
0933: }
0934:
0935: if (header == NNTP_HEADER_UNKNOWN) {
0936: pw
0937: .println("500 command not recognized (unknown header name="
0938: + parameters[1] + ")");
0939: } else if (items == null || items.size() == 0) {
0940: pw.println("430 no such article");
0941: } else {
0942: pw.println("221 Header follows");
0943:
0944: // Only support XHDR on the following 6 required headers...
0945: if (header == NNTP_HEADER_FROM
0946: || header == NNTP_HEADER_DATE
0947: || header == NNTP_HEADER_NEWSGROUP
0948: || header == NNTP_HEADER_SUBJECT
0949: || header == NNTP_HEADER_MESSAGE_ID
0950: || header == NNTP_HEADER_PATH
0951: || header == NNTP_HEADER_REFERENCES
0952: || header == NNTP_HEADER_LINES) {
0953:
0954: Iterator itemIter = items.iterator();
0955: while (itemIter.hasNext()) {
0956: Item item = (Item) itemIter.next();
0957:
0958: if (!useMessageId) {
0959: pw.print(item.getArticleNumber());
0960: } else {
0961: pw.print("<"
0962: + item.getSignature()
0963: + "@"
0964: + item.getChannel()
0965: .getName() + ">");
0966: }
0967:
0968: pw.print(' ');
0969:
0970: switch (header) {
0971: case NNTP_HEADER_FROM:
0972: pw.println(item.getChannel()
0973: .getAuthor());
0974: break;
0975: case NNTP_HEADER_DATE:
0976: pw.println(df
0977: .format(item.getDate()));
0978: break;
0979: case NNTP_HEADER_NEWSGROUP:
0980: pw.println(item.getChannel()
0981: .getName());
0982: break;
0983: case NNTP_HEADER_SUBJECT:
0984: pw.println(processSubject(item
0985: .getTitle()));
0986: break;
0987: case NNTP_HEADER_MESSAGE_ID:
0988: pw.println("<"
0989: + item.getSignature()
0990: + "@"
0991: + item.getChannel()
0992: .getName() + ">");
0993: break;
0994: case NNTP_HEADER_PATH:
0995: pw.println("nntprss");
0996: break;
0997: case NNTP_HEADER_REFERENCES:
0998: pw.println();
0999: break;
1000: case NNTP_HEADER_LINES:
1001: // TODO Calculate actual lines
1002: pw.println(10);
1003: break;
1004: default:
1005: pw.println();
1006: break;
1007: }
1008: }
1009: }
1010: pw.println(".");
1011: }
1012: } else if (command.equalsIgnoreCase("XOVER")) {
1013: if (currentGroupName == null) {
1014: pw
1015: .println("412 No news group currently selected");
1016: } else {
1017: pw.println("224 Overview information follows");
1018: // Interpret parameters and restrict return
1019: Channel channel = channelManager
1020: .channelByName(currentGroupName);
1021: int[] range = getIntRange(parameters[1]);
1022: List items = channelManager
1023: .getChannelManagerDAO().loadItems(
1024: channel, range, false);
1025:
1026: if (items.size() == 0) {
1027: pw.println("420 No article(s) selected");
1028: } else {
1029: Iterator itemIter = items.iterator();
1030: while (itemIter.hasNext()) {
1031: Item item = (Item) itemIter.next();
1032: try {
1033: pw.println(item.getArticleNumber()
1034: + "\t"
1035: + processSubject(item
1036: .getTitle()) + "\t"
1037: + channel.getAuthor()
1038: + "\t"
1039: + df.format(item.getDate())
1040: + "\t"
1041: + "<"
1042: + item.getSignature()
1043: + "@"
1044: + channel.getName()
1045: + ">"
1046: + "\t" // no references
1047: // FIXME calculate content size and line count
1048: // This is currently a 'hack' - return an arbitrary line length
1049: // of 10 lines.
1050: + "\t"
1051: + item.getDescription()
1052: .length() + "\t10"
1053: // + "\tXref: nntprss "
1054: // + item.getChannel().getName()
1055: // + ":"
1056: // + item.getArticleNumber()
1057: );
1058: } catch (Exception e) {
1059: if (log.isEnabledFor(Priority.WARN)) {
1060: log
1061: .warn(
1062: "Exception thrown in XOVER",
1063: e);
1064: }
1065: // e.printStackTrace();
1066: }
1067: }
1068: pw.println(".");
1069: }
1070: }
1071: } else if (command.equalsIgnoreCase("AUTHINFO")) {
1072: if (parameters.length > 1) {
1073: if (!nntpServer.isSecure()) {
1074: if (parameters[1].equalsIgnoreCase("USER")
1075: || parameters[1]
1076: .equalsIgnoreCase("PASS")) {
1077: pw
1078: .println("281 Authentication accepted");
1079: } else if (parameters[1]
1080: .equalsIgnoreCase("GENERIC")) {
1081: pw.println("501 command not supported");
1082: } else {
1083: pw
1084: .println("500 command not recognized");
1085: }
1086: } else {
1087: if (parameters[1].equalsIgnoreCase("USER")) {
1088: user = parameters[2];
1089: pw
1090: .println("381 More authentication information required");
1091: } else if (parameters[1]
1092: .equalsIgnoreCase("PASS")) {
1093: if (nntpServer.isValidUser(user,
1094: parameters[2])) {
1095: pw
1096: .println("281 Authentication accepted");
1097: userAuthenticated = true;
1098: } else {
1099: pw.println("502 No permission");
1100: }
1101: } else if (parameters[1]
1102: .equalsIgnoreCase("GENERIC")) {
1103: pw.println("501 command not supported");
1104: } else {
1105: pw
1106: .println("500 command not recognized");
1107: }
1108: }
1109: } else {
1110: pw.println("500 command not recognized");
1111: }
1112: } else if (command.length() > 0) {
1113: // Unknown command
1114: pw.println("500 command not recognized");
1115: }
1116: } else {
1117: // Bad request received!
1118: // pw.println("500 command not recognized");
1119: }
1120: pw.flush();
1121: }
1122: }
1123:
1124: private void cmdPost(BufferedReader br, PrintWriter pw)
1125: throws IOException {
1126: pw
1127: .println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
1128: pw.flush();
1129:
1130: Properties props = new Properties();
1131: Session s = Session.getInstance(props, null);
1132:
1133: StringWriter sw = new StringWriter();
1134: String line = br.readLine();
1135: while (line != null && (!line.equals("."))) {
1136: sw.write(line);
1137: sw.write("\r\n");
1138: line = br.readLine();
1139: }
1140:
1141: sw.flush();
1142:
1143: try {
1144: String content = null;
1145: MimeMessage test = new MimeMessage(s,
1146: new ByteArrayInputStream(sw.getBuffer().toString()
1147: .getBytes()));
1148:
1149: String newsgroup = test.getHeader("Newsgroups")[0];
1150:
1151: Channel channel = channelManager.channelByName(newsgroup);
1152:
1153: if (channel != null && channel.isPostingEnabled()) {
1154:
1155: boolean htmlContent = false;
1156: if (test.isMimeType("text/plain")) {
1157: content = (String) test.getContent();
1158: } else if (test.isMimeType("text/html")) {
1159: htmlContent = true;
1160: // If it is HTML, let's trim the whitespace...
1161: content = HTMLHelper.stripCRLF((String) test
1162: .getContent());
1163: } else if (test.isMimeType("multipart/alternative")) {
1164: Multipart mp = (Multipart) test.getContent();
1165: MimeBodyPart mbp = (MimeBodyPart) mp.getBodyPart(1);
1166: // HTML content...
1167: if (mbp.isMimeType("text/html")) {
1168: htmlContent = true;
1169: // If it is HTML, let's trim the whitespace...
1170: content = HTMLHelper.stripCRLF((String) mbp
1171: .getContent());
1172: }
1173: }
1174:
1175: if (htmlContent) {
1176: // Parse HTML content and extract pertinent content...
1177: String upperContent = content.toUpperCase();
1178: int bodyPos = upperContent.indexOf("<BODY");
1179: if (bodyPos != -1) {
1180: int endBodyOpenPos = upperContent.indexOf(">",
1181: bodyPos);
1182: if (endBodyOpenPos != -1) {
1183: int bodyClosePos = upperContent
1184: .indexOf("</BODY");
1185: if (bodyClosePos != -1) {
1186: content = content.substring(
1187: endBodyOpenPos + 1,
1188: bodyClosePos);
1189: }
1190: }
1191: }
1192: }
1193:
1194: Publisher pub = null;
1195: if (channel.getPublishAPI().equals("blogger")) {
1196: pub = new BloggerPublisher();
1197: } else if (channel.getPublishAPI().equals("metaweblog")) {
1198: pub = new MetaWeblogPublisher();
1199: } else if (channel.getPublishAPI()
1200: .equals("livejournal")) {
1201: pub = new LiveJournalPublisher();
1202: }
1203:
1204: Item item = new Item();
1205: item.setDescription(content);
1206: item.setTitle(test.getSubject());
1207: pub.publish(channel.getPublishConfig(), item);
1208:
1209: pw.println("240 article posted ok");
1210:
1211: // request repoll
1212: channel.setLastPolled(null);
1213:
1214: } else if (!channel.isPostingEnabled()) {
1215: pw.println("440 posting not allowed to " + newsgroup);
1216: } else {
1217: pw.println("441 posting failed");
1218: }
1219: } catch (MessagingException me) {
1220: pw.println("441 posting failed");
1221: } catch (PublisherException pe) {
1222: // XXX give more thorough information
1223: pw.println("441 posting failed " + pe.getMessage().trim());
1224: }
1225:
1226: }
1227:
1228: /**
1229: * Handles NNTP Client Conversation
1230: */
1231: public void run() {
1232: try {
1233: BufferedReader br = new BufferedReader(
1234: new InputStreamReader(client.getInputStream()));
1235:
1236: OutputStream os = client.getOutputStream();
1237:
1238: // Use CRLF PrintWriter wrapper class for output...
1239: // Changed to enforce utf-8 encoding on output...
1240: PrintWriter pw = new CRLFPrintWriter(
1241: new OutputStreamWriter(client.getOutputStream()));
1242:
1243: PrintWriter bodyPw = new CRLFPrintWriter(
1244: new OutputStreamWriter(client.getOutputStream(),
1245: "utf-8"));
1246:
1247: // Send 201 connection header
1248: pw.println("200 nntp//rss v" + AppConstants.VERSION
1249: + " news server ready");
1250: pw.flush();
1251:
1252: processRequestLoop(br, pw, bodyPw);
1253:
1254: br.close();
1255: pw.close();
1256: bodyPw.close();
1257: client.close();
1258:
1259: } catch (Exception e) {
1260: if (!(e instanceof IOException)) {
1261: if (log.isEnabledFor(Priority.WARN)) {
1262: log.warn("Unexpected exception thrown", e);
1263: }
1264: }
1265: } finally {
1266: if (log.isInfoEnabled()) {
1267: log.info("NNTP Client connection closed");
1268: }
1269: }
1270: }
1271:
1272: private String processSubject(String subject) {
1273: StringBuffer strippedString = new StringBuffer();
1274: boolean lastCharBreak = false;
1275: for (int i = 0; i < subject.length(); i++) {
1276: char c = subject.charAt(i);
1277: if (c == '\n' || c == '\t') {
1278: if (!lastCharBreak) {
1279: strippedString.append(' ');
1280: }
1281: lastCharBreak = true;
1282: } else if (c != '\r') {
1283: strippedString.append(c);
1284: lastCharBreak = false;
1285: }
1286: }
1287: return HTMLHelper.unescapeString(strippedString.toString());
1288: }
1289:
1290: }
|