aboutsummaryrefslogblamecommitdiffstats
path: root/etherpad/src/main.js
blob: 53671cfd33d66f8b542ff2dd92673ab7e495202c (plain) (tree)

















































                                                                           


















                                              

                                 























                                                                                    

                                    






                                             

                                     
























































































































































                                                                                    

                                                                    







                                                                              
                                      





                                       
                          
                                                                  












                                                                        
                                                                                     



















                                                                          

                                           
                                
                                          
                                 











































                                                                   

                                    

                                                                 


                                                                










                                                                 

     


                                            
















                                                                                         
                             






                                                          
                      











                                                                 
                                        










                              
/**
 * Copyright 2009 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import("dispatch.{Dispatcher,PrefixMatcher,DirMatcher,forward}");
import("exceptionutils");
import("fastJSON");
import("jsutils.*");
import("sqlbase.sqlcommon");
import("stringutils");
import("sessions.{readLatestSessionsFromDisk,writeSessionsToDisk}");

import("etherpad.billing.team_billing");
import("etherpad.globals.*");
import("etherpad.log.{logRequest,logException}");
import("etherpad.log");
import("etherpad.utils.*");
import("etherpad.statistics.statistics");
import("etherpad.sessions");
import("etherpad.db_migrations.migration_runner");
import("etherpad.importexport.importexport");
import("etherpad.legacy_urls");

import("etherpad.control.aboutcontrol");
import("etherpad.control.admincontrol");
import("etherpad.control.blogcontrol");
import("etherpad.control.connection_diagnostics_control");
import("etherpad.control.global_pro_account_control");
import("etherpad.control.historycontrol");
import("etherpad.control.loadtestcontrol");
import("etherpad.control.maincontrol");
import("etherpad.control.pad.pad_control");
import("etherpad.control.pne_manual_control");
import("etherpad.control.pne_tracker_control");
import("etherpad.control.pro.admin.license_manager_control");
import("etherpad.control.pro_beta_control");
import("etherpad.control.pro.pro_main_control");
import("etherpad.control.pro_signup_control");
import("etherpad.control.scriptcontrol");
import("etherpad.control.static_control");
import("etherpad.control.store.storecontrol");
import("etherpad.control.testcontrol");

import("etherpad.pne.pne_utils");
import("etherpad.pro.pro_pad_editors");
import("etherpad.pro.pro_utils");
import("etherpad.pro.pro_config");

import("etherpad.collab.collabroom_server");
import("etherpad.collab.collab_server");
import("etherpad.collab.readonly_server");
import("etherpad.collab.genimg");
import("etherpad.pad.model");
import("etherpad.pad.dbwriter");
import("etherpad.pad.pad_migrations");
import("etherpad.pad.noprowatcher");

import("etherpad.admin.plugins");

jimport("java.lang.System.out.println");

serverhandlers.startupHandler = function() {
  // Order matters.
  checkSystemRequirements();

  var sp = function(k) { return appjet.config['etherpad.SQL_'+k] || null; };
  sqlcommon.init(sp('JDBC_DRIVER'), sp('JDBC_URL'), sp('USERNAME'), sp('PASSWORD'));

  log.onStartup();
  statistics.onStartup();
  migration_runner.onStartup();
  pad_migrations.onStartup();
  model.onStartup();
  collab_server.onStartup();
  pad_control.onStartup();
  dbwriter.onStartup();
  blogcontrol.onStartup();
  importexport.onStartup();
  pro_pad_editors.onStartup();
  noprowatcher.onStartup();
  team_billing.onStartup();
  collabroom_server.onStartup();
  readLatestSessionsFromDisk();

  plugins.callHook('serverStartup');
};

serverhandlers.resetHandler = function() {
  statistics.onReset();
}

serverhandlers.shutdownHandler = function() {
  plugins.callHook('serverShutdown');

  appjet.cache.shutdownHandlerIsRunning = true;

  log.callCatchingExceptions(writeSessionsToDisk);
  log.callCatchingExceptions(dbwriter.onShutdown);
  log.callCatchingExceptions(sqlcommon.onShutdown);
  log.callCatchingExceptions(pro_pad_editors.onShutdown);
};

//----------------------------------------------------------------
// request handling
//----------------------------------------------------------------

serverhandlers.requestHandler = function() {
  checkRequestIsWellFormed();
  sessions.preRequestCookieCheck();
  checkHost();
  checkHTTPS();
  handlePath();
};

// In theory, this should never get called.
// Exceptions that are thrown in frontend etherpad javascript should
//   always be caught and treated specially.
// If serverhandlers.errorHandler gets called, then it's a bug in the frontend.
serverhandlers.errorHandler = function(ex) {
  logException(ex);
  response.setStatusCode(500);
  if (request.isDefined) {
    render500(ex);
  } else {
    if (! isProduction()) {
      response.write(exceptionutils.getStackTracePlain(ex));
    } else {
      response.write(ex.getMessage());
    }
  }
};

serverhandlers.postRequestHandler = function() {
  logRequest();
};

//----------------------------------------------------------------
// Scheduled tasks
//----------------------------------------------------------------

serverhandlers.tasks.writePad = function(globalPadId) {
  dbwriter.taskWritePad(globalPadId);
};
serverhandlers.tasks.flushPad = function(globalPadId, reason) {
  dbwriter.taskFlushPad(globalPadId, reason);
};
serverhandlers.tasks.checkForStalePads = function() {
  dbwriter.taskCheckForStalePads();
};
serverhandlers.tasks.statisticsDailyUpdate = function() {
  //statistics.dailyUpdate();
};
serverhandlers.tasks.doSlowFileConversion = function(from, to, bytes, cont) {
  return importexport.doSlowFileConversion(from, to, bytes, cont);
};
serverhandlers.tasks.proPadmetaFlushEdits = function(domainId) {
  pro_pad_editors.flushEditsNow(domainId);
};
serverhandlers.tasks.noProWatcherCheckPad = function(globalPadId) {
  noprowatcher.checkPad(globalPadId);
};
serverhandlers.tasks.collabRoomDisconnectSocket = function(connectionId, socketId) {
  collabroom_server.disconnectDefunctSocket(connectionId, socketId);
};

//----------------------------------------------------------------
// cometHandler()
//----------------------------------------------------------------

serverhandlers.cometHandler = function(op, id, data) {
  checkRequestIsWellFormed();
  if (!data) {
    // connect/disconnect message, notify all comet receivers
    collabroom_server.handleComet(op, id, data);
    return;
  }

  while (data[data.length-1] == '\u0000') {
    data = data.substr(0, data.length-1);
  }

  var wrapper;
  try {
    wrapper = fastJSON.parse(data);
  } catch (err) {
    try {
      // after removing \u0000 might have to add '}'
      wrapper = fastJSON.parse(data+'}');
    }
    catch (err) {
      log.custom("invalid-json", {data: data});
      throw err;
    }
  }
  if(wrapper.type == "COLLABROOM") {
    collabroom_server.handleComet(op, id, wrapper.data);
  } else {
    //println("incorrectly wrapped data: " + wrapper['type']);
  }
};

//----------------------------------------------------------------
// sarsHandler()
//----------------------------------------------------------------

serverhandlers.sarsHandler = function(str) {
  str = String(str);
  println("sarsHandler: parsing JSON string (length="+str.length+")");
  var message = fastJSON.parse(str);
  println("dispatching SARS message of type "+message.type);
  if (message.type == "migrateDiagnosticRecords") {
    pad_control.recordMigratedDiagnosticInfo(message.records);
    return 'OK';
  }
  return 'UNKNOWN_MESSAGE_TYPE';
};

//----------------------------------------------------------------
// checkSystemRequirements()
//----------------------------------------------------------------
function checkSystemRequirements() {
  var jv = Packages.java.lang.System.getProperty("java.version");
  jv = +(String(jv).split(".").slice(0,2).join("."));
  if (jv < 1.6) {
    println("Error: EtherPad requires JVM 1.6 or greater.");
    println("Your version of the JVM is: "+jv);
    println("Aborting...");
    Packages.java.lang.System.exit(1);
  }
}

function checkRequestIsWellFormed() {
  // We require the "host" field to be present.
  // This should always be true, as long as the protocl is HTTP/1.1
  // TODO: check (request.protocol != "HTTP/1.1")
  if (request.isDefined && !request.host) {
    response.setStatusCode(505);
    response.setContentType('text/plain');
    response.write('Protocol not supported.  HTTP/1.1 required.');
    response.stop();
  }
}

//----------------------------------------------------------------
// checkHost()
//----------------------------------------------------------------
function checkHost() {
  var trueRegex = /\s*true\s*/i;
  if (trueRegex.test(appjet.config['etherpad.skipHostnameCheck'])) {
    return;
  }

  if (isPrivateNetworkEdition()) {
    return;
  }

  // we require the domain to either be <superdomain> or a pro domain request.
  if (domainEnabled(request.domain)) {
    return;
  }
  if (pro_utils.isProDomainRequest()) {
    return;
  }

  // redirect to main site
  var newurl = "http://pad.spline.inf.fu-berlin.de/"+request.path;
  if (request.query) { newurl += "?"+request.query; }
  response.redirect(newurl);
}

