const EXPORTED_SYMBOLS = ["CookiesService"];
Components.utils.import("resource://yoono/yoonoLog.js");

// First, start with some various debug functions
const DIRSERVICE = Components.classes['@mozilla.org/file/directory_service;1'].getService(Components.interfaces.nsIProperties);

function log(component,msg) {

 return;

  if (msg)
    Components.utils.reportError("Cookies::"+component+" - "+msg);
  else
    Components.utils.reportError("Cookies - "+component);
}

function inspect(obj) {
  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);
  var win = wm.getMostRecentWindow("navigator:browser");
  if (!win.inspectObject) return;
  win.inspectObject(obj);
}

function alert(m) {
  var hiddenWindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
         .getService(Components.interfaces.nsIAppShellService)
         .hiddenDOMWindow;
  hiddenWindow.alert(m);
}



///////////////////////////////////////////////////////////////////////////
// Class Cookie
//-------------------------------------------------------------------------
// Represent *one* cookie key/value
// Have two static methods : cookiesListToString and parseCookieDate
//
function Cookie(cookieAttributes) {
  this.name = cookieAttributes.name;
  this.value = cookieAttributes.value;
  
  this.host = cookieAttributes.host;
  this.path = cookieAttributes.path;
  
  this.isSecure = cookieAttributes.isSecure;
  this.isHttpOnly = cookieAttributes.isHttpOnly;
  
  this.expires = cookieAttributes.expires;
  this.maxage = cookieAttributes.maxage;
}

Cookie.parseCookieDate = function (s) { // note: static function ...
  return new Date(s.replace(/-/g," "));
}

Cookie.cookiesListToString = function (list) {
  var cookieData = "";
  for (var i = 0; i < list.length; ++i) {
    var cookie = list[i];
    
    // check if we have anything to write
    if (cookie.name.length>0 || cookie.value.length>0) {
      // if we've already added a cookie to the return list, append a "; " so
      // that subsequent cookies are delimited in the final list.
      if (cookieData.length>0) {
        cookieData += "; ";
      }

      if (cookie.name.length>0) {
        // we have a name and value - write both
        cookieData += cookie.name + "=" + cookie.value;
      } else {
        // just write value
        cookieData += cookie.value;
      }
    }
  }
  return cookieData;
}

const mTLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"]
                  .getService(Components.interfaces.nsIEffectiveTLDService);

Cookie.prototype._checkDomain = function(uri) {
  // JS equivalent of CheckDomain
  // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1932
  
  var hostFromURI = uri.asciiHost;
  if (!hostFromURI) return false;
  
  // trim trailing dots
  hostFromURI = hostFromURI.replace(/(^\.)|(\.$)/g,"");

  // if a domain is given, check the host has permission
  if (this.host && this.host.length>0) {
    // switch to lowercase now, to avoid case-insensitive compares everywhere
    this.host = this.host.replace(/(^\.)|(\.$)/g,"").toLowerCase();
    
    // get the base domain for the host URI.
    // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk", which
    // represents the lowest level domain a cookie can be set for.
    try {
    var baseDomain = mTLDService.getBaseDomain(uri);
    baseDomain = baseDomain.replace(/(^\.)|(\.$)/g,"");
    } catch(e) {
      // check whether the host is an IP address, and leave the cookie as
      // a non-domain one. this will require an exact host match for the cookie,
      // so we eliminate any chance of IP address funkiness (e.g. the alias 127.1
      // domain-matching 99.54.127.1). bug 105917 originally noted the
      // requirement to deal with IP addresses.
      if (e.name=="NS_ERROR_HOST_IS_IP_ADDRESS") {
        return hostFromURI == this.host;
      }
      log("SetCondition","Ignore cookie on domain 1");
      return false;
    }

    // ensure the proposed domain is derived from the base domain; and also
    // that the host domain is derived from the proposed domain (per RFC2109).
    // we prepend a dot before the comparison to ensure e.g.
    // "mybbc.co.uk" isn't matched as a superset of "bbc.co.uk".
    hostFromURI = "."+hostFromURI;
    this.host = "."+this.host;
    baseDomain = "."+baseDomain;
    function StringEndsWith(base, str) {
      return base.length >= str.length && base.lastIndexOf(str) + str.length == base.length
    }
    var accept = StringEndsWith(this.host, baseDomain) &&
           StringEndsWith(hostFromURI, this.host);
    if (!accept)
      log("SetCondition","Ignore cookie on domain 2 : "+this.host+"/"+baseDomain+" -- "+hostFromURI+"/"+this.host);
    return accept;

    /*
     * note: RFC2109 section 4.3.2 requires that we check the following:
     * that the portion of host not in domain does not contain a dot.
     * this prevents hosts of the form x.y.co.nz from setting cookies in the
     * entire .co.nz domain. however, it's only a only a partial solution and
     * it breaks sites (IE doesn't enforce it), so we don't perform this check.
     */
  }

  // block any URIs without a host that aren't file:/// URIs
  if (!hostFromURI || hostFromURI.length==0) {
    if (!uri.schemeIs("file")) {
      log("SetCondition","Ignore cookie on domain 3");
      return false;
    }
  }

  // no domain specified, use hostFromURI
  this.host = hostFromURI;

  return true;
}

Cookie.prototype._checkPath = function(aHostURI) {
  // JS equivalent of CheckPath
  // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#2003
  
  // if a path is given, check the host has permission
  if (!this.path || this.path.length==0) {
    log("checkPath","Empty path '"+this.path+"' -> compute automatically!");
    // strip down everything after the last slash to get the path,
    // ignoring slashes in the query string part.
    // if we can QI to nsIURL, that'll take care of the query string portion.
    // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
    try {
      aHostURI.QueryInterface(Components.interfaces.nsIURL);
      this.path = aHostURI.directory;
    } catch(e) {
      this.path = aHostURI.path;
      var idx = this.path.lastIndexOf('/');
      if (idx!=-1)
        this.path = this.path.substring(0,idx-1);
    }
  }

  if (this.path && this.path.length > 1024 ||
      this.path.indexOf('\t')!=-1 )
    return false;

  return true;
}

Cookie.prototype.canSetFrom = function (uri) {
  // JS equivalent of SetCookieInternal (part of)
  // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1385
  
  if (this.name.length + this.value.length >4096) {
    log("SetCondition","cookie too long");
    return false;
  }
  if (this.name.indexOf('\t')!=-1) {
    log("SetCondition","invalid name character : \\t");
    return false;
  }
  
  if (!this._checkDomain(uri)) {
    log("SetCondition","Refuse this domain!!!");
    return false;
  }
  
  if (!this._checkPath(uri)) {
    log("SetCondition","Refuse this path!!!");
    return false;
  }
  
  return true;
}

Cookie.prototype.computeExpiracy = function(serverTime) {
  // JS equivalent of GetExpiry
  // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#2048
  
  // Compute expicary time
  var isSessionCookie=false;
  var delta;
  if (this.maxage && this.maxage.length>0) {
    try {
      delta = parseInt(this.maxage);
    } catch(e){
      log("ComputeExpiracy","maxage not an int");
      isSessionCookie = true;
    }
  } else if (this.expires && this.expires.length>0) {
  
    try {
      var temp = Cookie.parseCookieDate(this.expires);
      //Components.utils.reportError("got expires : "+temp.getTime()+" - "+serverTime);
      delta = (temp.getTime()) - serverTime;
    } catch(e){
      log("ComputeExpiracy","expires not a valid date");
      isSessionCookie = true;
    }
  } else {
    isSessionCookie = true;
  }
  if (delta)
    this.expiry = new Date().getTime()+delta;
  this.isSession = isSessionCookie;
}

