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: }