// browser/webmail/Address.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Class to represent an email address, and utilities for handling lists of them.


function Address(name_,mailbox_,host_)  // "name" <mailbxo@host>
{
  this.name=name_;
  this.mailbox=mailbox_;
  this.host=host_;

  // Return the 'email address' i.e. mailbox@host
  this.get_email = function()
  {
    return this.mailbox+"@"+this.host;
  }

  // Return a displayable string, i.e. Real Name or user@domain if no real name is 
  // known.
  this.render_displayable = function()
  {
    var stripped;
    if (this.name && this.name!="NIL"
        && (stripped=strip_any_quotes(strip_spaces(this.name)))!="") {
      // not sure why there are sometimes quotes around the name;
      // maybe this is an issue with the server?
      return decode_rfc2047(stripped);
    } else {
      return this.get_email();
    }
  }

  // Return a span element of class mailto containing the displayable email address 
  // which is bound to compose_to to this address when clicked on.
  this.render_link = function(parent)
  {
    var s = span(parent,"",this.render_displayable());
    addattr(s,"title",this.render_rfc822());
    addclass(s,"mailto");
    bind_click(s, compose_to.bind_fn(this));
  }

  // Return the address in rfc822 format i.e. "Real Name" <name@domain>
  this.render_rfc822 = function()
  {
    var s="";
    if (this.name && this.name!="NIL") {
      s = quoted(strip_any_quotes(this.name)) + " ";
    }
    s += "<" + this.get_email() + ">";
    return s;
  }
}



function render_addrlist_text(addrlist)
{
  if (!addrlist) {
    return "<EMPTY LIST>";
  }
  var s="";
  for (var i=0; i<addrlist.length; i++) {
    if (i>0) {
      s+= ", ";
    }
    s += addrlist[i].render_displayable();
  }
  return s;
}

function render_addrlist_links(addrlist,parent)
{
  for (var i=0; i<addrlist.length; i++) {
    if (i>0) {
      textnode(parent,", ");
    }
    addrlist[i].render_link(parent);
  }
}

function render_addrlist_rfc822(addrlist)
{
  var s="";
  for (var i=0; i<addrlist.length; i++) {
    if (i>0) {
      s += ", ";
    }
    s += addrlist[i].render_rfc822();
  }
  return s;
}

// browser/common/base64.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Base64 coding, as specified by RFC2045 section 6.8.
// Imagine bits written out left-to-write, with the MSB of the first 
// byte on the left.  Then group the bits into sixes, again taking the 
// left bit as the MSB.

// It seems that some browsers have built-in functions for this with 
// the obscure names window.atob and window.btoa.  Note carefully that 
// atob *decodes* while btoa *encodes*.  These are used if they 
// are present.  Note that it is necessary to add/remove line breaks
// when using these builtin functions.


function encode_base64(s)
{
  if (window.btoa) {
    return builtin_encode_base64(s);
  } else {
    return fallback_encode_base64(s);
  }
}


/*private*/ function builtin_encode_base64(s)
{
  var p = 0;
  var r = "";
  var b = window.btoa(s);
  var l = b.length;
  while (p<l) {
    r += b.substr(p,72)+"\n";
    p += 72;
  }
  return r;
}


/*private*/ function fallback_encode_base64(s)
{
  var p = 0;
  var r = "";
  var l = s.length;
  var rl = 0;
  while (p<l) {
    var chunk = s.substr(p,3);
    r += encode_chunk(chunk);
    p += 3;
    rl += 4;
    if (rl>72) {
      r += "\n";
      rl = 0;
    }
  }
  return r;
}

var encode_b64_char = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                    + "abcdefghijklmnopqrstuvwxyz"
                    + "0123456789+/";

/*private*/ function encode_chunk(chunk)
{
  var cl = chunk.length;

  var b0 = chunk.charCodeAt(0);
  var b1 = (cl>1) ? chunk.charCodeAt(1) : 0;
  var b2 = (cl>2) ? chunk.charCodeAt(2) : 0;

  // b0      b1      b2
  // 765432107654321076543210
  // 543210543210543210543210
  // h0    h1    h2    h3

  var h0 = (b0>>2);
  var h1 = ((b0&3)<<4) | (b1>>4);
  var h2 = ((b1&15)<<2) | (b2>>6);
  var h3 = (b2&63);

  if (cl==3) {
    return encode_b64_char[h0] + encode_b64_char[h1]
         + encode_b64_char[h2] + encode_b64_char[h3];
  } else if (cl==2) {
    return encode_b64_char[h0] + encode_b64_char[h1]
         + encode_b64_char[h2] + "=";
  } else if (cl==1) {
    return encode_b64_char[h0] + encode_b64_char[h1] + "==";
  }
}



function decode_base64(s)
{
  if (window.atob) {
    return builtin_decode_base64(s);
  } else {
    return fallback_decode_base64(s);
  }
}


/*private*/ function builtin_decode_base64(s)
{
  s = strip_all_whitespace(s);
  return window.atob(s);
}


/*private*/ function fallback_decode_base64(s)
{
  s = strip_all_whitespace(s);
  var p = 0;
  var r = "";
  var l = s.length;
  while (p<l) {
    var chunk = s.substr(p,4);
    r += decode_chunk(chunk);
    p += 4;
  }
  return r;
}


/*private*/ function decode_chunk(chunk)
{
  var h0 = decode_b64_char(chunk[0]);
  var h1 = decode_b64_char(chunk[1]);
  var h2 = decode_b64_char(chunk[2]);
  var h3 = decode_b64_char(chunk[3]);

  // h0    h1    h2    h3
  // 543210543210543210543210
  // 765432107654321076543210
  // b0      b1      b2

  var b0 = (h0<<2) | (h1>>4);
  var b1 = ((h1&15)<<4) | (h2>>2);
  var b2 = ((h2&3)<<6) | (h3);

  if (chunk[3]=="=") {
    if (chunk[2]=="=") {
      return String.fromCharCode(b0);
    } else {
      return String.fromCharCode(b0) + String.fromCharCode(b1);
    }
  } else {
    return String.fromCharCode(b0) + String.fromCharCode(b1)
         + String.fromCharCode(b2);
  }
}


/*private*/ function decode_b64_char(c)
{
  var code = c.charCodeAt(0);
  if (c>="A" && c<="Z") {
    return code - code_for_A;
  } else if (c>="a" && c<="z") {
    return code - code_for_a + 26;
  } else if (c>="0" && c<="9") {
    return code - code_for_0 + 52;
  } else if (c=="+") {
    return 62;
  } else if (c=="/") {
    return 63;
  } else if (c=="=") {
    return 0;
  }
}


/*private*/ function strip_all_whitespace(s)
{
  var r = "";
  var l = s.length;
  for (var p=0; p<l; p++) {
    var c = s[p];
    if (!is_space(c)) {
      r += c;
    }
  }
  return r;
}

// browser/common/bind.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Create temporary function-objects (functors) bound to paramters, including the 
// "this" current-object pseudo-parameter.

// For a description of the method used, see
// See http://www.brockman.se/writing/method-references.html.utf8
// I believe that Prototype does something similar.

// Usage:

// If you don't need to bind to any parameters or to "this", you don't need any of 
// this:
// bind_click(el,do_something);

// To bind the normal parameters of a non-member function, use bind_fn:
// bind_click(el,debug.bind("el clicked"));

// To bind a member function, use bind_mem.  You can bind to a member of the 
// current "this":
// bind_click(el,this.do_something.bind_mem(this));
// (Hmm, maybe that could be simplified a bit)
// or to a member of another object:
// bind_click(el,thing.do_something.bind_mem(thing));
// (I don't think that that complexity can be simplified)

// You can also pass normal arguments to bind_mem after the object.


/*private*/ function entries(collection) {
  var result = [];
  for (var i=0; i<collection.length; i++) {
    result.push(collection[i]);
  }
  return result;
} 

Function.prototype.bind_mem = function (object) {
  var method = this;
  var oldargs = entries(arguments).slice(1);
  return function () {
    var newargs = entries(arguments);
    return method.apply(object, oldargs.concat(newargs));
  };
}

Function.prototype.bind_fn = function () {
  var fn = this;
  var oldargs = entries(arguments);
  return function () {
    var newargs = entries(arguments);
    return fn.apply(this, oldargs.concat(newargs));
  };
}

// browser/common/classes.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// CSS classes that can be applied to display elements for UI feedback:


function select(el)
{
  addclass(el,"selected");
}

function deselect(el)
{
  rmvclass(el,"selected");
}

function waiting(el)
{
  addclass(el,"waiting");
}

function endwait(el)
{
  rmvclass(el,"waiting");
}
// browser/common/config.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Configuration:

// You need to uncomment and adjust these to suit your local setup.

// For connecting to the Http2Email proxy:
//var http2email_url = "http://www.example.com/http2email/";
// Note that the URL above should be the one that Apache handles (and proxies to 
// Http2Email), not the URL to the Http2Email program itself.  I.e. it should 
// *not* be something like http://example.com:4134.

// For reading messages using IMAP:
// If used_fixed_username_password is true below then you are automatically logged on as the 
// user specified by fixed_username and fixed_password below.  If 
// used_fixed_username_password is false below then fixed_username and fixed_password are 
// ignored and you will be presented with a login dialog.
//var used_fixed_username_pasword = true;
//var fixed_username = "your-name";
//var fixed_password = "your-password";

// For sending messages using SMTP:
var hostname = "localhost";  // used in HELO
//var identity = "username@example.com";  // default From: address
//var servername = "example.com";  // used in Message-ID: header

// Display debug output?
var show_status_pane = true;

// Bcc: copies of sent messages somewhere?
var default_bcc="";

// Mailbox to select after logon; empty string for none
var initial_mailbox="";

// Alert when new mail arrives?
// (Unfortunately the Javascript interpretter blocks until you OK the
// alert; if this takes you too long the proxy will think that the
// session has terminated.)
var alert_on_new_mail=false;

// Send periodic NOOPs?
// There are two reasons for sending NOOPs to the IMAP server:
// 1. The IMAP server may close the connection if it sees no activity for a 
// long period.  RFC2060 requires that this timeout must be at least 30 
// mins.
// 2. The IMAP server may not send new mail notifications except in 
// response to a command; in this case a NOOP must be sent periodically in 
// order to detect new mail.
// Sending NOOPs more often than is necessary is wasteful, so you should 
// establish what your server requires.  Decimail Server has no idle 
// timeout and sends new mail notifications at any time, so no NOOPs are 
// required.  Dovecot has a 30 min idle timeout and sends new mail 
// notifications at any time, so NOOPs every 25 minutes are sufficient.
// This setting is the number of seconds between NOOPs.  Setting it to zero 
// means no NOOPs are sent.
var noop_interval = 0;

// Show hint-of-the-day?
var show_hint = true;

// browser/common/Data.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Data class stores a lump of data and converts it between different 
// encodings.


function Data(d,e)
{
  this.data = d;
  this.encoding = e ? e.toLowerCase() : null;


  this.recode = function(new_enc)
  {
    if (new_enc==this.encoding) {
      return;
    }
    if (new_enc=="8bit" && this.encoding=="7bit") {
      this.encoding="8bit";
      return;
    }
    if (new_enc=="7bit" && this.encoding=="8bit") {
      // We don't check whether 7bit is OK for this data; the caller 
      // needs to be sure of that before asking for it.
      this.encoding="7bit";
      return;
    }
    info("Recoding from "+this.encoding+" to "+new_enc);
    var plain;
    if (this.encoding=="quoted-printable") {
      plain = decode_quoted_printable(this.data);
    } else if (this.encoding=="base64") {
      plain = decode_base64(this.data);
    } else {
      plain = this.data;
    }
    if (new_enc=="quoted-printable") {
      this.data = encode_quoted_printable(plain);
    } else if (new_enc=="base64") {
      this.data = encode_base64(plain);
    } else {
      this.data = plain;
    }
    this.encoding = new_enc;
    info("OK");
  }

  
  this.choose_transfer_encoding = function()
  {
    var safe_chars = 0;
    var unsafe_chars = 0;
    var p = 0;
    var l = this.data.length;
    while (p<l) {
      var code = this.data.charCodeAt(p);
      var safe = (code>=32 && code<=126);
      if (safe) {
        safe_chars++;
      } else {
        unsafe_chars++;
      }
      if (p>1000 && unsafe_chars) {
        break;
      }
      p++;
    }
    if (!unsafe_chars) {
      return "7bit";
    } else if ((safe_chars + 3*unsafe_chars)
               <= ((safe_chars+unsafe_chars)*1.25)) {
      return "quoted-printable";
    } else {
      return "base64";
    }
  }


  this.recode_for_transfer = function()
  {
    this.recode(this.choose_transfer_encoding());
  }


  this.set_encoding = function(e)
  {
    this.encoding = e.toLowerCase();
  }

}

// browser/common/dom.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Generic DOM manipulation helpers:


// There's no excuse for DOM having these "magic numbers":

var ELEMENT_NODE_TYPE=1;
var ATTRIBUTE_NODE_TYPE=2;
var TEXT_NODE_TYPE=3;


// Trivial stuff to save typing:

function getid(id)
{
  return document.getElementById(id);
}

function addattr(el,attrname,attrval)
{
  el.setAttribute(attrname,attrval);
}


// Manipulation of classes:

function hasclass(el,cl)
{
  var c = " "+el.className+" ";
  return c.indexOf(" "+cl+" ") != -1;
}

function addclass(el,cl)
{
  if (hasclass(el,cl)) {
    return;
  } else if (el.className=="") {
    el.className = cl;
  } else {
    el.className += " "+cl;
  }
}

function rmvclass(el,cl)
{
  if (!hasclass(el,cl)) {
    return;
  }
  var c = " "+el.className+" ";
  var p=c.indexOf(" "+cl+" ");
  el.className = c.substr(0,p)+c.substr(p+cl.length+1);
  //alert("new class = "+el.className);
}

function clearclasses(el)
{
  el.className="";
}

// Remove given class from given element and all of its descendents:
function rmvclass_rec(el,cl)
{
  rmvclass(el,cl);
  var children = el.childNodes;
  for (var i=0; i<children.length; ++i) {
    var child = children[i];
    if (child.nodeType==ELEMENT_NODE_TYPE) {
      rmvclass_rec(child,cl);
    }
  }
}


// Build a data: URL

function dataurl(mimetype,b64_content)
{
  return "data:"+mimetype+";base64,"+b64_content;
}


// Set the content of an element to to specified text.
// All existing text and child elements are removed, but any attributes are 
// retained.

function settext(el,t) {
  var c=el.childNodes;
  for(var i=0; i<c.length; i++) {
    var ty=c[i].nodeType;
    if (ty==ELEMENT_NODE_TYPE || ty==TEXT_NODE_TYPE) {
      el.removeChild(c[i]);
    }
  }
  var n=document.createTextNode(t);
  el.appendChild(n);
}


// Delete the content of an element.  Attributes are also removed.

function delete_contents(el)
{
  var child = el.firstChild;
  while (child) {
    var nextchild = child.nextSibling;
    el.removeChild(child);
    child = nextchild;
  }
}


// Escaping:

function escape_for_id(s)
{
  return s.replace(/[^a-zA-Z0-9]/g,
                   function (c) {
                     return "-" + encode_hex_char_code(c.charCodeAt(0));
                   });
}

function escape_for_html(s)
{
  return s.replace(/&/g,"&amp;")
          .replace(/</g,"&lt;")
          .replace(/>/g,"&gt;");
}


// Element position information:

// Returns an {x: y:} co-ordinate object of the element's top left corner.
function find_element_pos(el)
{
  if (el.offsetParent) {
    var x=0; var y=0;
    while (el.offsetParent) {
      x+=el.offsetLeft-el.scrollLeft;
      y+=el.offsetTop-el.scrollTop;
      el=el.offsetParent;
    }
    return {x:x, y:y};
  } else {
    return {x:el.x, y:el.y};
  }
}

// Returns an {x1: y1: x2: y2:} bounding-box object for the element.
function find_element_bbox(el)
{
  var xy = find_element_pos(el);
  return {x1:xy.x, x2:xy.x+el.offsetWidth,
          y1:xy.y, y2:xy.y+el.offsetHeight};
}


// Extract the mouse position from an event.
// This is intended to cope with a variety of browsers.

function get_mouse_pos(ev)
{
  // This should work for Moz, Safari, Opera.
  // ??? is it broken when the mouse pos is (0,0) ???
  if (ev.pageX || ev.pageY) {
    return {x:ev.pageX,y:ev.pageY};

  // This should work for IE.
  } else if (ev.clientX || ev.clientY) {
    if (document.documentElement) {
      return {x:ev.clientX+document.documentElement.scrollLeft,
	      y:ev.clientY+document.documentElement.scrollTop};
    } else {
      return {x:ev.clientX+document.body.scrollLeft,
	      y:ev.clientY+document.body.scrollTop};
    }
  }
}

// browser/common/elements.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Convenience functions to create DOM elements.

// In each case the first two arguments are the parent element (to which this will 
// be appended as the last child) and the id (which can be omitted, or "" passed, 
// to not set an id).  Additional arguments depend on the element.  Elements that 
// have text content will generally have the text as the last argument.


function el(elname,parent,id,text)
{
  var e = document.createElement(elname);
  if (id) {
    e.id = id;
  }
  if (text!=undefined) {
    var t = document.createTextNode(text);
    e.appendChild(t);
  }  
  parent.appendChild(e);
  return e;
}

function div(parent,id,text)
{
  return el("DIV",parent,id,text);
}

function p(parent,id,text)
{
  return el("P",parent,id,text);
}

function table(parent,id)
{
  return el("TABLE",parent,id);
}

function thead(parent,id)
{
  return el("THEAD",parent,id);
}

function tbody(parent,id)
{
  return el("TBODY",parent,id);
}

function tr(parent,id)
{
  return el("TR",parent,id);
}

function th(parent,id,text)
{
  return el("TH",parent,id,text);
}

function td(parent,id,text)
{
  return el("TD",parent,id,text);
}

function col(parent,id)
{
  return el("COL",parent,id);
}

function ul(parent,id)
{
  return el("UL",parent,id);
}

function li(parent,id,text)
{
  return el("LI",parent,id,text);
}

function a(parent,id,href,text)
{
  var a = el("A",parent,id,text);
  addattr(a,"href",href);
  return a;
}

function span(parent,id,text)
{
  return el("SPAN",parent,id,text);
}

function input(parent,id,type,value)
{
  var i = el("INPUT",parent,id);
  if (type) {
    addattr(i,"type",type);
  }
  if (value) {
    addattr(i,"value",value);
  }
  return i;
}

function textarea(parent,id)
{
  return el("TEXTAREA",parent,id);
}

function select_el(parent,id)
{
  return el("SELECT",parent,id);
}

function option(parent,id,value,text)
{
  var o = el("OPTION",parent,id,text);
  addattr(o,"value",value);
  return o;
}

function img(parent,id,src)
{
  var i = el("IMG",parent,id);
  if (src) {
    addattr(i,"src",src);
  }
  return i;
}

function button(parent,id,text)
{
  return el("BUTTON",parent,id,text);
}

function label(parent,id,forid,text)
{
  var l = el("LABEL",parent,id,text);
  addattr(l,"for",forid);
  return l;
}

function form(parent,id)
{
  return el("FORM",parent,id);
}

function link(parent,id)
{
  return el("LINK",parent,id);
}

function textnode(parent,text)
{
  var node = document.createTextNode(text);
  parent.appendChild(node);
  return node;
}

// browser/common/events.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Bind to interaction events on user-interface object.

// Helper function for binding.  This is intended to support IE's method of 
// supplying events to handler functions.
// It is also responsible for reporting errors that occur during event handlers.

/*private*/ function eventhandler(fn,e)
{
  if (!e) {
    e = window.event;
  }
  return report_any_errors(fn.bind_fn(e));
}


// Event binder functions.  These know about "this", so it should be possible to 
// pass member functions to them without worrying about bind_mem.  Using bind_mem 
// is safe though.

// Events on elements:

function bind_click(el,fn)
{
  el.onclick = eventhandler.bind_mem(this,fn);
}

function bind_dblclick(el,fn)
{
  el.ondblclick = eventhandler.bind_mem(this,fn);
}

function bind_mousedown(el,fn)
{
  el.onmousedown = eventhandler.bind_mem(this,fn);
}

function bind_change(el,fn)
{
  el.onchange = eventhandler.bind_mem(this,fn);
}

function bind_submit(el,fn)
{
  el.onsubmit = eventhandler.bind_mem(this,fn);
}

function bind_focus(el,fn)
{
  el.onfocus = eventhandler.bind_mem(this,fn);
}

function bind_blur(el,fn)
{
  el.onblur = eventhandler.bind_mem(this,fn);
}

function bind_keypress(el,fn)
{
  el.onkeypress = eventhandler.bind_mem(this,fn);
}


// Bind global events:

function gbind_click(fn)
{
  document.onclick = eventhandler.bind_mem(this,fn);
}

function gbind_mousemove(fn)
{
  document.onmousemove = eventhandler.bind_mem(this,fn);
}

function gbind_mouseup(fn)
{
  document.onmouseup = eventhandler.bind_mem(this,fn);
}

function gbind_keypress(fn)
{
  document.onkeypress = eventhandler.bind_mem(this,fn);
}

// Unbind global events:

function ungbind_mousemove()
{
  document.onmousemove = null;
}

function ungbind_mouseup()
{
  document.onmouseup = null;
}

function ungbind_keypress()
{
  document.onkeypress = null;
}

// browser/common/exceptions.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Exception handling:


var status_pane=null;

function maybe_build_status_pane(pane)
{
  if (show_status_pane) {
    status_pane = div(pane,"status-pane");
    appendstatus("To remove this status area, change show_status_pane to false "
                +"in common/config.js\n");
  }
}


/*private*/ function appendstatus(msg)
{
  if (status_pane) {
    status_pane.innerHTML += escape_for_html(msg);
  }
}

function debug(msg)
{
  appendstatus("DEBUG: "+msg+"\n");
}

function info(msg)
{
  appendstatus("INFO: "+msg+"\n");
  window.status = msg;
}

function warn(msg)
{
  alert("Warning:\n"+msg);
}


function Error(msg_)
{
  this.msg = msg_;

  this.report = function()
  {
    alert("Error:\n"+this.msg);
  }
}

function error(msg)
{
  throw new Error(msg);
}


var reload_on_fatal = true;

function Fatal(msg_)
{
  this.msg = msg_;

  this.report = function()
  {
    alert("Fatal Error:\n"+this.msg+(reload_on_fatal?"\n\nWebmail will now restart":""));
    if (reload_on_fatal) {
      window.location.reload();
    }
  }
}

function fatal(msg)
{
  throw new Fatal(msg);
}


function report_exception(e)
{
  if (e.report) {
    e.report();
  } else {
    alert("An unexpected exception has occured:\n"+e
          +"\nYou may wish to reload the page.");
  }
}