Cookie.prototype.canGetFrom = function (pathFromURI, isSecure, aHttpBound) {
  // JS equivalent of GetCookieInternal (part of)
  // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1276
  
  function ispathdelimiter(c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
  
  // if the cookie is secure and the host scheme isn't, we can't send it
  if (this.isSecure && !isSecure) {
    log("GetCondition","Ignore secure : "+this.name);
    return false;
  }

  // if the cookie is httpOnly and it's not going directly to the HTTP
  // connection, don't send it
  if (this.isHttpOnly && !aHttpBound) {
    log("GetCondition","Ignore httponly : "+this.name);
    return false;
  }

  // calculate cookie path length, excluding trailing '/'
  var cookiePathLen = this.path.length;
  if (cookiePathLen > 0 && this.path[cookiePathLen-1] == '/') {
    --cookiePathLen;
  }

  // if the nsIURI path is shorter than the cookie path, don't send it back
  if (pathFromURI.indexOf(this.path.substring(0,cookiePathLen))!=0) {
    log("GetCondition","Ignore on path : "+this.name+" -> "+pathFromURI+" -- "+this.path.substring(0,cookiePathLen));
    return false;
  }

  if (pathFromURI.length > cookiePathLen &&
      !ispathdelimiter(pathFromURI[cookiePathLen])) {
    /*
     * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
     * '/' is the "standard" case; the '?' test allows a site at host/abc?def
     * to receive a cookie that has a path attribute of abc.  this seems
     * strange but at least one major site (citibank, bug 156725) depends
     * on it.  The test for # and ; are put in to proactively avoid problems
     * with other sites - these are the only other chars allowed in the path.
     */
    log("GetCondition","Ignore on path 2 : "+pathFromURI+"/"+this.path.substring(0,cookiePathLen)+" -- "+pathFromURI[cookiePathLen]);
    return false;
  }

  // check if the cookie has expired
  if (this.expiry && this.expiry <= new Date().getTime()) {
    log("GetCondition","Ignore expired : "+this.name);
    return false;
  }
  return true;
}


///////////////////////////////////////////////////////////////////////////
// Class CookieStringIterator
//-------------------------------------------------------------------------
// Iterator over a HTTP or JS cookie string 
// in order to return Cookies objects
function CookieStringIterator(str) {
  this._hasNext = true;
  this._str = str;
}
CookieStringIterator.prototype.hasNext = function () {
  return this._hasNext;
}
CookieStringIterator.prototype._getCurrentString = function () {
  return this._str;
}
CookieStringIterator.prototype._setNewString = function (str, hasNext) {
  this._str = str;
  this._hasNext = hasNext;
}
CookieStringIterator._getTokenValue = function(str, startIndex) {
  // JS equivalent of GetTokenValue
  // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1658
  
  function iswhitespace     (c) { return c == ' '  || c == '\t'; }
  function isterminator     (c) { return c == '\n' || c == '\r'; }
  function isquoteterminator(c) { return isterminator(c) || c == '"'; }
  function isvalueseparator (c) { return isterminator(c) || c == ';'; }
  function istokenseparator (c) { return isvalueseparator(c) || c == '='; }

  var tokenString = ""
  var tokenValue = ""
  var lastIndex = str.length-1;
  
  var i = startIndex;
  // find <token>, including any <LWS> between the end-of-token and the
  // token separator. we'll remove trailing <LWS> next
  while (i != lastIndex && iswhitespace(str[i]))
    ++i;
  start = i;
  while (i != lastIndex && !istokenseparator(str[i]))
    ++i;

  // remove trailing <LWS>; first check we're not at the beginning
  lastSpace = i;
  if (lastSpace != start) {
    while (--lastSpace != start && iswhitespace(str[lastSpace]));
    ++lastSpace;
  }
  
  //log("Parse","start:"+start+" i:"+i+" lastSpace:"+lastSpace+"("+str[lastSpace]+") lastIndex:"+lastIndex+"("+str[lastIndex]+") lastIndex+1:"+str[lastIndex+1]);
  if (lastSpace==lastIndex && !istokenseparator(str[lastIndex]))
    tokenString = str.substring(start, lastIndex+1);
  else
    tokenString = str.substring(start, lastSpace);

  var equalsFound = (str[i] == '=');
  if (equalsFound && i>=lastIndex) {
    tokenValue = "";
  } else if (equalsFound) {
    //log("Parse","Equals found");
    // find <value>
    while (++i != lastIndex && iswhitespace(str[i]));

    start = i;

    if (str[i] == '"') {
      //log("Parse","Quote mode");
      // process <quoted-string>
      // (note: cookie terminators, CR | LF, can't happen:
      // they're removed by necko before the header gets here)
      // assume value mangled if no terminating '"', return
      while (++i != lastIndex && !isquoteterminator(str[i])) {
        // if <qdtext> (backwhacked char), skip over it. this allows '\"' in <quoted-string>.
        // we increment once over the backwhack, nullcheck, then continue to the 'while',
        // which increments over the backwhacked char. one exception - we don't allow
        // CR | LF here either (see above about necko)
        if (str[i] == '\\' && (++i == lastIndex || isterminator(str[i])))
          break;
      }

      if (i != lastIndex && !isterminator(str[i])) {
        // include terminating quote in attribute string
        tokenValue = str.substring(start, ++i);
        // skip to next ';'
        while (i != lastIndex && !isvalueseparator(str[i]))
          ++i;
      }
    } else {
      
      // process <token-value>
      // just look for ';' to terminate ('=' allowed)
      while (i != lastIndex && !isvalueseparator(str[i]))
        ++i;
      //log("Parse","NewI:"+i+" start:"+start+" lastIndex:"+lastIndex+" str[i]:"+str[i]);
      // remove trailing <LWS>; first check we're not at the beginning
      //if (i != start) {
        lastSpace = i;
        while (--lastSpace != start && iswhitespace(str[lastSpace])) 
          ;
        //log("Parse",i+"-"+lastSpace+"/"+lastIndex+"-"+str[lastSpace]+"/"+str[lastIndex]+"/"+str[lastIndex+1]);
        if (lastSpace+1==lastIndex && !isvalueseparator(str[lastIndex]))
          tokenValue = str.substring(start, lastIndex+1);
        else
          tokenValue = str.substring(start, ++lastSpace);
      //}
    }
  }
  
  var terminator = false;
  // i is on ';', or terminator, or EOS
  if (i != lastIndex) {
    // if on terminator, increment past & return PR_TRUE to process new cookie
    if (isterminator(str[i])) {
      ++i;
      terminator = true;
    } else {
      // fall-through: i is on ';', increment and return PR_FALSE
      ++i;
    }
  }
  var res= {
    tokenString: tokenString, tokenValue: tokenValue,
    newCookie: terminator, newIndex: i,
    equalsFound : equalsFound
  };
  log("getTokenValue", tokenString+" = "+tokenValue);
  //log("getTokenValue","("+str+", "+startIndex+") --> "+res.toSource());
  return res;
}

CookieStringIterator.prototype.getNext = function () {
  // JS equivalent of ParseAttributes function
  // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1743
  
  var str = this._getCurrentString();
  if (!str || str.length==0) return;
  
  var cookieStart = 0;
  var cookieEnd = str.length-1;
  
  var newCookie = false;
  
  var cookieAttributes = {};
  cookieAttributes.isSecure = false;
  cookieAttributes.isHttpOnly = false;

  // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
  // if we find multiple cookies, return for processing
  // note: if there's no '=', we assume token is <VALUE>. this is required by
  //       some sites (see bug 169091).
  // XXX fix the parser to parse according to <VALUE> grammar for this case
  var result = CookieStringIterator._getTokenValue(str, cookieStart);
  cookieStart = result.newIndex;
  newCookie = result.newCookie;
  if (result.equalsFound) {
    cookieAttributes.name = result.tokenString;
    cookieAttributes.value = result.tokenValue;
  } else {
    cookieAttributes.name = "";
    cookieAttributes.value = result.tokenString;
  }
  
  // extract remaining attributes
  while (cookieStart != cookieEnd && !newCookie) {
    result = CookieStringIterator._getTokenValue(str, cookieStart);
    cookieStart = result.newIndex;
    newCookie = result.newCookie;

    if (result.tokenValue && result.tokenValue.length>0) {
      var tempEnd = result.tokenValue.length-1;
      
      if (result.tokenValue[0] == '"' && result.tokenValue[--tempEnd] == '"') {
        // our parameter is a quoted-string; remove quotes for later parsing
        result.tokenValue = result.tokenValue.substring(1,tempEnd);
      }
    }

    // decide which attribute we have, and copy the string
    if (result.tokenString.toLowerCase() == "path")
      cookieAttributes.path = result.tokenValue;

    else if (result.tokenString.toLowerCase() == "domain")
      cookieAttributes.host = result.tokenValue;

    else if (result.tokenString.toLowerCase() == "expires")
      cookieAttributes.expires = result.tokenValue;

    else if (result.tokenString.toLowerCase() == "max-age")
      cookieAttributes.maxage = result.tokenValue;

    // ignore any tokenValue for isSecure; just set the boolean
    else if (result.tokenString.toLowerCase() == "secure")
      cookieAttributes.isSecure = true;
      
    // ignore any tokenValue for isHttpOnly (see bug 178993);
    // just set the boolean
    else if (result.tokenString.toLowerCase() == "httponly")
      cookieAttributes.isHttpOnly = true;
  }

  // rebind aCookieHeader, in case we need to process another cookie
  this._setNewString(str.substring(cookieStart),newCookie);
  
  return new Cookie(cookieAttributes);
}


///////////////////////////////////////////////////////////////////////////
// Service CookiesStorage
//-------------------------------------------------------------------------
// Only handle effective storage to a sqlite database
//
var CookiesStorage = {}
CookiesStorage._db = null;

CookiesStorage.closeDB = function () {
  try {
    if(this._db) {

      this.statementWithoutValue.finalize();
      this.statementWithValue.finalize();
      this.statementWithoutValue = null;
      this.statementWithValue = null;
      this._db.close();
      this._db = null;
    }
  } catch(e) {
    YOONO_LOG.exception(e);
  }
}

CookiesStorage._openDB = function () {
try {
  var dbService = Components.classes["@mozilla.org/storage/service;1"].getService(Components.interfaces.mozIStorageService);
  var dirService = Components.classes['@mozilla.org/file/directory_service;1'].getService(Components.interfaces.nsIProperties);
  var dbFile = dirService.get("ProfDS", Components.interfaces.nsIFile);
  dbFile.append('yoono');
  dbFile.append('cookies.sqlite');
  if (!dbFile.exists())
    dbFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0655);
  var dbConnection = dbService.openUnsharedDatabase(dbFile);
  this._db = dbConnection;
  //Components.utils.reportError("Current cookies DB version : "+dbConnection.schemaVersion);
  if (dbConnection.schemaVersion==1) {
    try {
      dbConnection.executeSimpleSQL("ALTER TABLE sessions ADD COLUMN network TEXT");
      dbConnection.executeSimpleSQL("ALTER TABLE sessions ADD COLUMN network_id INTEGER");
    } catch(e) {
      if (this._db)
        Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError+"\n"+e);
      else
        Components.utils.reportError(e);
    }
    dbConnection.schemaVersion = 2;
  }
  if (dbConnection.schemaVersion==2) {
    try {
      dbConnection.executeSimpleSQL("ALTER TABLE sessions ADD COLUMN home TEXT");
    } catch(e) {
      if (this._db)
        Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError+"\n"+e);
      else
        Components.utils.reportError(e);
    }
    dbConnection.schemaVersion = 3;
  }
  var currentVersion = 3;
  if ( dbConnection.schemaVersion!=currentVersion ) {
    try {
      dbConnection.executeSimpleSQL("DROP TABLE cookies");
    } catch(e) {}
    
    try {
      dbConnection.executeSimpleSQL("DROP TABLE sessions");
    } catch(e) {}
    
    var createCookiesTable = "CREATE TABLE cookies (";
    createCookiesTable += "session_id TEXT, name TEXT, value TEXT, ";
    createCookiesTable += "host TEXT, path TEXT, expiry INTEGER, ";
    createCookiesTable += "lastAccessed INTEGER, isSecure INTEGER, ";
    createCookiesTable += "isHttpOnly INTEGER, isSession INTEGER, ";
    createCookiesTable += "PRIMARY KEY (session_id, name, host, path) ";
    createCookiesTable += ")";
    dbConnection.executeSimpleSQL(createCookiesTable);
    
    var createSessionsTable = "CREATE TABLE sessions (";
    createSessionsTable += "id TEXT PRIMARY KEY, name TEXT, avatar BLOB, network TEXT, network_id INTEGER, home TEXT";
    createSessionsTable += ")";
    dbConnection.executeSimpleSQL(createSessionsTable);
    
    dbConnection.schemaVersion = currentVersion;
  } else {
  
    // Performances improvements
    dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF");
    // remove this when debugging in order to use sqlitemanager when firefox is launched
    dbConnection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");

    var cleanup1 = this._db.createStatement("DELETE FROM cookies WHERE session_id like 'temp_%'");
    cleanup1.executeAsync();
    cleanup1.finalize();

    var cleanup2 = this._db.createStatement("DELETE FROM sessions WHERE id like 'temp_%'");
    cleanup2.executeAsync();
    cleanup2.finalize();

    var cleanup3 = this._db.createStatement("DELETE FROM cookies WHERE expiry >0 and expiry<:expiry");
    cleanup3.params.expiry = new Date().getTime();
    cleanup3.executeAsync();
    cleanup3.finalize();
  }

  if (!this.statementWithValue || !this.statementWithoutValue) {
    var insertSQL1 = "";
    insertSQL1 += "INSERT OR REPLACE INTO cookies ";
    insertSQL1 += "(session_id, name, ";
    insertSQL1 += "host, path, expiry, lastAccessed, isSecure, isHttpOnly, isSession) ";
    insertSQL1 += "VALUES ";
    insertSQL1 += "(:session_id, :name, ";
    insertSQL1 += ":host, :path, :expiry, DATETIME('NOW'), :is_secure, :is_http_only, :is_session)";

    var insertSQL2 = "";
    insertSQL2 += "INSERT OR REPLACE INTO cookies ";
    insertSQL2 += "(session_id, name, ";
    insertSQL2 += "value, ";
    insertSQL2 += "host, path, expiry, lastAccessed, isSecure, isHttpOnly, isSession) ";
    insertSQL2 += "VALUES ";
    insertSQL2 += "(:session_id, :name, ";
    insertSQL2 += ":value, ";
    insertSQL2 += ":host, :path, :expiry, DATETIME('NOW'), :is_secure, :is_http_only, :is_session)";

    this.statementWithoutValue = this._db.createStatement(insertSQL1);
    this.statementWithValue = this._db.createStatement(insertSQL2);
  }
} catch(e) {
  if (this._db)
    Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError+"\n"+e);
  else
    Components.utils.reportError(e);
}
}