//----------------------------------------------------------------
// checkHTTPS()
//----------------------------------------------------------------

// Check for HTTPS
function checkHTTPS() {
  /* Open-source note: this function used to check the protocol and make
   * sure that pages that needed to be secure went over HTTPS, and pages
   * that didn't go over HTTP.  However, when we open-sourced the code,
   * we disabled HTTPS because we didn't want to ship the pad.spline.inf.fu-berlin.de
   * private crypto keys. --aiba */
  return;


  if (stringutils.startsWith(request.path, "/static/")) { return; }

  if (sessions.getSession().disableHttps || request.params.disableHttps) {
    sessions.getSession().disableHttps = true;
    println("setting session diableHttps");
    return;
  }

  var _ports = {
    http: appjet.config.listenPort,
    https: appjet.config.listenSecurePort
  };
  var _defaultPorts = {
    http: 80,
    https: 443
  };
  
  // Require HTTPS for the following paths:
  var _requiredHttpsPrefixes = [
    '/ep/admin',      // pro and main site
    '/ep/account',    // pro only
  ];

  var httpsRequired = false;
  _requiredHttpsPrefixes.forEach(function(p) {
    if (stringutils.startsWith(request.path, p)) {
      httpsRequired = true;
    }
  });

  if (isProDomainRequest() && pro_config.getConfig().alwaysHttps) {
    httpsRequired = true;
  }

  if (httpsRequired && !request.isSSL) {
    _redirectToScheme("https");
  }
  if (!httpsRequired && request.isSSL) {
    _redirectToScheme("http");
  }

  function _redirectToScheme(scheme) {
    var url = scheme + "://";
    url += request.host.split(':')[0]; // server

    if (_ports[scheme] != _defaultPorts[scheme]) {
      url += ':'+_ports[scheme];
    }

    url += request.path;
    if (request.query) {
      url += "?"+request.query;
    }
    response.redirect(url);
  }
}