// Invoke the function fn and return its result, if any.
// If an exception is thrown inside fn, report it.
// (This can be disabled to help debugging.)
// This is needed in event handler callbacks etc. to ensure that their exceptions 
// don't just vanish into the Mozilla error console.

var trap_errors = true;

function report_any_errors(fn)
{
  if (trap_errors) {
    try {
      return fn();
    }
    catch (e) {
      report_exception(e);
      return;
    }
  } else {
    return fn();
  }
}

// browser/common/format_flowed.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// See RFC2646 for the definition of format="flowed" and format="fixed"


// Given a message body string (i.e. from the compose window) with newlines between 
// paragraphs, add additional soft newlines ready for sending as format="flowed".

function encode_format_flowed(s)
{
  var t = strip_eol_spaces(s);
  var u = wrap(t,72);
  var v = space_stuff(u);
  return v;
}


/*private*/ function strip_eol_spaces(s)
{
  var lines = split_into_lines(s);
  var r = "";
  for (var i=0; i<lines.length; i++) {
    var l = lines[i];
    var q = l.length;
    if (l[0]!=">") {
      while (l[q-1]==" ") {
        q--;
      }
    }
    r += l.substr(0,q) + "\n";
  }
  return r;
}


/*private*/ function wrap(s,maxlen)
{
  var r="";
  var p = 0;
  while (true) {
    var q = s.indexOf("\n",p);
    var l;
    if (q==-1) {
      l = s.substr(p);
    } else {
      l = s.substr(p,q-p);
    }
    if (l[0]==">") {
      r += l+"\n";
    } else {
      r += split_para(l,maxlen);
    }
    if (q==-1) {
      break;
    }
    p = q+1;
  }
  return r;
}


/*private*/ function split_para(s,maxlen)
{
  var r = "";
  var p = 0;
  var l = s.length;
  while (true) {
    var q = p;
    while (true) {
      var qq = s.indexOf(" ",q);
      if (qq==-1) {
        q = l;
        break;
      }
      while (s[qq]==" ") {
        qq++;
      }
      if ((qq-p)>maxlen) {
        if (q==p) {
          q = qq;
        }
        break;
      }
      q = qq;
    }
    r += s.substr(p,q-p) + "\n";
    if (q==l) {
      break;
    }
    p = q;
  }
  return r;
}


/*private*/ function space_stuff(s)
{
  var r = "";
  var p = 0;
  while (true) {
    if (s[p]==" " /* || s[p]==">" */ || s.substr(p,5)=="From ") {
      r += " ";
    }
    var q = s.indexOf("\n",p);
    if (q==-1) {
      r += s.substr(p);
      break;
    }
    q += 1;
    r += s.substr(p,q-p);
    p = q;
  }
  return r;
}



// Given a message body string in format="flowed" (e.g. received from the server), 
// break it into paragraphs.  The return value is an array where each element 
// represents a paragraph, being an object with two fields:
// indent: the amount of indenting, i.e. the number of '>'s.
// text: the text content.

function decode_format_flowed(s)
{
  var lines = split_into_lines(s);

  var lineinfo = [];
  var i;
  for (i=0; i<lines.length; i++) {
    var l = lines[i];
    var indent = 0;
    while (l[indent]==">") {
      indent++;
    }
    var chop = indent;
    if (indent>0 && l[chop]==" ") {
      chop++;
    }
    var empty = true;
    for (var p = chop; p<l.length; p++) {
      if (l[p]!=" ") {
        empty = false;
        break;
      }
    }
    var hardnewline = (l[l.length-1]!=" ");
    lineinfo.push({indent: indent,
                   hardnewline: hardnewline,
                   empty: empty,
                   text: (empty?nbsp:l.substr(chop))});
  }

  var paras = [];
  var para = "";
  for (i=0; i<lineinfo.length; i++) {
    var li = lineinfo[i];
    para += li.text;
    if (li.hardnewline
        || li.empty
        || i==lineinfo.length-1
        || li.indent!=lineinfo[i+1].indent
        || lineinfo[i+1].empty) {
      paras.push({indent: li.indent, text: leading_nbsps(para)});
      para = "";
    }
  }
  return paras;
}


/*private*/ function leading_nbsps(s)
{
  var q = 0;
  var t = "";
  while (s[q]==" ") {
    q++;
    t += nbsp;
  }
  return t + s.substr(q);
}
// browser/common/http.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// HTTP:

function sync_load(url, method, params)
{
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.open(method, url, false);
  xmlhttp.send(params);
  if (xmlhttp.status!=200) {
    throw "Http2Email server returned status code "+xmlhttp.status+":\n"
          +xmlhttp.responseText;
  }
  return xmlhttp.responseText;
}



function async_load(url, method, params, callback)
{
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.open(method, url, true);
  xmlhttp.onreadystatechange = async_load_readystatechange.bind_fn(xmlhttp,callback);
  xmlhttp.send(params);
}


/*private*/ function async_load_readystatechange(xmlhttp,callback)
{
  if (xmlhttp.readyState==4) {
    var status;
    {
      // Bad things can happen here when we close the page.  It looks as the 
      // outstanding uncompleted request is terminated abruptly and the attempt to 
      // read the status below is attempted yet fails.
      try {
        status = xmlhttp.status;
      }
      catch (e) {
        // I can't see how to distinguish that particular exception from others, and 
        // attempting to inspect the exception object seems to trigger further 
        // errors.  I can't think of any reasons why we would legitimately get an 
        // exception here, so we'll silently continue in all cases.
        return;
      }
    }
    if (status==200) {
      if (callback) {
        report_any_errors(callback.bind_fn(xmlhttp.responseText));
      }
    } else {
      report_any_errors(fatal.bind_fn("Server returned status code "
                                      +status+":\n"+xmlhttp.responseText));
    }
  }
}

// browser/common/human_size.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Format a size in bytes in human-friendly units.

function human_size(size)
{
  if (size>1024*1024) {
    return Math.round(size/(1024*1024))+"M";
  } else if (size>1024) {
    return Math.round(size/1024)+"k";
  } else {
    return size+"B";
  }
}

// browser/common/js_tricks.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.



// Misc. Javascript tricks:

// Test if something is undefined by comparing it with the following variable, 
// whose value is undefined.
var undefined;


function ifdef(x,dflt)
{
  if (x==undefined) {
    return dflt;
  } else {
    return x;
  }
}

// Check the type of an object:

function is_string(thing)
{
  return typeof(thing)=='string';
}





function objmerge(o1, o2) {
  var o3 = {};
  for (i in o1) {
    o3[i]=o1[i];
  }
  for (i in o2) {
    o3[i]=o2[i];
  }
  return o3;
}



var nbsp = String.fromCharCode(160);

var code_for_0 = String("0").charCodeAt(0);
var code_for_A = String("A").charCodeAt(0);
var code_for_a = String("a").charCodeAt(0);



// browser/common/keys.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Keys:


var keybindings = {};

var global_keys_enabled = true;

function global_bind_key(key, fn)
{
  keybindings[key] = fn;
}


function get_keycode_from_event(ev)
{
  return ev.keyCode  ? ev.keyCode :
         ev.charCode ? ev.charCode :
	 ev.which    ? ev.which : 0;
}


function process_global_key(ev)
{
  if (!global_keys_enabled) {
    return;
  }
  var key = String.fromCharCode(get_keycode_from_event(ev));
  if (keybindings[key]) {
    keybindings[key]();
  }
}


function disable_global_keys()
{
  global_keys_enabled = false;
}

function enable_global_keys()
{
  global_keys_enabled = true;
}


function disable_global_keys_for(el)
{
  bind_focus(el,disable_global_keys);
  bind_blur(el,enable_global_keys);
}


gbind_keypress(process_global_key);
var http2email_url = "http://decimail.org/http2email/";
var use_fixed_username_password = true;
var fixed_username = "webmaildemo";
var fixed_password = "omedliambew";
var hostname = "localhost";  // used in HELO
var identity = "";  // default From: address
var servername = "decimail.org";  // used in Message-ID: header
var show_status_pane = false;
var default_bcc="";
var initial_mailbox="";
var alert_on_new_mail=false;
var noop_interval = 0;
var show_hint = true;

// browser/webmail/Message.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Class representing a message.


function Message()
{
  // Message header data.  This is populated when the mailbox is selected and the 
  // message envelope is read from the server.
  // It's important that all fields are initialised to null here as otherwise 'add' 
  // below won't work.
  this.date=null;
  this.subject=null;
  this.from=null;
  this.sender=null;
  this.reply_to=null;
  this.to=null;
  this.cc=null;
  this.bcc=null;
  this.in_reply_to=null;
  this.message_id=null;
  this.references=new Array;
  this.flags=null;
  this.size=null;
  this.uid=null;
  this.seen=false;
  this.deleted=false;
  this.answered=false;

  // This is needed so that 'add' works, below
  this.flags_set=false;

  // The parts making up the message.  This is populated when the body structure is 
  // read, when the message is selected; got_structure is then set.  Part data is not 
  // obtained at that point.
  this.parts={};
  this.got_structure=false;

  // Body data (this is filled in by the parser if it receives it, but is not used at 
  // present):
  this.rfc822_text=null;
  this.rfc822_header=null;

  // Merge another message into this one.  This is needed because the parser creates a 
  // new Message object when it gets new FETCH data; it has to do that because it 
  // doesn't necessarily see the UID of the message that the data refers to 
  // immediately.  Once the data including the UID has been received by the parser it 
  // then finds any existing Message object with that UID (i.e. this one) and calls 
  // this method to merge in the new data.
  this.add = function(msg)
  {
    for (i in msg) {
      if (i=="parts") {
        for (j in msg.parts) {
          if (!this.parts[j]) {
            this.parts[j] = new Part(this,j);
          }
          for (k in msg.parts[j]) {
            this.parts[j][k] = msg.parts[j][k];
          }
          this.parts[j].msg=this;
        }
      } else if (msg[i]) {
        this[i]=msg[i];
      }
    }
    if (msg.flags_set) {
      this.seen = msg.seen;
      this.deleted = msg.deleted;
      this.answered = msg.answered;
    }
  }

  // This is called when this message is dragged to a mailbox.  The mailbox name is 
  // passed as the parameter.
  this.copy_to_mailbox = function(mailbox)
  {
    info("Copying message "+this.uid+" to mailbox "+mailbox.imap_name+"...");
    uid_copy_cmd(this.uid,mailbox.imap_name,this.copy_to_mailbox_done.bind_mem(this,mailbox));
  }

  this.copy_to_mailbox_done = function(mailbox)
  {
    info("Copying message "+this.uid+" to mailbox "+mailbox.imap_name+"... Done");
  }  

}


// browser/common/minmax.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.



function max(a,b)
{
  return (a>b) ? a : b;
}

function min(a,b)
{
  return (a<b) ? a : b;
}

// browser/common/newlines.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Given a string, break it into lines and return an array with one element per 
// line.  The array elements are not terminated with newlines.  Any type of newline 
// (CR, LF, CRLF) is recognised in the input.

function split_into_lines(s) {
  var a = [];
  var p = 0;
  var l = s.length;
  while (true) {
    var rpos = s.indexOf("\r",p);
    var npos = s.indexOf("\n",p);
    var q;
    if (rpos==-1 & npos==-1) {
      q = l;
    } else if (rpos==-1) {
      q = npos;
    } else if (npos==-1) {
      q = npos;
    } else if (rpos<npos) {
      q = rpos;
    } else {
      q = npos;
    }
    a.push(s.substr(p,q-p));
    if (q==l) {
      break;
    }
    if (s.substr(q,2)=="\r\n") {
      p = q+2;
    } else {
      p = q+1;
    }
  }
  return a;
}


// Given an input with newlines in any format (CR, LF, CRLF) reformat it to have \n 
// newlines only, including one at EOF.

function normalise_newlines(s) {
  var a = split_into_lines(s);
  return a.join("\n")+"\n";
}

// browser/common/Part.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// A message MIME part


function Part(msg_,name_)
{
  // This class represents one MIME part of a message.

  // Reference to the message that this is a part of:
  this.msg=msg_;

  // Name in IMAP syntax, e.g. "1", "1.2" etc:
  this.name=name_;

  // MIME type:
  this.type=null;
  this.subtype=null;

  // More attributes from the IMAP message structure information.
  this.encoding=null;
  this.size=null;
  this.p_format="fixed";
  this.p_charset="ISO-8859-1";

  // The data.  This is a Data object, if it is present.  It isn't loaded until it is 
  // actually needed.
  this.content=null;

  // Load the content, if it is not already present.
  this.load_content = function(cont)
  {
    if (this.content) {
      cont();
      return;
    }

    if (this.p_charset.toUpperCase()!=current_charset.toUpperCase()) {
      info("Changing character set to "+this.p_charset);
      change_charset(this.p_charset, this.load_content_cont0.bind_mem(this,cont));
      return;
    }
    this.load_content_cont0(cont);
  }

  this.load_content_cont0 = function(cont)
  {
    info("Getting data from message "+this.msg.uid+" part "+this.name+"...");
    uid_fetch_cmd(this.msg.uid, "BODY["+this.name+"]",
                  this.load_content_cont1.bind_mem(this,cont));
  }

  this.load_content_cont1 = function(cont)
  {
    this.content.set_encoding(this.encoding);
    info("OK");
    if (cont) {
      cont();
    }
  }
}





// browser/common/popupmenu.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Pop-up menus:


function popup_menu(parent)
{
  var p = div(parent);
  addclass(p,"popupmenu");
  var i = img(p,"","imgs/poper.png");
  bind_click(i,openmenu.bind_fn(p));

  var m_ul = ul(p);
  addclass(m_ul,"menu");
  return m_ul;
}


var current_menu_el = null;
var menu_just_clicked = false;

function openmenu(menu_el)
{
  clearmenus();
  addclass(menu_el,"clicked");
  current_menu_el = menu_el;
  menu_just_clicked = true;
}

function clearmenus()
{
  if (current_menu_el && !menu_just_clicked) {
    rmvclass(current_menu_el,"clicked");
  }
  menu_just_clicked = false;
}


// browser/common/quoted_printable.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Quoted-printable encoding, as defined in RFC 2045 section 6.7.


/*private*/ function decode_hex_digit(d)
{
  if (d>='0' && d<='9') {
    return d-0;
  } else {
    return d.toUpperCase().charCodeAt(0) - code_for_A + 10;
  }
}


/*private*/ function decode_hex_char_code(s)
{
  var c1=s.substr(0,1);
  var c2=s.substr(1,1);
  var code = 16*decode_hex_digit(c1) + decode_hex_digit(c2);
  var c = String.fromCharCode(code);
  return c;
}


function decode_quoted_printable(e)
{
  var d="";
  var p=0;
  while (1) {
    var q = e.indexOf("=",p);
    if (q==-1) {
      break;
    }
    d+=e.substr(p,(q-p));
    if (e.charCodeAt(q+1)==13 && e.charCodeAt(q+2)==10) {
    } else {
      code=e.substr(q+1,2);
      d+=decode_hex_char_code(code);
    }
    p=q+3;
  }
  d+=e.substr(p);
  return d;
}


/*private*/ function encode_hex_digit(d)
{
  if (d<10) {
    return String.fromCharCode(code_for_0 + d);
  } else {
    return String.fromCharCode(code_for_A + d - 10);
  }
}


/*private*/ function encode_hex_char_code(code)
{
  return encode_hex_digit(code>>4) + encode_hex_digit(code&15);
}


function encode_quoted_printable(s)
{
  var r = "";
  var l = s.length;
  var ll = 0;
  for (var p=0; p<l; p++) {
    var c = s[p];
    var code = c.charCodeAt(0);
    safe = (code>=32 && code<=126 && code!=61)
        || (c=="\r" && s[p+1]=="\n")
        || (c=="\n" && s[p-1]=="\r")
        || (c=="\t");
    // TODO: space and tab are not allowed before newline
    if (safe) {
      r += c;
      ll++;
      if (c=="\r" || c=="\n") {
        ll = 0;
      }
    } else {
      r += "=" + encode_hex_char_code(code);
      ll += 3;
    }
    if (ll>73) {
      r += "=\r\n";
      ll = 0;
    }
  }
  return r;
}

// browser/common/rfc2047.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// RFC2047 specifies how non-US-ASCII characters can be represented in 
// email headers using =?...?= syntax.

// Here is the syntax of the encoding:
//
//   encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
//
//   charset = token
//
//   encoding = token
//
//   token = 1*<Any CHAR except SPACE, CTLs, and especials>
//
//   especials = "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "
//               <"> / "/" / "[" / "]" / "?" / "." / "="
//
//   encoded-text = 1*<Any printable ASCII character other than "?"
//                     or SPACE>


function decode_rfc2047(s)
{
  if (s=="") {
    return "";
  }

  var pos1 = s.indexOf("=?");
  if (pos1==-1) {
    return s;
  }

  var pos2 = s.indexOf("?",pos1+2);
  if (pos2==-1) {
    // error
    return s;
  }

  var pos3 = s.indexOf("?",pos2+1);
  if (pos3==-1) {
    // error
    return s;
  }

  var pos4 = s.indexOf("?=",pos3+1);
  if (pos4==-1) {
    // error
    return s;
  }

  var charset = s.substr(pos1+2,pos2-pos1-2);
  var encoding = s.substr(pos2+1,pos3-pos2-1);
  var encoded_text = s.substr(pos3+1,pos4-pos3-1);

  return s.substr(0,pos1) 
         + decode_word(charset,encoding,encoded_text)
         + decode_rfc2047(s.substr(pos4+2));
}


/*private*/ function decode_word(charset,encoding,encoded_text)
{
  var s;
  encoding=encoding.toLowerCase();
  if (encoding=="q") {
    s = decode_quoted_printable(encoded_text);
    s = s.replace(/_/g," ");
  } else if (encoding=="b") {
    s = decode_base64(encoded_text);
  } else {
    // error
    return encoded_text;
  }

  // charset not yet transcoded
  return s;
}



// browser/common/sliders.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Sliders for pane resizing:


function Slider()
{
  this.slider = div(document.getElementsByTagName("BODY")[0]);
  addclass(this.slider,"slider");

  this.press = function(ev)
  {
    gbind_mousemove(this.move.bind_mem(this));
    gbind_mouseup(this.release.bind_mem(this));
    this.last_mouse_pos = get_mouse_pos(ev);
  }

  this.move = function(ev)
  {
    var new_mouse_pos = get_mouse_pos(ev);
    var dx = new_mouse_pos.x - this.last_mouse_pos.x;
    var dy = new_mouse_pos.y - this.last_mouse_pos.y;
    this.displace(dx,dy);
    this.last_mouse_pos = new_mouse_pos;
  }

  this.release = function()
  {
    ungbind_mousemove();
    ungbind_mouseup();
  }

  this.setpos = function(x,y)
  {
    this.slider.style.left = x + "px";
    this.slider.style.top  = y + "px";
  }

  bind_mousedown(this.slider, this.press.bind_mem(this));
}


function HSlider(leftpanes_, rightpanes_, rightpanes_nostretch_)
{
  this.inheritfrom = Slider;
  this.inheritfrom();

  this.leftpanes = leftpanes_;
  this.rightpanes = rightpanes_;
  this.rightpanes_nostretch = rightpanes_nostretch_;

  addclass(this.slider,"hslider");

  this.displace = function(dx,dy)
  {
    var oldpos = find_element_pos(this.slider);
    this.setpos(oldpos.x+dx, oldpos.y);
    for (var i = 0; i<this.leftpanes.length; i++) {
      var el = this.leftpanes[i];
      el.style.width = (el.offsetWidth+dx) + "px";
    }
    for (var i = 0; i<this.rightpanes.length; i++) {
      var el = this.rightpanes[i];
      var pos = find_element_pos(el);
      el.style.left = (pos.x+dx) + "px";
      el.style.width = (el.offsetWidth-dx) + "px";
    }
    for (var i = 0; i<this.rightpanes_nostretch.length; i++) {
      var el = this.rightpanes_nostretch[i];
      var pos = find_element_pos(el);
      el.style.left = (pos.x+dx) + "px";
    }
  }

  var el = leftpanes_[0];
  var bbox = find_element_bbox(el);
  this.setpos(bbox.x2-8, bbox.y1);
}


function VSlider(abovepanes_, belowpanes_, belowpanes_nostretch_)
{
  this.inheritfrom = Slider;
  this.inheritfrom();

  this.abovepanes = abovepanes_;
  this.belowpanes = belowpanes_;
  this.belowpanes_nostretch = belowpanes_nostretch_;

  addclass(this.slider,"vslider");

  this.displace = function(dx,dy)
  {
    var oldpos = find_element_pos(this.slider);
    this.setpos(oldpos.x, oldpos.y+dy);
    for (var i = 0; i<this.abovepanes.length; i++) {
      var el = this.abovepanes[i];
      el.style.height = (el.offsetHeight+dy) + "px";
    }
    for (var i = 0; i<this.belowpanes.length; i++) {
      var el = this.belowpanes[i];
      var pos = find_element_pos(el);
      el.style.top = (pos.y+dy) + "px";
      el.style.height = (el.offsetHeight-dy) + "px";
    }
    for (var i = 0; i<this.belowpanes_nostretch.length; i++) {
      var el = this.belowpanes_nostretch[i];
      var pos = find_element_pos(el);
      el.style.top = (pos.y+dy) + "px";
    }
  }

  var el = abovepanes_[0];
  var bbox = find_element_bbox(el);
  this.setpos(bbox.x1, bbox.y2);
}



// browser/common/string_utils.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// String utilities:

// Strip leading and trailing spaces:
function strip_spaces(s)
{
  var p=0;
  while (s[p]==" ") {
    p++;
  }
  s=s.substr(p);
  var p=0;
  while (s[s.length-p-1]==" ") {
    p++;
  }
  return s.substr(0,s.length-p);
}

// Strip leading and trailing quotes (even if they don't match)
function strip_any_quotes(s)
{
  if (s.charAt(0)=="\"") {
    s=s.substr(1);
  }
  if (s.charAt(s.length-1)=="\"") {
    s=s.substr(0,s.length-1);
  }
  return s;
}


// Case-insensitive comparison:
function ci_equal(s1,s2)
{
  if (!s1 || !s2) {
    return false;
  }
  return s1.toLowerCase()==s2.toLowerCase();
}


// Compare string prefix:
function starts_with(s,prefix)
{
  return s.substr(0,prefix.length)==prefix;
}


// Escape " and \ and put "..." around the string:
function quoted(s)
{
  return "\"" + s.replace("\\","\\\\","g").replace("\"","\\\"","g") + "\"";
}


function make_spaces_nonbreaking(s)
{
  return s.replace(/ /g,nbsp);
}

function is_space(c)
{
  return (c==" " || c=="\t" || c=="\n" || c=="\r");
}