CookiesStorage.deleteSession = function(id) {
try {
  if (!this._db) this._openDB();
  this._db.beginTransaction();
  var statement = this._db.createStatement("DELETE FROM cookies WHERE session_id = :id");
  statement.params.id = id;
  statement.executeStep();
  statement.finalize();
  var statement = this._db.createStatement("DELETE FROM sessions WHERE id = :id");
  statement.params.id = id;
  statement.executeStep();
  statement.finalize();
  this._db.commitTransaction();
} catch(e) {
  try {
    Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError);
  } catch(e) {
    Components.utils.reportError(e);
  }
}
}

CookiesStorage.renameSession = function(src, dst) {
try {
  if (!this._db) this._openDB();
  this._db.beginTransaction();
  var statement = this._db.createStatement("DELETE FROM cookies WHERE session_id = :dst");
  statement.params.dst = dst;
  statement.executeStep();
  statement.finalize();
  var statement = this._db.createStatement("DELETE FROM sessions WHERE id = :dst");
  statement.params.dst = dst;
  statement.executeStep();
  statement.finalize();
  var statement = this._db.createStatement("UPDATE sessions SET id=:dst WHERE id = :src");
  statement.params.src = src;
  statement.params.dst = dst;
  statement.executeStep();
  statement.finalize();
  var statement = this._db.createStatement("UPDATE cookies SET session_id=:dst WHERE session_id = :src");
  statement.params.src = src;
  statement.params.dst = dst;
  statement.executeStep();
  statement.finalize();
  this._db.commitTransaction();
  
  // TODO: do this anywhere else!
  var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
               .getService(Components.interfaces.nsIWindowWatcher);
  var enumerator = ww.getWindowEnumerator();
  while (enumerator.hasMoreElements()) {
    var win = enumerator.getNext();
    if (!win.gBrowser) continue;
    var num = win.gBrowser.mPanelContainer.childNodes.length;
    for (var i = 0; i < num; i++) {
      var browser = win.gBrowser.getBrowserAtIndex(i);
      var tab = win.gBrowser.tabContainer.childNodes[i];
      if (browser.__cookies && browser.__cookies._sessionId==src) {
        browser.__cookies._sessionId = dst;
        var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
                    .getService(Components.interfaces.nsISessionStore);
        ss.setTabValue(tab, "cookies-db-id", dst);
      }
    }
  }
  if (this._updateBuffer[src])
    this._updateBuffer[dst] = this._updateBuffer[src];
  delete this._updateBuffer[src];
} catch(e) {
  try {
    Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError);
  } catch(e) {
    Components.utils.reportError(e);
  }
}
}

CookiesStorage.setSessionInfo = function (sessionId, name, avatar, network, networkId, homepageURL) {
try {
  if (homepageURL && (!avatar || avatar.length==0)) {
    try {
      var faviconService = Components.classes["@mozilla.org/browser/favicon-service;1"].getService(Components.interfaces.nsIFaviconService);
      var IOService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
      var uri = IOService.newURI(homepageURL, null, null);
      //avatar = faviconService.getFaviconForPage(uri).spec;
      avatar = uri.prePath + "/favicon.ico";
    } catch(e) {
      Components.utils.reportError(e);
    }
  }
  if (!this._db) this._openDB();
  var statement = this._db.createStatement("INSERT OR REPLACE INTO sessions (id, name, avatar, network, network_id, home) VALUES (:id, :name, :avatar, :network, :network_id, :home)");
  statement.params.id = sessionId;
  statement.params.name = name || "";
  statement.params.avatar = avatar || "";
  statement.params.network = network || "";
  statement.params.network_id = networkId || -1;
  statement.params.home = homepageURL || "";
  statement.executeStep();
  statement.finalize();
} catch(e) {
  if (this._db)
    Components.utils.reportError(sessionId+"-"+name+"-"+avatar+"-"+network+"-"+networkId+" "+this._db.lastErrorString+" / "+this._db.lastError+"\n"+e);
  else
    Components.utils.reportError(e);
}
}