//----------------------------------------------------------------
// dispatching
//----------------------------------------------------------------

function handlePath() {
  // Default.  Can be overridden in case of static files.
  response.neverCache();

  plugins.registerClientHandlerJS();

  // these paths are handled identically on all sites/subdomains.
  var commonDispatcher = new Dispatcher();

  commonDispatcher.addLocations(plugins.callHook('handlePath'));

  commonDispatcher.addLocations([
    ['/favicon.ico', forward(static_control)],
    ['/robots.txt', forward(static_control)],
    ['/crossdomain.xml', forward(static_control)],
    [PrefixMatcher('/static/'), forward(static_control)],
    [PrefixMatcher('/ep/genimg/'), genimg.renderPath],
    [PrefixMatcher('/ep/pad/'), forward(pad_control)],
    [PrefixMatcher('/ep/script/'), forward(scriptcontrol)],
    [/^\/([^\/]+)$/, pad_control.render_pad],
    [DirMatcher('/ep/unit-tests/'), forward(testcontrol)],
    [DirMatcher('/ep/pne-manual/'), forward(pne_manual_control)],
  ]);

  // these paths are main site only
  var mainsiteDispatcher = new Dispatcher();
  mainsiteDispatcher.addLocations([
    ['/', maincontrol.render_main],
    [DirMatcher('/ep/beta-account/'), forward(pro_beta_control)],
    [DirMatcher('/ep/pro-signup/'), forward(pro_signup_control)],
    [DirMatcher('/ep/about/'), forward(aboutcontrol)],
    [DirMatcher('/ep/admin/'), forward(admincontrol)],
    [DirMatcher('/ep/blog/posts/'), blogcontrol.render_post],
    [DirMatcher('/ep/blog/'), forward(blogcontrol)],
    [DirMatcher('/ep/connection-diagnostics/'), forward(connection_diagnostics_control)],
    [DirMatcher('/ep/loadtest/'), forward(loadtestcontrol)],
    [DirMatcher('/ep/tpne/'), forward(pne_tracker_control)],
    [DirMatcher('/ep/pro-account/'), forward(global_pro_account_control)],
    [/^\/ep\/pad\/history\/(\w+)\/(.*)$/, historycontrol.render_history],
    [PrefixMatcher('/ep/pad/slider/'), pad_control.render_slider],
    [DirMatcher('/ep/store/'), forward(storecontrol)],
    [PrefixMatcher('/ep/'), forward(maincontrol)]
  ]);

  // these paths are pro only
  var proDispatcher = new Dispatcher();
  proDispatcher.addLocations([
    ['/', pro_main_control.render_main],
    [PrefixMatcher('/ep/'), forward(pro_main_control)],
  ]);

  // dispatching logic: first try common, then dispatch to
  // main site or pro.

  if (commonDispatcher.dispatch()) {
    return;
  }

  // Check if there is a pro domain associated with this request.
  if (isProDomainRequest()) {
    pro_utils.preDispatchAccountCheck();
    if (proDispatcher.dispatch()) {
      return;
    }
  } else {
    if (mainsiteDispatcher.dispatch()) {
      return;
    }
  }

  if (!isProDomainRequest()) {
    legacy_urls.checkPath();
  }

  render404();
}