// browser/common/version.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


dmwebmail_version="3alpha16";

// browser/webmail/actions.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.



function mark_all_read()
{
  uid_store_plusflags_cmd("1:*","\\Seen");
}

// browser/webmail/complete.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


var the_completions;
var the_completion_callback;

var enable_completion = false;  // enabled if the XCOMPLETE capability is reported.


function request_completions(maxitems,header_name,pattern,callback)
{
  if (enable_completion) {
    the_completion_callback = callback;
    complete_cmd(maxitems,header_name,pattern,complete_cmd_done);
  }
}

function complete_cmd_done()
{
  the_completion_callback(the_completions);
}


function process_completions(completions)
{
  the_completions = completions;
}

// browser/webmail/compose.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Message composition window:


var compose_win;

function compose(to, cc, subject, in_reply_to, references, body, callback)
{
  window.compose_info = {to: render_addrlist_rfc822(to),
                         cc: render_addrlist_rfc822(cc), 
                         subject: subject,
                         in_reply_to: in_reply_to,
                         references: references,
                         body: body,
                         callback: callback};
  var window_attributes = "width=800,height=700,resizeable=yes,"
                        + "toolbar=no,location=no,directories=no,"
                        + "status=no,memnubar=no,copyhistory=no";
  compose_win = window.open("compose.html","",window_attributes);
}


function compose_to(to)
{
  compose([to],[],"","",[],"",null);
}

// browser/webmail/dragdrop.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Drag and drop:


var dropfn = null;

function make_dragable(el, fn)
{
  bind_mousedown(el, dragstart.bind_fn(fn));
  addclass(el,"dragsource");
}


function dragstart(fn)
{
  dropfn = fn;
  gbind_mousemove(dragfirstmove);
  gbind_mouseup(dragdidntstart);
}

function dragdidntstart()
{
  dropfn = null;
  ungbind_mousemove();
  ungbind_mouseup();
}

function dragfirstmove(ev)
{
  dragreallystart();
  gbind_mousemove(dragmove);
  gbind_mouseup(dragdrop);
  dragmove(ev);
}

var currentlyover;
var currentlyover_data;

function dragmove(ev)
{
  var mousexy = get_mouse_pos(ev);
  var eldata = dropindex.find(mousexy.x, mousexy.y);
  var el = eldata ? eldata.el : null;
  if (el != currentlyover) {
    if (currentlyover) {
      rmvclass(currentlyover,"droptarget");
    }
    if (el) {
      addclass(el,"droptarget");
    }
  }
  currentlyover = el;
  currentlyover_data = eldata ? eldata.data : null;
}

function dragdrop()
{
  ungbind_mousemove();
  ungbind_mouseup();
  if (currentlyover) {
    rmvclass(currentlyover,"droptarget");
    dropfn(currentlyover_data);
  }
  rmvclass(document.getElementsByTagName("BODY")[0],"draginprogress");
  dropfn = null;
}

function dragreallystart()
{
  addclass(document.getElementsByTagName("BODY")[0],"draginprogress");
  make_dropindex();
  currentlyover = null;
  currentlyover_data = null;
}


var dropables = [];
var dropindex = null;

function make_dropable(el,data)
{
  // In some unusual cases this is called again for the same element with new 
  // data.
  if (el.dropable) {
    for (var i=0; i<dropables.length; i++) {
      var e = dropables[i];
      if (e==el) {
        e.data = data;
      }
    }
  } else {
    dropables.push({el:el,data:data});
    el.dropable = true;
  }
}

function make_dropindex()
{
  dropindex = new SpatialIndex();
  for (var i=0; i<dropables.length; i++) {
    var eldata = dropables[i];
    var bbox = find_element_bbox(eldata.el);
    dropindex.add(bbox.x1,bbox.x2,bbox.y1,bbox.y2,eldata);
  }
}

// browser/webmail/EditInPlace.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Display element that is normally a span but can be editted in place by conversion 
// to an input element


function EditInPlace(parent_,initial_text_)
{
  this.parent = parent_;
  this.text = initial_text_;
  this.edit_in_progress = false;

  this.span = span(this.parent,"",this.text);
  this.input = null;

  this.on_click = null;
  this.on_dblclick = null;

  this.start_edit = function(cont)
  {
    this.input = input(this.parent,"","text",this.text);
    this.parent.insertBefore(this.input,this.span);
    this.parent.removeChild(this.span);
    this.span = null;
    disable_global_keys_for(this.input);
    bind_keypress(this.input,this.process_keypress.bind_mem(this,cont));
    this.input.focus();
    this.edit_in_progress = true;
  }

  this.process_keypress = function(cont,ev)
  {
    var keycode = get_keycode_from_event(ev);
    if (keycode==27) { // ESC
      this.end_edit(cont,false);
      return false;
    } else if (keycode==13) { // Return
      this.end_edit(cont,true);
      return false;
    }
    return true;
  }

  this.end_edit = function(cont,commit)
  {
    if (commit) {
      this.text = this.input.value;
    }
    this.span = span(this.parent,"",this.text);
    this.parent.insertBefore(this.span,this.input);
    this.parent.removeChild(this.input);
    this.input = null;
    if (this.on_click) {
      bind_click(this.span,this.on_click);
    }
    if (this.on_dblclick) {
      bind_dblclick(this.span,this.on_dblclick);
    }
    this.edit_in_progress = false;
    if (cont && commit) {
      cont();
    }
  }

  this.bind_click = function(fn)
  {
    this.on_click = fn;
    if (this.span) {
      bind_click(this.span,fn);
    }
  }

  this.bind_dblclick = function(fn)
  {
    this.on_dblclick = fn;
    if (this.span) {
      bind_dblclick(this.span,fn);
    }
  }

  this.set_text = function(t)
  {
    this.text = t;
    if (this.input) {
      this.input.value = t;
    } else {
      settext(this.span,t);
    }
  }
  
}


// browser/webmail/footer.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Content of the footer pane.

// Please read section 2(c) of the GPL before changing the following 
// text, which is displayed at the bottom of the screen.  If you 
// distribute a modified version of this program, it must display an 
// announcement that it is free software when it is run.

// Do please respect this requirement.  With previous web-based code 
// that I have released under the GPL I have found copies on web sites 
// with the copyright information removed.  This is unacceptable.

var footer_contents =
 "<a href=\"http://decimail.org/webmail/\" target=\"_blank\">Decimail Webmail 3</a> "
+"version "+dmwebmail_version+" "
+"(c) 2006-2007 Philip Endecott<br>"
+"This program is free software; you can redistribute it and/or "
+"modify it under the terms of the "
+"<a href=\"LICENSE.txt\" target=\"_blank\">GNU General Public License</a>.";

// browser/webmail/HeaderPartUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// User interface for the message header 'part'


function HeaderPartUI()
{
  this.score = 0;

  this.render0 = function()
  {
    this.part.content.recode("8bit");
    addclass(this.pane,"raw");
    render_with_links(this.pane, normalise_newlines(this.part.content.data));
  }

  this.get_description = function()
  {
    return "Header";
  }
}

// browser/webmail/hints.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Hint-of-the-day:


var hints = [
  "This program is free software: you are free to change it and "
    +"redistribute it under the terms of the GNU General Public License.",
  "You can disable these hints by editing config.js.",
  "We need help to make Decimail Webmail better.  Please get in touch if "
    +"you would like to contribute.",
  "You can copy messages to other folders by drag-and-drop.",
  "To mark all messages in the current mailbox as read, press shift-C.",
  "Many actions are bound to keys, e.g. r = reply, shift-R = reply-to-all.",
  "You can resize the different panes of this display using the sliders.",
  "Take care when viewing HTML email from untrusted senders.  Some "
    +"sanitisation is performed but no doubt there are ways to avoid it.",
  "If you have any suggestions or questions, please visit the Decimail "
    +"Webmail forum at http://decimail.org/forum/",
  "To enable some features, including sending attachments, you need to "
    +"adjust your browser settings.  See the Decimail Webmail web site "
    +"for details.",
  "You can move between messages by pressing the cursor keys.",
  "You can sort the message list by clicking on a column header.",
  "Click the mouse on one of the small triangles to see a menu.",
  "You can save an attachment from the menu in its tab.",
  "To save the complete message, use the menu in the 'Raw' tab.",
  "If a text message is not line-wrapped correctly, try changing the "
    +"'Flow' setting in its tab menu.",
  "You can click on an email address (e.g. in a message's To: or From: line) "
    +"to start writing a message to that address.",
  "Do please report your experiences if you try Decimail Webmail on a "
    +"browser other than Mozilla, e.g. Safari or Opera.  We would like to "
    +"support these if we can.",
  "To create a new mailbox, first open the menu of its parent by clicking on "
    +"the small triangle to the right of its name.",
  "Auto-completion of recipient addresses in the composition window is possible "
    +"if you're using Decimail Server.",
  "You can double click on a mailbox name to rename it."
];


function maybe_show_hint()
{
  if (show_hint) {
    var n = Math.floor(Math.random()*hints.length);
    var hint = hints[n];
    alert("Hint of the day:\n"+hint);
  }
}


// browser/webmail/HtmlPartUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// User interface to a text/html message part


function HtmlPartUI()
{
  this.score = 7;

  this.render0 = function()
  {
    this.part.content.recode("8bit");
    addclass(this.pane,"text-html");
    this.pane.innerHTML = sanitise_html(this.part.content.data);
  }

  this.get_description0 = function()
  {
    return "HTML";
  }
}


// browser/webmail/http2imap.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Http2Imap connection:

var sessionid;
var current_charset = "ISO-8859-1";

var http2imap_url = http2email_url+"imap/";

var connection_open = false;

function establish_connection()
{
  info("Connecting to server...");
  sessionid = sync_load(http2imap_url,"POST","");
  debug("Got session_id "+sessionid);
  get_greeting();
  connection_open = true;
  get_responses();
}

function http2imap_send(s)
{
  async_load(http2imap_url+sessionid+"-"+current_charset,"POST",s);
}


function get_greeting()
{
  while (!response_parser.seen_greeting) {
    var s = sync_load(http2imap_url+sessionid,"POST","");
    debug("Initial response: "+s);
    response_parser.input(s);
  }
}


function get_responses()
{
  async_load(http2imap_url+sessionid+"-"+current_charset,
             "POST","",process_response);
}

function process_response(s)
{
  debug("Response: "+s);
  if (s) {
    response_parser.input(s);
  }
  if (connection_open) {
    get_responses();
  }
}


function change_charset(charset, callback)
{
  current_charset = charset;
  noop_cmd(callback);
}


function close_connection()
{
  connection_open = false;
}

// browser/webmail/ImagePartUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// User interface for an image/* message part


function ImagePartUI()
{
  this.score = 3;

  this.render0 = function()
  {
    this.part.content.recode("base64");
    addclass(this.pane,"image");
    if (this.part.p_name) {
      p(this.pane,"",this.part.p_name);
    }
    img(this.pane,"",dataurl("image/"+this.part.subtype,this.part.content.data));
  }

  this.get_description0 = function()
  {
    return this.part.subtype.toUpperCase() + " image";
  }
}


// browser/webmail/imap_cmd.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Imap commands:

var nexttag=0;
var commands={};
var command_okcallbacks={};
var command_nocallbacks={}

function imapcmd(cmd, okcallback, nocallback)
{
  nexttag++;
  commands[nexttag]=cmd;
  command_okcallbacks[nexttag]=okcallback;
  command_nocallbacks[nexttag]=nocallback;
  var data = nexttag+" "+cmd+"\n";
  debug("imap command: "+data);
  http2imap_send(data);
}

function cmd_response(tag,status,text)
{
  var cmd = commands[tag];
  debug("response for command '"+cmd+"' was '"+status+"'");
  if (status=="OK") {
    var okcallback = command_okcallbacks[tag];
    if (okcallback) {
      okcallback();
    }
  } else if (status=="NO") {
    var nocallback = command_nocallbacks[tag];
    if (nocallback) {
      nocallback();
    } else {
      warn("The IMAP server returned the following message:\n"+text);
      window.status="OK";
    }
  } else if (status=="BAD") {
    fatal("The following IMAP command failed:\n"+cmd
         +"\nThe server message was:\n"+text);
  }
  commands[tag]=null;
  command_okcallbacks[tag]=null;
  command_nocallbacks[tag]=null;
}


function login_cmd(username, password, okcallback, nocallback)
{
  imapcmd("LOGIN "+quoted(username)+" "+quoted(password), okcallback, nocallback);
}

function list_cmd(reference, pattern, okcallback, nocallback)
{
  imapcmd("LIST "+quoted(reference)+" "+quoted(pattern), okcallback, nocallback);
}

function select_cmd(mailbox, okcallback, nocallback)
{
  imapcmd("SELECT "+quoted(mailbox), okcallback, nocallback);
}

var envelope_elements = "ENVELOPE FLAGS RFC822.SIZE UID";

function fetch_all_envelope_cmd(okcallback, nocallback)
{
  imapcmd("FETCH 1:* ("+envelope_elements+")", okcallback, nocallback);
}

function uid_fetch_envelope_cmd(uids, okcallback, nocallback)
{
  imapcmd("UID FETCH "+uids+" ("+envelope_elements+")",
          okcallback, nocallback);
}

function uid_fetch_cmd(uids, data_item_names, okcallback, nocallback)
{
  imapcmd("UID FETCH "+uids+" ("+data_item_names+")", okcallback, nocallback);
}

function uid_search_all_cmd(okcallback, nocallback)
{
  imapcmd("UID SEARCH ALL", okcallback, nocallback);
}

function noop_cmd(okcallback, nocallback)
{
  imapcmd("NOOP", okcallback, nocallback);
}

function create_cmd(mailbox_name, okcallback, nocallback)
{
  imapcmd("CREATE "+quoted(mailbox_name), okcallback, nocallback);
}

function delete_cmd(mailbox_name, okcallback, nocallback)
{
  imapcmd("DELETE "+quoted(mailbox_name), okcallback, nocallback);
}

function rename_cmd(old_mailbox_name, new_mailbox_name,
                    okcallback, nocallback)
{
  imapcmd("RENAME "+quoted(old_mailbox_name)+" "+quoted(new_mailbox_name),
          okcallback, nocallback);
}

function uid_copy_cmd(uid, mailbox_name, okcallback, nocallback)
{
  imapcmd("UID COPY "+uid+" "+quoted(mailbox_name),
          okcallback, nocallback);
}

function uid_store_plusflags_cmd(uid, flag, okcallback, nocallback)
{
  imapcmd("UID STORE "+uid+" +FLAGS ("+flag+")",
          okcallback, nocallback);
}

function uid_store_minusflags_cmd(uid, flag, okcallback, nocallback)
{
  imapcmd("UID STORE "+uid+" -FLAGS ("+flag+")",
          okcallback, nocallback);
}

function logout_cmd(okcallback, nocallback)
{
  imapcmd("LOGOUT", okcallback, nocallback);
}

function complete_cmd(maxitems, header_name, pattern, okcallback, nocallback)
{
  imapcmd("XCOMPLETE "+maxitems+" "+quoted(header_name)+" "+quoted(pattern),
          okcallback, nocallback);
}

function capability_cmd(okcallback, nocallback)
{
  imapcmd("CAPABILITY", okcallback, nocallback);
}

// browser/webmail/init.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Initialisation:


function initialise()
{
  report_any_errors(initialise_cont);
}

function initialise_cont()
{
  get_username_password(initialise_cont_cont);
}

function initialise_cont_cont()
{
  info("Starting...");
  mailboxes = new Mailboxes();
  messagelist = new MessageList;
  ui = new UI();
  waiting(ui.pane);
  response_parser = new imap_response_parser;
  establish_connection();
  start_periodic_noops();
  login();
}


window.onload = initialise;
// browser/webmail/login.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Log in and stuff.


var username;
var password;

function login()
{
  info("Loging in...");
  login_cmd(username,password,post_login_0,login_failed);
}

function login_failed()
{
  alert("Your credentials were rejected by the IMAP server.\n"
       +"Click OK to restart.");
  window.location.reload();
}

function post_login_0()
{
  response_parser.list_root_hierarchy = 1;
  info("Getting server settings...");
  list_cmd("","",post_login_1);
}

function post_login_1()
{
  info("Getting server settings......");
  capability_cmd(list_root_mailboxes);
}

function list_root_mailboxes()
{
  ui.mailboxesui.init_tree(post_list_mailboxes);
}

function post_list_mailboxes()
{
  endwait(ui.pane);
  if (initial_mailbox) {
    var mb = mailboxes.mailboxes[initial_mailbox];
    ui.select_mailbox(mb, maybe_show_hint);
  } else {
    maybe_show_hint();
  }
}



function get_username_password(cont)
{
  if (use_fixed_username_password) {    
    username = fixed_username;
    password = fixed_password;
    if (cont) {
      cont();
    }
  } else {
    login_dialog(cont);
  }
}

var login_div;
var username_i;
var password_i;

function login_dialog(cont)
{
  login_div = div(document.getElementsByTagName("BODY")[0], "logindialog");
  var username_l = label(login_div,"","loginusername","Username");
  username_i = input(login_div,"loginusername","text");
  var password_l = label(login_div,"","loginpassword","Password");
  password_i = input(login_div,"loginpassword","password");
  var submit_button = input(login_div,"","submit","Login");
  bind_click(submit_button,login_dialog_submit.bind_fn(cont));
}


function login_dialog_submit(cont)
{
  username = username_i.value;
  password = password_i.value;

  delete_contents(login_div);
  document.getElementsByTagName("BODY")[0].removeChild(login_div);

  if (cont) {
    cont();
  }
  return false; // prevent form submission.
}

// browser/webmail/logout.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Log out:


function logout()
{
  var ok = confirm("Really logout?");
  if (!ok) {
    return;
  }

  info("Loging out...");
  close_connection();
  logout_cmd(post_logout);
}

function post_logout()
{
  delete_contents(document.getElementsByTagName("BODY")[0]);
  info("Terminated");
}

// browser/webmail/Mailboxes.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// The list of mailboxes that the server has told us about.


function Mailboxes()
{
  // An associative array of mailboxes, indexed by their IMAP names:
  this.mailboxes = {};

  // A callback function that can be set to get notifications when a mailbox is added.  
  // It is called with the Mailbox object as a parameter.
  // ?? Redundant ??
  this.on_add_mailbox = null;

  // Similarly for notification when a mailbox is deleted.  imap_name of 
  // deleted mailbox is passed.
  this.on_delete_mailbox = null;

  // Add and return a new mailbox, given its IMAP name and flags.
  // Note that this adds a new Mailbox object corresponding to an existing mailbox on 
  // the server.  To actually create a new mailbox on the server see the next function.
  this.add_new_mailbox = function(imap_name, flags)
  {
    var mb = new Mailbox(imap_name, flags);
    this.mailboxes[imap_name] = mb;
    if (this.on_add_mailbox) {
      this.on_add_mailbox(mb);
    }
    return mb;
  }

  // Create a new mailbox on the server, along with the corresponding Mailbox object.
  this.create = function(imap_name,cont)
  {
    // What flags for a new mailbox?
    var flags = {};
    var mb = new Mailbox(imap_name, flags);
    mb.create(this.create_cont.bind_mem(this,mb,cont));
  }

  this.create_cont = function(mb,cont)
  {
    this.mailboxes[mb.imap_name] = mb;
    if (cont) {
      cont(mb);
    }
  }


  // Delete a mailbox:
  this.del = function(mailbox,cont)
  {
    var imap_name = mailbox.imap_name;
    mailbox.del(this.delete_cont.bind_mem(this,imap_name,cont));
  }

  this.delete_cont = function(imap_name,cont)
  {
    delete this.mailboxes[imap_name];
    if (cont) {
      cont();
    }
  }


  // Rename the leafname of a mailbox:
  this.rename_leaf = function(mailbox, new_leafname, cont)
  {
    var old_imap_name = mailbox.imap_name;
    mailbox.rename_leaf(new_leafname,
                        this.rename_cont.bind_mem(this,mailbox,old_imap_name,cont));
  }

  this.rename_cont = function(mailbox, old_imap_name, cont)
  {
    this.mailboxes[mailbox.imap_name] = mailbox;
    delete this.mailboxes[old_imap_name];
    if (cont) {
      cont();
    }
  }


  // Rename the complete path of a mailbox:
  this.rename_path = function(mailbox, new_path, cont)
  {
    var old_imap_name = mailbox.imap_name;
    mailbox.rename_path(new_path,
                        this.rename_cont.bind_mem(this,mailbox,old_imap_name,cont));
  }





  // Redundant???
  // Notification when a mailbox has been deleted:
  this.mailbox_deleted = function(mb)
  {
    delete this.mailboxes[mb.imap_name];
    delete mb;
  }
  
  
}
// browser/webmail/MailboxesUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Tree UI to the mailbox list.