CookiesStorage.getSessionInfo = function (sessionId, callback) {
try {
  var result;
  if (!this._db) this._openDB();
  var statement = this._db.createStatement("SELECT * FROM sessions WHERE id = :id");
  statement.params.id = sessionId;
  statement.executeAsync({  
    handleResult: function(aResultSet) {
      var row = aResultSet.getNextRow();
      if (row)
        result = CookiesStorage._sqlToJSSession(row);
    },

    handleError: function(aError) {
      callback(result);
    },

    handleCompletion: function(aReason) {
      if (aReason != Components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) {
        //print("Query canceled or aborted!");
      }
      callback(result);
    }
  });
  statement.finalize();
} catch(e) {
  try {
    Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError);
  } catch(e) {
    Components.utils.reportError(e);
  }
}
}

CookiesStorage._sqlToJSSession = function (row) {
  return {
    id: row.getResultByName("id"),
    name: row.getResultByName("name"),
    avatar: row.getResultByName("avatar"),
    network: row.getResultByName("network"),
    networkId: row.getResultByName("network_id"),
    homepage: row.getResultByName("home")
  };
}

CookiesStorage.getAllSessions = function (callback) {
try {
  var results = [];
  if (!this._db) this._openDB();
  var statement = this._db.createStatement("SELECT * FROM sessions");
  statement.executeAsync({  
    handleResult: function(aResultSet) {
      while (row = aResultSet.getNextRow()) {
        results.push( CookiesStorage._sqlToJSSession(row) );
      }
    },

    handleError: function(aError) {
      callback(results);
    },

    handleCompletion: function(aReason) {
      if (aReason != Components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) {
        //print("Query canceled or aborted!");
      }
      callback(results);
    }
  });
  statement.finalize();
} catch(e) {
  try {
    Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError);
  } catch(e) {
    Components.utils.reportError(e);
  }
}
}

CookiesStorage.getCookiesForSession = function (sessionId, callback) {
try {
  var list = [];
  if (!this._db) this._openDB();
  
  var statement = this._db.createStatement("SELECT * FROM cookies WHERE session_id = :session_id and (expiry=0 or expiry>=:expiry)");
  statement.params.session_id = sessionId;
  statement.params.expiry = new Date().getTime();
  statement.executeAsync({  
    handleResult: function(aResultSet) {
      var row;
      while (row = aResultSet.getNextRow()) {
        var cookie = new Cookie({
          name: row.getResultByName("name"),
          value: row.getResultByName("value"), 
          host: row.getResultByName("host"),
          path: row.getResultByName("path"),
          isSecure: row.getResultByName("isSecure")==1?true:false,
          isHttpOnly: row.getResultByName("isHttpOnly")==1?true:false,
          expires: null,
          maxage: null
        });
        cookie.expiry = row.getResultByName("expiry");
        if (cookie.expiry==0)
          cookie.expiry = null;
        cookie.isSession = row.getResultByName("isSession")==1?true:false;
        list.push(cookie);
      }
      
    },

    handleError: function(aError) {
      //print("Error: " + aError.message);
    },

    handleCompletion: function(aReason) {  
      if (aReason != Components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) {
        //print("Query canceled or aborted!");
      }
      callback(list);
    }
  });
  statement.finalize();
} catch(e) {
  try {
    Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError);
  } catch(e) {
    Components.utils.reportError(e);
  }
}
}

CookiesStorage.deleteCookie = function (sessionId, cookie) {
try {
  if (this._updateBuffer && this._updateBuffer[sessionId]
      && this._updateBuffer[sessionId][cookie.host]
      && this._updateBuffer[sessionId][cookie.host][cookie.path])
    delete this._updateBuffer[sessionId][cookie.host][cookie.path][cookie.name];
  return;
  if (cookie.name && cookie.name.length>0
      && this.cookiesByDomain
      && this.cookiesByDomain[cookie.host]
      && this.cookiesByDomain[cookie.host][cookie.path]
      && this.cookiesByDomain[cookie.host][cookie.path].named
      && this.cookiesByDomain[cookie.host][cookie.path].named[cookie.name])
    delete this.cookiesByDomain[cookie.host][cookie.path].named[cookie.name];
  else if (this.cookiesByDomain && this.cookiesByDomain[cookie.host]
          && this.cookiesByDomain[cookie.host][cookie.path]
          && this.cookiesByDomain[cookie.host][cookie.path].unnamed
          && this.cookiesByDomain[cookie.host][cookie.path].unnamed[cookie.value]) 
    delete this.cookiesByDomain[cookie.host][cookie.path].unnamed[cookie.value];
  return;
  if (!this._db) this._openDB();
  Components.utils.reportError("DELETE COOKIE : "+cookie.host+":"+cookie.path+" "+cookie.name);//+"/"+cookie.value);
  var statement;
  /*if (cookie.value)
    statement = this._db.createStatement("DELETE FROM cookies WHERE session_id=:session_id and name=:name and value=:value and host=:host and path=:path");
  else*/
    statement = this._db.createStatement("DELETE FROM cookies WHERE session_id=:session_id and name=:name and host=:host and path=:path");
  statement.params.session_id = sessionId;
  statement.params.name = cookie.name;
  //if (cookie.value)
  //  statement.params.value = cookie.value;
  statement.params.host = cookie.host;
  statement.params.path = cookie.path;
  statement.executeStep();
  statement.finalize();
} catch(e) {
  try {
    Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError+"\n"+e);
  } catch(e) {
    Components.utils.reportError("exception : "+e);
  }
}
}

CookiesStorage._updateBuffer = {};
CookiesStorage.updateCookie = function (sessionId, cookie) {
  if (!this._updateBuffer[sessionId])
    this._updateBuffer[sessionId] = {};
  if (!this._updateBuffer[sessionId][cookie.host])
    this._updateBuffer[sessionId][cookie.host] = {};
  if (!this._updateBuffer[sessionId][cookie.host][cookie.path])
    this._updateBuffer[sessionId][cookie.host][cookie.path] = {};
  this._updateBuffer[sessionId][cookie.host][cookie.path][cookie.name] = cookie;
  
  if (this._bufferTimeout) return;
  this._bufferTimeout =  Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  this._bufferTimeout.initWithCallback({ notify: function () {
    try {
      CookiesStorage._bufferTimeout = null;
      CookiesStorage._goUpdateBuffer();
      log("CookiesStorage","Buffer saved!");
    } catch(e) {
      Components.utils.reportError(e);
    }
  }}, 4000,
  Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}

CookiesStorage._goUpdateBuffer = function () {
  if (!this._db) this._openDB();

  var start=new Date().getTime();
  var count = 0;
  this._db.beginTransaction();
  for(var session in this._updateBuffer) {
    var bySession = this._updateBuffer[session];
    for(var host in bySession) {
      var byHost = bySession[host];
      for(var path in byHost) {
        var byPath = byHost[path];
        for(var name in byPath) {
          //log("CookiesStorage","save this : "+name);
          this._goUpdateOne(session, byPath[name]);
          count++;
        }
      }
    }
  }
  this._db.commitTransaction();
  //Components.utils.reportError(count+" cookies updates in "+(new Date().getTime()-start)+"ms");
  this._updateBuffer = {};
}

CookiesStorage._goUpdateOne = function (sessionId, cookie) {
try {
  var statement = cookie.value?this.statementWithValue:this.statementWithoutValue;
  statement.params.session_id = sessionId;
  statement.params.name = cookie.name;
  if (cookie.value)
    statement.params.value = cookie.value;
  statement.params.host = cookie.host;
  statement.params.path = cookie.path;
  statement.params.expiry = cookie.expiry?cookie.expiry:0;
  statement.params.is_secure = cookie.isSecure?1:0;
  statement.params.is_http_only = cookie.isHttpOnly?1:0;
  statement.params.is_session = cookie.isSession?1:0;
  
  statement.executeAsync();
} catch(e) {
  try {
    Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError+"\n"+e);
    YOONO_LOG.exception(e);
  } catch(e) {
    Components.utils.reportError(e);
  }
}
}



///////////////////////////////////////////////////////////////////////////
// Class CookiesDB
//-------------------------------------------------------------------------
// "Man is the middle"
// Mean to be an instance of one independant Cookies database
// Main object between website (http request/JS cookie attribute) and Cookies class/services
//
function CookiesDB(sessionId, onReadyCallback) {
  this._sessionId = sessionId;
  this.cookiesByDomain = {};
  var _self = this;
  CookiesStorage.getCookiesForSession(sessionId, 
    function (cookies) {
      for(var i=0; i<cookies.length; i++) {
        _self._addCookie(cookies[i]);
      }
      if (typeof onReadyCallback=="function")
        onReadyCallback(_self);
    });
}

