All Implemented Interfaces:
Serializable, Servlet, ServletConfig

public class WebMailextends HttpServlet
Main webmail application class providing email functionality for I2P.
See Also:
  • Field Details

  • Constructor Details

    • WebMail

      public WebMail()
      A fresh servlet: the logger is built by the field initialiser and every other setting is static, so GenericServlet.init() and the requests do the rest.
  • Method Details

    • safeEquals

      public static boolean safeEquals(String a, String b)
      Compares two strings, counting null as equal only to null.
      Parameters:
      a - one of the two strings to compare, may be null
      b - the string compared against a, may be null
      Returns:
      true if both are null or both non-null and equal, so a null operand never throws
    • button

      private static String button(String name, String label)
      Returns html string of a form button with name and label.
      Parameters:
      name - the button name, also used as its CSS class; some known names get extra classes or a title attribute
      label - the text displayed on the button
      Returns:
      the HTML for an input type=submit element, non-null
    • button2

      private static String button2(String name, String label)
      Returns html string of a disabled form button with name and label.
      Parameters:
      name - the button name, also used as its CSS class
      label - the text displayed on the button
      Returns:
      the HTML for a disabled input type=submit element, non-null
    • sortHeader

      private static String sortHeader(String name, String label, String imgPath, String currentName, Folder.SortOrder currentOrder, int page, String folder)
      Returns a html string of the label and two imaged links using the parameter name (used for sorting buttons in folder view).
      Parameters:
      name - the column name this header sorts by, also the sort link's parameter value
      label - the text displayed in the header cell
      imgPath - URL prefix for the up and down arrow images
      currentName - the column currently sorted by; when it equals name the active arrow is drawn as a plain image
      currentOrder - the current sort direction, UP (descending) or DOWN (ascending)
      page - current page number, kept in the sort links
      folder - current folder name, kept in the sort links
      Returns:
      the HTML markup for the header cell: the label followed by the up and down image links
    • buttonPressed

      private static boolean buttonPressed(RequestWrapper request, String key)
      Check whether the named submit button "was pressed" in the request.
      Parameters:
      request - the current HTTP request
      key - the button name to look up as a request parameter
      Returns:
      true if the parameter is present; CONFIGURE and NEW_UIDL also count when sent with an empty value
    • showPart

      private static void showPart(PrintWriter out, MailPart mailPart, int level, boolean html, WebMail.HtmlMode allowHtml)
      Recursively render all mail body parts. 1. if type is multipart/alternative, look for preferred section and ignore others 2. if type is multipart/*, recursively call all these parts 3. if type is text/plain (or mail is not mime), print out 4. in all other cases print out message, that part is not displayed
      Parameters:
      out - where the rendered part is written
      mailPart - the MIME part to render, recursed into for multipart types
      level - is increased by recursively calling sub parts
      html - use html styling
      allowHtml - allow display of text/html parts
    • toTitleCase

      private static String toTitleCase(String input)
      Capitalizes the first letter of each word.
      Parameters:
      input - the text to convert
      Returns:
      the text in title case
    • quoteHTML

      static String quoteHTML(String line)
      Prepare line for presentation between html tags. Escapes html tags
      Parameters:
      line - null OK
      Returns:
      escaped string or "" for null input
    • processLogin

      private static WebMail.State processLogin(WebMail.SessionObject sessionObject, RequestWrapper request, WebMail.State state)
      Start of state change and button processing: handle the login form, connecting to the mailbox.
      Parameters:
      sessionObject - the session to authenticate and store the mailbox in
      request - the current HTTP request carrying the login form fields
      state - the current state; acted on only when it is AUTH
      Returns:
      the next state: LOADING or LIST after a successful login, otherwise the incoming state unchanged
    • threadedStartup

      private static WebMail.State threadedStartup(WebMail.SessionObject sessionObject, boolean offline, WebMail.State state, String host, int pop3PortNo, String user, String pass)
      Starts one thread to load the emails from disk, and in parallel starts a second thread to connect to the POP3 server (unless user clicked the 'read mail offline' at login). Either could finish first, but unless the local disk cache is really big, the loading will probably finish first. Once the POP3 connects, it waits for the disk loader to finish, and then does the fetching of new emails. The user may view the local folder once the first (loader) thread is done.
      Parameters:
      sessionObject - the session to initialize: caches, mailbox, and credentials are stored here
      offline - true to skip the POP3 connection and read only the disk cache
      state - the current state; ignored, always replaced
      host - the mail server host name to connect to
      pop3PortNo - the POP3 port number, 0-65535
      user - the mailbox user name
      pass - the mailbox password
      Returns:
      LOADING if the disk load is still running, LIST if it finished, AUTH if creating the disk cache failed
    • processLogout

      private static WebMail.State processLogout(WebMail.SessionObject sessionObject, RequestWrapper request, boolean isPOST, WebMail.State state)
      End the session and show the login page.
      Parameters:
      sessionObject - the session to invalidate and disconnect on logout
      request - the current HTTP request
      isPOST - true for a POST request; logout is only honored then
      state - the current state; replaced by AUTH on logout
      Returns:
      the next state: AUTH after a logout, otherwise the incoming state unchanged
    • processStateChangeButtons

      private static WebMail.State processStateChangeButtons(WebMail.SessionObject sessionObject, RequestWrapper request, boolean isPOST, WebMail.State state)
      Process all buttons, which possibly change internal state. Also processes ?show=x for a GET.
      Parameters:
      sessionObject - the current session with the mailbox and caches
      request - the current HTTP request
      isPOST - disallow button pushes if false
      state - the current state, replaced when a button changes it
      Returns:
      the next state to render, never null
    • getCheckedItems

      private static List<String> getCheckedItems(RequestWrapper request)
      Returns e.g. 3,5 for ?check3=1&check5=1 (or POST equivalent).
      Parameters:
      request - the current HTTP request, scanned for checkN parameters set to 1
      Returns:
      non-null List of Base64 UIDLs, or attachment numbers as Strings
    • processGenericButtons

      private static WebMail.State processGenericButtons(WebMail.SessionObject sessionObject, RequestWrapper request, WebMail.State state)
      Handle the common folder and message buttons.
      Parameters:
      sessionObject - the current session, whose mailbox is reconnected on refresh
      request - the current HTTP request
      state - the current state, returned unless the connection is lost
      Returns:
      the state to render next: WebMail.State.AUTH if the connection was lost, otherwise the incoming state unchanged
    • processComposeButtons

      private static WebMail.State processComposeButtons(WebMail.SessionObject sessionObject, RequestWrapper request)
      Process buttons of compose message dialog This must be called BEFORE processStateChangeButtons so we can add the attachment before SEND.
      Parameters:
      sessionObject - the current session; uploaded attachments are stored in its attachments list
      request - the current HTTP request, read for the uploaded file and the draft UIDL
      Returns:
      NEW if an attachment was uploaded or deleted, null otherwise
    • processMessageButtons

      private static String processMessageButtons(WebMail.SessionObject sessionObject, String showUIDL, RequestWrapper request)
      Process buttons of message view.
      Parameters:
      sessionObject - the current session, used to locate the mail cache for delete and move
      showUIDL - the UIDL of the message being shown, returned unchanged when no button takes effect
      request - the current HTTP request
      Returns:
      the next UIDL to see if PREV or NEXT was pushed, or null if REALLYDELETE was pushed, or showUIDL, or "delete" if DELETE was pushed. If null, next state should be LIST.
    • processDownloadLink

      private static boolean processDownloadLink(WebMail.SessionObject sessionObject, String showUIDL, RequestWrapper request, HttpServletResponse response)
      Process download link in message view.
      Parameters:
      sessionObject - the current session, used to locate the mail cache
      showUIDL - the UIDL of the message whose attachment is requested
      request - the current HTTP request, read for the download or attachment parameter
      response - the response the attachment or the 404 error is written to
      Returns:
      If true, we sent an attachment or 404, do not send any other response
    • processDraftAttachmentLink

      private static void processDraftAttachmentLink(WebMail.SessionObject sessionObject, RequestWrapper request, HttpServletResponse response)
      Process thumbnail link in compose view Draft attachments are stored in the SessionObject and identified by hashcode only.
      Parameters:
      sessionObject - the current session, whose attachment list is searched for the id
      request - the current HTTP request, read for the draft attachment id
      response - the response the attachment bytes or the 404 error are written to
    • processSaveAsLink

      private static boolean processSaveAsLink(WebMail.SessionObject sessionObject, String showUIDL, RequestWrapper request, HttpServletResponse response)
      Process save-as link in message view.
      Parameters:
      sessionObject - the current session, used to locate the mail cache
      showUIDL - the UIDL of the message to save
      request - the current HTTP request, read for the save-as parameter
      response - the response the file or the 404 error is written to
      Returns:
      If true, we sent the file or 404, do not send any other response
    • getMailPartFromID

      private static MailPart getMailPartFromID(MailPart part, int id)
      Find the part with the given id, recursively.
      Parameters:
      part - the root part to search, may be null
      id - as retrieved from getID()
      Returns:
      the part with that id, or null if part is null or no sub-part matches
    • getMailPartFromID

      private static MailPart getMailPartFromID(MailPart part, String id)
      Find the part with the given content-id, recursively.
      Parameters:
      part - the root part to search, may be null
      id - a content-id, without the surrounding <> or trailing @ part
      Returns:
      the part with that content-id, or null if part is null or no sub-part matches
    • processFolderButtons

      private static int processFolderButtons(WebMail.SessionObject sessionObject, int page, RequestWrapper request)
      Process buttons of folder view.
      Parameters:
      sessionObject - the current session; error and info messages and the reallyDelete flag are updated here
      page - the current page number, 1-based
      request - the current HTTP request
      Returns:
      the page to display next, unchanged unless a paging button was pressed
    • processSortingButtons

      private static void processSortingButtons(WebMail.SessionObject sessionObject, RequestWrapper request)
      Applies the sort buttons found in the request.
      Parameters:
      sessionObject - the session to update
      request - the request to read
    • processConfigButtons

      private static WebMail.State processConfigButtons(WebMail.SessionObject sessionObject, RequestWrapper request, boolean isPOST, WebMail.State state)
      Processes the config buttons, both entering and exiting the page.
      Parameters:
      sessionObject - the session to update
      request - the request to read
      isPOST - disallow button pushes if false
      state - the state assumed so far
      Returns:
      the new state, or null if unknown
    • getSessionObject

      private WebMail.SessionObject getSessionObject(HttpSession httpSession)
      The session's state, creating it if needed.
      Parameters:
      httpSession - the servlet session the state is stored in under the "sessionObject" attribute
      Returns:
      the session state, created and attached if absent, never null
    • isMobile

      private static boolean isMobile(String ua)
      Either mobile or text browser Copied from net.i2p.router.web.CSSHelper.
      Parameters:
      ua - null ok
      Returns:
      true if the user agent is a small-screen or text browser, false for a null agent or a full browser
    • getCurrentMailCache

      private static MailCache getCurrentMailCache(WebMail.SessionObject session, RequestWrapper request)
      The mail cache for the requested folder.
      Parameters:
      session - the current session, holding the caches by folder name
      request - the current HTTP request, read for the folder name
      Returns:
      the cache for the requested folder, falling back to the inbox, or null if none is available
    • getCurrentFolder

      private static Folder<String> getCurrentFolder(WebMail.SessionObject session, RequestWrapper request)
      The folder selected in the request.
      Parameters:
      session - the current session, holding the caches by folder name
      request - the current HTTP request, read for the folder name
      Returns:
      the folder of the current mail cache, or null if there is no cache
    • waitForLoad

      private static void waitForLoad(WebMail.SessionObject sessionObject, MailCache mc)
      Blocking wait, bounded to 5 seconds so a stuck loader cannot hang the servlet thread indefinitely.
      Parameters:
      sessionObject - the session whose monitor is held while waiting for the load notification
      mc - the cache to load; returns once it is loaded or the wait times out
    • pageName

      static String pageName(String stateName)
      Plain-language name of the page a state value leads to, for the request log.

      The state names are internal (LIST, SHOW, NEW), and a log line that prints one says nothing to a reader trying to work out why a page came up empty. This maps each to what the browser is about to show. An unknown value falls back to the name itself so the log stays greppable against the code.

      Parameters:
      stateName - the State name, as given by state.name()
      Returns:
      the page description, never null
    • pageName

      private static String pageName(WebMail.State state)
      Null-tolerant wrapper for the log call sites.

      state starts null and stays that way on a GET for a mailbox that needs no login step, so the call sites cannot assume a value.

      Parameters:
      state - the state to describe, or null before any page is chosen
      Returns:
      the page description, never null
    • shortSessionId

      static String shortSessionId(String id)
      A Jetty session id is a long hex blob that nothing in the log viewer links, and it repeats on every request, so only its head is kept and bracketed.
      Parameters:
      id - the http session id, may be null
      Returns:
      the bracketed head of the id, never null
    • processRequest

      private void processRequest(HttpServletRequest httpRequest, HttpServletResponse response, boolean isPOST) throws IOException, ServletException
      The entry point for all web page loads.
      Parameters:
      httpRequest - the incoming request, read for parameters, headers, and session
      response - the response the page, redirect, or attachment is written to
      isPOST - disallow button pushes if false
      Throws:
      IOException - if a redirect or attachment response cannot be written
      ServletException - declared for the servlet API contract, not actually thrown by this method
    • renderNotifications

      private static void renderNotifications(StringBuilder buf, boolean showRefresh, WebMail.SessionObject sessionObject)
      Renders the notification banners if needed. This separates errors and info/notes so that each type is handled independently, and at most one of each is shown.
      Parameters:
      buf - The StringBuilder to append markup to.
      showRefresh - Whether a refresh indicator should be shown.
      sessionObject - The current session context.
    • isOverrideCssActive

      public boolean isOverrideCssActive()
      Determine if a user-provided override.css file is active.
      Returns:
      true if an override.css exists in the current theme's susimail directory
    • sendRedirect

      private void sendRedirect(HttpServletRequest req, HttpServletResponse resp, String q) throws IOException
      Redirect a POST to a GET (P-R-G), replacing the query string.
      Parameters:
      req - the current request, source of the redirect URL
      resp - the response the 303 status and Location header are set on
      q - starting with '?' or null
      Throws:
      IOException - if the response output stream cannot be opened
    • consoleGetString

      private static String consoleGetString(String s, I2PAppContext ctx)
      Translate with the console bundle.
      Parameters:
      s - the English string to look up
      ctx - the app context supplying the translation
      Returns:
      the translated string, or the original if untranslated
    • sendAttachment

      private static boolean sendAttachment(WebMail.SessionObject sessionObject, MailPart part, HttpServletResponse response, boolean isRaw)
      Stream one attachment part to the browser.
      Parameters:
      sessionObject - the current session, used for logging
      part - the MIME part to stream, or null for nothing to send
      response - the response the attachment is written to, zipped unless isRaw
      isRaw - if true, don't zip it
      Returns:
      true if the part was written to the response, false if the part was null or the write threw an IOException
    • sendMailSaveAs

      private static boolean sendMailSaveAs(WebMail.SessionObject sessionObject, Mail mail, HttpServletResponse response)
      Send the mail to be saved by the browser.
      Parameters:
      sessionObject - the current session, used for logging a failed copy
      mail - the message whose raw body is copied out
      response - the response the .eml file is written to
      Returns:
      true if the message was copied to the response, false if it has no body or the copy threw an IOException
    • composeDraft

      private static StringBuilder composeDraft(WebMail.SessionObject sessionObject, RequestWrapper request)
      Take the data from the request, and put it in a StringBuilder suitable for writing out as a Draft. We do no validation of recipients, total length, sender, etc. here.
      Parameters:
      sessionObject - only for error messages and attachments. All other data is in the request.
      request - the current HTTP request, read for the compose form fields
      Returns:
      null on error
    • composeDraft

      private static StringBuilder composeDraft(WebMail.SessionObject sessionObject, String from, String to, String cc, String bcc, String subject, String text, List<Attachment> attachments)
      Take the data from the parameters, and put it in a StringBuilder suitable for writing out as a Draft. We do no validation of recipients, total length, sender, etc. here. All params except session may be null.
      Parameters:
      sessionObject - only for error messages. All data is in the parameters.
      from - the From header value, replaced by the default sender when the sender is fixed in the config
      to - the To recipients, a comma-separated list
      cc - the Cc recipients, a comma-separated list
      bcc - the Bcc recipients, kept only in the draft
      subject - the subject line, "no subject" when blank
      text - the message body, quoted-printable encoded
      attachments - the files to attach, null for none
      Returns:
      null on error, with the reason appended to the session's error field
    • saveDraft

      private static boolean saveDraft(WebMail.SessionObject sessionObject, String uidl, StringBuilder draft)
      Write out the draft.
      Parameters:
      sessionObject - only for error messages.
      uidl - the UIDL identifying the draft in the Drafts folder
      draft - the composed draft text to write out
      Returns:
      true if the draft was written to the Drafts folder, false if there is no Drafts folder or the write failed
    • saveDraftLocked

      private static boolean saveDraftLocked(Log log, WebMail.SessionObject sessionObject, MailCache toMC, String uidl, StringBuilder draft)
      Caller must hold the MailCache write lock.
      Parameters:
      log - the session log, used for debug messages
      sessionObject - the current session; the failure reason is appended to its error field
      toMC - the Drafts cache to write into
      uidl - the UIDL identifying the draft in that cache
      draft - the composed draft text, null if composing failed
      Returns:
      true if the draft was written, false on any error
    • sendMail

      private static boolean sendMail(WebMail.SessionObject sessionObject, Draft draft)
      Take the data from the request, and put it in a StringBuilder suitable for writing out as a Draft.
      Parameters:
      sessionObject - only for error messages. All data is in the draft.
      draft - the saved draft message to send, read for sender, recipients, subject, and attachments
      Returns:
      true if the message passed validation and was handed to the SMTP client, false if any check failed and no send was attempted
    • doGet

      public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
      Overrides:
      doGet in class HttpServlet
      Throws:
      IOException
      ServletException
    • doPost

      public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
      Overrides:
      doPost in class HttpServlet
      Throws:
      IOException
      ServletException
    • allowOrigin

      private boolean allowOrigin(HttpServletRequest request)
      Validate Origin header for POST requests. Allows requests with matching Origin (same-origin), or no Origin header. Rejects cross-origin POST requests to prevent CSRF attacks.
      Parameters:
      request - the POST request, read for the Origin and Host headers
      Returns:
      false only for a parseable cross-origin mismatch, true for a same-origin, missing, or unparseable Origin
    • arrayToCSV

      private static String arrayToCSV(String[] arr)
      Join the array elements into a comma-separated string.
      Parameters:
      arr - the values to join, may be null
      Returns:
      the joined string, empty if arr is null
    • showCompose

      private static void showCompose(PrintWriter out, WebMail.SessionObject sessionObject, RequestWrapper request)
      Show the compose form.
      Parameters:
      out - where the form is written
      sessionObject - the current session; supplies the Drafts cache and pending attachments, errors are appended to it
      request - the current HTTP request, read for the draft UIDL and form values
    • getDefaultSender

      private static String getDefaultSender(WebMail.SessionObject sessionObject)
      The sender string.
      Parameters:
      sessionObject - the current session, supplying the logged-in user
      Returns:
      the sender as an RFC 822 personal name plus angle-addr, e.g. Name <user@mail.i2p>, never null
    • showLogin

      private static void showLogin(PrintWriter out)
      Show the login form.
      Parameters:
      out - where the form is written
    • showLoading

      private static void showLoading(PrintWriter out, WebMail.SessionObject sessionObject, RequestWrapper request)
      Show the loading page, refreshed by meta tag.
      Parameters:
      out - where the page is written
      sessionObject - the current session, unused by the current page
      request - the current HTTP request, unused by the current page
    • showFolder

      private static void showFolder(PrintWriter out, WebMail.SessionObject sessionObject, MailCache mc, RequestWrapper request)
      Show the message list for a folder.
      Parameters:
      out - where the list is written
      sessionObject - the current session, supplying the fetch state and error output
      mc - the cache of the folder to display, supplying the folder and its pages
      request - the current HTTP request, read for paging and sorting
    • renderMailRow

      private static String renderMailRow(Mail mail, String b64UIDL, String loc, String jslink, boolean isSpamFolder, boolean showToColumn)
      Renders a single mail row for the mailbox list. This method encapsulates all per-row HTML construction for a given mail item. It returns the row markup as a String so the caller can append it to the overall page builder. The caller is responsible for providing the correct state (spam status, whether to show "To" vs "From", sorting context, etc.).
      Parameters:
      mail - the message to render, read for its flags, date, subject, sender, and recipients
      b64UIDL - the Base64-encoded UIDL of the mail used for identifiers
      loc - the URL that the row should link to when opened
      jslink - the CSS class string that enables JavaScript-driven row interactions
      isSpamFolder - true if the current folder is the spam folder, rendering its rows in spam style
      showToColumn - true to show the "To" column, false to show "From"
      Returns:
      the HTML markup for a single <tr> representing the mail row, never null
    • renderDateCell

      private static String renderDateCell(Mail mail, String jslink)
      Builds the HTML for the date cell of a mail row.
      Parameters:
      mail - The Mail object for which the date cell is being rendered
      jslink - The string used to enable JavaScript-driven row interactions
      Returns:
      the date cell markup, empty if mail is null
    • escapeHtml

      private static String escapeHtml(String s)
      HTML escaping helper.
      Parameters:
      s - the text to escape, may be null
      Returns:
      the escaped text, empty if s is null or empty
    • renderPageNav

      private static void renderPageNav(PrintWriter out, String folderName, int page, int pages, boolean outputHidden)
      Render pagination controls for a given page.
      Parameters:
      out - writer to emit HTML
      folderName - folder being listed, unused by the current markup
      page - current page number (1-based)
      pages - total number of pages
      outputHidden - whether to emit hidden inputs to preserve state
    • showFolderSelect

      private static String showFolderSelect(String currentName, boolean disableCurrent)
      HTML select control listing the folders.
      Parameters:
      currentName - the folder being viewed, left out of the list since one cannot move or switch to the current folder
      disableCurrent - true for move to folder, false for select folder
      Returns:
      the HTML for the select element, non-null
    • showMessage

      private static void showMessage(PrintWriter out, WebMail.SessionObject sessionObject, MailCache mc, String showUIDL, boolean reallyDelete, WebMail.HtmlMode allowHTML)
      Show one message with its parts.
      Parameters:
      out - where the rendered message is written
      sessionObject - the session holding the mailbox and the view settings
      mc - the cache the message is fetched from
      showUIDL - the UIDL of the message to show
      reallyDelete - was the delete button pushed, if so, show the really delete? message
      allowHTML - allow display of text/html parts
    • buildRecipientLine

      private static String buildRecipientLine(String[] to)
      Build a comma-separated recipient line from the addresses.
      Parameters:
      to - the addresses, null yields an empty line
      Returns:
      the HTML-quoted CSV line, never null
    • showConfig

      private static void showConfig(PrintWriter out, Folder<String> folder)
      Simple configure page.
      Parameters:
      out - where the page is written
      folder - the folder whose page size is shown, null before login
    • _x

      private static String _x(String s)
      Tag for translation.
      Parameters:
      s - the English string to mark for extraction
      Returns:
      the same string, unchanged
    • _t

      private static String _t(String s)
      Looks up a localized string in the message bundle.
      Parameters:
      s - the message key
      Returns:
      the localized string
    • _t

      private static String _t(String s, Object o)
      Looks up a localized string in the message bundle.
      Parameters:
      s - the message key
      o - the value substituted for {0}
      Returns:
      the localized string
    • _t

      private static String _t(String s, Object o, Object o2)
      Looks up a localized string in the message bundle.
      Parameters:
      s - the message key
      o - the value substituted for {0}
      o2 - the value substituted for {1}
      Returns:
      the localized string
    • ngettext

      private static String ngettext(String s, String p, int n)
      Looks up the localized plural form for n items.
      Parameters:
      s - the singular form
      p - the plural form
      n - the item count selecting the form
      Returns:
      the localized singular or plural form
    • getThemes

      private static String[] getThemes(I2PAppContext ctx)
      Find all themes installed under docs/themes/susimail.
      Parameters:
      ctx - the app context supplying the base directory
      Returns:
      array of all the theme directory names found, empty if the theme directory is missing