function MailboxesUI(pane_, mailboxes_)
{
  // The Mailboxes (mailbox list) that this interfaces to:
  this.mailboxes = mailboxes_;

  // The pane in which it is rendered:
  this.pane = pane_;

  // The MailboxUIs, indexed by imap_name:
  this.mailboxuis = {};
  // Note that indexing the MailboxUIs by imap_name makes things more 
  // complicated when they are renamed...


  // Build the Tree user interface.  This must wait until after login when 
  // root_mailbox is known.
  this.init_tree = function(cont)
  {
    var rui = new RootMailboxUI(root_mailbox,this);
    this.tree = new Tree(this.pane, rui, root_mailbox);
    rui.add_to_tree(this.tree);
    this.tree.expand(cont);
  }

  // This is a callback when a new mailbox is added:
  this.add_mailbox = function(mailbox)
  {
    var mbui = new MailboxUI(mailbox,this);
    this.mailboxuis[mailbox.imap_name] = mbui;
    mbui.add_to_tree(this.tree);
  }

  this.mailboxes.on_add_mailbox = this.add_mailbox.bind_mem(this);


  // Show selection:

  this.show_selected = function(mailbox)
  {
    this.mailboxuis[mailbox.imap_name].show_selected();
  }

  this.show_unselected = function(mailbox)
  {
    this.mailboxuis[mailbox.imap_name].show_unselected();
  }


  // Mailbox search stuff:

  this.find_results_a = [];
  this.find_results_by_imap_name = {};

  this.search_mailboxes = function()
  {
    waiting(this.pane);
    info("Finding mailboxes...");
    var searchterm = this.mailbox_search_input.value;
    this.mailbox_search_input.value = "";
    rmvclass_rec(this.pane,"findresult");
    add_mailbox_find_mode = true;
    list_cmd(root_mailbox,"*"+searchterm+"%",
             this.post_search_mailboxes.bind_mem(this));
    return false;  // prevent form submission
  }

  this.add_find_result = function(imap_name, flags)
  {
    this.find_results_by_imap_name[imap_name] = true;
    this.find_results_a.push(imap_name);
  }

  this.post_search_mailboxes = function()
  {
    add_mailbox_find_mode = false;
    if (this.find_results_a.length>0) {
      var path = this.find_results_a.shift().split(hierarchy_delimiter);
      this.process_find_result(path);
      return;
    }
    this.find_results_by_imap_name = {};
    endwait(this.pane);
    info("OK");
  }

  this.process_find_result = function(path)
  {
    var branchpath = [].concat(path);
    branchpath.splice(branchpath.length-1,1);
    this.tree.expand_down_to(branchpath,
                             this.process_find_result_cont.bind_mem(this,path))
  }

  this.process_find_result_cont = function(path)
  {
    var t = this.tree.find(path);
    addclass(t.line,"findresult");
    this.post_search_mailboxes();
  }


  // Build the mailbox search widget
  var searchdiv = div(this.pane,"mailboxsearch");
  var searchform = form(searchdiv);
  var labeld = div(searchform);
  var labell = label(labeld,"","searchmailbox","Find mailboxes");
  this.mailbox_search_input = input(searchform,"searchmailbox","text");
  disable_global_keys_for(this.mailbox_search_input);
  bind_submit(searchform,this.search_mailboxes.bind_mem(this));


  // Create sub-mailbox:

  this.create_submailbox = function(parent_mailbox)
  {
    var nmbui = new NewMailboxUI(parent_mailbox,this);
    nmbui.callback = this.create_submailbox_cont.bind_mem(this,nmbui,parent_mailbox);
    nmbui.add_to_tree(this.tree);
    this.tree.find(parent_mailbox.path).expand();
  }

  this.create_submailbox_cont = function(nmbui,parent_mailbox,new_leafname)
  {
    this.tree.remove(nmbui.path);
    var new_imap_name = parent_mailbox.imap_name + hierarchy_delimiter + new_leafname;
    this.mailboxes.create(new_imap_name,this.add_mailbox.bind_mem(this));
  }


  // Prompt for and create a new mailbox:

  this.create_mailbox = function()
  {
    var mailbox_name = prompt("New mailbox name:");
    if (!mailbox_name) {
      return;
    }
    this.mailboxes.create(mailbox_name,this.add_mailbox.bind_mem(this));
  }


  // Rename:

  this.rename_leaf = function(mailbox)
  {
    var mailboxui = this.mailboxuis[mailbox.imap_name];
    mailboxui.irename_leaf(this.rename_leaf_cont.bind_mem(this,mailbox,mailboxui));
  }

  this.rename_leaf_cont = function(mailbox,mailboxui,new_leafname)
  {
    var old_imap_name = mailbox.imap_name;
    this.mailboxes.rename_leaf(mailbox,new_leafname,
        this.rename_leaf_contcont.bind_mem(this,mailbox,mailboxui,old_imap_name));
  }

  this.rename_leaf_contcont = function(mailbox,mailboxui,old_imap_name)
  {
    mailboxui.update_name();
    this.mailboxuis[mailbox.imap_name] = mailboxui;
    delete this.mailboxuis[old_imap_name];
  }


  // Drag-drop to move:

  this.dragdrop_mailbox = function(src_mailbox, dest_mailbox)
  {
    // Do nothing if dropped on itself:
    if (src_mailbox == dest_mailbox) {
      return;
    }

    // src_mailbox will retain its leafname, but will be reparented under 
    // dest_mailbox.

    var old_path = src_mailbox.path;
    var old_imap_name = src_mailbox.imap_name;
    var new_path = dest_mailbox.path.concat(src_mailbox.leafname);
    this.mailboxes.rename_path(src_mailbox,new_path,
        this.dragdrop_mailbox_cont.bind_mem(this,src_mailbox,old_path,old_imap_name));
  }

  this.dragdrop_mailbox_cont = function(mailbox,old_path,old_imap_name)
  {
    // Rather than attempt to rename the existing MailboxUI we'll destroy it and 
    // make a new one for the new position.
    // Hmm, need to consider the case when we move the selected mailbox.
    this.tree.remove(old_path);
    delete this.mailboxuis[old_imap_name];
    this.add_mailbox(mailbox);
  }


  // Delete:

  this.del = function(mailbox,cont)
  {
    var imap_name = mailbox.imap_name;
    var ok = confirm("Really delete mailbox "+imap_name+" ?\n"
                    +"All of the messages in this mailbox will be deleted,\n"
                    +"and the operation CANNOT BE UNDONE.");
    if (!ok) {
      return;
    }
    var del_selected = (mailbox==ui.selected_mailbox);
    var path = mailbox.path;
    this.mailboxes.del(mailbox,
            this.delete_cont.bind_mem(this,del_selected,path,imap_name,cont));
  }

  this.delete_cont = function(del_selected,path,imap_name,cont)
  {
    if (del_selected) {
      ui.select_no_mailbox();
    }
    this.tree.remove(path);
    delete this.mailboxuis[imap_name];
    if (cont) {
      cont();
    }
  }

}


var add_mailbox_find_mode = false;

// browser/webmail/Mailbox.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// A single mailbox.



function Mailbox(imap_name_, flags)
{
  // this.imap_name is the name of this mailbox as it is known to the IMAP server, i.e. 
  // a single string with a server-dependent hierarchy delimiter.
  this.imap_name = imap_name_;

  // Compute the path by splitting the IMAP name:
  this.path = this.imap_name.split(hierarchy_delimiter);

  // The leaf name is just the end of the path:
  this.leafname = this.path[this.path.length-1];

  // Decipher the flags:
  this.selectable = ! flags["\\Noselect"];
  this.children = ! flags["\\Noinferiors"]
               && ! flags["\\HasNoChildren"];
  this.allows_children = ! flags["\\Noinferiors"];


  // delete must only be called by mailboxes.delete, otherwise that container's 
  // index will become out of sync.
  this.del = function(cont)
  {
    info("Deleting mailbox "+this.imap_name+"...");
    delete_cmd(this.imap_name,this.delete_cont.bind_mem(this,cont));
  }

  this.delete_cont = function(cont)
  {
    info("OK");
    if (cont) {
      cont();
    }
  }


  this.rename_leaf = function(new_leafname,cont)
  {
    if (new_leafname == this.leafname) {
      return;  // Hmm, cont?
    }
    var new_path = [].concat(this.path);
    new_path[new_path.length-1] = new_leafname;
    var new_imap_name = new_path.join(hierarchy_delimiter);
    rename_cmd(this.imap_name,new_imap_name,this.rename_done.bind_mem(this,new_path,cont));
  }

  this.rename_done = function(new_path,cont)
  {
    this.path = new_path;
    this.leafname = this.path[this.path.length-1];
    this.imap_name = this.path.join(hierarchy_delimiter);
    if (cont) {
      cont();
    }
  }


  this.rename_path = function(new_path,cont)
  {
    var new_imap_name = new_path.join(hierarchy_delimiter);
    if (new_imap_name == this.imap_name) {
      return;  // Hmm, cont?
    }
    rename_cmd(this.imap_name,new_imap_name,this.rename_done.bind_mem(this,new_path,cont));
  }


  // To create a new mailbox on the server, first create a new Mailbox object with the 
  // desired imap_name, and then invoke its create method.
  this.create = function(cont)
  {
    info("Creating mailbox "+this.imap_name+"...");
    create_cmd(this.imap_name,this.create_cont.bind_mem(this,cont));
  }

  this.create_cont = function(cont)
  {
    info("OK");
    if (cont) {
      cont();
    }
  }

}
// browser/webmail/MailboxUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Interface to a single mailbox as represented in the mailbox tree.



function MailboxUI(mailbox_, mailboxesui_)
{
  // The Mailbox object that this is the UI for:
  this.mailbox = mailbox_;

  // The MailboxesUI object that this is part of:
  this.mailboxesui = mailboxesui_;


  // Called to interactively rename this mailbox's leafname.
  // Invokes the continuation with the new leafname.
  // This doesn't actually change the name, it just does the user interaction.
  // To actually do the whole business, call mailboxesui.rename.
  this.irename_leaf = function(cont)
  {
    if (this.nameeditor.edit_in_progress) {
      return;
    }
    this.nameeditor.start_edit(this.irename_leaf_cont.bind_mem(this,cont));
  }

  /*private*/ this.irename_leaf_cont = function(cont)
  {
    // TODO show intermediate state while rename occurs
    // For now we have to temporarily revert to the old name in case the rename 
    // command fails.
    var new_leafname = this.nameeditor.text;
    this.nameeditor.set_text(this.mailbox.leafname);
    cont(new_leafname);
  }


  // Change the leafname to whatever's now in the mailbox.  Called after the 
  // mailbox has been renamed.
  this.update_name = function()
  {
    this.nameeditor.set_text(this.mailbox.leafname);
    // We don't need to do this now that the dragdrop data is the mailbox itself 
    // rather than its imap_name.
    //this.setup_dragdrop();
    this.treenode.rename_leaf(this.mailbox.leafname);
  }


  this.clicked = function()
  {
    if (this.mailbox.selectable) {
      ui.select_mailbox(this.mailbox);
    } else {
      this.treenode.toggle();
    }
  }

  // Called by the tree.
  this.render = function(parent)
  {
    this.nameeditor = new EditInPlace(parent,this.mailbox.leafname);
    this.nameeditor.bind_click(this.clicked.bind_mem(this));
    this.nameeditor.bind_dblclick(this.mailboxesui.rename_leaf.bind_mem(this.mailboxesui,this.mailbox));

    var m = popup_menu(parent);
    menuitem(m,"Delete",this.mailboxesui.del.bind_mem(this.mailboxesui,this.mailbox,null));
    menuitem(m,"Rename",this.mailboxesui.rename_leaf.bind_mem(this.mailboxesui,this.mailbox));
    if (this.mailbox.allows_children) {
      menuitem(m,"Create sub-mailbox",this.mailboxesui.create_submailbox.bind_mem(this.mailboxesui,this.mailbox));
    }
  }


  this.add_to_tree = function(tree)
  {
    this.treenode = tree.insert(this.mailbox.path, this);
    if (!this.treenode) {
      fatal("Adding to Tree with missing parents");
    }
    if (this.mailbox.children) {
      this.treenode.bind_expand(expand_mailbox_tree.bind_fn(this.mailbox.imap_name));
    }
    this.setup_dragdrop();
    this.tree_line = this.treenode.line;
  }

  this.setup_dragdrop = function()
  {
    this.treenode.make_dropable(this.mailbox);
    this.treenode.make_dragable(
      this.mailboxesui.dragdrop_mailbox.bind_mem(this.mailboxesui,this.mailbox));
  }

  this.show_selected = function()
  {
    select(this.tree_line);
  }

  this.show_unselected = function()
  {
    deselect(this.tree_line);
  }

}
// browser/webmail/menubar.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Menubar:


function menu(menubar, title)
{
  var m_li = li(menubar,"");
  var m_li_span = span(m_li,"",title);
  bind_click(m_li_span,openmenu.bind_fn(m_li));

  var m_ul = ul(m_li);
  addclass(m_ul,"menu");
  return m_ul;
}

function menuitem(menu, title, fn)
{
  var mi = li(menu,"",make_spaces_nonbreaking(title));
  if (fn) {
    bind_click(mi,fn);
  }
  return mi;
}


function build_menubar(pane, ui)
{
  var menubar = ul(pane);
  addclass(menubar,"menubar");

  var server_menu = menu(menubar,"Server");
  menuitem(server_menu,"Get Mail",get_mail);
  menuitem(server_menu,"Logout",logout);

  var mailbox_menu = menu(menubar,"Mailbox");
  menuitem(mailbox_menu,"Delete",ui.delete_selected_mailbox.bind_mem(ui));
  menuitem(mailbox_menu,"Create",ui.create_mailbox.bind_mem(ui));
  menuitem(mailbox_menu,"Rename",ui.rename_selected_mailbox_leaf.bind_mem(ui));

  var sort_menu = menu(menubar,"Sort");
  menuitem(sort_menu,"by Subject",ui.sort_by_subject.bind_mem(ui));
  menuitem(sort_menu,"by From",   ui.sort_by_from.bind_mem(ui));
  menuitem(sort_menu,"by Date",   ui.sort_by_date.bind_mem(ui));
  menuitem(sort_menu,"by Size",   ui.sort_by_size.bind_mem(ui));
  menuitem(sort_menu,"server order", ui.sort_by_uid.bind_mem(ui));

  var message_menu = menu(menubar,"Message");
  menuitem(message_menu,"Delete",ui.deletetoggle_msg.bind_mem(ui));
  menuitem(message_menu,"Reply",ui.reply.bind_mem(ui));
  menuitem(message_menu,"Reply to All",ui.reply_all.bind_mem(ui));
  menuitem(message_menu,"Forward",ui.forward.bind_mem(ui));
  menuitem(message_menu,"Edit / Resend",ui.resend.bind_mem(ui));
  menuitem(message_menu,"Print",ui.print.bind_mem(ui));

  var actions_menu = menu(menubar,"Actions");
  menuitem(actions_menu,"Mark All as Read",mark_all_read);
}

// browser/webmail/MessageList.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// The Message List:


function MessageList()
{
  // The messages in the list, in the order that they are currently displayed:
  this.messages = [];

  // The messages in the list, indexed by their UIDs:
  this.messages_by_uid = {};

  // The last message in the list, if any.  This was used as the insertion 
  // point for new messages, but that will now need to be changed due to 
  // sorting, so maybe this is redundant.
  this.last_message = null;

  // User-interface elements:
  this.msglist_pane=null;
  this.ml_subject=null;
  this.ml_from=null;
  this.ml_date=null;
  this.ml_size=null;
  this.ml_tbody=null;

  // Table rows and flag cells per UID:
  this.rows_by_uid = {};
  this.flags_by_uid = {};

  // Build the empty list UI:
  this.build_ui = function()
  {
    var ml_table = table(this.msglist_pane);
    var ml_thead = thead(ml_table);
    var ml_flags_c = col(ml_table,"flags");
    var ml_subject_c = col(ml_table,"subject");
    var ml_from_c = col(ml_table,"from");
    var ml_date_c = col(ml_table,"date");
    var ml_size_c = col(ml_table,"size");
    var ml_spacer_c = col(ml_table,"spacer");
    var ml_headrow = tr(ml_thead);
    var ml_flags = th(ml_headrow,"","");
    this.ml_subject = th(ml_headrow,"","Subject");
    this.ml_from = th(ml_headrow,"","From");
    this.ml_date = th(ml_headrow,"","Date");
    this.ml_size = th(ml_headrow,"","Size");
    var ml_spacer = th(ml_headrow,"","");
    this.ml_tbody = tbody(ml_table);

    bind_click(this.ml_subject,this.sort_by_subject.bind_mem(this));
    bind_click(this.ml_from,this.sort_by_from.bind_mem(this));
    bind_click(this.ml_date,this.sort_by_date.bind_mem(this));
    bind_click(this.ml_size,this.sort_by_size.bind_mem(this));

    // We could put HSliders between the col elements to allow the columns 
    // to be resized, except for https://bugzilla.mozilla.org/show_bug.cgi?id=327645
  }


  // Remove all of the messages from the list.  This is called when a new 
  // mailbox is about to be selected.
  this.reset = function()
  {
    this.messages = [];
    this.messages_by_uid = {};
    this.rows_by_uid = {};
    this.flags_by_uid = {};
    this.last_message = null;
    delete_contents(this.ml_tbody);
  }


  // Add a new message just received by the parser.
  // Currently it is inserted after the last message, but this will need to 
  // change based on sorting.
  this.process_fetch_response = function(msg)
  {
    if (this.messages_by_uid[msg.uid]) {
      // Some details of this message are already known, so merge in the new 
      // info.
      this.messages_by_uid[msg.uid].add(msg);
      update_msglist(this.messages_by_uid[msg.uid]);
    } else {
      // This is an entirely new message, add it at the end.
      msg.prev_message = this.last_message;
      if (this.last_message) {
        this.last_message.next_message = msg;
      }
      this.last_message = msg;
      this.messages_by_uid[msg.uid] = msg;
      this.messages.push(msg);
      this.render_row(msg);
    }
  }


  // Create and add a row to the message list for a message.  It is currently added 
  // at the end of the messagelist; this will have to become more flexible because of 
  // sorting.
  // A reference to the row is kept in this.rows_by_uid and to the flags cell in 
  // this.flags_by_uid so that they can be updated as flags change
  this.render_row = function(msg)
  {
    var row = tr(this.ml_tbody,uid_to_row_id(msg.uid));
    this.rows_by_uid[msg.uid] = row;
    var flags = td(row);
    this.flags_by_uid[msg.uid] = flags;
    addclass(flags,"flags");
    set_msglist_flags(flags,msg);
    var msg_subject = td(row,"",decode_rfc2047(msg.subject));
    var msg_from = td(row,"",render_addrlist_text(msg.from));
    var msg_date = td(row,"",msg.date);
    var msg_size = td(row,"",human_size(msg.size));
    addclass(msg_size,"size");
    set_msglist_classes(row,msg);
    bind_click(row, ui.select_message.bind_mem(ui,msg));
    make_dragable(row, msg.copy_to_mailbox.bind_mem(msg));
  }


  this.show_selected = function(msg)
  {
    select(getid(uid_to_row_id(msg.uid)));
  }

  this.show_unselected = function(msg)
  {
    deselect(getid(uid_to_row_id(msg.uid)));
  }


  this.inherit = add_sort_methods;
  this.inherit();
}



// Map from a message UID to the DOM ID of the messagelist table row 
// corresponding to it:
function uid_to_row_id(uid)
{
  return "msg-"+uid;
}


// Update the message list row to reflect the flag status:
function update_msglist(msg)
{
  set_msglist_classes(messagelist.rows_by_uid[msg.uid],msg);
  set_msglist_flags(messagelist.flags_by_uid[msg.uid],msg);
}

function set_msglist_classes(row,msg)
{
  if (msg.seen) {
    rmvclass(row,"unseen");
  } else {
    addclass(row,"unseen");
  }
  if (msg.deleted) {
    addclass(row,"deleted");
  } else {
    rmvclass(row,"deleted");
  }
}

function set_msglist_flags(flagcell,msg)
{
  var flagtext = msg.answered ? "A" : "";
  settext(flagcell,flagtext);
}




// browser/webmail/MessageUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Class representing the user interface to a message.


function MessageUI(pane_, printmode_)
{
  // The pane into which the MessageUI will be rendered:
  this.pane = pane_;

  // Is this a print window?
  this.printmode = printmode_;

  // The currently-displayed message, if any.
  this.msg=null;

  // The currently-active part UI, if any.
  this.partui=null;

  // Display a message.  This is called when the message is clicked on 
  // in the message list.  The default part is displayed unless a part is specified.
  this.render = function(msg_,part,cont)
  {
    if (msg_==this.msg && this.partui && part==this.partui.part) {
      return;
    }

    this.msg = msg_;

    delete_contents(this.pane);

    // If this is the first time that this message has been selected we ask the server 
    // for a description of the MIME parts making up the message and for the header.  
    // The parser will receive this description and this.msg.parts will be populated.  
    // Then render_message_cont will be invoked.
    if (!this.msg.got_structure) {
      this.msg.parts["HEADER"] = new Part(this.msg,"HEADER");
      this.msg.parts["HEADER"].encoding = "8bit";
      this.msg.parts["TEXT"] = new Part(this.msg,"TEXT");
      this.msg.parts["TEXT"].encoding = "8bit";
      this.msg.got_structure = true;
      uid_fetch_cmd(this.msg.uid,"BODYSTRUCTURE BODY[HEADER]",
                    this.render_cont.bind_mem(this,part,cont));
    } else {
      this.render_cont(part,cont)
    }
  }

  this.render_cont = function(part,cont)
  {
    // Build and populate the message header

    this.msgheader_pane = div(this.pane,"msgheader-pane");
    this.msgbody_pane = div(this.pane,"msgbody-pane");

    var header = table(this.msgheader_pane,"header");

    var hr_from = tr(header,"header-from");
    th(hr_from,"","From:");
    render_addrlist_links(this.msg.from,td(hr_from));

    if (this.msg.reply_to.length) {
      var hr_reply_to = tr(header,"header-reply-to");
      th(hr_reply_to,"","Reply-To:");
      render_addrlist_links(this.msg.reply_to,td(hr_reply_to));
    }

    var hr_subject = tr(header,"header-subject");
    th(hr_subject,"","Subject:");
    td(hr_subject,"",decode_rfc2047(this.msg.subject));

    var hr_to = tr(header,"header-to");
    th(hr_to,"","To:");
    render_addrlist_links(this.msg.to,td(hr_to));

    if (this.msg.cc.length) {
      var hr_cc = tr(header,"header-cc");
      th(hr_cc,"","Cc:");
      render_addrlist_links(this.msg.cc,td(hr_cc));
    }

    if (this.msg.bcc.length) {
      var hr_bcc = tr(header,"header-bcc");
      th(hr_bcc,"","Bcc:");
      render_addrlist_links(this.msg.bcc,td(hr_bcc));
    }

    var hr_date = tr(header,"header-date");
    th(hr_date,"","Date:");
    td(hr_date,"",this.msg.date);

    if (!this.printmode) {
      // The per-part tabs:
      this.tabset = new Tabset(this.msgheader_pane);
    }

    // Create the per-part UIs and their tabs:
    this.partuis = {};
    for (i in this.msg.parts) {
      // If this is a multipart message then we don't want a tab for the body 
      // (TEXT) part.  It's only needed so that the Raw pseudo-part can use it.
      // Detecting this is a bit of a hack; does it always work?
      if (i=="TEXT" && this.msg.parts["1"]) {
        continue;
      }
      var p = this.msg.parts[i];
      var partui = new PartUI(this.msgbody_pane,p,this);
      if (!this.printmode) {
        partui.render_tab(this.tabset);
      }
      this.partuis[i] = partui;
    }

    var rawpartui = new RawPartUI(this.msgbody_pane,this.msg,this);
    if (!this.printmode) {
      rawpartui.render_tab(this.tabset);
    }
    this.partuis["RAW"] = rawpartui;

    // We need to manually adjust the top of the message body; it can't be done with
    // CSS for some reason that I now forget.
    this.msgbody_pane.style.top=this.msgheader_pane.offsetHeight+"px";

    // Display the default part, unless another one has been requested:

    if (part) {
      this.partui = this.partuis[part.name];
    } else {
      this.partui = this.default_partui();
    }
    if (!this.printmode) {
      this.partui.tab.activate();
    }
    this.msgbody_pane.focus();
    this.partui.render(cont);
  }


  this.change_partui = function(partui_,cont)
  {
    this.partui = partui_;
    delete_contents(this.msgbody_pane);
    clearclasses(this.msgbody_pane);
    this.partui.render(cont);
  }


  // Ask the server for the data for the named part.
  // This is called by the part when it has to render itself.
  this.get_part_content = function(partname,cont)
  {
    info("Getting data from message "+this.msg.uid+" part "+partname+"...");
    waiting(this.msgbody_pane); 
    uid_fetch_cmd(this.msg.uid, "BODY["+partname+"]",
                  function() { info("OK"); endwait(this.msgbody_pane); if (cont) cont();});
  }


  // Find the 'best' message partui to display by default, based on the scores that 
  // the parts give themselves.
  this.default_partui = function()
  {
    var best_partui = null;
    var best_score = 0;
    for (partui_name in this.partuis) {
      var this_partui = this.partuis[partui_name];
      var this_score = this_partui.score;
      if (this_score>best_score) {
        best_partui=this_partui;
        best_score=this_score;
      }
    }
    return best_partui;
  }


}