CookiesDB.prototype._addCookie = function (cookie) {
  if (!this.cookiesByDomain[cookie.host])
    this.cookiesByDomain[cookie.host] = {};
  if (!this.cookiesByDomain[cookie.host][cookie.path])
    this.cookiesByDomain[cookie.host][cookie.path] = {named:{},unnamed:{}};
  
  if (cookie.name && cookie.name.length>0)
    delete this.cookiesByDomain[cookie.host][cookie.path].named[cookie.name];
  else
    delete this.cookiesByDomain[cookie.host][cookie.path].unnamed[cookie.value];
  
  if (cookie.expiry && cookie.expiry <= new Date().getTime()) {
    //if (!cookie.isSession)
    //  CookiesStorage.deleteCookie(this._sessionId, cookie);
    //return log("SetCondition","cookie expired!");
  } else {
    //log("SetCondition",cookie.name+" not expired : "+cookie.expiry+" <= "+new Date().getTime());
  }
  
  if (cookie.name && cookie.name.length>0)
    this.cookiesByDomain[cookie.host][cookie.path].named[cookie.name]=cookie;
  else
    this.cookiesByDomain[cookie.host][cookie.path].unnamed[cookie.value]=cookie;
  
  //if (!cookie.isSession) {
    CookiesStorage.updateCookie(this._sessionId, cookie);
  //}
}

CookiesDB.prototype._getCookiesForDomain = function (domain) {
  var byDomain = this.cookiesByDomain[domain];
  //log("GetCondition","getCookiesForDomain("+domain+")="+(byDomain?byDomain.length:"null"));
  if (!byDomain) return [];
  var list=[];
  for(var path in byDomain) {
    var byPath = byDomain[path];
    for(var name in byPath.named) {
      list.push(byPath.named[name])
    }
    for(var v in byPath.unnamed) {
      list.push(byPath.unnamed[v])
    }
  }
  return list;
}

CookiesDB.prototype._getCookieString = function (hostFromURI, pathFromURI, isSecure, aHttpBound) {
  // JS equivalent of GetCookieInternal (part of)
  // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1218
  
  // trim trailing dots
  hostFromURI = hostFromURI.replace(/(^\.)|(\.$)/g,"")
  // insert a leading dot, so we begin the hash lookup with the
  // equivalent domain cookie host
  hostFromURI = "."+hostFromURI;
  
  var currentDot = hostFromURI;
  var nextDot = hostFromURI.substring(1);
  
  var foundCookieList = [];
  
  // begin hash lookup, walking up the subdomain levels.
  // we use nextDot to force a lookup of the original host (without leading dot).
  do {
    var list = this._getCookiesForDomain(currentDot);
    for (var i=0; i<list.length; i++) {
      var cookie = list[i];
      
      if (!cookie.canGetFrom(pathFromURI, isSecure, aHttpBound)) continue;
      
      // all checks passed - add to list and check if lastAccessed stamp needs updating
      foundCookieList.push(cookie);
    }

    currentDot = nextDot;
    if (currentDot) {
      var temp = currentDot.substring(1);
      var idx = temp.indexOf('.');
      if (idx==-1)
        nextDot = null;
      else
        nextDot = temp.substring(idx);
    }

  } while (currentDot && currentDot.length>0);

  if (foundCookieList.length == 0)
    return "";
  
  // return cookies in order of path length; longest to shortest.
  // this is required per RFC2109.  if cookies match in length,
  // then sort by creation time (see bug 236772).
  // foundCookieList.Sort(CompareCookiesForSendingComparator());
  
  return Cookie.cookiesListToString(foundCookieList);
}

CookiesDB.prototype.getCookieStringFromJS = function (location) {
  var path = location.pathname;
  var idx = path.lastIndexOf('/');
  if (idx!=-1)
    path = path.substring(0,idx);
  return this._getCookieString(location.hostname, path, location.protocol=="https:", false);
}

const ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);  

CookiesDB.prototype.setCookieStringFromJS = function (location, str) {
  var uri = ioService.newURI(location.href, null, null);
  var iterator = new CookieStringIterator(str);
  var cookie = iterator.getNext();
  if (cookie && cookie.canSetFrom(uri)) {
    cookie.computeExpiracy(new Date().getTime());
    this._addCookie(cookie);
  }
}

CookiesDB.prototype.getCookieStringFromHttp = function (uri) {
  // get host and path from the nsIURI
  // note: there was a "check if host has embedded whitespace" here.
  // it was removed since this check was added into the nsIURI impl (bug 146094).
  var hostFromURI = uri.asciiHost;
  var pathFromURI = uri.path;
  
  // check if aHostURI is using an https secure protocol.
  // if it isn't, then we can't send a secure cookie over the connection.
  // if SchemeIs fails, assume an insecure connection, to be on the safe side
  var isSecure = uri.schemeIs("https");
  
  return this._getCookieString(hostFromURI, pathFromURI, isSecure, true);
}

CookiesDB.prototype.setCookieStringFromHttp = function (dateHeader, cookiesHeader, uri) {
  var serverTime = new Date().getTime();
  try {
    serverTime = Cookie.parseCookieDate(dateHeader).getTime();
  } catch(e) {}
  
  var iterator = new CookieStringIterator(cookiesHeader);
  while(iterator.hasNext()) {
    var cookie = iterator.getNext();
    if (cookie && cookie.canSetFrom(uri)) {
      cookie.computeExpiracy(serverTime);
      this._addCookie(cookie);
    }
  }
}




///////////////////////////////////////////////////////////////////////////
// Service MainRequestsObserver 
//-------------------------------------------------------------------------
// Watch for all HTTP requests
// in order to replace cookies going out with cookies from our database
// and to put cookies going in into our database
//
// And in the same time, overload DOM "cookie" attribute in all documents which use a specific profile.
const MainRequestsObserver = {};
const nsILoadContext = Components.interfaces.nsILoadContext;
MainRequestsObserver.findWindowForRequest = function(channel) {
  
  // First, we need to find loadContext for this request
  // channel is an nsIHttpChannel which inherits from nsIChannel then from nsIRequest :
  // http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsIChannel.idl#60
  // \--> notificationCallbacks
  // http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsIRequest.idl#50
  // \--> loadGroup
  
  var loadContext;
  try {
    loadContext = channel.notificationCallbacks.getInterface(nsILoadContext);
  } catch (ex) {
    try {
      loadContext = channel.loadGroup.notificationCallbacks.getInterface(nsILoadContext);
    } catch (ex) {
      loadContext = null;
    }
  }
  
  // Some firefox internal request doesn't have loadGroup : 
  // - safebrowsing requests
  // - search suggest queries
  // - ssl certificate verification
  // ...
  
  // But for web content, it appears that only this case doesn't have loadGroup :
  // - CSS Icons explicitely lets loadGroup empty : 
  // http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsImageFrame.cpp#1653
  
  if (!loadContext) {
    //log("FindBrowser","Unable to find load context for "+channel.URI.spec+"\n");
    return null;
  }
  
  // Then we just have to retrieve associated window from the loadContext
  // http://mxr.mozilla.org/mozilla-central/source/docshell/base/nsILoadContext.idl
  
  var win;
  try {
    win = loadContext.associatedWindow;
  } catch(e) {}
  if (!win) {
    //log("FindBrowser","Unable to find associated window for "+channel.URI.spec+"\n");
    return null;
  }
  return win;
}

MainRequestsObserver.findBrowserForContentWindow = function (win) {
  // Now get the top xul ChromeWindow object with some QueryInterface magic
  // (all chrome requests fail here)
  var browserWindow;
  try {
    browserWindow = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                   .getInterface(Components.interfaces.nsIWebNavigation)
                   .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
                   .rootTreeItem
                   .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                   .getInterface(Components.interfaces.nsIDOMWindow).wrappedJSObject; 
  } catch(e) {}
  
  if (!browserWindow) {
    //log("FindBrowser","Unable to find top xul window "+channel.URI.spec+"\n");
    return null;
  }
  
  // Need to iterate above windows to go through iframes up to the top content window
  while( win!=win.top ) {
    win = win.top;
  }
  
  // And finally, find the matching tab
  var browser;
  if (browserWindow.gBrowser)
    browser = browserWindow.gBrowser.getBrowserForDocument(win.document);
  
  // Hack for Yoono Desktop
  if (browserWindow && browserWindow.ynWebPanel)
    browser = browserWindow.ynWebPanel.getBrowser();
  
  if (!browser) {
    //log("FindBrowser","Unable to find browser "+channel.URI.spec+"\n");
    return null;
  }
  
  return browser;
}

