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: 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; 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; chris@3: import org.sonews.config.Config; 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: */ chris@3: public class Article extends ArticleHead chris@3: { chris@3: chris@3: /** chris@3: * Loads the Article identified by the given ID from the JDBCDatabase. chris@3: * @param messageID chris@3: * @return null if Article is not found or if an error occurred. chris@3: */ chris@3: public static Article getByMessageID(final String messageID) chris@3: { chris@3: try chris@3: { chris@3: return StorageManager.current().getArticle(messageID); chris@3: } chris@3: catch(StorageBackendException ex) chris@3: { chris@3: ex.printStackTrace(); chris@3: return null; chris@3: } chris@3: } chris@3: chris@3: private byte[] body = new byte[0]; chris@3: chris@3: /** chris@3: * Default constructor. chris@3: */ chris@3: public Article() chris@3: { chris@3: } chris@3: chris@3: /** chris@3: * Creates a new Article object using the date from the given chris@3: * raw data. chris@3: */ chris@3: public Article(String headers, byte[] body) chris@3: { chris@3: try chris@3: { chris@3: this.body = body; chris@3: chris@3: // Parse the header chris@3: this.headers = new InternetHeaders( chris@3: new ByteArrayInputStream(headers.getBytes())); chris@3: chris@3: this.headerSrc = headers; chris@3: } chris@3: catch(MessagingException ex) chris@3: { chris@3: ex.printStackTrace(); chris@3: } chris@3: } chris@3: chris@3: /** chris@3: * Creates an Article instance using the data from the javax.mail.Message cli@33: * object. This constructor is called by the Mailinglist gateway. chris@3: * @see javax.mail.Message chris@3: * @param msg chris@3: * @throws IOException chris@3: * @throws MessagingException chris@3: */ chris@3: public Article(final Message msg) chris@3: throws IOException, MessagingException chris@3: { chris@3: this.headers = new InternetHeaders(); chris@3: chris@3: for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();) chris@3: { chris@3: final Header header = (Header)e.nextElement(); chris@3: this.headers.addHeader(header.getName(), header.getValue()); chris@3: } cli@33: cli@33: // Reads the raw byte body using Message.writeTo(OutputStream out) cli@33: this.body = readContent(msg); chris@3: chris@3: // Validate headers chris@3: validateHeaders(); chris@3: } chris@3: chris@3: /** cli@33: * Reads from the given Message into a byte array. chris@3: * @param in chris@3: * @return chris@3: * @throws IOException chris@3: */ cli@33: private byte[] readContent(Message in) cli@33: throws IOException, MessagingException chris@3: { chris@3: ByteArrayOutputStream out = new ByteArrayOutputStream(); cli@33: in.writeTo(out); chris@3: return out.toByteArray(); chris@3: } chris@3: chris@3: /** chris@3: * Removes the header identified by the given key. chris@3: * @param headerKey chris@3: */ chris@3: public void removeHeader(final String headerKey) chris@3: { chris@3: this.headers.removeHeader(headerKey); chris@3: this.headerSrc = null; chris@3: } chris@3: chris@3: /** chris@3: * Generates a message id for this article and sets it into chris@3: * the header object. You have to update the JDBCDatabase manually to make this chris@3: * change persistent. chris@3: * Note: a Message-ID should never be changed and only generated once. chris@3: */ chris@3: private String generateMessageID() chris@3: { chris@3: String randomString; chris@3: MessageDigest md5; chris@3: try chris@3: { chris@3: md5 = MessageDigest.getInstance("MD5"); chris@3: md5.reset(); chris@3: md5.update(getBody()); chris@3: md5.update(getHeader(Headers.SUBJECT)[0].getBytes()); chris@3: md5.update(getHeader(Headers.FROM)[0].getBytes()); chris@3: byte[] result = md5.digest(); chris@3: StringBuffer hexString = new StringBuffer(); chris@3: for (int i = 0; i < result.length; i++) chris@3: { chris@3: hexString.append(Integer.toHexString(0xFF & result[i])); chris@3: } chris@3: randomString = hexString.toString(); chris@3: } chris@3: catch (NoSuchAlgorithmException e) chris@3: { chris@3: e.printStackTrace(); chris@3: randomString = UUID.randomUUID().toString(); chris@3: } chris@3: String msgID = "<" + randomString + "@" chris@3: + Config.inst().get(Config.HOSTNAME, "localhost") + ">"; chris@3: chris@3: this.headers.setHeader(Headers.MESSAGE_ID, msgID); chris@3: chris@3: return msgID; chris@3: } chris@3: chris@3: /** chris@3: * Returns the body string. chris@3: */ chris@3: public byte[] getBody() chris@3: { chris@3: return body; chris@3: } chris@3: chris@3: /** chris@3: * @return Numerical IDs of the newsgroups this Article belongs to. chris@3: */ chris@3: public List getGroups() chris@3: { chris@3: String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(","); chris@3: ArrayList groups = new ArrayList(); chris@3: chris@3: try chris@3: { chris@3: for(String newsgroup : groupnames) chris@3: { chris@3: newsgroup = newsgroup.trim(); chris@3: Group group = StorageManager.current().getGroup(newsgroup); chris@3: if(group != null && // If the server does not provide the group, ignore it chris@3: !groups.contains(group)) // Yes, there may be duplicates chris@3: { chris@3: groups.add(group); chris@3: } chris@3: } chris@3: } chris@3: catch(StorageBackendException ex) chris@3: { chris@3: ex.printStackTrace(); chris@3: return null; chris@3: } chris@3: return groups; chris@3: } chris@3: chris@3: public void setBody(byte[] body) chris@3: { chris@3: this.body = body; chris@3: } chris@3: chris@3: /** chris@3: * chris@3: * @param groupname Name(s) of newsgroups chris@3: */ chris@3: public void setGroup(String groupname) chris@3: { chris@3: this.headers.setHeader(Headers.NEWSGROUPS, groupname); chris@3: } chris@3: chris@3: /** chris@3: * Returns the Message-ID of this Article. If the appropriate header chris@3: * is empty, a new Message-ID is created. chris@3: * @return Message-ID of this Article. chris@3: */ chris@3: public String getMessageID() chris@3: { chris@3: String[] msgID = getHeader(Headers.MESSAGE_ID); chris@3: return msgID[0].equals("") ? generateMessageID() : msgID[0]; chris@3: } chris@3: chris@3: /** chris@3: * @return String containing the Message-ID. chris@3: */ chris@3: @Override chris@3: public String toString() chris@3: { chris@3: return getMessageID(); chris@3: } chris@3: chris@3: }