// browser/webmail/NewMailboxUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Interface to a new mailbox as represented in the mailbox tree.



function NewMailboxUI(parent_mailbox_, mailboxui_)
{
  // The parent mailbox object that this will create a sub-mailbox for:
  this.parent_mailbox = parent_mailbox_;

  // The MailboxUI that this is part of:
  this.mailboxui = mailboxui_;


  // A callback function that is invoked when the name has been entered.
  // It is called with the new leafname as a parameter.
  this.callback = null;


  // Called by the tree.
  this.render = function(parent)
  {
    this.nameeditor = new EditInPlace(parent,"");
    this.nameeditor.start_edit(this.name_set.bind_mem(this));
  }

  this.name_set = function()
  {
    var new_leafname = this.nameeditor.text;
    if (this.callback) {
      this.callback(new_leafname);
    }
  }

  this.add_to_tree = function(tree)
  {
    this.path = this.parent_mailbox.imap_name.split(hierarchy_delimiter);
    this.path.push("new folder");
    this.treenode = tree.insert(this.path, this);
    if (!this.treenode) {
      fatal("Adding to Tree with missing parents");
    }
    this.tree_line = this.treenode.line;
  }
}
// browser/webmail/new_mail.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Respond to new mail notifications:


// Similar notifications occur after a SELECT command to select a new mailbox.  We 
// ignore them.
var select_in_progress = false;

// This is called by the parser when it gets an 'EXISTS' response.
// If this isn't part of a SELECT command we send "UID SEARCH ALL", which gets the 
// UIDs of all the messages currently in the mailbox.
function maybe_new_mail()
{
  if (select_in_progress) {
    return;
  }
  if (response_parser.n_exists>0) {
    uid_search_all_cmd(post_search_new);
  } else {
    // We can't ask for "1:*" UIDs if there are 0 messages; Dovecot (at least)
    // considers this an error.
    post_search_new();
  }
}

// The parser has saved the result of the UID SEARCH ALL.  We now compare it with 
// the current message list, remove messages that have gone, and fetch envelopes for 
// the new ones.  The arrival of the envelopes will cause them to be added to the 
// message list.
function post_search_new()
{
  var old_messages_by_uid = messagelist.messages_by_uid;
  var new_messages_by_uid = {};
  var new_uids = (response_parser.n_exists==0) ? [] : response_parser.search_uids;
  var added_uids = [];
  for (var i=0; i<new_uids.length; i++) {
    var new_uid = new_uids[i];
    if (old_messages_by_uid[new_uid]) {
      new_messages_by_uid[new_uid] = old_messages_by_uid[new_uid];
    } else {
      added_uids.push(new_uid);
    }
  }
  var removed_uids = {};
  for (old_uid in old_messages_by_uid) {
    if (!new_messages_by_uid[old_uid]) {
      removed_uids[old_uid] = true;
    }
  }
  for (var i=0; i<messagelist.messages.length; ++i) {
    if (removed_uids[messagelist.messages[i].uid]) {
      delete_el(getid(uid_to_row_id(messagelist.messages[i].uid)));
      delete messagelist.messages[i];
    }
  }
  messagelist.messages_by_uid = new_messages_by_uid;
  if (added_uids.length>0) {
    info("Reading envelopes for "+added_uids.length+" new messages...");
    uid_fetch_envelope_cmd(added_uids.join(","),
                           maybe_alert_new_mail.bind_mem(this,added_uids.length));
  }
}

// Alert the users about the new messages.
function maybe_alert_new_mail(n)
{
  var m = (n==1) ? "New mail has arrived" : (n+" new mails have arrived");
  info(m);
  if (alert_on_new_mail) {
    alert(m);
  }
}

// browser/webmail/parse_imap.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Parse IMAP responses:


function imap_response_parser()
{
  // This class is a top-down recursive-descent parser for IMAP responses, 
  // based on the RFC 2060 BNF.

  // Mostly the responses can be interepreted free of context.  When context 
  // is needed it is provided by setting variables here.
  this.list_root_hierarchy=0;
  this.seen_greeting=0;

  this.s="";
  this.pos=0;

  this.match_since = function(start)
  {
    return this.s.substr(start,(this.pos-start));
  }

  // The parser functions try to match something at position pos in s; if they 
  // succeed, they advance pos and return true.  If they fail they leave pos 
  // unchanged and return false.

  // Parser utility functions:

  this.parse_str = function(str)
  // Recognise string str
  {
    if (this.s.substr(this.pos,str.length)==str) {
      this.pos -=- str.length;
      return str;
    } else {
      return false;
    }
  }

  this.parse_cistr = function(str)
  // Recognise string str case-insenstively
  {
    if (this.s.substr(this.pos,str.length).toLowerCase()
        ==str.toLowerCase()) {
      this.pos -=- str.length;
      return str;
    } else {
      return false;
    }
  }

  this.parse_space = function()
  {
    return this.parse_str(" ");
  }

  this.parse_charset = function(charset)
  // Recognise a sequence of 1 or more characters from charset
  {
    var s="";
    while (1) {
      var c = this.s.substr(this.pos,1);
      if (c=="" || charset.indexOf(c)==-1) {
        break;
      }
      s += c;
      this.pos++;
    }
    return s;
  }

  this.parse_n_or_more = function(rule,n)
  // Recognise a sequence of n or more of rule
  // Returns an array of the matches
  {
    var p = this.pos;
    var c = 0;
    var r = [];
    var m;
    while (m=rule()) {
      r.push(m);
      c++;
    }
    if (c>=n) {
      return r;
    } else {
      this.pos = p;
      return false;
    }
  }

  this.parse_zero_or_more = function(rule)
  {
    return this.parse_n_or_more(rule,0);
  }

  this.parse_one_or_more = function(rule)
  {
    return this.parse_n_or_more(rule,1);
  }

  this.parse_n_or_more_separated = function(rule,n,sep)
  // Recognise a sequence of n or more of rule, separated by sep
  // Returns an array of the matches
  {
    var p = this.pos;
    var c = 0;
    var r = [];
    while (1) {
      var m;
      if (!(m=rule())) {
        if (c==0) {
          break;
        } else {
          this.pos = p;
          return (c>=n) ? r : false;
        }
      }
      c++;
      r.push(m);
      if (c>=n) {
        p = this.pos;
      }
      if (!this.parse_str(sep)) {
        break;
      }
    }
    if (c>=n) {
      return r;
    } else {
      this.pos = p;
      return false;
    }
  }

  this.parse_n_or_more_spaced = function(rule,n)
  // Recognise a sequence of n or more of rule, space-separated
  {
    return this.parse_n_or_more_separated(rule,n," ");
  }

  this.parse_zero_or_more_spaced = function(rule)
  {
    return this.parse_n_or_more_spaced(rule,0);
  }

  this.parse_one_or_more_spaced = function(rule)
  {
    return this.parse_n_or_more_spaced(rule,1);
  }


  // Main parser rules, based on the RFC grammer:

  this.parse_address = function()
  // address ::= "(" addr_name SPACE addr_adl SPACE addr_mailbox SPACE addr_host ")"
  {
    var p = this.pos;
    var addr = new Address;
    var ok = this.parse_str("(")
          && (addr.name=this.parse_addr_name())
          && this.parse_space()
          && (addr.adl=this.parse_addr_adl())
          && this.parse_space()
          && (addr.mailbox=this.parse_addr_mailbox())
          && this.parse_space()
          && (addr.host=this.parse_addr_host())
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
      return false;
    }
    return addr;
  }

  this.parse_addr_adl = function()
  // addr_adl ::= nstring
  {
    return this.parse_nstring();
  }

  this.parse_addr_host = function()
  // addr_host ::= nstring
  {
    return this.parse_nstring();
  }

  this.parse_addr_mailbox = function()
  // addr_mailbox ::= nstring
  {
    var m = this.parse_nstring();
//debug("got mailbox = "+m);
    return m;
  }

  this.parse_addr_name = function()
  // addr_name ::= nstring
  {
    return this.parse_nstring();
  }

  this.parse_astring = function()
  // astring ::= atom | string
  {
    return this.parse_string() || this.parse_atom();
  }

  this.parse_atom = function()
  // atom ::= 1*ATOM_CHAR
  // ATOM_CHAR ::= <any CHAR except atom_specials>
  // atom_specials ::= "(" | ")" | "{" | SPACE | CTL | list_wildcards | quoted_specials
  // list_wildcards ::= "%" | "*"
  // quoted_specials ::= <"> | "\"
  // CHAR ::= <any 7-bit US-ASCII character except NUL, 0x01-0x7f>
  // CTL ::= <any ASCII control character and DEL, 0x00-0x1f, 0x7f>
  {
    return this.parse_charset("!#$&'+,-./:;<=>?@[]^_`|~"
                             +"0123456789"
                             +"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                             +"abcdefghijklmnopqrstuvwxyz");
  }

  this.parse_body = function()
  // body ::= "(" body_type_1part | body_type_mpart ")"
  {
//debug("p_body starting");
    var p = this.pos;
    var part1=false;
    var parts;
    var ok = this.parse_str("(")
          && ((part1=this.parse_body_type_1part())
           || (parts=this.parse_body_type_mpart()))
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
      return false;
    }
//debug("p_body done");
    if (part1) {
      parts={};
      parts[part1.name]=part1;
    }
    return parts;
  }

  this.parse_body_extension = function()
  // body_extension ::= nstring | number | "(" 1#body_extension ")"
  {
    if (this.parse_nstring() || this.parse_number()) {
      return true;
    }
    var p = this.pos;
    var ok = this.parse_str("(")
          && this.parse_one_or_more_spaced(this.parse_body_extension.bind_mem(this))
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
      return false;
    }
    return true;
  }
  
  this.parse_body_ext_1part = function()
  // body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp [SPACE body_fld_lang 
  // [SPACE 1#body_extension]]]
  {
    if (!this.parse_body_fld_md5()) {
      return false;
    }
    var p = this.pos;
    if (this.parse_space()) {
      if (!this.parse_body_fld_dsp()) {
        this.pos=p;
        return true;
      }
      p = this.pos;
      if (this.parse_space()) {
        if (!this.parse_body_fld_lang()) {
          this.pos=p;
          return true;
        }
        p = this.pos;
        if (this.parse_space()) {
          if (!this.parse_one_or_more_spaced(this.parse_body_extension.bind_mem(this))) {
            this.pos=p;
            return true;
          }
        }
      }
    }
    return true;
  }

  this.parse_body_ext_mpart = function()
  // body_ext_mpart ::= body_fld_param [SPACE body_fld_dsp [SPACE body_fld_lang 
  // [SPACE 1#body_extension]]
  {
    var params;
    if (!(params=this.parse_body_fld_param())) {
      return false;
    }
    var p = this.pos;
    if (this.parse_space()) {
      if (!this.parse_body_fld_dsp()) {
        this.pos=p;
        return params;
      }
      p = this.pos;
      if (this.parse_space()) {
        if (!this.parse_body_fld_lang()) {
          this.pos=p;
          return params;
        }
        p = this.pos;
        if (this.parse_space()) {
          if (!this.parse_one_or_more_spaced(this.parse_body_extension.bind_mem(this))) {
            this.pos=p;
            return params;
          }
        }
      }
    }
    return params;
  }

  this.parse_body_fields = function()
  // body_fields ::= body_fld_param SPACE body_fld_id SPACE body_fld_desc SPACE 
  // body_fld_enc SPACE body_fld_octets
  {
//debug("p_body_fields starting");
    var p = this.pos;
    var encoding;
    var size;
    var params;
    var ok = (params=this.parse_body_fld_param())
          && this.parse_space()
          && this.parse_body_fld_id()
          && this.parse_space()
          && this.parse_body_fld_desc()
          && this.parse_space()
          && (encoding=this.parse_body_fld_enc())
          && this.parse_space()
          && (size=this.parse_body_fld_octets());
    if (!ok) {
      this.pos = p;
      return false;
    }
//debug("p_body_fields done");
    params.encoding = this.decode_quoted(encoding);
    params.size = this.decode_number(size);
    return params;
  }

  this.parse_body_fld_desc = function()
  // body_fld_desc ::= nstring
  {
    return this.parse_nstring();
  }

  this.parse_body_fld_dsp = function()
  // body_fld_dsp ::= "(" string SPACE body_fld_param ")" | nil
  {
    if (this.parse_nil()) {
      return true;
    }
    var p = this.pos;
    var params;
    var ok = this.parse_str("(")
          && this.parse_string()
          && this.parse_space()
          && (params=this.parse_body_fld_param())
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
      return false;
    }
    return params;
  }

  this.parse_body_fld_enc = function()
  // body_fld_end ::= (<"> ("7BIT" | "8BIT" | "BINARY" | "BASE64" | 
  // "QUOTED-PRINTABLE") <">) | string
  // (simplified)
  {
    return this.parse_string();
  }

  this.parse_body_fld_id = function()
  // body_fld_id ::= nstring
  {
    return this.parse_nstring();
  }

  this.parse_body_fld_lang = function()
  // body_fld_lang ::= nstring | "(" 1#string ")"
  {
    if (this.parse_nstring()) {
      return true;
    }
    var p = this.pos;
    var ok = this.parse_str("(")
          && this.parse_one_or_more_spaced(this.parse_string.bind_mem(this))
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
      return false;
    }
    return true;
  }

  this.parse_body_fld_lines = function()
  // body_fld_lines ::= number
  {
    return this.parse_number();
  }

  this.parse_body_fld_md5 = function()
  // body_fld_md5 ::= nstring
  {
    return this.parse_nstring();
  }

  this.parse_body_fld_octets = function()
  // body_fld_octets ::= number
  {
    return this.parse_number();
  }

  this.parse_body_fld_param = function()
  // body_fld_param ::= "(" 1#(string SPACE string) ")" | nil
  {
    if (this.parse_nil()) {
      return {};
    }
    var p = this.pos;
    var param_a;
    var ok = this.parse_str("(")
          && (param_a=this.parse_one_or_more_spaced(this.parse_name_space_value.bind_mem(this)))
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
      return false;
    }
    var params = {};
    for (var i=0; i<param_a.length; i++) {
      params["p_"+param_a[i].name]=param_a[i].value;
    }
    return params;
  }

  this.parse_name_space_value = function()
  {
    var p = this.pos;
    var name;
    var value;
    var ok = (name=this.parse_string())
          && this.parse_space()
          && (value=this.parse_string());
    if (!ok) {
      this.pos = p;
      return false;
    }
    return {name: name, value: value};
  }

  this.parse_body_type_1part = function()
  // body_type_1part ::= (body_type_basic | body_type_msg | body_type_text) 
  // [SPACE body_ext_1part]
  {
//debug("p_body_type_1part starting");
    var partinfo;
    var ok = (partinfo=this.parse_body_type_msg())
          || (partinfo=this.parse_body_type_text())
          || (partinfo=this.parse_body_type_basic());
    if (!ok) {
      return false;
    }
    var p = this.pos;
    var ok = this.parse_space()
          && this.parse_body_ext_1part();
    if (!ok) {
      this.pos = p;
    }
    var part = new Part(this.msg,"TEXT");
    for (i in partinfo) {
      part[i]=partinfo[i];
    }
    return part;
  }

  this.parse_body_type_basic = function()
  // body_type_basic ::= media_basic SPACE body_fields
  {
//debug("p_body_type_basic starting");
    var p = this.pos;
    var mimetype;
    var fieldinfo;
    var ok = (mimetype=this.parse_media_basic())
          && this.parse_space()
          && (fieldinfo=this.parse_body_fields());
    if (!ok) {
      this.pos=p;
      return false;
    }
    return objmerge(mimetype,fieldinfo);
  }

  this.parse_body_type_mpart = function()
  // body_type_mpart ::= 1*body SPACE media_subtype [SPACE body_ext_mpart]
  {
//debug("p_body_type_mpart starting");
    var p = this.pos;
    var parts_a;
    var subtype;
    var ok = (parts_a=this.parse_one_or_more(this.parse_body.bind_mem(this)))
          && this.parse_space()
          && (subtype=this.parse_media_subtype());
    if (!ok) {
      this.pos = p;
      return false;
    }
    var p = this.pos;
    var ok = this.parse_space()
          && this.parse_body_ext_mpart();
    if (!ok) {
      this.pos = p;
    }
    var parts = {};
    for (var i=0; i<parts_a.length; i++) {
      var hier=parts_a[i];
      for (var j in hier) {
        var part=hier[j];
        if (part.name=="TEXT") {
          part.name = (i+1);
        } else {
          part.name = (i+1) + "." + part.name;
        }
        parts[part.name] = part;
      }
    }
//debug("p_body_type_mpart done");
    return parts;
  }

  this.parse_body_type_msg = function()
  // body_type_msg ::= media_message SPACE body_fields SPACE envelope SPACE 
  // body SPACE body_fld_lines
  {
//debug("p_body_type_msg starting");
    var p = this.pos;
    var mimetype;
    var fieldinfo;
    var ok = (mimetype=this.parse_media_message())
          && this.parse_space()
          && (fieldinfo=this.parse_body_fields())
          && this.parse_space()
          && this.parse_envelope()
          && this.parse_space()
          && this.parse_body()
          && this.parse_space()
          && this.parse_body_fld_lines();
    if (!ok) {
      this.pos = p;
      return false;
    }
    return objmerge(mimetype,fieldinfo);
  }

  this.parse_body_type_text = function()
  // body_type_text ::= media_text SPACE body_fields SPACE body_fld_lines
  {
//debug("p_body_type_text starting");
    var p = this.pos;
    var mimetype;
    var fieldinfo;
    var ok = (mimetype=this.parse_media_text())
          && this.parse_space()
          && (fieldinfo=this.parse_body_fields())
          && this.parse_space()
          && this.parse_body_fld_lines();
    if (!ok) {
      this.pos = p;
      return false;
    }
    return objmerge(mimetype,fieldinfo);
  }

  this.parse_capability = function()
  // capability ::= "AUTH=" auth_type | atom
  // (simplified)
  {
    var capability = this.parse_atom();
    if (!capability) {
      return false;
    }
    if (capability=="XCOMPLETE") {
      enable_completion = true;
debug("** completion enabled **");
    }
    return capability;
  }

  this.parse_capability_data = function()
  // capability_data ::= "CAPABILITY" SPACE [1#capability SPACE] "IMAP4rev1" 
  // [SPACE 1#capability]
  // (simplified to 1#capability)
  {
    return this.parse_one_or_more_spaced(this.parse_capability.bind_mem(this));
  }

  this.parse_continue_req = function()
  // continue_req ::= "+" SPACE (resp_text | base64)
  {
    var p = this.pos;
    if (!this.parse_str("+ ")) {
      return false;
    }
    var ok = this.parse_resp_text()
          || this.parse_base64();
    if (!ok) {
      this.pos = p;
    }
    return ok;
  }

  this.parse_crlf = function()
  // CRLF ::= CR LF
  // CR ::= <ASCII CR, carriage return, 0x0D>
  // LF ::= <ASCII LF, line feed, 0x0A>
  {
    return this.parse_str("\r\n");
  }

  this.parse_date_time = function()
  // (simplified; date_time is delimited by quotes)
  {
    return this.parse_quoted();
  }

  this.parse_envelope = function()
  // envelope ::= "(" env_date SPACE env_subject SPACE env_from SPACE 
  // env_sender SPACE env_reply_to SPACE env_to SPACE env_cc SPACE env_bcc 
  // SPACE env_in_reply_to SPACE env_message_id ")"
  {
//debug("p_envelope starting");
    var p = this.pos;
    var ok = this.parse_str("(")
          && (this.msg.date=this.parse_env_date())
          && this.parse_space()
          && (this.msg.subject=this.parse_env_subject())
          && this.parse_space()
          && (this.msg.from=this.parse_env_from())
          && this.parse_space()
          && (this.msg.sender=this.parse_env_sender())
          && this.parse_space()
          && (this.msg.reply_to=this.parse_env_reply_to())
          && this.parse_space()
          && (this.msg.to=this.parse_env_to())
          && this.parse_space()
          && (this.msg.cc=this.parse_env_cc())
          && this.parse_space()
          && (this.msg.bcc=this.parse_env_bcc())
          && this.parse_space()
          && (this.msg.in_reply_to=this.parse_env_in_reply_to())
          && this.parse_space()
          && (this.msg.message_id=this.parse_env_message_id())
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
    }
//debug("p_envelopse done, "+(ok?"ok":"failed"));
    return ok;
  }

  this.parse_env_addresslist = function()
  // (added rule, since env_bcc, env_cc, env_from, env_reply_to, env_sender 
  // and env_to all have the same expansion)
  // env_addresslist ::= "(" 1*address ")" | nil
  {
    if (this.parse_nil()) {
      return [];
    }
    var p = this.pos;
    var addresslist;
    var ok = this.parse_str("(")
          && (addresslist=this.parse_one_or_more(this.parse_address.bind_mem(this)))
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
//debug("parse_env_addresslist failed with length-so-far "+this.addresslist.length);
      return false;
    }
    return addresslist;
  }

  this.parse_env_bcc = function()
  {      
//debug("p_env_bcc starting");
    return this.parse_env_addresslist();
  }

  this.parse_env_cc = function()
  {
//debug("p_env_cc starting");
    return this.parse_env_addresslist();
  }

  this.parse_env_date = function()
  // env_date ::= nstring
  {
    return this.parse_nstring();
  }

  this.parse_env_from = function()
  {
//debug("p_env_from starting");
    return this.parse_env_addresslist();
  }

  this.parse_env_in_reply_to = function()
  // env_in_reply_to ::= nstring
  {
//debug("p_env_in_reply_to starting");
    return this.parse_nstring();
  }

  this.parse_env_message_id = function()
  // env_message_id ::= nstring
  {
    return this.parse_nstring();
  }

  this.parse_env_reply_to = function()
  {
//debug("p_env_reply_to starting");
    return this.parse_env_addresslist();
  }

  this.parse_env_sender = function()
  {
//debug("p_env_sender starting");
    return this.parse_env_addresslist();
  }

  this.parse_env_subject = function()
  // env_subject ::= nstring
  {
    return this.parse_nstring();
  }

  this.parse_env_to = function()
  {
    return this.parse_env_addresslist();
  }

  this.parse_flag = function()
  // flag ::= "\Answered" | "\Flagged" | "\Deleted" | "\Seen" | "\Draft" | 
  //          flag_keyword | flag_extension
  // flag_keyword ::= "\" atom
  // flag_extension ::= atom
  // (simplified to: ["\"] atom)
  {
    var p = this.pos;
    this.parse_str("\\");
    var ok = this.parse_atom();
    if (!ok) {
      this.pos = p;
      return false;
    }
    return this.match_since(p);
  }

  this.parse_flag_list = function()
  // flag_list ::= "(" #flag ")"
  {
//debug("p_flag_list starting");
    var p = this.pos;
    var ok = this.parse_str("(")
          && this.parse_zero_or_more_spaced(this.parse_flag.bind_mem(this))
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
      return false;
    }