MainRequestsObserver.bindDOMCookieAttribute = function(browser, win) {
  if (!browser.__cookies) return;
  
  if (win.___cookiesHooked) return;
  win.___cookiesHooked = true;
  
  if (win.document.location.href=="about:blank") {
    win.addEventListener("XBLInit",function () {
      win.removeEventListener("XBLInit",arguments.callee,true);
      win.___cookiesHooked = false;
      MainRequestsObserver.bindDOMCookieAttribute(browser,win);
    },true);
  }
  
  //Components.utils.reportError("bindDOM : "+win.document.location.href+" -- "+win.XBLInited);
  
  var doc = win.document;
  
  doc.wrappedJSObject.__defineSetter__("cookie", function(v) {
    try {
      if (!browser.__cookies) return doc.cookie=v;
      //log("JSCookie","SET from "+doc.location+"\n"+v);
      browser.__cookies.setCookieStringFromJS(doc.location, v?v:"");
    } catch(e) {
      Components.utils.reportError(e);
    }
  });
  doc.wrappedJSObject.__defineGetter__("cookie", function() {
    try {
      if (!browser.__cookies) return doc.cookie;
      var s = browser.__cookies.getCookieStringFromJS(doc.location);
      //log("JSCookie","GET from "+doc.location+"\n"+s);
      return s;
    } catch(e) {
      //Components.utils.reportError(e);
    }
  });
}

MainRequestsObserver.observe = function (channel, aTopic, aData) {
try {
  if (aTopic == 'http-on-modify-request') {
    
    channel.QueryInterface(Components.interfaces.nsIHttpChannel);
    
    if (channel.loadFlags & (1 << 42) ) {
      // Set by Ajax.js, avoid the use of cookies for this request
      try {
        if (channel.getRequestHeader("Cookie"))
          channel.setRequestHeader("Cookie","",false);
      } catch(e) {} // getRequestHeader throw if there is no such header
    }
    
    var win = MainRequestsObserver.findWindowForRequest(channel);
    if (!win) return;
    var browser = MainRequestsObserver.findBrowserForContentWindow(win);
    if (!browser) return;
      
    if (!browser.__cookies && !browser.__cookiesChecked) {
      
      browser.__cookiesChecked = true; // try to restore cookies DB only once
      
      // First try to retrieve a cookie session id from session store 
      if (CookiesService._listener) {
        var session_id = CookiesService._listener.getBrowserSessionId(browser);
        if (session_id) {
          CookiesService.attachSessionToBrowser(browser, session_id);
        }
      }
      
      // Then try to find if the current document is opened by another one
      // typically: javascript window.open calls!
      var openerWindow = browser.contentWindow.opener;
      var openerBrowser = MainRequestsObserver.findBrowserForContentWindow(openerWindow);
      if (openerBrowser && openerBrowser.__cookies) {
        //Components.utils.reportError("sessionId from opener : "+openerBrowser.__cookies._sessionId);
        browser.__cookies = openerBrowser.__cookies;
        CookiesService._listener.onBrowserAttached(browser, browser.__cookies._sessionId);
      }
      
    }
    
    if (!browser.__cookies) {
      return;
    }
    
    MainRequestsObserver.bindDOMCookieAttribute(browser, win);
    
    // Get cookies from our DB and replace HTTP Cookie header
    var cookiesHeader = browser.__cookies.getCookieStringFromHttp(channel.URI);
    channel.setRequestHeader("Cookie",cookiesHeader,false);
    
    //log("HTTP","SEND cookies : "+channel.URI.spec+"\n<<"+cookiesHeader+">>");
    
  } else if (aTopic == 'http-on-examine-response') {
    
    channel.QueryInterface(Components.interfaces.nsIHttpChannel);
    
    if (channel.loadFlags & (1 << 42) ) {
      try {
        if (channel.getResponseHeader("Set-Cookie"))
          channel.setResponseHeader("Set-Cookie", "", false);
      } catch(e) {} // getResponseHeader throw if there is no such header
    }
    
    var win = MainRequestsObserver.findWindowForRequest(channel);
    if (!win) return;
    var browser = MainRequestsObserver.findBrowserForContentWindow(win);
    if (!browser) return;
    
    if (!browser.__cookies) {
      return;
    }
    
    // Get Set-Cookie header and update our cookie database
    var cookiesHeader;
    try {
      cookiesHeader = channel.getResponseHeader("Set-Cookie");
    } catch(e) { cookiesHeader = null; }
    if (!cookiesHeader) return;
    
    //log("HTTP","RECEIVE cookies : "+channel.URI.spec+"\n<<"+cookiesHeader+">>");
    
    var dateHeader;
    try {
      dateHeader = channel.getResponseHeader("Date");
    } catch(e){}
    
    browser.__cookies.setCookieStringFromHttp(dateHeader, cookiesHeader, channel.URI);
    
    // Reset cookie header in order to avoid mozilla to take account of these cookies
    channel.setResponseHeader("Set-Cookie", "", false);
    
    SessionObserver.onSessionUsed(browser, channel.URI.spec);
    
  }
} catch(e) {Components.utils.reportError(e);}
}
MainRequestsObserver.start = function () {
  var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
  observerService.addObserver(this, "http-on-modify-request", false);
  observerService.addObserver(this, "http-on-examine-response", false);
}
MainRequestsObserver.stop = function () {
  var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
  observerService.removeObserver(this, "http-on-modify-request", false);
  observerService.removeObserver(this, "http-on-examine-response", false);
}



const CookiesProgressListener = {};
 
CookiesProgressListener.onLocationChange = function(webProgress, request, location) {
  //Components.utils.reportError("location change : "+(request?request.name:"")+" --- "+(location?location.href:""));
  var win = webProgress.DOMWindow;
  if (!win) return;
  var browser = MainRequestsObserver.findBrowserForContentWindow(win);
  if (!browser) return;
  MainRequestsObserver.bindDOMCookieAttribute(browser,win);
}

CookiesProgressListener.onProgressChange = function(webProgress, request, curSelfProgress) {
  //Components.utils.reportError("progress change : "+(request?request.name:"")+" --- "+curSelfProgress);
}
CookiesProgressListener.onSecurityChange = function(webProgress, request, state) {
  //Components.utils.reportError("security change : "+(request?request.name:"")+" --- "+state);
}

CookiesProgressListener.onStateChange = function(webProgress, request, stateFlags, status) {
  /*
  Components.utils.reportError("state change : "+(request?request.name:"")+" --- "+stateFlags);
  Components.utils.reportError("START="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_START));
  Components.utils.reportError("REDIRECTING="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_REDIRECTING));
  Components.utils.reportError("STATE_TRANSFERRING="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_TRANSFERRING));
  Components.utils.reportError("STATE_NEGOTIATING="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_NEGOTIATING));
  Components.utils.reportError("STATE_STOP="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_STOP));
  Components.utils.reportError("STATE_IS_REQUEST="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_IS_REQUEST));
  Components.utils.reportError("STATE_IS_DOCUMENT="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_IS_DOCUMENT));
  Components.utils.reportError("STATE_IS_NETWORK="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_IS_NETWORK));
  Components.utils.reportError("STATE_IS_WINDOW="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_IS_WINDOW));
  
  if(stateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) {
    
  }
  */
}

CookiesProgressListener.onStatusChange = function(webProgress, request, status, message) {
  //Components.utils.reportError("status change : "+(request?request.name:"")+" --- "+status+" - "+message);
}

CookiesProgressListener.QueryInterface = function(aIID) { 
  if (aIID.equals(Components.interfaces.nsIWebProgressListener) || 
     aIID.equals(Components.interfaces.nsISupportsWeakReference) || 
     aIID.equals(Components.interfaces.nsIObserver) || 
     aIID.equals(Components.interfaces.nsISupports)) 
    return this; 
  throw Components.results.NS_NOINTERFACE; 
}

