chris@3: /*
chris@3: * SONEWS News Server
chris@3: * see AUTHORS for the list of contributors
chris@3: *
chris@3: * This program is free software: you can redistribute it and/or modify
chris@3: * it under the terms of the GNU General Public License as published by
chris@3: * the Free Software Foundation, either version 3 of the License, or
chris@3: * (at your option) any later version.
chris@3: *
chris@3: * This program is distributed in the hope that it will be useful,
chris@3: * but WITHOUT ANY WARRANTY; without even the implied warranty of
chris@3: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
chris@3: * GNU General Public License for more details.
chris@3: *
chris@3: * You should have received a copy of the GNU General Public License
chris@3: * along with this program. If not, see .
chris@3: */
chris@3: package org.sonews.storage;
chris@3:
chris@3: import java.io.ByteArrayInputStream;
chris@3: import java.io.ByteArrayOutputStream;
chris@3: import java.io.IOException;
chris@3: import java.security.MessageDigest;
chris@3: import java.security.NoSuchAlgorithmException;
chris@3: import java.util.UUID;
chris@3: import java.util.ArrayList;
chris@3: import java.util.Enumeration;
chris@3: import java.util.List;
cli@51: import java.util.logging.Level;
chris@3: import javax.mail.Header;
chris@3: import javax.mail.Message;
chris@3: import javax.mail.MessagingException;
chris@3: import javax.mail.internet.InternetHeaders;
franta-hg@117: import org.sonews.acl.User;
chris@3: import org.sonews.config.Config;
cli@51: import org.sonews.util.Log;
chris@3:
chris@3: /**
chris@3: * Represents a newsgroup article.
chris@3: * @author Christian Lins
chris@3: * @author Denis Schwerdel
chris@3: * @since n3tpd/0.1
chris@3: */
cli@51: public class Article extends ArticleHead {
franta-hg@101:
franta-hg@117: private User sender;
chris@3:
cli@37: /**
cli@37: * Loads the Article identified by the given ID from the JDBCDatabase.
cli@37: * @param messageID
cli@37: * @return null if Article is not found or if an error occurred.
cli@37: */
cli@51: public static Article getByMessageID(final String messageID) {
cli@37: try {
cli@37: return StorageManager.current().getArticle(messageID);
cli@37: } catch (StorageBackendException ex) {
cli@51: Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
cli@37: return null;
cli@37: }
cli@37: }
cli@37: private byte[] body = new byte[0];
chris@3:
cli@37: /**
cli@37: * Default constructor.
cli@37: */
cli@51: public Article() {
cli@37: }
chris@3:
cli@37: /**
cli@37: * Creates a new Article object using the date from the given
cli@37: * raw data.
cli@37: */
cli@51: public Article(String headers, byte[] body) {
cli@37: try {
cli@37: this.body = body;
cli@33:
cli@37: // Parse the header
cli@37: this.headers = new InternetHeaders(
cli@51: new ByteArrayInputStream(headers.getBytes()));
chris@3:
cli@37: this.headerSrc = headers;
cli@37: } catch (MessagingException ex) {
cli@51: Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
cli@37: }
cli@37: }
chris@3:
cli@37: /**
cli@37: * Creates an Article instance using the data from the javax.mail.Message
cli@37: * object. This constructor is called by the Mailinglist gateway.
cli@37: * @see javax.mail.Message
cli@37: * @param msg
cli@37: * @throws IOException
cli@37: * @throws MessagingException
cli@37: */
cli@37: public Article(final Message msg)
cli@51: throws IOException, MessagingException {
cli@37: this.headers = new InternetHeaders();
chris@3:
cli@37: for (Enumeration e = msg.getAllHeaders(); e.hasMoreElements();) {
cli@37: final Header header = (Header) e.nextElement();
cli@37: this.headers.addHeader(header.getName(), header.getValue());
cli@37: }
chris@3:
cli@37: // Reads the raw byte body using Message.writeTo(OutputStream out)
cli@37: this.body = readContent(msg);
chris@3:
cli@37: // Validate headers
cli@37: validateHeaders();
cli@37: }
chris@3:
cli@37: /**
cli@37: * Reads from the given Message into a byte array.
cli@37: * @param in
cli@37: * @return
cli@37: * @throws IOException
cli@37: */
cli@37: private byte[] readContent(Message in)
cli@51: throws IOException, MessagingException {
cli@37: ByteArrayOutputStream out = new ByteArrayOutputStream();
cli@37: in.writeTo(out);
cli@37: return out.toByteArray();
cli@37: }
chris@3:
cli@37: /**
cli@37: * Removes the header identified by the given key.
cli@37: * @param headerKey
cli@37: */
cli@51: public void removeHeader(final String headerKey) {
cli@37: this.headers.removeHeader(headerKey);
cli@37: this.headerSrc = null;
cli@37: }
chris@3:
cli@37: /**
cli@37: * Generates a message id for this article and sets it into
cli@37: * the header object. You have to update the JDBCDatabase manually to make this
cli@37: * change persistent.
cli@37: * Note: a Message-ID should never be changed and only generated once.
cli@37: */
cli@51: private String generateMessageID() {
cli@37: String randomString;
cli@37: MessageDigest md5;
cli@37: try {
cli@37: md5 = MessageDigest.getInstance("MD5");
cli@37: md5.reset();
cli@37: md5.update(getBody());
cli@37: md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
cli@37: md5.update(getHeader(Headers.FROM)[0].getBytes());
cli@37: byte[] result = md5.digest();
cli@51: StringBuilder hexString = new StringBuilder();
cli@37: for (int i = 0; i < result.length; i++) {
cli@37: hexString.append(Integer.toHexString(0xFF & result[i]));
cli@37: }
cli@37: randomString = hexString.toString();
cli@51: } catch (NoSuchAlgorithmException ex) {
cli@51: Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
cli@37: randomString = UUID.randomUUID().toString();
cli@37: }
cli@37: String msgID = "<" + randomString + "@"
cli@51: + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
cli@37:
cli@37: this.headers.setHeader(Headers.MESSAGE_ID, msgID);
cli@37:
cli@37: return msgID;
cli@37: }
cli@37:
cli@37: /**
cli@37: * Returns the body string.
cli@37: */
cli@51: public byte[] getBody() {
cli@37: return body;
cli@37: }
cli@37:
cli@37: /**
cli@37: * @return Numerical IDs of the newsgroups this Article belongs to.
cli@37: */
cli@51: public List getGroups() {
cli@37: String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
cli@37: ArrayList groups = new ArrayList();
cli@37:
cli@37: try {
cli@37: for (String newsgroup : groupnames) {
cli@37: newsgroup = newsgroup.trim();
cli@37: Group group = StorageManager.current().getGroup(newsgroup);
cli@37: if (group != null && // If the server does not provide the group, ignore it
cli@51: !groups.contains(group)) // Yes, there may be duplicates
cli@37: {
cli@37: groups.add(group);
cli@37: }
cli@37: }
cli@37: } catch (StorageBackendException ex) {
cli@51: Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
cli@37: return null;
cli@37: }
cli@37: return groups;
cli@37: }
cli@37:
cli@51: public void setBody(byte[] body) {
cli@37: this.body = body;
cli@37: }
cli@37:
cli@37: /**
cli@37: *
cli@37: * @param groupname Name(s) of newsgroups
cli@37: */
cli@51: public void setGroup(String groupname) {
cli@37: this.headers.setHeader(Headers.NEWSGROUPS, groupname);
cli@37: }
cli@37:
cli@37: /**
cli@37: * Returns the Message-ID of this Article. If the appropriate header
cli@37: * is empty, a new Message-ID is created.
cli@37: * @return Message-ID of this Article.
cli@37: */
cli@51: public String getMessageID() {
cli@37: String[] msgID = getHeader(Headers.MESSAGE_ID);
cli@37: return msgID[0].equals("") ? generateMessageID() : msgID[0];
cli@37: }
cli@37:
cli@37: /**
cli@37: * @return String containing the Message-ID.
cli@37: */
cli@37: @Override
cli@51: public String toString() {
cli@37: return getMessageID();
cli@37: }
franta-hg@101:
franta-hg@101: /**
franta-hg@117: * @return sender – currently logged user – or null, if user is not authenticated.
franta-hg@101: */
franta-hg@117: public User getUser() {
franta-hg@117: return sender;
franta-hg@101: }
franta-hg@101:
franta-hg@101: /**
franta-hg@101: * This method is to be called from POST Command implementation.
franta-hg@117: * @param sender current username – or null, if user is not authenticated.
franta-hg@101: */
franta-hg@117: public void setUser(User sender) {
franta-hg@117: this.sender = sender;
franta-hg@101: }
chris@3: }