//debug("p_flag_list returns "+ok);
    return true;
  }

  this.parse_greeting = function()
  // greeting ::= "*" SPACE (resp_cond_auth | resp_cond_bye) CRLF
  {
    var p = this.pos;
    var ok = this.parse_str("* ")
          && (this.parse_resp_cond_auth() || this.parse_resp_cond_bye())
          && this.parse_crlf();
    if (!ok) {
      this.pos = p;
      return false;
    }
    return true;
  }

  this.parse_header_fld_name = function()
  // header_fld_name ::= astring
  {
    return this.parse_astring();
  }

  this.parse_header_list = function()
  // header_list ::= "(" 1#header_fld_name ")"
  {
    var p = this.pos;
    var ok = this.parse_str("(")
          && this.parse_one_or_more_spaced(this.parse_header_fld_name.bind_mem(this))
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
      return false;
    }
    return true;
  }

  this.parse_literal = function()
  // literal ::= "{" number "}" CRLF *CHAR8
  {
    var p = this.pos;
    var n;
    var ok = this.parse_str("{")
          && (n = this.parse_number())
          && this.parse_str("}")
          && this.parse_crlf();
    if (!ok) {
      this.pos = p;
      return false;
    }
    n = this.decode_number(n);
    if (n==0) {
      return "<empty>";
    }
//debug("parsing a literal of length "+n);
    if (this.s.length-this.pos >= n) {
      var str = this.s.substr(this.pos,n);
      this.pos -=- n;
//debug("literal found: ["+str+"]");
//debug("this.pos = "+this.pos);
//debug("after literal: ["+this.s.substr(this.pos,10));
      return str;
    } else {
//debug("not enough data for literal");
      this.pos = p;
      return false;
    }
  }

  this.parse_mailbox = function()
  // mailbox ::= "INBOX" | astring
  // "INBOX" is case-insensitive
  // (simplified)
  {
    return this.parse_astring();
  }

  this.parse_mailbox_data = function()
  // mailbox_data ::=
  //     "FLAGS"   SPACE flag_list
  //   | "LIST"    SPACE mailbox_list
  //   | "LSUB"    SPACE mailbox_list
  //   | "MAILBOX" SPACE text
  //   | "SEARCH"  [SPACE 1#nz_number]
  //   | "STATUS"  SPACE mailbox SPACE "(" #<status_att number ")"
  //   | number SPACE "EXISTS"
  //   | number SPACE "RECENT"
  {
//debug("in p_mailbox_data, pos="+this.pos);
    var p = this.pos;
    var ok;
    var n_messages;
    if (n_messages = this.parse_number()) {
      if (this.parse_cistr(" EXISTS")) {
        this.n_exists = this.decode_number(n_messages);
        maybe_new_mail();
        ok = true;
      } else if (this.parse_cistr(" RECENT")) {
        ok = true;
      } else {
        ok = false;
      }
    } else if (this.parse_cistr("FLAGS ")) {
      ok = this.parse_flag_list();
    } else if (this.parse_cistr("LIST ")) {
      ok = this.parse_mailbox_list();
    } else if (this.parse_cistr("LSUB ")) {
      ok = this.parse_mailbox_list();
    } else if (this.parse_cistr("MAILBOX ")) {
      ok = this.parse_text();
    } else if (this.parse_cistr("SEARCH")) {
      if (this.parse_space()) {
        ok = (this.search_uids=this.parse_one_or_more_spaced(this.parse_nz_number.bind_mem(this)));
      } else {
        ok = [];
      }
    } else if (this.parse_cistr("STATUS")) {
      ok = this.parse_space()
        && this.parse_mailbox()
        && this.parse_space()
        && this.parse_str("(")
        && this.parse_zero_or_more_spaced(this.parse_status_att.bind_mem(this))
        && this.parse_number()
        && this.parse_str(")");
    } else {
      ok = false;
    }
    if (!ok) {
      this.pos = p;
      return false;
    }
//debug("p_mailbox_data returns "+ok);
    return true;
  }

  this.parse_mailbox_list = function()
  // mailbox_list ::= "(" #("\Marked" | "\Noinferiors" | "\Noselect" | 
  // "\Unmarked" | flag_extension) ")" SPACE (<"> QUOTED_CHAR <"> | nil)
  // SPACE mailbox
  // (simplified mailbox flags to existing flag rule)
  // (simplified (<"> QUOTED_CHAR <"> | nil) to nstring)
  // (Note also \HasChildren and \HasNoChildren, RFC 3348.)
  {
//debug("in p_mailbox_list, pos="+this.pos);
    var p = this.pos;
    var mailbox_flags_a;
    var mailbox_name;
    var delimiter;
    var ok = this.parse_str("(")
          && (mailbox_flags_a=this.parse_zero_or_more_spaced(this.parse_flag.bind_mem(this)))
          && this.parse_str(") ")
          && (delimiter=this.parse_nstring())
          && this.parse_space()
          && (mailbox_name=this.parse_mailbox());
    if (!ok) {
      this.pos = p;
      return false;
    }
    // TODO: decode mailbox_name from "modified UTF7"
    if (this.list_root_hierarchy) {
      root_mailbox = this.decode_quoted(mailbox_name);
      hierarchy_delimiter = delimiter;
      this.list_root_hierarchy = 0;
    } else {
      var mailbox_flags={};
      for (i=0; i<mailbox_flags_a.length; i++) {
        mailbox_flags[mailbox_flags_a[i]]=true;
      }
      add_mailbox(mailbox_name,mailbox_flags);
    }
    return true;
  }

  this.parse_media_basic = function()
  // media_basic ::= (<"> ("APPLICATION" | "AUDIO" | "IMAGE" | "MESSAGE" | 
  // "VIDEO") <">) | string) SPACE media_subtype
  // (simplified to string SPACE string)
  {
    var p = this.pos;
    var type;
    var subtype;
    var ok = (type=this.parse_string())
          && this.parse_space()
          && (subtype=this.parse_string());
    if (!ok) {
      this.pos = p;
      return false;
    }
    type = this.decode_quoted(type).toLowerCase();
    subtype = this.decode_quoted(subtype).toLowerCase();
    return {type: type, subtype: subtype};
  }

  this.parse_media_message = function()
  // media_message ::= <"> "MESSAGE" <"> SPACE <"> "RFC822" <">
  {
    var p = this.pos;
    var ok = (this.parse_cistr("\"message\""))
          && this.parse_space()
          && (this.parse_cistr("\"rfc822\""));
    if (!ok) {
      this.pos=p;
      return false;
    }
    return {type: "message", subtype: "rfc822"};
  }

  this.parse_media_subtype = function()
  // media_subtype ::= string
  {
    var subtype = this.parse_string();
    if (!subtype) {
      return false;
    }
    return subtype.toLowerCase();
  }

  this.parse_media_text = function()
  // media_text ::= <"> "TEXT" <"> SPACE media_subtype
  {
//debug("p_media_text starting");
    var p = this.pos;
    var subtype;
    var ok = (this.parse_cistr("\"text\""))
          && this.parse_space()
          && (subtype=this.parse_media_subtype());
    if (!ok) {
      this.pos=p;
      return false;
    }
    return {type: "text", subtype: subtype};
  }

  this.parse_message_data = function()
  // message_data ::= nz_number SPACE ("EXPUNGE | "FETCH" SPACE msg_att))
  {
//debug("p_message_data starting");
    var p = this.pos;
    var ok = this.parse_nz_number()
          && this.parse_space();
    if (!ok) {
      this.pos = p;
      return false;
    }
    if (this.parse_cistr("EXPUNGE")) {
      // TODO: need to do something with this
      return true;
    }
    ok = this.parse_cistr("FETCH ")
      && this.parse_msg_att();
    if (!ok) {
      this.pos = p;
      return false;
    }
    messagelist.process_fetch_response(this.msg);
    return true;
  }

  this.parse_msg_att = function()
  // msg_att ::= "(" 1#("ENVELOPE" SPACE envelope |
  //                    "FLAGS" SPACE "(" #(flag | "\Recent") ")" |
  //                    "INTERNALDATE" SPACE date_time |
  //                    "RFC822" [".HEADER" | ".TEXT"] SPACE nstring |
  //                    "RFC822.SIZE" SPACE number |
  //                    "BODY" ["STRUCTURE"] SPACE body |
  //                    "BODY" section ["<" number ">"] SPACE nstring |
  //                    "UID" SPACE uniqueid) ")"
  {
    var p = this.pos;
    this.msg = new Message();
    var ok = this.parse_str("(")
          && this.parse_one_or_more_spaced(this.parse_msg_att_body.bind_mem(this))
          && this.parse_str(")");
    if (!ok) {
      this.pos = p;
    }
    return ok;
  }

  this.parse_msg_att_body = function()
  {
//debug("p_msg_att_body starting");
    var p = this.pos;
    var ok;
    if (this.parse_cistr("ENVELOPE ")) {
      ok = this.parse_envelope();
    } else if (this.parse_cistr("FLAGS (")) {
      ok = this.parse_zero_or_more_spaced(this.parse_flag_or_recent.bind_mem(this))
        && this.parse_str(")");
      this.msg.flags_set = ok;
    } else if (this.parse_cistr("INTERNALDATE ")) {
      ok = this.parse_date_time();
    } else if (this.parse_cistr("RFC822.HEADER ")) {
      ok = (this.msg.rfc822_header=this.parse_nstring());
    } else if (this.parse_cistr("RFC822.TEXT ")) {
//debug("getting RFC822.TEXT");
      ok = (this.msg.rfc822_text=this.parse_nstring());
//debug("after RFC822.TEXT, next data = ["+this.s.substr(this.pos,10));
    } else if (this.parse_cistr("RFC822.SIZE ")) {
//debug("getting RFC822.SIZE");
      ok = (this.msg.size=this.parse_number());
      this.msg.size = this.decode_number(this.msg.size);
    } else if (this.parse_cistr("BODY ")) {
      ok = (this.msg.parts=this.parse_body());
    } else if (this.parse_cistr("BODYSTRUCTURE ")) {
      ok = (this.msg.parts=this.parse_body());
    } else if (this.parse_cistr("BODY")) {
      var section;
      var bytes;
      ok = (section=this.parse_section());
      if (ok) {
        if (this.parse_str("<")) {
          ok = this.parse_number()
            && this.parse_str(">");
        }
        if (ok) {
          ok = this.parse_space()
            && (bytes=this.parse_nstring());
          if (ok) {
            this.msg.parts[section] = {content: new Data(this.decode_quoted(bytes))};
          }
        }
      }
    } else if (this.parse_cistr("UID ")) {
      ok = (this.msg.uid=this.parse_uniqueid());
    } else {
      ok = false;
    }
    if (!ok) {
      this.pos = p;
    }
//debug("p_msg_att_body done");
    return ok;
  }

  this.parse_flag_or_recent = function()
  {
    if (this.parse_cistr("\\Recent")) {
      return "\\Recent";
    }
    var flag = this.parse_flag();
    if (flag=="\\Seen") {
      this.msg.seen = true;
    } else if (flag=="\\Deleted") {
      this.msg.deleted = true;
    } else if (flag=="\\Answered") {
      this.msg.answered = true;
    }
    return flag;
  }

  this.parse_nil = function()
  // nil ::= "NIL"
  {
    return this.parse_cistr("NIL");
  }

  this.parse_nstring = function()
  // nstring ::= string | nil
  {
    return this.parse_nil() || this.parse_string();
  }

  this.parse_number = function()
  // number ::= 1*digit
  {
    var n = this.parse_charset("0123456789");
    if (n=="0") {
      return "zero";
    } else {
      return n;
    }
  }

  this.decode_number = function(n)
  {
    if (n=="zero") {
      return 0;
    } else {
      return n;
    }
  }    

  this.parse_nz_number = function()
  // nz_number ::= digit_nz *digit
  {
    var n = this.parse_number();
    if (n=="zero") {
      return false;
    } else {
      return n;
    }
  }

  this.parse_quoted = function()
  // quoted ::= <"> *QUOTED_CHAR <">
  // QUOTED_CHAR ::= <any TEXT_CHAR except quoted_specials> |
  //                 "\" quoted_specials
  // quoted_specials ::= <"> | "\"
  // quoted matches the empty string, "".  But returning "" evaluates to 
  // false.  Instead, for the empty string, a special sentinel value is 
  // returned; this can be converted to an empty string when necessary by 
  // calling this.decode_quoted, below.
  {
    var p = this.pos;
    var match="";
    if (!this.parse_str("\"")) {
      return false;
    }
    while (1) {
      var c = this.s[this.pos];
      if (c=="\"") {
        this.pos++;
        return match ? match : "<empty>";
      }
      if (c=="\\") {
        this.pos++;
        c = this.s[this.pos];
      }
      if (!c) {
        this.pos = p;
        return false;
      }
      match += c;
      this.pos++;
    }
  }

  this.decode_quoted = function(s) {
    return (s=="<empty>") ? "" : s;
  }


  // response ::= *(continue_req | response_data) response_done
  // (we don't try to parse this; we parse the individual lines as they 
  // arrive.)


  this.parse_response_lines = function()
  {
//debug("Parsing these lines: '"+this.s.substr(this.pos)+"'");
//var t = this.s.substr(this.pos);
//debug("parsing lines starting '"+t.substr(0,40)+"'");
    var p = this.pos;
    var ok = this.parse_zero_or_more(this.parse_response_line.bind_mem(this));
    if (!ok) {
      this.pos = p;
      return false;
    }
//debug("parse_response_lines terminating, ok="+ok);
    return true;
  }

  this.parse_response_line = function()
  {
    if (this.seen_greeting) {
      return this.parse_continue_req_or_response_data_or_response_done();
    } else {
      return (this.seen_greeting=this.parse_greeting());
    }
  }

  this.parse_continue_req_or_response_data_or_response_done = function()
  {
//debug("in p_cr_rd_rd, pos="+this.pos);
    return this.parse_continue_req()
        || this.parse_response_data()
        || this.parse_response_done();
  }

  this.parse_response_data = function()
  // response_data ::= "*" SPACE (resp_cond_state | resp_cond_bye | mailbox_data 
  // | message_data | capability_data) CRLF
  // (RFC2060 omits the CRLF here, which seems to be a mistake)
  {
//debug("in p_response_data, pos="+this.pos);
    var p = this.pos;
    var ok = this.parse_str("* ") &&
              (   this.parse_resp_cond_state()
               || this.parse_resp_cond_bye()
               || this.parse_mailbox_data()
               || this.parse_message_data()
               || this.parse_complete_data()
               || this.parse_capability_data())
             && this.parse_crlf();
    if (!ok) {
      this.pos = p;
      return false;
    }
//debug("p_response_data returns "+ok);
    return true;
  }

  this.parse_response_done = function()
  // response_done ::= response_tagged | response_fatal
  {
//debug("looking for response_done");
    return this.parse_response_tagged() || this.parse_response_fatal();
  }

  this.parse_response_fatal = function()
  // response_fatal ::= "*" SPACE resp_cond_bye CRLF
  {
//debug("looking for response_fatal");
    var p = this.pos;
    var ok = this.parse_str("* ")
          && this.parse_resp_cond_bye()
          && this.parse_crlf();
    if (!ok) {
      this.pos = p;
      return false;
    }
    return true;
  }

  this.parse_response_tagged = function()
  // response_tagged ::= tag SPACE resp_cond_state CRLF
  {
//debug("looking for response_tagged");
    var p = this.pos;
    var tag;
    var status_text;
    var ok = (tag=this.parse_tag())
          && this.parse_space()
          && (status_text=this.parse_resp_cond_state())
          && this.parse_crlf();
    if (!ok) {
      this.pos = p;
      return false;
    }
    cmd_response(tag,status_text.status,status_text.text);
    return true;
  }

  this.parse_resp_cond_auth = function()
  // resp_cond_auth ::= ("OK" | "PREAUTH") SPACE resp_text
  {
    var p = this.pos;
    var ok = (this.parse_cistr("OK ") || this.parse_cistr("PREAUTH "))
          && this.parse_resp_text();
    if (!ok) {
      this.pos = p;
      return false;
    }
    return true;
  }

  this.parse_resp_cond_bye = function()
  // resp_cond_bye ::= "BYE" SPACE resp_text
  {
    var p = this.pos;
    var ok = this.parse_cistr("BYE ")
          && this.parse_resp_text();
    if (!ok) {
      this.pos = p;
      return false;
    }
    return true;
  }

  this.parse_resp_cond_state = function()
  // resp_cond_state ::= ("OK" | "NO" | "BAD") resp_text
  {
//debug("looking for resp_cond_state");
    var p = this.pos;
    var status;
    var text;
    var ok = (   (status=this.parse_cistr("OK"))
              || (status=this.parse_cistr("NO"))
              || (status=this.parse_cistr("BAD"))
             ) && (text=this.parse_resp_text());
    if (!ok) {
      this.pos = p;
      return false;
    }
    return {status: status, text: text};
  }

  this.parse_resp_text = function()
  // resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 | text)
  // (simplified to remove text_mime2)
  {
//debug("looking for resp_text");
    var p = this.pos;
    if (this.parse_str("[")) {
      var ok = this.parse_resp_text_code()
            && this.parse_str("]")
            && this.parse_space();
      if (!ok) {
        this.pos = p;
//debug("resp_text mismatch after a [");
        return false;
      }
    }
    var ok = this.parse_text();
    if (!ok) {
      this.pos = p;
      return false;
    }
//debug("resp_text returns "+ok);
    return this.match_since(p);
  }

  this.parse_resp_text_code = function()
  // resp_text_code ::= "ALERT" |
  //                    "PARSE" |
  //                    "PERMANENTFLAGS" SPACE "(" #(flag | "\*") ")" |
  //                    "READ-ONLY" |
  //                    "READ-WRITE" |
  //                    "TRYCREATE" |
  //                    "UIDVALIDITY" SPACE nz_number |
  //                    "UNSEEN" SPACE nz_number |
  //                    atom [SPACE 1*<any TEXT_CHAR except "]">]
  {
    var p = this.pos;
    var ok;
    if (this.parse_cistr("ALERT")) {
      ok = true;
    } else if (this.parse_cistr("PARSE")) {
      ok = true;
    } else if (this.parse_cistr("PERMANENTGLAGS (")) {
      ok = this.parse_zero_or_more_spaced(this.parse_flag_or_star.bind_mem(this))
        && this.parse_str(")");
    } else if (this.parse_cistr("READ-ONLY")) {
      ok = true;
    } else if (this.parse_cistr("READ-WRITE")) {
      ok = true;
    } else if (this.parse_cistr("TRYCREATE")) {
      ok = true;
    } else if (this.parse_cistr("UIDVALIDITY ")) {
      ok = this.parse_nz_number();
      // TODO: at this point we ought to invalidate all of our cached messages.
      // Do any servers do this frequently?
      // If not, can we just show an alert saying "please reload"?
    } else if (this.parse_cistr("UNSEEN ")) {
      ok = this.parse_nz_number();
    } else {
      ok = this.parse_atom();
      if (ok) {
        if (this.parse_space()) {
          ok = this.parse_charset(" !\"#$%&'()*+,-./:;<=>?@[\\^_`{|}~"
                                 +"0123456789"
                                 +"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                                 +"abcdefghijklmnopqrstuvwxyz");
        }
      }
    }
    if (!ok) {
      this.pos = p;
      return false;
    }
    return true;
  }

  this.parse_section = function()
  // section ::= "[" [  section_text
  //                  | (nz_number *["." nz_number] ["." (section_text | "MIME")])
  //                 ] "]"
  {
    var p = this.pos;
    var ok = this.parse_str("[");
    if (!ok) {
      this.pos = p;
      return false;
    }
    if (this.parse_section_text()) {
    } else if (this.parse_n_or_more_separated(this.parse_nz_number.bind_mem(this),1,".")) {
      if (this.parse_str(".")) {
        var ok = (this.parse_section_text())
              || (this.parse_cistr("MIME"));
      }
    } else {
      ok = false;
    }
    var part = this.match_since(p+1);
    if (ok) {
      ok = this.parse_str("]");
    }
    if (!ok) {
      this.pos = p;
      return false;
    }
    return part;
  }

  this.parse_section_text = function()
  // section_text ::= "HEADER"
  //                | "HEADER.FIELDS" [".NOT"] SPACE header_list
  //                | "TEXT"
  {
    var ok = this.parse_cistr("TEXT");
    if (ok) {
      return true;
    }
    var p = this.pos;
    ok = this.parse_cistr("HEADER");
    if (!ok) {
      return false;
    }
    if (this.parse_cistr(".FIELDS")) {
      this.parse_cistr(".NOT");
      var ok = this.parse_space()
            && this.parse_header_list();
    }
    if (!ok) {
      this.pos=p;
      false;
    }
    return true;
  }

  

  // status_att ::= "MESSAGES" | "RECENT" | "UIDNEXT" | "UIDVALIDITY" | "UNSEEN"

  this.parse_string = function()
  // string ::= quoted | literal
  {
    return this.parse_quoted() || this.parse_literal();
  }

  this.parse_tag = function()
  // tag ::= 1*<any ATOM_CHAR except "+">
  {
    return this.parse_charset("!#$&',-./:;<=>?@[]^_`|~"
                             +"0123456789"
                             +"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                             +"abcdefghijklmnopqrstuvwxyz");
  }

  this.parse_text = function()
  // text ::= 1*TEXT_CHAR
  // TEXT_CHAR ::= <any CHAR except CR and LF>
  // CHAR ::= <any 7-bit US-ASCII character except NUL, 0x01-0x7f>
  // (control characters are not allowed here)
  {
    return this.parse_charset(" !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
                             +"0123456789"
                             +"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                             +"abcdefghijklmnopqrstuvwxyz");
  }

  // text_mime2 ::= "=?" <charset> "?" <encoding> "?" <encoded-text> "?="
  // (eliminated by simplification)

  this.parse_uniqueid = function()
  // uniqueid ::= nz_number
  {
//debug("p_uniqueid starting");
    return this.parse_nz_number();
  }


  this.parse_complete_data = function()
  // complete_cmd ::= XCOMPLETE [ SPACE 1#completion ]
  // completion ::= astring
  {
    var p = this.pos;
    var completions = [];
    var ok = this.parse_cistr("XCOMPLETE")
    if (!ok) {
      this.pos=p;
      return false;
    }
    if (this.parse_space()) {
      completions=this.parse_zero_or_more_spaced(this.parse_astring.bind_mem(this));
    }
    process_completions(completions);
    return true;
  }


  // Timeout in seconds after data has been received before either it has been 
  // entirely parsed or more data has been received.
  this.inactivity_with_unparsed_data_timeout = 10;  // in seconds

  this.input = function(str) {
    this.clear_timeout();
debug("adding "+str.length+" chars to s");
    this.s += str;
    this.parse_response_lines();
debug("chomping "+this.pos+" chars out of "+this.s.length+" from s");
    this.s = this.s.substr(this.pos);
    this.pos = 0;

    if (this.s.length>0) {
      this.start_timeout();
    }
  }

  this.start_timeout = function() {
    this.timeout = setTimeout(this.timeout_expired.bind_mem(this),
                              this.inactivity_with_unparsed_data_timeout * 1000);
  }

  this.clear_timeout = function() {
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout=null;
    }
  }

  this.timeout_expired = function() {
    var c = confirm("No data has been received from the IMAP server for "
+this.inactivity_with_unparsed_data_timeout+" seconds, \
but Webmail thinks it needs more data in order to continue.  Currently it \
has "+this.s.length+" characters in its parser.  This could \
be because the server is being slow, or because something has gone wrong at one end or \
the other and communication has wedged.  A common cause is a character-set \
related deficiency in Webmail.  You may continue waiting, or you may give \
up (cancel) and Webmail will restart.\n\n\
Do you want to continue waiting?");
    if (!c) {
      fatal("Giving up waiting...");
    }
    this.start_timeout();
  }

}

// browser/webmail/PartUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// User interface to a message MIME part


function PartUI(pane_,part_,messageui_)
{
  // This is the generic PartUI class.  It is specialised by various per-MIME-type 
  // subclasses.  The code in this class calls the subclass code via various 
  // methods with names ending '0'.

  // The pane into which the UI is rendered:
  this.pane=pane_;

  // Reference to the part that this is a UI for:
  this.part=part_;

  // The MessageUI that this is part of:
  this.messageui=messageui_;


  // Load the part content:
  /*protected*/ this.load_content = function(cont)
  {
    this.part.load_content(cont);
  }

  // Get the actual content data which has previously been loaded:
  /*protected*/ this.get_content = function()
  {
    return this.part.content;
  }

  // Display this part.
  this.render = function(cont)
  {
    this.pane.scrollTop = 0;
    this.load_content(this.render_cont.bind_mem(this,cont));
  }

  this.render_cont = function(cont)
  {
    this.render0();
    if (cont) {
      cont();
    }
  }


  // Clear the pane
  this.clearpane = function()
  {
    delete_contents(this.pane);
    clearclasses(this.pane);
  }


  // Supply a description of the part to appear in the tab.
  this.get_description = function()
  {
    var desc = this.get_description0();

    // Show any filename that we know about.
    if (this.part.p_name) {
      desc += " ("+this.part.p_name+")";
    }
    // (Note that p_name above comes from the name parameter of the 
    // content-type header; the filename may also be supplied in the 
    // filename parameter of the content-disposition header, but IMAP 
    // doesn't give us that as easily.  There may also be a 
    // content-description header, which we also can't get at easily.)

    if (this.part.size) {
      desc += " (" + human_size(this.part.size) + ")";
    }

    return desc;
  }


  // Build the tab.
  this.render_tab = function(tabset)
  {
    this.tab = new Tab(tabset,this.get_description());
    this.tab.bind_click(this.messageui.change_partui.bind_mem(this.messageui,this));
    var menu = this.tab.add_menu();
    this.render_menuitems(menu);
    if (this.render_tab0) {
      this.render_tab0();
    }
  }


  // Build the tab's popup menu.
  this.render_menuitems = function(menu)
  {
    if (this.render_menuitems0) {
      this.render_menuitems0(menu);
    }
    menuitem(menu,"Open",this.open.bind_mem(this));
    menuitem(menu,"Save",this.save.bind_mem(this));
    menuitem(menu,"Print",print.bind_fn(this.part));
    if (this.subclass != TextPartUI && this.name!="raw") {
      menuitem(menu,"View as text",this.view_as_text.bind_mem(this));
    }
  }


  // Make a download data: URL and pass it to the continuation.  This is used by the 
  // 'save' menu item.  It needs the continuation because it may have to load the 
  // content.  If mime_type is null the part's real mime type is used.
  this.downloadurl = function(mime_type,cont)
  {
    this.load_content(this.downloadurl_cont.bind_mem(this,mime_type,cont));
  }

  this.downloadurl_cont = function(mime_type,cont)
  {
    var d = this.get_content();
    d.recode("base64");
    if (!mime_type) {
      mime_type = this.part.type+"/"+this.part.subtype;
    }
    cont(dataurl(mime_type,d.data));
  }


  // 'Open' function from the menu.
  // If the browser, or a plugin, knows the mime type it should attempt to view it.  
  // Otherwise the behaviour is the same as Save.
  this.open = function()
  {
    this.downloadurl(null,this.opensave_cont.bind_mem(this));
  }

  // 'Save' function from the menu.
  // Forcing the mime type to appliation/octet-stream should ensure that a save dialog 
  // is presented rather than invoking a plugin or whatever.
  this.save = function()
  {
    this.downloadurl("application/octet-stream",this.opensave_cont.bind_mem(this));
  }

  this.opensave_cont = function(durl)
  {
    var window_attributes = "width=800,height=700,resizeable=yes,scrollbars=yes";
    window.open(durl,"",window_attributes);
  }


  // From the 'view as text' tab popup menu item.
  this.view_as_text = function()
  {
    this.load_content(this.view_as_text_cont.bind_mem(this));
  }

  this.view_as_text_cont = function()
  {
    var d = this.get_content();
    d.recode("8bit");
    this.clearpane();
    addclass(this.pane,"text-plain");
    settext(this.pane, normalise_newlines(d.data));
  }


  if (this.inheritfrom) {
    return;
  }


  // Determine which subclass should handle this part and call it to do its 
  // construction.
  if (this.part.name=="HEADER") {
    this.subclass = HeaderPartUI;
  } else if (this.part.type=="text" && this.part.subtype=="plain"
          || this.part.type==""  && this.part.subtype=="") {
    this.subclass = TextPartUI;
  } else if (this.part.type=="text" && this.part.subtype=="html") {
    this.subclass = HtmlPartUI;
  } else if (this.part.type=="image") {
    this.subclass = ImagePartUI;
  } else {
    this.subclass = UnknownPartUI;
  }
  this.subclass();

}
// browser/webmail/periodic_noops.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Send periodic noops.  Some servers need these in order to send new mail 
// notifications.  Better servers don't need them.

function start_periodic_noops()
{
  if (noop_interval>0) {
    setTimeout(periodic_noop,noop_interval*1000);
  }
}

function periodic_noop()
{
  noop_cmd();
  setTimeout(periodic_noop,noop_interval*1000);
}
// browser/webmail/print.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Printing:


function print(part)
{
  var window_attributes = "width=800,height=700,resizeable=yes,scrollbars=yes";
  var print_win = window.open("","",window_attributes);
  var link_el = link(print_win.document.getElementsByTagName("HEAD")[0]);
  print_win.document.title="Webmail";
  addattr(link_el,"rel","stylesheet");
  addattr(link_el,"type","text/css");
  var cssurl = window.location.toString();
  cssurl = cssurl.substr(0,cssurl.lastIndexOf("/"))+"/print.css";
  addattr(link_el,"href",cssurl);
  var msgui = new MessageUI(print_win.document.body,true);
  msgui.render(part.msg, part, this.print_cont.bind_fn(print_win))
}

function print_cont(print_win)
{
  print_win.print();
}



// browser/webmail/RawPart.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// A pseudo message part UI for the 'Raw' tab.


function RawPartUI(pane_,msg,messageui_)
{
  this.inheritfrom = PartUI;
  this.inheritfrom(pane_,null,messageui_);

  // The HEADER and BODY parts that are combined to form the RAW part:
  this.headerpart = msg.parts["HEADER"];
  this.bodypart = msg.parts["TEXT"];

  this.score = 0;

  this.load_content = function(cont)
  {
    this.headerpart.load_content(this.load_content_cont.bind_mem(this,cont));
  }

  this.load_content_cont = function(cont)
  {
    this.bodypart.load_content(cont);
  }

  this.get_content = function()
  {
    this.headerpart.content.recode("8bit");
    this.bodypart.content.recode("8bit");
    return new Data(this.headerpart.content.data + "\r\n\r\n"
                   + this.bodypart.content.data);
  }

  this.render0 = function(cont)
  {
    addclass(this.pane,"raw");
    render_with_links(this.pane, normalise_newlines(this.get_content().data));
  }


  // Supply a description of the part to appear in the tab.
  this.get_description = function()
  {
    return "Raw";
  }

}

// browser/webmail/render_with_links.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Render text into parent, turning URLs into hyperlinks.


function render_with_links(parent, text)
{
  // RE for emails: 
  //     ([-a-zA-Z0-9_+.]+@([-a-zA-Z0-9]+\.)+[A-Za-z]{2,4})
  // Choosing a URI Regexp:
  //   - We won't try to recognise things that don't have a scheme i.e. http:// 
  //     or ftp:// at the start.
  //   - Valid punctuation in URIs: allow $%&-_=+:;@~#?,./ to start with; should 
  //     probably allow more.
  //   - It would be good to avoid picking up characters after the end of the URL, 
  //     i.e. things that are actually terminating the sentence containing the URL.
  //     So we have a separate list of those punctuation characters.

  var url_end_chars = "-a-zA-Z0-9$%&_=+@~#?\\/";
  var url_chars     = url_end_chars + ":;,.!";
  var url_re   = "(http|https|ftp):\\/\\/["+url_chars+"]+["+url_end_chars+"]";
  var email_re = "[-a-zA-Z0-9_+.=]+@([-a-zA-Z0-9]+\\.)+[A-Za-z]{2,4}";
  var re_str   = "("+url_re+")|("+email_re+")";

  var re = new RegExp(re_str,"gm");
  var last_pos = 0;
  var A;
  while ((A = re.exec(text)) != undefined) {
    var match_start_pos = A.index;
    var match_end_pos = re.lastIndex;
    var pre_text = text.substr(last_pos,match_start_pos-last_pos);
    var link_text = text.substr(match_start_pos,match_end_pos-match_start_pos);
    last_pos = match_end_pos;

    textnode(parent,pre_text);
    if (link_text.substr(0,4)=="http" || link_text.substr(0,3)=="ftp") {
      a(parent,"",link_text,link_text);
    } else {
      var link = span(parent,"",link_text);
      addclass(link,"mailto");
      var parts = link_text.match(/(.*)@(.*)/);
      bind_click(link,compose_to.bind_fn(new Address("",parts[1],parts[2])));
    }
  }
  var end_text = text.substr(last_pos);
  textnode(parent,end_text);
}

// browser/webmail/reply_utils.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.



function re(subject,prefix)
{
  if (subject.substr(0,prefix.length).toLowerCase()==prefix.toLowerCase()) {
    return subject;
  } else {
    return prefix+" "+subject;
  }
}


function quote(data, from)
{
  var q = render_addrlist_text(from) + " wrote:\n";
  data.recode("8bit");
  var lines = split_into_lines(data.data);
  for (var i=0; i<lines.length; i++) {
    var l = lines[i];
    if (l[0]==">" || l=="") {
      q += ">"+l+"\n";
    } else {
      q += "> "+l+"\n";
    }
  }
  return q;
}


function mark_replied(uid)
{
  uid_store_plusflags_cmd(uid,"\\Answered");
}

// browser/webmail/RootMailboxUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Like MailboxUI, but degenerate case for the root of the mailbox tree.


function RootMailboxUI(root_mailbox_imap_name_, mailboxui_)
{
  this.root_mailbox_imap_name = root_mailbox_imap_name_;

  this.render = function(parent)
  {
  }

  this.add_to_tree = function(tree)
  {
    this.treenode = tree;
    this.treenode.bind_expand(expand_mailbox_tree.bind_fn(this.root_mailbox_imap_name));
    this.treenode.make_dropable(this.root_mailbox_imap_name);
  }
}
// browser/webmail/sanitise_html.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Sanitise HTML, removing potentially dangerous things like scripts.

// The policy is as follows:
//   - Scripts are stripped.  This means the <script> element
//     and the onSomething attributes.
//   - Other things that really don't belong in an email, such as
//     applets and frames, are stripped.
//   - Things that would cause an immediate  fetch from a server are 
//     stripped.  This means things like images and the background attribute.
//     A way to reinstate these for trusted messages could perhaps
//     be added in the future.  We might also allow images with data:
//     URLs.
//   - URLs that need interaction for activation, e.g. links and forms, are 
//     not stripped.  But their URLs are checked and only sane schemes
//     (i.e. not javascript:) are allowed.
//   - Unknown tags and attributes are stripped (based on the HTML 4 spec).
//
// Inline stylesheets are currently allowed.  This is dangerous because
// CSS can include URLs, e.g. for background images.  We ought to detect 
// and strip URLs from CSS.
//
// This code could almost certainly be fooled by strange HTML syntax,
// odd character sets (in general character sets have not been
// considered at all in this app) and the like.
// Improvements are welcome. 


// These tags are allowed:
var oktags = " A ABBR ACRONYM ADDRESS AREA"
           + " B BASE BASEFONT BDO BIG BLOCKQUOTE BODY BR BUTTON"
           + " CAPTION CENTER CITE CODE COL COLGROUP"
           + " DD DEL DFN DIR DIV DL DT"
           + " EM"
           + " FIELDSET FONT FORM"
           + " H1 H2 H3 H4 H5 H6 HEAD HR HTML"
           + " I IMG INPUT INS ISINDEX"
           + " KBD"
           + " LABEL LEGEND LI LINK"
           + " MAP MENU"
           + " NOFRAMES NOSCRIPT"
           + " OL OPTGROUP OPTION"
           + " P PRE"
           + " Q"
           + " S SAMP SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP"
           + " TABLE TBODY TD TEXTAREA TFOOT TH THEAD TITLE TR TT"
           + " U UL"
           + " ";

// Anything other than the above is not allowed.  Here are some of the
// things:
var badtags = " APPLET FRAME FRAMESET IFRAME META OBJECT PARAM SCRIPT VAR ";


// These attributes are allowed, when they appear in the oktags above:
var okattrs = " ABBR ACCEPT-CHARSET ACCEPT ACCESSKEY ACTION ALIGN ALINK ALT AXIS"
            + " BGCOLOR BORDER"
            + " CELLPADDING CELLSPACING CHAR CHAROFF CHARSET CHECKED CLASS"
            + " CLEAR COLOR COLS COLSPAN COMPACT COORDS"
            + " DATETIME DIR DISABLED ENCTYPE"
            + " FACE FOR FRAME"
            + " HEADERS HEIGHT HREF HREFLANG HSPACE"
            + " ID ISMAP"
            + " LABEL LANG LINK LONGDESC"
            + " MARGINHEIGHT MARGINWIDTH MAXLENGTH MEDIA METHOD MULTIPLE"
            + " NAME NOHREF NOSHADE NOWRAP"
            + " PROMPT"
            + " READONLY REL REV ROWS ROWSPAN RULES"
            + " SCOPE SELECTED SHAPE SIZE SPAN START STYLE SUMMARY"
            + " TABINDEX TARGET TEXT TITLE TYPE"
            + " USEMAP"
            + " VALIGN VALUE VERSION VLINK VSPACE"
            + " WIDTH"
            + " ";

// Any attribute not listed above is removed.  Here are some of them:
var badattrs = " ARCHIVE BACKGROUND CLASSID CODE CODEBASE CODETYPE CONTENT DATA"
             + " DECLARE DEFER FRAMEBORDER HTTP-EQUIV LANGUAGE NORESIZE "
             + " OBJECT ONBLUR ONCHANGE ONCLICK ONDBLCLICK ONFOCUS"
             + " ONKEYDOWN ONKEYPRESS ONKEYUP ONLOAD ONMOUSEDOWN"
             + " ONMOUSEMOVE ONMOUSEOUT ONMOUSEOVER ONMOUSEUP"
             + " ONRESET ONSELECT ONSUBMIT ONUNLOAD PROFILE SCHEME"
             + " SCROLLING SRC STANDBY VALUETYPE"
             + " ";

// These attributes are allowed, but they contain URLs which are sanitised:
var urlattrs = " ACTION CITE HREF LONGDESC SRC USEMAP "



function sanitise_html(s)
{
debug("sanitise_html starting");

  var r = "";
  var p = 0;
  while (true) {
    var tagstart = s.indexOf("<",p);
    if (tagstart==-1) {
      r += s.substr(p);
      break;
    }
    r += s.substr(p,tagstart-p);
    var tagend = s.indexOf(">",tagstart+1);
    if (tagend==-1) {
      break;
    }
    var tag = s.substr(tagstart,tagend-tagstart+1);
    r += sanitise_tag(tag);
    p = tagend+1;
  }
  return r;
}


function sanitise_tag(tag)
{
  if (tag.substr(0,4)=="<!--" || tag.substr(0,9)=="<!DOCTYPE") {
    return tag;
  }
//debug("sanitising tag "+tag);
  var endtag = (tag[1]=="/");
  var namestart = endtag ? 2 : 1;
  var sp = tag.indexOf(" ");
  var nameend = (sp==-1) ? tag.length-1 : sp;
  var name = tag.substr(namestart,nameend-namestart).toUpperCase();
  var attrs = tag.substr(nameend,tag.length-nameend-1);

  if (badtags.indexOf(" "+name+" ")!=-1) {
//info("Removing bad tag '"+name+"'");
    if (endtag) {
      return "</div>";
    } else {
      return '<div class="badtag">' + attrs;
    }
  }

  if (oktags.indexOf(" "+name+" ")==-1) {
//info("Removing unknown tag '"+name+"'");
    if (endtag) {
      return "</div>";
    } else {
      return '<div class="unknowntag">' + attrs;  
    }
  }

  if (endtag) {
    return "</"+name+">";
  } else {
    return "<"+name+sanitise_attrs(attrs)+">";
  }
}


function sanitise_attrs(s)
{
  var r = "";
  var p = 0;
  while (true) {
    while (is_space(s[p])) {
      p++;
    }
    r += " ";
    var attrname="";
    while (true) {
      var c = s[p];
      if (!c || c=="=" || is_space(c)) {
        break;
      }
      attrname += c;
      p++;
    }
    if (attrname=="") {
      break;
    }
    while (is_space(s[p])) {
      p++;
    }
    var attrval;
    var quotechar='"';
    if (s[p]=="=") {
      p++;
      while (is_space(s[p])) {
        p++;
      }
      if (s[p]=='"') {
        var q = s.indexOf('"',p+1);
        if (q==-1) {
          break;
        }
        attrval = s.substr(p+1,q-p-1);
        p = q+1;
      } else if (s[p]=="'") {
        quotechar="'";
        var q = s.indexOf("'",p+1);
        if (q==-1) {
          break;
        }
        attrval = s.substr(p+1,q-p-1);
        p = q+1;
      } else {
        while (true) {
          var c = s[p];
          if (!c || is_space(c)) {
             break;
          }
          attrval += c;
          p++;
        }
      }
    } else {
      attrval=attrname;
    }
//debug("considering attribute "+attrname);
    attrname = attrname.toUpperCase();
    if (okattrs.indexOf(" "+attrname+" ")!=-1) {
      attrval = sanitise_attrval(attrname,attrval);
      r += attrname + "=" + quotechar + attrval + quotechar;
    } else {
//info("Removing bad attribute '"+attrname+"'");
    }
  }
  return r;
}


function sanitise_attrval(name,val)
{
  if (urlattrs.indexOf(" "+name+" ")!=-1) {
    return sanitise_url(val);
  } else {
    return val;
  }
}


function sanitise_url(url)
{
//debug("sanitising url "+url);
  if (starts_with(url,"http:") || starts_with(url,"ftp://")
      || starts_with(url,"mailto:") || starts_with(url,"#")) {
    return url;
  } else {
//info("Removing bad url '"+url+"'");
    return "";
  }
}


// browser/webmail/sort.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Message list sorting.
// This is added to the MessageList class.


function add_sort_methods()
{
  this.current_sort="";
  this.current_sort_hdr=null;
  
  this.sort_by_subject = function()
  {
    this.clear_sort_classes();
    var sorter;
    if (this.current_sort=="subject") {
      sorter=textcmp;
      this.current_sort="rsubject";
      addclass(this.ml_subject,"rsort");
    } else {
      sorter=textcmp_r;
      this.current_sort="subject";
      addclass(this.ml_subject,"sort");
    }
    this.current_sort_hdr=this.ml_subject;
  
    this.do_sort(subject_keygetter, sorter);
  }
  
  this.sort_by_from = function()
  {
    this.clear_sort_classes();
    var sorter;
    if (this.current_sort=="from") {
      sorter=textcmp;
      this.current_sort="rfrom";
      addclass(this.ml_from,"rsort");
    } else {
      sorter=textcmp_r;
      this.current_sort="from";
      addclass(this.ml_from,"sort");
    }
    this.current_sort_hdr=this.ml_from;
  
    this.do_sort(from_keygetter, sorter);
  }
  
  this.sort_by_date = function()
  {
    this.clear_sort_classes();
    var sorter;
    if (this.current_sort=="date") {
      sorter=textcmp;
      this.current_sort="rdate";
      addclass(this.ml_date,"rsort");
    } else {
      sorter=textcmp_r;
      this.current_sort="date";
      addclass(this.ml_date,"sort");
    }
    this.current_sort_hdr=this.ml_date;
  
    this.do_sort(date_keygetter, sorter);
  }
  
  this.sort_by_size = function()
  {
    this.clear_sort_classes();
    var sorter;
    if (this.current_sort=="size") {
      sorter=numcmp;
      this.current_sort="rsize";
      addclass(this.ml_size,"rsort");
    } else {
      sorter=numcmp_r;
      this.current_sort="size";
      addclass(this.ml_size,"sort");
    }
    this.current_sort_hdr=this.ml_size;
  
    this.do_sort(size_keygetter, sorter);
  }


  this.sort_by_uid = function()
  {
    this.clear_sort_classes();
    var sorter;
    if (this.current_sort=="uid") {
      sorter=numcmp;
      this.current_sort="ruid";
    } else {
      sorter=numcmp_r;
      this.current_sort="uid";
    }
    this.current_sort_hdr=null;
  
    this.do_sort(uid_keygetter, sorter);
  }
  
  /*private*/ this.clear_sort_classes = function()
  {
    if (this.current_sort_hdr) {
      clearclasses(this.current_sort_hdr);
    }
  }
  
  
  /*private*/ this.do_sort = function(keygetter,sorter)
  {
    for (var i=0; i<this.messages.length; ++i) {
      var msg = this.messages[i];
      msg.cached_sortkey = keygetter(msg);
    }
    this.messages.sort(msgcmp.bind_fn(sorter));
  
    delete_contents(this.ml_tbody);
  
    this.last_message=null;
    for (var i=0; i<this.messages.length; ++i) {
      var msg = this.messages[i];
      if (this.last_message) {
        this.last_message.next_message = msg;
      }
      msg.next_message = null;
      msg.prev_message = this.last_message;
      this.last_message = msg;
      this.render_row(msg);
    }
    this.show_selected(ui.selected_message);
  }
}


/*private:*/  

function textcmp(a,b)
{
  if (b<a) {
    return -1;
  } else if (b>a) {
    return +1;
  } else {
    return 0;
  }
}

function textcmp_r(a,b)
{
  return textcmp(b,a);
}

function numcmp(a,b)
{
  return b-a;
}

function numcmp_r(a,b)
{
  return a-b;
}

function msgcmp(cmp,a,b)
{
  return cmp(a.cached_sortkey,b.cached_sortkey);
}

function subject_keygetter(msg)
{
  var s = msg.subject.toLowerCase();
  s = s.replace(/^Re(\[[0-9]\])?:/i,"");
  s = s.replace(/^[ \[]*/,"");
  return s;
}

function from_keygetter(msg)
{
  return render_addrlist_text(msg.from).toLowerCase();
}

function date_keygetter(msg)
{
  return msg.date;
}

function size_keygetter(msg)
{
  return msg.size;
}

function uid_keygetter(msg)
{
  return msg.uid;
}

// browser/webmail/spatialindex.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Spatial indexing:


function SpatialIndex(xmin_,xmax_,ymin_,ymax_)
{
  if (xmin_) {
    this.xmin = xmin_;
  } else {
    this.xmin = 0;
  }
  if (xmax_) {
    this.xmax = xmax_;
  } else {
    this.xmax = 4000;
  }
  this.xmid = (this.xmin+this.xmax)/2;

  if (ymin_) {
    this.ymin = ymin_;
  } else {
    this.ymin = 0;
  }
  if (ymax_) {
    this.ymax = ymax_;
  } else {
    this.ymax = 4000;
  }
  this.ymid = (this.ymin+this.ymax)/2;
//debug("creating a SpatialIndex for ["+this.xmin+","+this.xmax+"],"
//     +"["+this.ymin+","+this.ymax+"]");


  this.locals = [];

  this.bottomleft = null;
  this.bottomright = null;
  this.topleft = null;
  this.topright = null;

  this.add = function(x1, x2, y1, y2, obj)
  {
//debug("adding a box ["+x1+","+x2+"],["+y1+","+y2+"]");

    if (   (x1 < this.xmid) && (x2 >= this.xmid)
        || (y1 < this.ymid) && (y2 >= this.ymid)) {
      this.locals.push({x1: x1, x2: x2, y1: y1, y2: y2, obj: obj});
    } else if ((x2 < this.xmid) && (y2 < this.ymid)) {
      if (!this.bottomleft) {
        this.bottomleft = new SpatialIndex(this.xmin, this.xmid, this.ymin, this.ymid);
      }
      this.bottomleft.add(x1,x2,y1,y2,obj);
    } else if ((x1 >= this.xmid) && (y2 < this.ymid)) {
      if (!this.bottomright) {
        this.bottomright = new SpatialIndex(this.xmid, this.xmax, this.ymin, this.ymid);
      }
      this.bottomright.add(x1,x2,y1,y2,obj);
    } else if ((x2 < this.xmid) && (y1 >= this.ymid)) {
      if (!this.topleft) {
        this.topleft = new SpatialIndex(this.xmin, this.xmid, this.ymid, this.ymax);
      }
      this.topleft.add(x1,x2,y1,y2,obj);
    } else if ((x1 >= this.xmid) && (y2 >= this.ymid)) {
      if (!this.topright) {
        this.topright = new SpatialIndex(this.xmid, this.xmax, this.ymid, this.ymax);
      }
      this.topright.add(x1,x2,y1,y2,obj);
    }
  }


  this.find = function(x,y)
  {
//debug("searching in index ["+this.xmin+","+this.xmax+"],"
//     +"["+this.ymin+","+this.ymax+"] for point ("+x+","+y+")");

    for (var i=0; i<this.locals.length; i++) {
      var a = this.locals[i];
      if (   (a.x1 <= x) && (x <= a.x2)
          && (a.y1 <= y) && (y <= a.y2)) {
        return a.obj;
      }
    }
    if (y < this.ymid) {
      if (x < this.xmid) {
        if (this.bottomleft) {
          return this.bottomleft.find(x,y);
        } else {
          return null;
        }
      } else {
        if (this.bottomright) {
          return this.bottomright.find(x,y);
        } else {
          return null;
        }
      }
    } else {
      if (x < this.xmid) {
        if (this.topleft) {
          return this.topleft.find(x,y);
        } else {
          return null;
        }
      } else {
        if (this.topright) {
          return this.topright.find(x,y);
        } else {
          return null;
        }
      }
    }
  }
}

// browser/webmail/Tabset.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Tabsets:


function Tabset(parent)
{
  this.body = ul(parent);
  addclass(this.body,"tabset");
  this.current_tab = null;
}


function Tab(tabset_,text_)
{
  this.tabset = tabset_;

  this.bind_click = function(fn)
  {
    this.onclick = fn;
  }

  this.add_menu = function()
  {
    return popup_menu(this.body);
  }

  /*private*/ this.activate = function()
  {
    this.tabset.current_tab = this;
    addclass(this.body,"active");
  }

  /*private*/ this.deactivate = function()
  {
    this.tabset.current_tab = null;
    rmvclass(this.body,"active");
  }

  /*private*/ this.clicked = function()
  {
    if (this.tabset.current_tab) {
      this.tabset.current_tab.deactivate();
    }
    this.activate();
    this.onclick();
  }

  this.onclick = null;
  this.body = li(this.tabset.body);
  this.text = span(this.body,"",make_spaces_nonbreaking(text_));
  bind_click(this.text,this.clicked.bind_mem(this));
}

// browser/webmail/TextPartUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// User interface for a text/plain message part.


function TextPartUI()
{
  this.score = 10;

  this.render0 = function()
  {
    this.part.content.recode("8bit");
    if (this.part.p_format=="flowed") {
      this.render_text_plain_flowed();
    } else {
      delete_contents(this.pane);
      addclass(this.pane,"text-plain");
      render_with_links(this.pane, normalise_newlines(this.part.content.data));
    }
  }

  /*private*/ this.render_text_plain_flowed = function()
  {
    addclass(this.pane,"text-plain-flowed");
    var paras = decode_format_flowed(this.part.content.data);
    for (var i=0; i<paras.length; i++) {
      var parent = this.pane;
      for (var j=0; j<paras[i].indent; j++) {
        parent = p(parent);
        addclass(parent,"quoted");
      }
      render_with_links(p(parent),paras[i].text);
    }
  }

  this.get_description0 = function()
  {
    return "Plain text";
  }

  this.render_menuitems0 = function(menu)
  {
    var mi = menuitem(menu,"");
    this.fmtcheck = input(mi,"","checkbox","flowed");
    this.fmtcheck.checked = (this.part.p_format=="flowed");
    bind_change(this.fmtcheck, this.change_format.bind_mem(this));
    textnode(mi,"Flow");
  }

  /*private*/ this.change_format = function()
  {
    this.part.p_format = this.fmtcheck.checked ? "flowed" : "fixed";
    this.clearpane();
    this.render();
  }
}


// browser/webmail/toolbar.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Toolbar:


function build_button(toolbar,text,fn)
{
  var btn = button(toolbar,"",text);
  bind_click(btn,fn);
}


function build_toolbar(pane,ui)
{
  build_button(pane,"Get Mail",     get_mail);
  build_button(pane,"Compose",      ui.compose_new.bind_mem(ui));
  build_button(pane,"Reply",        ui.reply.bind_mem(ui));
  build_button(pane,"Reply to All", ui.reply_all.bind_mem(ui));
  build_button(pane,"Forward",      ui.forward.bind_mem(ui));
  build_button(pane,"Delete",       ui.deletetoggle_msg.bind_mem(ui));
  build_button(pane,"Print",        ui.print.bind_mem(ui));
}


function get_mail()
{
  waiting(ui.pane);
  info("Checking for new mail...");
  noop_cmd(get_mail_done);
}

function get_mail_done()
{
  endwait(ui.pane);
  info("OK");
}

// browser/webmail/Tree.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// General purpose Tree widget.


function Tree(parentdiv, content_, name_)
{
  // this.content is an object that records (and knows how to render) the content of 
  // the node.
  this.content = content_;

  // this.name is the leafname of the node, used to identify nodes.
  this.name = name_;

  // The Tree widget is recursive.
  // List of subtrees:
  this.subtrees = [];


  // Insert a new node.
  // The path is an array of node names.
  // The node must not already exist.
  // If any parent nodes do not exist, returns false.
  this.insert = function(path, content)
  {
    if (path.length==0) {
      fatal("Tree.insert called with path.length==0");
    } else if (path.length==1) {
      var path_head = path[0];
      var t = new Tree(this.div, content, path_head);
      this.subtrees.push(t);
      return t;
    } else {
      var path_tail = [].concat(path);
      var path_head = path_tail.shift();
      var t = null;
      for (var i=0; i<this.subtrees.length; i++) {
        var st = this.subtrees[i];
        if (st.name == path_head) {
          t = st;
          break;
        }
      }
      if (t) {
        return t.insert(path_tail, content);
      } else {
        return false;
      }
    }
  }


  // Find a node.
  // The path is an array of node names.
  // Returns false if not found.
  // Does not expand anything.
  this.find = function(path)
  {
    if (path.length==0) {
      return this;
    } else {
      var path_tail = [].concat(path);
      var path_head = path_tail.shift();
      var t = null;
      for (var i=0; i<this.subtrees.length; i++) {
        var st = this.subtrees[i];
        if (st.name == path_head) {
          t = st;
          break;
        }
      }
      if (t) {
        return t.find(path_tail);
      } else {
        return false;
      }
    }
  }


  // Expand down a specified path.
  this.expand_down_to = function(path, cont)
  {
    if (this.expanded) {
      if (path.length==0) {
        if(cont) {
          cont();
        }
        return;
      } else {
        var path_tail = [].concat(path);
        var path_head = path_tail.shift();
        var t = null;
        for (var i=0; i<this.subtrees.length; i++) {
          var st = this.subtrees[i];
          if (st.name == path_head) {
            t = st;
            break;
          }
        }
        if (t) {
          t.expand_down_to(path_tail, cont);
          return;
        } else {
          fatal("Didn't find expected node "+path+" while expanding Tree");
        }
      }
    } else {
      this.expand(this.expand_down_to.bind_mem(this,path,cont));
    }
  }


  // Remove a node.  The path is an array of node names.
  // This is only intended for removing leaves.
  this.remove = function(path)
  {
    if (path.length==0) {
      if (this.subtrees.length>0) {
        warn("Removing something with subthings!");
      }
      this.remove_self();
    } else {
      var firstname = path.shift();
      for (var i=0; i<this.subtrees.length; i++) {
        var t = this.subtrees[i];
        if (t.name == firstname) {
          t.remove(path);
          if (path.length==1) {
            this.subtrees.splice(i,1);
            delete t;
          }
          break;
        }
      }
    }
  }


  /*private*/ this.remove_self = function()
  {
    this.div.parentNode.removeChild(this.div);
  }


  this.rename_leaf = function(new_leafname)
  {
    this.name = new_leafname;
    if (this.subtrees.length>0) {
      warn("Renaming non-leaf mailboxes is not fully implemented.");
    }
  }


  /*private*/ this.toggler_clicked = function()
  {
    this.toggle();
  }


  this.onexpand = null;

  this.bind_expand = function(fn)
  {
    this.onexpand = fn;
    this.update_toggler();
  }

  this.expand = function(cont)
  {
    if (this.ever_expanded) {
      this.reveal_subtrees();
      this.expanded = true;
    } else if (this.onexpand) {
      this.expanded = true;
      this.ever_expanded = true;
      this.onexpand(this.expand_cont.bind_mem(this,cont));
      return;
    }
    this.update_toggler();
    if (cont) {
      cont();
    }
  }

  this.expand_cont = function(cont)
  {
    this.update_toggler();
    if (cont) {
      cont();
    }
  }

  this.unexpand = function()
  {
    this.hide_subtrees();
    this.expanded = false;
    this.update_toggler();
  }

  /*private*/ this.reveal_subtrees = function()
  {
    for (var i=0; i<this.subtrees.length; i++) {
      rmvclass(this.subtrees[i].div,"hidden");
    }
  }

  /*private*/ this.hide_subtrees = function()
  {
    for (var i=0; i<this.subtrees.length; i++) {
      addclass(this.subtrees[i].div,"hidden");
    }
  }

  /*private*/ this.toggle = function()
  {
    if (this.expanded) {
      this.unexpand();
    } else {
      this.expand();
    }
  }


  /*private*/ this.update_toggler = function()
  {
    var toggler_fn;
    var toggler_text;
    if (this.subtrees.length>0 || this.onexpand) {
      if (this.expanded) {
        toggler_text = "-[-] ";
        toggler_fn = "tree_minus.png";
      } else {
        toggler_text = "-[+] ";
        toggler_fn = "tree_plus.png";
      }
    } else {
      toggler_text="";
      toggler_fn="tree_none.png";
    }
    this.toggler.src = "imgs/"+toggler_fn;
    this.toggler.alt = toggler_text;
  }


  this.make_dropable = function(dropdata)
  {
    make_dropable(this.line,dropdata);
  }

  this.make_dragable = function(fn)
  {
    make_dragable(this.line,fn);
  }


  this.div = div(parentdiv);
  addclass(this.div,"treebox");
  this.line = div(this.div);
  addclass(this.line,"treeline");
  this.toggler = img(this.line);
  bind_click(this.toggler, this.toggler_clicked.bind_mem(this));

  this.content.render(this.line);

  this.expanded = false;
  this.ever_expanded = false;

  this.update_toggler();
}

// browser/webmail/UI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Webmail User Interface top-level:


function UI() {

  this.pane = document.getElementsByTagName("BODY")[0];

  this.selected_mailbox = null;

  this.select_mailbox = function(mailbox, callback)
  {
    if (mailbox == this.selected_mailbox) {
      if (callback) {
        callback();
      }
      return;
    }
    info("Selecting mailbox "+mailbox.imap_name+"...");
    if (this.selected_mailbox) {
      this.mailboxesui.show_unselected(this.selected_mailbox);
    }
    this.selected_mailbox = mailbox;
    this.mailboxesui.show_selected(this.selected_mailbox);
    waiting(messagelist.msglist_pane);
    messagelist.reset();
    this.selected_message = null;
    select_in_progress=true;
    select_cmd(this.selected_mailbox.imap_name,
               this.get_envelopes.bind_mem(this,callback));
  }

  this.select_no_mailbox = function()
  {
    messagelist.reset();
    this.selected_mailbox = null;
  }

  this.get_envelopes = function(callback)
  {
    select_in_progress=false;
    if (response_parser.n_exists>0) {
      fetch_all_envelope_cmd(this.select_mailbox_done.bind_mem(this,callback));
    } else {
      this.select_mailbox_done(callback);
    }
  }

  this.select_mailbox_done = function(callback)
  {
    endwait(messagelist.msglist_pane);
    info("OK");
    if (callback) {
      callback();
    }
  }


  this.rename_selected_mailbox_leaf = function()
  {
    this.mailboxesui.rename_leaf(this.selected_mailbox);
  }

  this.delete_selected_mailbox = function()
  {
    this.mailboxesui.del(this.selected_mailbox);
  }

  this.create_mailbox = function()
  {
    this.mailboxesui.create_mailbox();
  }


  this.select_message = function(msg)
  {
    info("Selecting message "+msg.uid+"...");
    waiting(this.message_pane);
    if (this.selected_message) {
      messagelist.show_unselected(this.selected_message);
    }
    messagelist.show_selected(msg);
    this.selected_message = msg;
    this.messageui.render(msg,null,this.select_message_done.bind_mem(this));
  }

  this.select_message_done = function()
  {
    endwait(this.message_pane);
    info("OK");
  }


  /*private*/ this.get_msg = function()
  {
    if (!this.selected_message) {
      error("No message is selected.");
    }
    return this.selected_message;
  }


  this.compose_new = function()
  {
    compose([],[],"","",[],"");
  }


  this.reply = function()
  {
    var msg = this.get_msg();
    compose(msg.reply_to.length ? msg.reply_to : msg.from,
            [],
            re(msg.subject,"Re:"),
            msg.message_id, 
            msg.references.concat([msg.message_id]),
            quote(this.messageui.partui.part.content, msg.from),
            mark_replied.bind_fn(msg.uid));
  }


  this.reply_all = function()
  {
    var msg = this.get_msg();
    compose(msg.reply_to.length ? msg.reply_to : msg.from,
            msg.cc.concat(msg.to),
            re(msg.subject,"Re:"),
            msg.message_id, 
            msg.references.concat([msg.message_id]),
            quote(this.messageui.partui.part.content, msg.from),
            mark_replied.bind_fn(msg.uid));
  }


  this.forward = function()
  {
    var msg = this.get_msg();
    compose([],
            [],
            re(msg.subject,"Fwd:"),
            "",
            [],
            quote(this.messageui.partui.part.content, msg.from) );
  }


  this.resend = function()
  {
    var msg = this.get_msg();
    this.messageui.partui.part.content.recode("8bit");
    compose(msg.to,
            msg.cc,
            msg.subject,
            "",
            [],
            this.messageui.partui.part.content.data );
  }


  this.deletetoggle_msg = function()
  {
    var msg = this.get_msg();
    if (msg.deleted) {
      uid_store_minusflags_cmd(msg.uid,"\\Deleted");
    } else {
      uid_store_plusflags_cmd(msg.uid,"\\Deleted");
    }
  }


  this.sort_by_subject = function()
  {
    messagelist.sort_by_subject();
  }

  this.sort_by_from = function()
  {
    messagelist.sort_by_from();
  }

  this.sort_by_date = function()
  {
    messagelist.sort_by_date();
  }

  this.sort_by_size = function()
  {
    messagelist.sort_by_size();
  }

  this.sort_by_uid = function()
  {
    messagelist.sort_by_uid();
  }


  this.print = function()
  {
    this.messageui.partui.print();
  }
    

  this.next_message = function()
  {
    if (!this.selected_message) {
      return;
    }
    var msg = this.selected_message.next_message;
    if (msg) {
      this.select_message(msg);
    }
  }

  this.prev_message = function()
  {
    if (!this.selected_message) {
      return;
    }
    var msg = this.selected_message.prev_message;
    if (msg) {
      this.select_message(msg);
    }
  }




  this.menubar_pane = div(this.pane,"menubar-pane");
  this.toolbar_pane = div(this.pane,"toolbar-pane");
  this.footer_pane  = div(this.pane,"footer-pane");
  this.mailbox_pane = div(this.pane,"mailbox-pane");
  messagelist.msglist_pane = div(this.pane,"msglist-pane");
  this.message_pane = div(this.pane,"message-pane");

  this.footer_pane.innerHTML = footer_contents;

  build_menubar(this.menubar_pane,this);
  build_toolbar(this.toolbar_pane,this);
  this.mailboxesui = new MailboxesUI(this.mailbox_pane, mailboxes);
  messagelist.build_ui();
  this.messageui = new MessageUI(this.message_pane);

  this.slider1 = new VSlider([messagelist.msglist_pane, messagelist.ml_tbody],[this.message_pane],[]);
  this.slider2 = new HSlider([this.mailbox_pane],[messagelist.msglist_pane,this.message_pane],[this.slider1.slider]);

  maybe_build_status_pane(this.pane);


  this.selected_message = null;

  global_bind_key("g", get_mail);
  global_bind_key("w", this.compose_new.bind_mem(this));
  global_bind_key("r", this.reply.bind_mem(this));
  global_bind_key("R", this.reply_all.bind_mem(this));
  global_bind_key("f", this.forward.bind_mem(this));
  global_bind_key("d", this.deletetoggle_msg.bind_mem(this));
  global_bind_key("C", mark_all_read);
  global_bind_key("(", this.next_message.bind_mem(this));  // We get ( for cursor-down
  global_bind_key("&", this.prev_message.bind_mem(this));  // We get & for cursor-up

  gbind_click(clearmenus);
}
// browser/webmail/UnknownPartUI.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// user interface for a message part whose type is not recognised and cannot be 
// displayed.

function UnknownPartUI()
{
  this.score = 1;

  this.render0 = function()
  {
    p(this.pane,"","Webmail doesn't know how to display this content of MIME type "
     +this.part.type+"/"+this.part.subtype);
    p(this.pane,"","You can save it for viewing with another program, or view it as text, "
     +"from the popup menu in the tab above.");
  }

  this.get_description0 = function()
  {
    return this.part.type+"/"+this.part.subtype;
  }
}

// browser/webmail/webmail.js
// This file is part of Decimail; see http://decimail.org/
// (C) 2006-2007 Philip Endecott

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Webamail2 core:

var ui;

var messagelist;
var response_parser;

var hierarchy_delimiter;

var selected_mailbox="";

var mailboxes;


function get_mailbox_leafname(mailbox_name)
{
  var mailbox_name_components = mailbox_name.split(hierarchy_delimiter);
  var leafname = mailbox_name_components[mailbox_name_components.length-1];
  return leafname;
}

function add_mailbox(imap_name, flags)
{
  // Callback from IMAP response parser when it gets a LIST response
  if (add_mailbox_find_mode) {
    ui.mailboxesui.add_find_result(imap_name);
  } else {
    mailboxes.add_new_mailbox(imap_name, flags);
  }
}


function expand_mailbox_tree(mailbox_name,cont)
{
  waiting(ui.mailbox_pane);
  if (mailbox_name!=root_mailbox) {
    mailbox_name += hierarchy_delimiter;
    info("Listing mailboxes under "+mailbox_name+"...");
  } else {
    info("Listing mailboxes...");
  }
  list_cmd(mailbox_name, "%",
           post_expand_mailbox_tree.bind_fn(cont));
}

function post_expand_mailbox_tree(cont)
{
  endwait(ui.mailbox_pane);
  info("OK");
  if (cont) {
    cont();
  }
}


// 'initialise' must have been defined before we get to this point.
window.onload = initialise;