CookiesProgressListener.addProgressListener = function (prog) {
  prog.addProgressListener(CookiesProgressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
}

CookiesProgressListener.removeProgressListener = function (prog) {
  prog.removeProgressListener(CookiesProgressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
}



///////////////////////////////////////////////////////////////////////////
// Service FirefoxWindowsWatcher 
//-------------------------------------------------------------------------
// Utils for Firefox,
// Overload functions which open new tab or window
//
var FirefoxWindowsWatcher = {};
FirefoxWindowsWatcher.QueryInterface = function(aIID) { 
  if (aIID.equals(Components.interfaces.nsISupportsWeakReference) || 
     aIID.equals(Components.interfaces.nsIObserver) || 
     aIID.equals(Components.interfaces.nsISupports)) 
    return this; 
  throw Components.results.NS_NOINTERFACE; 
}

FirefoxWindowsWatcher._onWindowLoad = function (win) {
  CookiesProgressListener.addProgressListener(win.gBrowser);
  
  win.addEventListener("TabClose",function (event) {
    var browser = event.originalTarget.linkedBrowser;
    if (!browser) return;
    CookiesService.onBrowserClosed(browser);
  }, false);
  
  // /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ /!\
  // /!\ A lot of extension use a really bad practice /!\
  //    win.methodToOverload = eval(win.methodToOverload.toSource().replace(...code..., ...a lot of code)) 
  // Tab mix plus, tree style tab, ...
  // The main problem is that it brokes the scope of our function
  // so we need to take care to never use local scope variables
  // Here we must not use : previous, win, previousOpenNewTabWith, etc.
  
  // Watch for website inner link clicks
  var Ci = Components.interfaces;
  var previous = win.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow;
  win.QueryInterface(Components.interfaces.nsIDOMChromeWindow).browserDOMWindow = {
    previous : previous,
    MainRequestsObserver : MainRequestsObserver,
    openURI: function (aURI, aOpener, aWhere, aContext) {
      var newWindow = this.previous.openURI(aURI, aOpener, aWhere, aContext);
      try {
      var openerBrowser;
      if (aOpener)
        openerBrowser = this.MainRequestsObserver.findBrowserForContentWindow(aOpener);
      if (openerBrowser && openerBrowser.__cookies) {
        var newBrowser = this.MainRequestsObserver.findBrowserForContentWindow(newWindow);
        if (newBrowser) {
          newBrowser.__cookies = openerBrowser.__cookies;
          //var reloadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
          //newBrowser.reload(reloadFlags);
          CookiesService._listener.onBrowserAttached(newBrowser, newBrowser.__cookies._sessionId);
        }
      }
      }catch(e){
        Components.utils.reportError(e);
      }
      return newWindow;
    },
    isTabContentWindow: function (aWindow) {
      return this.previous.isTabContentWindow(aWindow);
    }
  };
  
  // Watch for context menu->open in tab
  //var previousOpenNewTabWith = win.openNewTabWith;
  win.previousOpenNewTabWith = win.openNewTabWith;
  win.openNewTabWith = function (aURL, aDocument, aPostData, aEvent, aAllowThirdPartyFixup, aReferrer) {
    //Components.utils.reportError("openNewTabWith");
    var openerBrowser;
    var newTab;
    if (aDocument && aDocument.defaultView)
      openerBrowser = this.MainRequestsObserver.findBrowserForContentWindow(aDocument.defaultView);
    if (openerBrowser && openerBrowser.__cookies) {
      newTab = this.previousOpenNewTabWith.call(this, "about:blank", aDocument, null, null, null, null);
      var newBrowser;
      if (newTab && newTab.linkedBrowser)
        newBrowser = newTab.linkedBrowser;
      if (newBrowser) {
        newBrowser.__cookies = openerBrowser.__cookies;
        CookiesService._listener.onBrowserAttached(newBrowser, newBrowser.__cookies._sessionId);
        var charsetArg = null;
        var wintype = newBrowser.ownerDocument.documentElement.getAttribute("windowtype");
        if (wintype == "navigator:browser")
          charsetArg = "charset=" + newBrowser.ownerDocument.defaultView.content.document.characterSet;
        
        var referrerURI = aDocument ? aDocument.documentURIObject : aReferrer;
        if (aPostData === undefined)
          aPostData = null;
        var flags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
        if (aAllowThirdPartyFixup) {
          flags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
        }
        newBrowser._originalLoadURIWithFlags(aURL, flags, referrerURI, charsetArg, aPostData);
      }
    } else {
      newTab = this.previousOpenNewTabWith.call(this, aURL, aDocument, aPostData, aEvent, aAllowThirdPartyFixup, aReferrer);
    }
    return newTab;
  };
  
  // Watch for context menu->open in window
  //var previousOpenNewWindowWith = win.openNewWindowWith;
  win.previousOpenNewWindowWith = win.openNewWindowWith;
  win.MainRequestsObserver = MainRequestsObserver;
  win.openNewWindowWith = function (aURL, aDocument, aPostData, aAllowThirdPartyFixup, aReferrer) {
    var newWindow;
    var openerBrowser;
    if (aDocument && aDocument.defaultView)
      openerBrowser = this.MainRequestsObserver.findBrowserForContentWindow(aDocument.defaultView);
    if (openerBrowser && openerBrowser.__cookies) {
      newWindow = this.previousOpenNewWindowWith.call(this, "about:blank", aDocument, null, null, null);
      newWindow.addEventListener("load",function () {
        newWindow.removeEventListener("load",arguments.callee,false);
        var newBrowser;
        if (newWindow && newWindow.gBrowser && newWindow.gBrowser.mCurrentBrowser)
          newBrowser = newWindow.gBrowser.mCurrentBrowser;
        
        if (newBrowser) {
          newBrowser.__cookies = openerBrowser.__cookies;
          CookiesService._listener.onBrowserAttached(newBrowser, newBrowser.__cookies._sessionId);
          var charsetArg = null;
          var wintype = newBrowser.ownerDocument.documentElement.getAttribute("windowtype");
          if (wintype == "navigator:browser")
            charsetArg = "charset=" + newBrowser.ownerDocument.defaultView.content.document.characterSet;
          
          var referrerURI = aDocument ? aDocument.documentURIObject : aReferrer;
          if (aPostData === undefined)
            aPostData = null;
          var flags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
          if (aAllowThirdPartyFixup) {
            flags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
          }

          newBrowser._originalLoadURIWithFlags(aURL, flags, referrerURI, charsetArg, aPostData);
        }
      }, false);
    } else {
      newWindow = this.previousOpenNewWindowWith.call(this, aURL, aDocument, aPostData, aAllowThirdPartyFixup, aReferrer);
    }
    return newWindow;
  };
}

FirefoxWindowsWatcher.observe = function (aSubject, aTopic, aData) {
  if (aTopic == "domwindowopened") {
    var win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
    if (!win.gBrowser) return;
    var type = win.document.documentElement.getAttribute("windowtype");
    if (!type) {
      win.addEventListener("load",function () {
        win.removeEventListener("load", arguments.callee, false);
        if (win.document.documentElement.getAttribute("windowtype") != "navigator:browser") return;
        FirefoxWindowsWatcher._onWindowLoad(win);
      }, false);
    } else if (type == "navigator:browser") {
      FirefoxWindowsWatcher._onWindowLoad(win);
    }
  }
  else if (aTopic == "domwindowclosed") {
    var win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
    if (win.document.documentElement.getAttribute("windowtype") != "navigator:browser") return;
    if (!win.gBrowser) return;
    CookiesProgressListener.removeProgressListener(win.gBrowser);
    CookiesStorage.closeDB();
    
  }
}

FirefoxWindowsWatcher.start = function () {
  var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
               .getService(Components.interfaces.nsIWindowWatcher);
  var enumerator = ww.getWindowEnumerator();
  while (enumerator.hasMoreElements()) {
    FirefoxWindowsWatcher.observe(enumerator.getNext(),"domwindowopened",null);
  }
  ww.registerNotification(this);
}

FirefoxWindowsWatcher.stop = function () {
  var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
               .getService(Components.interfaces.nsIWindowWatcher);
  ww.unregisterNotification(this);
  var enumerator = ww.getWindowEnumerator();
  while (enumerator.hasMoreElements()) {
    FirefoxWindowsWatcher.observe(enumerator.getNext(),"domwindowclosed",null);
  }
}


const SessionObserver = {};

SessionObserver._stateCache = {};

SessionObserver.onSessionUsed = function (browser, url) {
  //Components.utils.reportError("onSessionUsed : "+url);
  var sessionId = browser.__cookies._sessionId;
  var state = this._stateCache[sessionId];
  if (state) {
    // Force a state refresh when we load signin/signout urls
    if (
        url.indexOf("twitter.com/sessions")!=-1 
        || url.indexOf("twitter.com/account/logout")!=-1
        || url.indexOf("www.facebook.com/logout.php")!=-1
        || url.indexOf("facebook.com/login.php?")!=-1
        || url.indexOf("myspace.com/index.cfm?fuseaction=signout")!=-1
        || url.indexOf("myspace.com/index.cfm?fuseaction=login.process")!=-1
       ) {
      state.lastCheckTime = 0;
      //Components.utils.reportError("got a session change : "+url);
    }
    // Check state every hour
    if (state.value=="pending" || new Date().getTime()-state.lastCheckTime<60*60*1000) return;
  }
  
  this._stateCache[sessionId] = {
    lastCheckTime: new Date().getTime(),
    value: "pending"
  };
  
  var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  timer.initWithCallback({
      notify: function(timer) {
        try {
          CookiesStorage.getSessionInfo(sessionId, 
            function (session) {
              if (!session || !session.networkId || !session.name) return;
              if (!SessionObserver["checkNetwork_"+session.networkId]) {
                //Components.utils.reportError("SESSION-CHECK: no function for "+session.networkId);
                return;
              }
              SessionObserver["checkNetwork_"+session.networkId](browser, session);
            });
        } catch(e) {
          Components.utils.reportError(e);
        }
      }
    }, 3000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
  
}

SessionObserver.updateState = function (browser, session, state) {
  this._stateCache[session.id] = state;
  this._stateCache[session.id].lastCheckTime = new Date().getTime();
  CookiesService.onSessionState(browser, session, state);
}

SessionObserver.genericCheckNetwork = function (browser, session, url, regexp) {
  var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]  
                        .createInstance(Components.interfaces.nsIXMLHttpRequest);
  
  CookiesService.getDatabaseForSession(session.id, 
    function (cookiesDb) {
      var IOService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
      var uri = IOService.newURI(url, null, null);
      var cookies = cookiesDb.getCookieStringFromHttp(uri);
      xmlhttp.open("GET",url,true);
      xmlhttp.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; 
      xmlhttp.channel.loadFlags |= Components.interfaces.nsIChannel.LOAD_ANONYMOUS;
      xmlhttp.setRequestHeader("Cookie",cookies);
      xmlhttp.onload = function (evt) {
        if (xmlhttp.readyState != 4) return;
        if (xmlhttp.status != 200) return;
        var text = xmlhttp.responseText;
        var state;
        if (text.indexOf(session.name)!=-1) {
          // Don't be too smart on this
          // If the username is displayed on the home page,
          // consider this network has correctly signed in!
          state = { value: "ok" };
        } else {
          var m = text.match(regexp);
          if (m) {
            state = { value: "another-identity", identity: m[1] };
          } else {
            state = { value: "signed-out" };
          }
        }
        SessionObserver.updateState(browser, session, state);
      }
      
      xmlhttp.send(null);
    });
}

SessionObserver.checkNetwork_1 = function (browser, session) {
  // <meta content="y" name="session-loggedin" />
  // <meta content="34649987" name="session-userid" />
  // <meta content="yoonotest2" name="session-user-screen_name" />
  SessionObserver.genericCheckNetwork(
      browser,
      session, 
      "http://twitter.com", 
      /<meta content="([^"]+)" name="session-user-screen_name" \/>/
    );
}

SessionObserver.checkNetwork_2 = function (browser, session) {
  SessionObserver.genericCheckNetwork(
      browser,
      session, 
      "http://www.facebook.com", 
      // /id="fb_menu_account"><a href="[^"]+" class="fb_menu_link">([^<]+)<\/a>/
         / id="navAccountName">([^<]+)<\/a>/
    );
}

SessionObserver.checkNetwork_3 = function (browser, session) {
  //<a class="" href="http://profile.myspace.com/index.cfm?fuseaction=user.viewprofile&amp;friendid=422343183" id="nav2500000" onmouseover="msglobalnav.toggle('2500000',true);" onmouseout="msglobalnav.toggle('2500000',false);">Josh Yoono&nbsp;<small>?</small>
  SessionObserver.genericCheckNetwork(
      browser,
      session, 
      "http://www.myspace.com", 
      /name: "([^"]+)",/
    );
}


////////////////////////////////////////////////////////////////////////////////
// JS Module API
// 
const CookiesService = {
  _listener : null,
  
  FirefoxWindowsWatcher : FirefoxWindowsWatcher,
  
  init : function (listener) {
    try {
      if (typeof listener!="object") throw "CookiesService.init: listener is mandatory";
      if (typeof listener.getBrowserSessionId!="function") throw "CookiesService.init: listener need to have a getBrowserSessionId function";
      if (typeof listener.onBrowserAttached!="function") throw "CookiesService.init: listener need to have a onBrowserAttached function";
      this._listener = listener;
      
      MainRequestsObserver.start();
      
      var sss = Components.classes["@mozilla.org/content/style-sheet-service;1"]
                    .getService(Components.interfaces.nsIStyleSheetService);
      var ios = Components.classes["@mozilla.org/network/io-service;1"]
                          .getService(Components.interfaces.nsIIOService);
      var uri = ios.newURI("resource://yoono/xbl.css", null, null);
      sss.loadAndRegisterSheet(uri, sss.USER_SHEET);

    } catch(e) {
      Components.utils.reportError(e);
    }
  },
  
  // Register a new <browser> object
  // (Allow to catch JS getter/setter of cookies)
  registerBrowser : function (browser) {
    var req = browser.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
    var prog = req.getInterface(Components.interfaces.nsIWebProgress);
    CookiesProgressListener.addProgressListener(prog);
  },
  unregisterBrowser : function (browser) {
    var req = browser.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
    var prog = req.getInterface(Components.interfaces.nsIWebProgress);
    CookiesProgressListener.removeProgressListener(prog);
  },
  
  renameSession : function (src_session_id, dst_session_id) {
    CookiesStorage.renameSession(src_session_id, dst_session_id);
  },
  
  setSessionInfo : function (session_id, user_name, user_avatar, user_network_name, user_network_id, homepage) {
    CookiesStorage.setSessionInfo(session_id, user_name, user_avatar, user_network_name, user_network_id, homepage);
  },
  
  getSessionInfo : function (browser, callback) {
    if (browser.__cookies && browser.__cookies._sessionId)
      CookiesStorage.getSessionInfo(browser.__cookies._sessionId, function (session) {
        if (!session) {
          CookiesService.detachSessionFromBrowser(browser);
        }
        callback(session);
      });
    else if (typeof callback=="function")
      callback();
  },
  
  deleteSessionInfo : function (session_id) {
    CookiesStorage.deleteSession(session_id);
  },
  
  getSessionId : function (browser) {
    if (browser && browser.__cookies && browser.__cookies._sessionId!="undefined")
      return browser.__cookies._sessionId;
    return null;
  },
  
  getAllSessions : function (callback) {
    CookiesStorage.getAllSessions(callback);
  },
  
  _cookiesDBCache : {},
  getDatabaseForSession : function (session_id, callback) {
    if (this._cookiesDBCache[session_id] && this._cookiesDBCache[session_id].get()) {
      callback(this._cookiesDBCache[session_id].get());
    } else {
      //Components.utils.reportError("New cookies database instance : "+session_id);
      new CookiesDB(session_id, function (db) {
        CookiesService._cookiesDBCache[session_id] = Components.utils.getWeakReference(db);
        callback(db);
      });
    }
  },
  
  attachSessionToBrowser : function (browser, session_id, onReadyCallback) {
    this.getDatabaseForSession(session_id,
      function (db) {
        browser.__cookies = db;
        CookiesService._listener.onBrowserAttached(browser, session_id);
        if (typeof onReadyCallback=="function")
          onReadyCallback();
      });
  },
  
  detachSessionFromBrowser : function (browser) {
    browser.__cookies = null;
    CookiesService._listener.onBrowserDetached(browser);
  },
  
  onBrowserClosed : function (browser) {
    delete browser.__cookies;
  },
  
  onSessionState : function (browser, session, state) {
    CookiesService._listener.onBrowserStateChange(browser, session, state);
  },
  
  addCustomProfile : function (name, home, avatar, callback) {
    
    var id = "custom_"+new Date().getTime();
    CookiesStorage.setSessionInfo(id, name, avatar, null, null, home);
    if (typeof callback=="function")
      callback(id);
    
  },
  
  getSigninURL : function (session) {
    var id = session.networkId;
    if (id==1)
      return "http://twitter.com/login";
    else if (id==2) 
      return "http://facebook.com";
    else if (id==3) 
      return "http://myspace.com";
    else if (id==4) 
      return "http://flickr.com";
    else if (id==5) 
      return "http://friendfeed.com";
    else if (id==6) 
      return "http://imeem.com";
    else if (id==7) 
      return "http://viadeo.com";
    else if (id==8) 
      return "http://yoono.com";
    else if (id==9) 
      return "http://www.linkedin.com/home";
    else if (id==10) 
      return "http://products.aim.com/";
    else if (id==11) 
      return "http://www.google.com/talk/";
    else if (id==12) 
      return "http://home.live.com/";
    else if (id==13) 
      return "http://messenger.yahoo.com/";
    return session.homepage;
  },

  getSignoutURL : function (session) {
    var id = session.networkId;
    if (id==1)
      return "http://twitter.com/logout";
    else if (id==2) 
      return "http://www.facebook.com/logout.php?confirm=1";
    else if (id==3) 
      return "http://www.myspace.com/index.cfm?fuseaction=signout";
    else if (id==4) 
      return "http://flickr.com";
    else if (id==5) 
      return "http://friendfeed.com";
    else if (id==6) 
      return "http://imeem.com";
    else if (id==7) 
      return "http://viadeo.com";
    else if (id==8) 
      return "http://yoono.com";
    else if (id==9) 
      return "http://www.linkedin.com/home";
    else if (id==10) 
      return "http://products.aim.com/";
    else if (id==11) 
      return "http://www.google.com/talk/";
    else if (id==12) 
      return "http://home.live.com/";
    else if (id==13) 
      return "http://messenger.yahoo.com/";
    return session.homepage;
  }
  
};
