<commit_before>(function(doc) {

  log = noop; // noOp, remove this line to enable debugging

  var coordinateSystemForElementFromPoint;

  function main(config) {
    config = config || {};

    coordinateSystemForElementFromPoint = navigator.userAgent.match(/OS [1-4](?:_\d+)+ like Mac/) ? "page" : "client";

    var div = doc.createElement('div');
    var dragDiv = 'draggable' in div;
    var evts = 'ondragstart' in div && 'ondrop' in div;

    var needsPatch = !(dragDiv || evts) || /iPad|iPhone|iPod|Android/.test(navigator.userAgent);
    log((needsPatch ? "" : "not ") + "patching html5 drag drop");

    if(!needsPatch) {
        return;
    }

    if(!config.enableEnterLeave) {
      DragDrop.prototype.synthesizeEnterLeave = noop;
    }

    doc.addEventListener("touchstart", touchstart);
  }

  function DragDrop(event, el) {

    this.dragData = {};
    this.dragDataTypes = [];
    this.dragImage = null;
    this.dragImageTransform = null;
    this.dragImageWebKitTransform = null;
    this.el = el || event.target;

    log("dragstart");

    this.dispatchDragStart();
    this.createDragImage();

    this.listen();

  }

  DragDrop.prototype = {
    listen: function() {
      var move = onEvt(doc, "touchmove", this.move, this);
      var end = onEvt(doc, "touchend", ontouchend, this);
      var cancel = onEvt(doc, "touchcancel", cleanup, this);

      function ontouchend(event) {
        this.dragend(event, event.target);
        cleanup.call(this);
      }
      function cleanup() {
        log("cleanup");
        this.dragDataTypes = [];
        if (this.dragImage !== null) {
          this.dragImage.parentNode.removeChild(this.dragImage);
          this.dragImage = null;
          this.dragImageTransform = null;
          this.dragImageWebKitTransform = null;
        }
        this.el = this.dragData = null;
        return [move, end, cancel].forEach(function(handler) {
          return handler.off();
        });
      }
    },
    move: function(event) {
      var pageXs = [], pageYs = [];
      [].forEach.call(event.changedTouches, function(touch) {
        pageXs.push(touch.pageX);
        pageYs.push(touch.pageY);
      });

      var x = average(pageXs) - (parseInt(this.dragImage.offsetWidth, 10) / 2);
      var y = average(pageYs) - (parseInt(this.dragImage.offsetHeight, 10) / 2);
      this.translateDragImage(x, y);

      this.synthesizeEnterLeave(event);
    },
    // We use translate instead of top/left because of sub-pixel rendering and for the hope of better performance
    // http://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/
    translateDragImage: function(x, y) {
      var translate = " translate(" + x + "px," + y + "px)";

      if (this.dragImageWebKitTransform !== null) {
        this.dragImage.style["-webkit-transform"] = this.dragImageWebKitTransform + translate;
      }
      if (this.dragImageTransform !== null) {
        this.dragImage.style.transform = this.dragImageTransform + translate;
      }
    },
    synthesizeEnterLeave: function(event) {
      var target = elementFromTouchEvent(this.el,event)
      if (target != this.lastEnter) {
        if (this.lastEnter) {
          this.dispatchLeave(event);
        }
        this.lastEnter = target;
        if (this.lastEnter) {
          this.dispatchEnter(event);
        }
      }
      if (this.lastEnter) {
        this.dispatchOver(event);
      }
    },
    dragend: function(event) {

      // we'll dispatch drop if there's a target, then dragEnd.
      // drop comes first http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
      log("dragend");

      if (this.lastEnter) {
        this.dispatchLeave(event);
      }

      var target = elementFromTouchEvent(this.el,event)
      if (target) {
        log("found drop target " + target.tagName);
        this.dispatchDrop(target, event);
      } else {
        log("no drop target");
      }

      var dragendEvt = doc.createEvent("Event");
      dragendEvt.initEvent("dragend", true, true);
      this.el.dispatchEvent(dragendEvt);
    },
    dispatchDrop: function(target, event) {
      var dropEvt = doc.createEvent("Event");
      dropEvt.initEvent("drop", true, true);

      var touch = event.changedTouches[0];
      var x = touch[coordinateSystemForElementFromPoint + 'X'];
      var y = touch[coordinateSystemForElementFromPoint + 'Y'];
      dropEvt.offsetX = x - target.x;
      dropEvt.offsetY = y - target.y;

      dropEvt.dataTransfer = {
        types: this.dragDataTypes,
        getData: function(type) {
          return this.dragData[type];
        }.bind(this)
      };
      dropEvt.preventDefault = function() {
         // https://www.w3.org/Bugs/Public/show_bug.cgi?id=14638 - if we don't cancel it, we'll snap back
      }.bind(this);

      once(doc, "drop", function() {
        log("drop event not canceled");
      },this);

      target.dispatchEvent(dropEvt);
    },
    dispatchEnter: function(event) {

      var enterEvt = doc.createEvent("Event");
      enterEvt.initEvent("dragenter", true, true);
      enterEvt.dataTransfer = {
        types: this.dragDataTypes,
        getData: function(type) {
          return this.dragData[type];
        }.bind(this)
      };

      var touch = event.changedTouches[0];
      enterEvt.pageX = touch.pageX;
      enterEvt.pageY = touch.pageY;

      this.lastEnter.dispatchEvent(enterEvt);
    },
    dispatchOver: function(event) {

      var overEvt = doc.createEvent("Event");
      overEvt.initEvent("dragover", true, true);
      overEvt.dataTransfer = {
        types: this.dragDataTypes,
        getData: function(type) {
          return this.dragData[type];
        }.bind(this)
      };

      var touch = event.changedTouches[0];
      overEvt.pageX = touch.pageX;
      overEvt.pageY = touch.pageY;

      this.lastEnter.dispatchEvent(overEvt);
    },
    dispatchLeave: function(event) {

      var leaveEvt = doc.createEvent("Event");
      leaveEvt.initEvent("dragleave", true, true);
      leaveEvt.dataTransfer = {
        types: this.dragDataTypes,
        getData: function(type) {
          return this.dragData[type];
        }.bind(this)
      };

      var touch = event.changedTouches[0];
      leaveEvt.pageX = touch.pageX;
      leaveEvt.pageY = touch.pageY;

      this.lastEnter.dispatchEvent(leaveEvt);
      this.lastEnter = null;
    },
    dispatchDragStart: function() {
      var evt = doc.createEvent("Event");
      evt.initEvent("dragstart", true, true);
      evt.dataTransfer = {
        setData: function(type, val) {
          this.dragData[type] = val;
          if (this.dragDataTypes.indexOf(type) == -1) {
            this.dragDataTypes[this.dragDataTypes.length] = type;
          }
          return val;
        }.bind(this),
        dropEffect: "move"
      };
      this.el.dispatchEvent(evt);
    },
    createDragImage: function() {
      this.dragImage = this.el.cloneNode(true);

      duplicateStyle(this.el, this.dragImage);

      this.dragImage.style.opacity = "0.5";
      this.dragImage.style.position = "absolute";
      this.dragImage.style.left = "0px";
      this.dragImage.style.top = "0px";
      this.dragImage.style.zIndex = "999999";

      var transform = this.dragImage.style.transform;
      if (typeof transform !== "undefined") {
        this.dragImageTransform = "";
        if (transform != "none") {
          this.dragImageTransform = transform.replace(/translate\(\D*\d+[^,]*,\D*\d+[^,]*\)\s*/g, '');
        }
      }

      var webkitTransform = this.dragImage.style["-webkit-transform"];
      if (typeof webkitTransform !== "undefined") {
        this.dragImageWebKitTransform = "";
        if (webkitTransform != "none") {
          this.dragImageWebKitTransform = webkitTransform.replace(/translate\(\D*\d+[^,]*,\D*\d+[^,]*\)\s*/g, '');
        }
      }

      this.translateDragImage(-9999, -9999);

      doc.body.appendChild(this.dragImage);
    }
  };

  // event listeners
  function touchstart(evt) {
    var el = evt.target;
    do {
      if (el.draggable === true) {
        // If draggable isn't explicitly set for anchors, then simulate a click event.
        // Otherwise plain old vanilla links will stop working.
        // https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events#Handling_clicks
        if (!el.hasAttribute("draggable") && el.tagName.toLowerCase() == "a") {
          var clickEvt = document.createEvent("MouseEvents");
          clickEvt.initMouseEvent("click", true, true, el.ownerDocument.defaultView, 1,
            evt.screenX, evt.screenY, evt.clientX, evt.clientY,
            evt.ctrlKey, evt.altKey, evt.shiftKey, evt.metaKey, 0, null);
          el.dispatchEvent(clickEvt);
          log("Simulating click to anchor");
        }
        evt.preventDefault();
        new DragDrop(evt,el);
      }
    } while((el = el.parentNode) && el !== doc.body);
  }

  // DOM helpers
  function elementFromTouchEvent(el,event) {
    var touch = event.changedTouches[0];
    var target = doc.elementFromPoint(
      touch[coordinateSystemForElementFromPoint + "X"],
      touch[coordinateSystemForElementFromPoint + "Y"]
    );
    return target;
  }

  function onEvt(el, event, handler, context) {
    if(context) {
      handler = handler.bind(context);
    }
    el.addEventListener(event, handler);
    return {
      off: function() {
        return el.removeEventListener(event, handler);
      }
    };
  }

  function once(el, event, handler, context) {
    if(context) {
      handler = handler.bind(context);
    }
    function listener(evt) {
      handler(evt);
      return el.removeEventListener(event,listener);
    }
    return el.addEventListener(event,listener);
  }

  // duplicateStyle expects dstNode to be a clone of srcNode
  function duplicateStyle(srcNode, dstNode) {
    // Is this node an element?
    if (srcNode.nodeType == 1) {
      // Remove any potential conflict attributes
      dstNode.removeAttribute("id");
      dstNode.removeAttribute("class");
      dstNode.removeAttribute("style");
      dstNode.removeAttribute("draggable");

      // Clone the style
      var cs = window.getComputedStyle(srcNode);
      for (var i = 0; i < cs.length; i++) {
        var csName = cs[i];
        dstNode.style.setProperty(csName, cs.getPropertyValue(csName), cs.getPropertyPriority(csName));
      }

      // Pointer events as none makes the drag image transparent to document.elementFromPoint()
      dstNode.style.pointerEvents = "none";
    }

    // Do the same for the children
    if (srcNode.hasChildNodes()) {
      for (var j = 0; j < srcNode.childNodes.length; j++) {
        duplicateStyle(srcNode.childNodes[j], dstNode.childNodes[j]);
      }
    }
  }

  // general helpers
  function log(msg) {
    console.log(msg);
  }

  function average(arr) {
    if (arr.length === 0) return 0;
    return arr.reduce((function(s, v) {
      return v + s;
    }), 0) / arr.length;
  }

  function noop() {}

  main(window.iosDragDropShim);

})(document);
<commit_msg>Add dropEffect 'move' to event.DataTransfer on dispatchDrop<commit_after>(function(doc) {

  log = noop; // noOp, remove this line to enable debugging

  var coordinateSystemForElementFromPoint;

  function main(config) {
    config = config || {};

    coordinateSystemForElementFromPoint = navigator.userAgent.match(/OS [1-4](?:_\d+)+ like Mac/) ? "page" : "client";

    var div = doc.createElement('div');
    var dragDiv = 'draggable' in div;
    var evts = 'ondragstart' in div && 'ondrop' in div;

    var needsPatch = !(dragDiv || evts) || /iPad|iPhone|iPod|Android/.test(navigator.userAgent);
    log((needsPatch ? "" : "not ") + "patching html5 drag drop");

    if(!needsPatch) {
        return;
    }

    if(!config.enableEnterLeave) {
      DragDrop.prototype.synthesizeEnterLeave = noop;
    }

    doc.addEventListener("touchstart", touchstart);
  }

  function DragDrop(event, el) {

    this.dragData = {};
    this.dragDataTypes = [];
    this.dragImage = null;
    this.dragImageTransform = null;
    this.dragImageWebKitTransform = null;
    this.el = el || event.target;

    log("dragstart");

    this.dispatchDragStart();
    this.createDragImage();

    this.listen();

  }

  DragDrop.prototype = {
    listen: function() {
      var move = onEvt(doc, "touchmove", this.move, this);
      var end = onEvt(doc, "touchend", ontouchend, this);
      var cancel = onEvt(doc, "touchcancel", cleanup, this);

      function ontouchend(event) {
        this.dragend(event, event.target);
        cleanup.call(this);
      }
      function cleanup() {
        log("cleanup");
        this.dragDataTypes = [];
        if (this.dragImage !== null) {
          this.dragImage.parentNode.removeChild(this.dragImage);
          this.dragImage = null;
          this.dragImageTransform = null;
          this.dragImageWebKitTransform = null;
        }
        this.el = this.dragData = null;
        return [move, end, cancel].forEach(function(handler) {
          return handler.off();
        });
      }
    },
    move: function(event) {
      var pageXs = [], pageYs = [];
      [].forEach.call(event.changedTouches, function(touch) {
        pageXs.push(touch.pageX);
        pageYs.push(touch.pageY);
      });

      var x = average(pageXs) - (parseInt(this.dragImage.offsetWidth, 10) / 2);
      var y = average(pageYs) - (parseInt(this.dragImage.offsetHeight, 10) / 2);
      this.translateDragImage(x, y);

      this.synthesizeEnterLeave(event);
    },
    // We use translate instead of top/left because of sub-pixel rendering and for the hope of better performance
    // http://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/
    translateDragImage: function(x, y) {
      var translate = " translate(" + x + "px," + y + "px)";

      if (this.dragImageWebKitTransform !== null) {
        this.dragImage.style["-webkit-transform"] = this.dragImageWebKitTransform + translate;
      }
      if (this.dragImageTransform !== null) {
        this.dragImage.style.transform = this.dragImageTransform + translate;
      }
    },
    synthesizeEnterLeave: function(event) {
      var target = elementFromTouchEvent(this.el,event)
      if (target != this.lastEnter) {
        if (this.lastEnter) {
          this.dispatchLeave(event);
        }
        this.lastEnter = target;
        if (this.lastEnter) {
          this.dispatchEnter(event);
        }
      }
      if (this.lastEnter) {
        this.dispatchOver(event);
      }
    },
    dragend: function(event) {

      // we'll dispatch drop if there's a target, then dragEnd.
      // drop comes first http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
      log("dragend");

      if (this.lastEnter) {
        this.dispatchLeave(event);
      }

      var target = elementFromTouchEvent(this.el,event)
      if (target) {
        log("found drop target " + target.tagName);
        this.dispatchDrop(target, event);
      } else {
        log("no drop target");
      }

      var dragendEvt = doc.createEvent("Event");
      dragendEvt.initEvent("dragend", true, true);
      this.el.dispatchEvent(dragendEvt);
    },
    dispatchDrop: function(target, event) {
      var dropEvt = doc.createEvent("Event");
      dropEvt.initEvent("drop", true, true);

      var touch = event.changedTouches[0];
      var x = touch[coordinateSystemForElementFromPoint + 'X'];
      var y = touch[coordinateSystemForElementFromPoint + 'Y'];
      dropEvt.offsetX = x - target.x;
      dropEvt.offsetY = y - target.y;

      dropEvt.dataTransfer = {
        types: this.dragDataTypes,
        getData: function(type) {
          return this.dragData[type];
        }.bind(this),
        dropEffect: "move"
      };
      dropEvt.preventDefault = function() {
         // https://www.w3.org/Bugs/Public/show_bug.cgi?id=14638 - if we don't cancel it, we'll snap back
      }.bind(this);

      once(doc, "drop", function() {
        log("drop event not canceled");
      },this);

      target.dispatchEvent(dropEvt);
    },
    dispatchEnter: function(event) {

      var enterEvt = doc.createEvent("Event");
      enterEvt.initEvent("dragenter", true, true);
      enterEvt.dataTransfer = {
        types: this.dragDataTypes,
        getData: function(type) {
          return this.dragData[type];
        }.bind(this)
      };

      var touch = event.changedTouches[0];
      enterEvt.pageX = touch.pageX;
      enterEvt.pageY = touch.pageY;

      this.lastEnter.dispatchEvent(enterEvt);
    },
    dispatchOver: function(event) {

      var overEvt = doc.createEvent("Event");
      overEvt.initEvent("dragover", true, true);
      overEvt.dataTransfer = {
        types: this.dragDataTypes,
        getData: function(type) {
          return this.dragData[type];
        }.bind(this)
      };

      var touch = event.changedTouches[0];
      overEvt.pageX = touch.pageX;
      overEvt.pageY = touch.pageY;

      this.lastEnter.dispatchEvent(overEvt);
    },
    dispatchLeave: function(event) {

      var leaveEvt = doc.createEvent("Event");
      leaveEvt.initEvent("dragleave", true, true);
      leaveEvt.dataTransfer = {
        types: this.dragDataTypes,
        getData: function(type) {
          return this.dragData[type];
        }.bind(this)
      };

      var touch = event.changedTouches[0];
      leaveEvt.pageX = touch.pageX;
      leaveEvt.pageY = touch.pageY;

      this.lastEnter.dispatchEvent(leaveEvt);
      this.lastEnter = null;
    },
    dispatchDragStart: function() {
      var evt = doc.createEvent("Event");
      evt.initEvent("dragstart", true, true);
      evt.dataTransfer = {
        setData: function(type, val) {
          this.dragData[type] = val;
          if (this.dragDataTypes.indexOf(type) == -1) {
            this.dragDataTypes[this.dragDataTypes.length] = type;
          }
          return val;
        }.bind(this),
        dropEffect: "move"
      };
      this.el.dispatchEvent(evt);
    },
    createDragImage: function() {
      this.dragImage = this.el.cloneNode(true);

      duplicateStyle(this.el, this.dragImage);

      this.dragImage.style.opacity = "0.5";
      this.dragImage.style.position = "absolute";
      this.dragImage.style.left = "0px";
      this.dragImage.style.top = "0px";
      this.dragImage.style.zIndex = "999999";

      var transform = this.dragImage.style.transform;
      if (typeof transform !== "undefined") {
        this.dragImageTransform = "";
        if (transform != "none") {
          this.dragImageTransform = transform.replace(/translate\(\D*\d+[^,]*,\D*\d+[^,]*\)\s*/g, '');
        }
      }

      var webkitTransform = this.dragImage.style["-webkit-transform"];
      if (typeof webkitTransform !== "undefined") {
        this.dragImageWebKitTransform = "";
        if (webkitTransform != "none") {
          this.dragImageWebKitTransform = webkitTransform.replace(/translate\(\D*\d+[^,]*,\D*\d+[^,]*\)\s*/g, '');
        }
      }

      this.translateDragImage(-9999, -9999);

      doc.body.appendChild(this.dragImage);
    }
  };

  // event listeners
  function touchstart(evt) {
    var el = evt.target;
    do {
      if (el.draggable === true) {
        // If draggable isn't explicitly set for anchors, then simulate a click event.
        // Otherwise plain old vanilla links will stop working.
        // https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events#Handling_clicks
        if (!el.hasAttribute("draggable") && el.tagName.toLowerCase() == "a") {
          var clickEvt = document.createEvent("MouseEvents");
          clickEvt.initMouseEvent("click", true, true, el.ownerDocument.defaultView, 1,
            evt.screenX, evt.screenY, evt.clientX, evt.clientY,
            evt.ctrlKey, evt.altKey, evt.shiftKey, evt.metaKey, 0, null);
          el.dispatchEvent(clickEvt);
          log("Simulating click to anchor");
        }
        evt.preventDefault();
        new DragDrop(evt,el);
      }
    } while((el = el.parentNode) && el !== doc.body);
  }

  // DOM helpers
  function elementFromTouchEvent(el,event) {
    var touch = event.changedTouches[0];
    var target = doc.elementFromPoint(
      touch[coordinateSystemForElementFromPoint + "X"],
      touch[coordinateSystemForElementFromPoint + "Y"]
    );
    return target;
  }

  function onEvt(el, event, handler, context) {
    if(context) {
      handler = handler.bind(context);
    }
    el.addEventListener(event, handler);
    return {
      off: function() {
        return el.removeEventListener(event, handler);
      }
    };
  }

  function once(el, event, handler, context) {
    if(context) {
      handler = handler.bind(context);
    }
    function listener(evt) {
      handler(evt);
      return el.removeEventListener(event,listener);
    }
    return el.addEventListener(event,listener);
  }

  // duplicateStyle expects dstNode to be a clone of srcNode
  function duplicateStyle(srcNode, dstNode) {
    // Is this node an element?
    if (srcNode.nodeType == 1) {
      // Remove any potential conflict attributes
      dstNode.removeAttribute("id");
      dstNode.removeAttribute("class");
      dstNode.removeAttribute("style");
      dstNode.removeAttribute("draggable");

      // Clone the style
      var cs = window.getComputedStyle(srcNode);
      for (var i = 0; i < cs.length; i++) {
        var csName = cs[i];
        dstNode.style.setProperty(csName, cs.getPropertyValue(csName), cs.getPropertyPriority(csName));
      }

      // Pointer events as none makes the drag image transparent to document.elementFromPoint()
      dstNode.style.pointerEvents = "none";
    }

    // Do the same for the children
    if (srcNode.hasChildNodes()) {
      for (var j = 0; j < srcNode.childNodes.length; j++) {
        duplicateStyle(srcNode.childNodes[j], dstNode.childNodes[j]);
      }
    }
  }

  // general helpers
  function log(msg) {
    console.log(msg);
  }

  function average(arr) {
    if (arr.length === 0) return 0;
    return arr.reduce((function(s, v) {
      return v + s;
    }), 0) / arr.length;
  }

  function noop() {}

  main(window.iosDragDropShim);

})(document);
<|endoftext|>


<commit_before>/**
 * class Renderer
 *
 * Generates HTML from parsed token stream. Each instance has independent
 * copy of rules. Those can be rewritten with ease. Also, you can add new
 * rules if you create plugin and adds new token types.
 **/
'use strict';

var assign          = require('./common/utils').assign;
var unescapeMd      = require('./common/utils').unescapeMd;
var replaceEntities = require('./common/utils').replaceEntities;
var escapeHtml      = require('./common/utils').escapeHtml;

////////////////////////////////////////////////////////////////////////////////

var rules = {};

rules.blockquote_open  = function () { return '
\n'; };
rules.blockquote_close = function () { return '
\n'; };

rules.code_block = function (tokens, idx /*, options, env */) {
  return '
' + escapeHtml(tokens[idx].content) + '
\n';
};
rules.code_inline = function (tokens, idx /*, options, env */) {
  return '' + escapeHtml(tokens[idx].content) + '';
};

rules.fence = function (tokens, idx, options /*, env, self*/) {
  var token = tokens[idx];
  var langClass = '';
  var langPrefix = options.langPrefix;
  var langName = '';
  var highlighted;

  if (token.params) {
    langName = escapeHtml(replaceEntities(unescapeMd(token.params.split(/\s+/g)[0])));
    langClass = ' class="' + langPrefix + langName + '"';
  }

  if (options.highlight) {
    highlighted = options.highlight(token.content, langName) || escapeHtml(token.content);
  } else {
    highlighted = escapeHtml(token.content);
  }

  return  '
'
        + highlighted
        + '
\n';
};

rules.heading_open = function (tokens, idx /*, options, env */) {
  return '<h' + tokens[idx].hLevel + '>';
};
rules.heading_close = function (tokens, idx /*, options, env */) {
  return '</h' + tokens[idx].hLevel + '>\n';
};

rules.hr = function (tokens, idx, options /*, env */) {
  return (options.xhtmlOut ? '
\n' : '
\n');
};

rules.bullet_list_open   = function () { return '
\n'; };
rules.bullet_list_close  = function () { return '
\n'; };
rules.list_item_open     = function (tokens, idx /*, options, env */) {
  var next = tokens[idx + 1];
  if ((next.type === 'list_item_close') ||
      (next.type === 'paragraph_open' && next.tight)) {
    return '
';
  }
  return '
\n';
};
rules.list_item_close    = function () { return '
\n'; };
rules.ordered_list_open  = function (tokens, idx /*, options, env */) {
  if (tokens[idx].order > 1) {
    return '
\n';
  }
  return '
\n';
};
rules.ordered_list_close = function () { return '
\n'; };

rules.paragraph_open = function (tokens, idx /*, options, env */) {
  return tokens[idx].tight ? '' : '
';
};
rules.paragraph_close = function (tokens, idx /*, options, env */) {
  // We have 2 cases of "hidden" paragraphs
  //
  // 1. In tight lists
  // 2. When content was stripped (reference definition, for example)
  //
  if (tokens[idx].tight === true) {
    if (!tokens[idx - 1].content) {
      return '';
    }
    if (tokens[idx + 1].type.slice(-5) === 'close') {
      return '';
    }
    return '\n';
  }
  return '
\n';
};

rules.link_open = function (tokens, idx /*, options, env */) {
  var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
  var target = tokens[idx].target ? (' target="' + escapeHtml(tokens[idx].target) + '"') : '';
  return '';
};
rules.link_close = function (/* tokens, idx, options, env */) {
  return '';
};

rules.image = function (tokens, idx, options, env, self) {
  var src = ' src="' + escapeHtml(tokens[idx].src) + '"';
  var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
  var alt = ' alt="' + self.renderInlineAsText(tokens[idx].tokens, options, env) + '"';
  var suffix = options.xhtmlOut ? ' /' : '';
  return '';
};

rules.table_open  = function () { return '
\n'; };
rules.table_close = function () { return '
\n'; };
rules.thead_open  = function () { return '
\n'; };
rules.thead_close = function () { return '
\n'; };
rules.tbody_open  = function () { return '
\n'; };
rules.tbody_close = function () { return '
\n'; };
rules.tr_open     = function () { return '
'; };
rules.tr_close    = function () { return '
\n'; };
rules.th_open     = function (tokens, idx /*, options, env */) {
  if (tokens[idx].align) {
    return '';
  }
  return '';
};
rules.th_close    = function () { return '	'; };
rules.td_open     = function (tokens, idx /*, options, env */) {
  if (tokens[idx].align) {
    return '';
  }
  return '';
};
rules.td_close    = function () { return '	'; };

rules.strong_open  = function () { return ''; };
rules.strong_close = function () { return ''; };

rules.em_open  = function () { return ''; };
rules.em_close = function () { return ''; };

rules.s_open  = function () { return '<s>'; };
rules.s_close = function () { return '</s>'; };

rules.hardbreak = function (tokens, idx, options /*, env */) {
  return options.xhtmlOut ? '
\n' : '
\n';
};
rules.softbreak = function (tokens, idx, options /*, env */) {
  return options.breaks ? (options.xhtmlOut ? '
\n' : '
\n') : '\n';
};

rules.text = function (tokens, idx /*, options, env */) {
  return escapeHtml(tokens[idx].content);
};

rules.html_block = function (tokens, idx /*, options, env */) {
  return tokens[idx].content;
};
rules.html_inline = function (tokens, idx /*, options, env */) {
  return tokens[idx].content;
};

/**
 * new Renderer()
 *
 * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults.
 **/
function Renderer() {

  /**
   * Renderer#rules -> Object
   *
   * Contains render rules for tokens. Can be updated and extended.
   *
   * ##### Example
   *
   * ```javascript
   * var md = require('markdown-it')();
   *
   * md.renderer.rules.strong_open  = function () { return '<b>'; };
   * md.renderer.rules.strong_close = function () { return '</b>'; };
   *
   * var result = md.renderInline(...);
   * ```
   *
   * Each rule is called as independed static function with fixed signature:
   *
   * ```javascript
   * function my_token_render(tokens, idx, options, env, renderer) {
   *   // ...
   *   return renderedHTML;
   * }
   * ```
   *
   * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js)
   * for more details and examples.
   **/
  this.rules = assign({}, rules);
}

/**
 * Renderer.renderInline(tokens, options, env) -> String
 * - tokens (Array): list on block tokens to renter
 * - options (Object): params of parser instance
 * - env (Object): additional data from parsed input (references, for example)
 *
 * The same as [[Renderer.render]], but for single token of `inline` type.
 **/
Renderer.prototype.renderInline = function (tokens, options, env) {
  var result = '',
      _rules = this.rules;

  for (var i = 0, len = tokens.length; i < len; i++) {
    result += _rules[tokens[i].type](tokens, i, options, env, this);
  }

  return result;
};

/** internal
 * Renderer.renderInlineAsText(tokens, options, env) -> String
 * - tokens (Array): list on block tokens to renter
 * - options (Object): params of parser instance
 * - env (Object): additional data from parsed input (references, for example)
 *
 * Special kludge for image `alt` attributes to conform CommonMark spec.
 * Don't try to use it! Spec requires to show `alt` content with stripped markup,
 * instead of simple escaping.
 **/
Renderer.prototype.renderInlineAsText = function (tokens, options, env) {
  var result = '',
      _rules = this.rules;

  for (var i = 0, len = tokens.length; i < len; i++) {
    if (tokens[i].type === 'text') {
      result += _rules.text(tokens, i, options, env, this);
    } else if (tokens[i].type === 'image') {
      result += this.renderInlineAsText(tokens[i].tokens, options, env);
    }
  }

  return result;
};

/**
 * Renderer.render(tokens, options, env) -> String
 * - tokens (Array): list on block tokens to renter
 * - options (Object): params of parser instance
 * - env (Object): additional data from parsed input (references, for example)
 *
 * Takes token stream and generates HTML. Probably, you will never need to call
 * this method directly.
 **/
Renderer.prototype.render = function (tokens, options, env) {
  var i, len,
      result = '',
      _rules = this.rules;

  for (i = 0, len = tokens.length; i < len; i++) {
    if (tokens[i].type === 'inline') {
      result += this.renderInline(tokens[i].children, options, env);
    } else {
      result += _rules[tokens[i].type](tokens, i, options, env, this);
    }
  }

  return result;
};

module.exports = Renderer;
<commit_msg>removed unused code<commit_after>/**
 * class Renderer
 *
 * Generates HTML from parsed token stream. Each instance has independent
 * copy of rules. Those can be rewritten with ease. Also, you can add new
 * rules if you create plugin and adds new token types.
 **/
'use strict';

var assign          = require('./common/utils').assign;
var unescapeMd      = require('./common/utils').unescapeMd;
var replaceEntities = require('./common/utils').replaceEntities;
var escapeHtml      = require('./common/utils').escapeHtml;

////////////////////////////////////////////////////////////////////////////////

var rules = {};

rules.blockquote_open  = function () { return '
\n'; };
rules.blockquote_close = function () { return '
\n'; };

rules.code_block = function (tokens, idx /*, options, env */) {
  return '
' + escapeHtml(tokens[idx].content) + '
\n';
};
rules.code_inline = function (tokens, idx /*, options, env */) {
  return '' + escapeHtml(tokens[idx].content) + '';
};

rules.fence = function (tokens, idx, options /*, env, self*/) {
  var token = tokens[idx];
  var langClass = '';
  var langPrefix = options.langPrefix;
  var langName = '';
  var highlighted;

  if (token.params) {
    langName = escapeHtml(replaceEntities(unescapeMd(token.params.split(/\s+/g)[0])));
    langClass = ' class="' + langPrefix + langName + '"';
  }

  if (options.highlight) {
    highlighted = options.highlight(token.content, langName) || escapeHtml(token.content);
  } else {
    highlighted = escapeHtml(token.content);
  }

  return  '
'
        + highlighted
        + '
\n';
};

rules.heading_open = function (tokens, idx /*, options, env */) {
  return '<h' + tokens[idx].hLevel + '>';
};
rules.heading_close = function (tokens, idx /*, options, env */) {
  return '</h' + tokens[idx].hLevel + '>\n';
};

rules.hr = function (tokens, idx, options /*, env */) {
  return (options.xhtmlOut ? '
\n' : '
\n');
};

rules.bullet_list_open   = function () { return '
\n'; };
rules.bullet_list_close  = function () { return '
\n'; };
rules.list_item_open     = function (tokens, idx /*, options, env */) {
  var next = tokens[idx + 1];
  if ((next.type === 'list_item_close') ||
      (next.type === 'paragraph_open' && next.tight)) {
    return '
';
  }
  return '
\n';
};
rules.list_item_close    = function () { return '
\n'; };
rules.ordered_list_open  = function (tokens, idx /*, options, env */) {
  if (tokens[idx].order > 1) {
    return '
\n';
  }
  return '
\n';
};
rules.ordered_list_close = function () { return '
\n'; };

rules.paragraph_open = function (tokens, idx /*, options, env */) {
  return tokens[idx].tight ? '' : '
';
};
rules.paragraph_close = function (tokens, idx /*, options, env */) {
  if (tokens[idx].tight === true) {
    return tokens[idx + 1].type.slice(-5) === 'close' ? '' : '\n';
  }
  return '
\n';
};

rules.link_open = function (tokens, idx /*, options, env */) {
  var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
  var target = tokens[idx].target ? (' target="' + escapeHtml(tokens[idx].target) + '"') : '';
  return '';
};
rules.link_close = function (/* tokens, idx, options, env */) {
  return '';
};

rules.image = function (tokens, idx, options, env, self) {
  var src = ' src="' + escapeHtml(tokens[idx].src) + '"';
  var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
  var alt = ' alt="' + self.renderInlineAsText(tokens[idx].tokens, options, env) + '"';
  var suffix = options.xhtmlOut ? ' /' : '';
  return '';
};

rules.table_open  = function () { return '
\n'; };
rules.table_close = function () { return '
\n'; };
rules.thead_open  = function () { return '
\n'; };
rules.thead_close = function () { return '
\n'; };
rules.tbody_open  = function () { return '
\n'; };
rules.tbody_close = function () { return '
\n'; };
rules.tr_open     = function () { return '
'; };
rules.tr_close    = function () { return '
\n'; };
rules.th_open     = function (tokens, idx /*, options, env */) {
  if (tokens[idx].align) {
    return '';
  }
  return '';
};
rules.th_close    = function () { return '	'; };
rules.td_open     = function (tokens, idx /*, options, env */) {
  if (tokens[idx].align) {
    return '';
  }
  return '';
};
rules.td_close    = function () { return '	'; };

rules.strong_open  = function () { return ''; };
rules.strong_close = function () { return ''; };

rules.em_open  = function () { return ''; };
rules.em_close = function () { return ''; };

rules.s_open  = function () { return '<s>'; };
rules.s_close = function () { return '</s>'; };

rules.hardbreak = function (tokens, idx, options /*, env */) {
  return options.xhtmlOut ? '
\n' : '
\n';
};
rules.softbreak = function (tokens, idx, options /*, env */) {
  return options.breaks ? (options.xhtmlOut ? '
\n' : '
\n') : '\n';
};

rules.text = function (tokens, idx /*, options, env */) {
  return escapeHtml(tokens[idx].content);
};

rules.html_block = function (tokens, idx /*, options, env */) {
  return tokens[idx].content;
};
rules.html_inline = function (tokens, idx /*, options, env */) {
  return tokens[idx].content;
};

/**
 * new Renderer()
 *
 * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults.
 **/
function Renderer() {

  /**
   * Renderer#rules -> Object
   *
   * Contains render rules for tokens. Can be updated and extended.
   *
   * ##### Example
   *
   * ```javascript
   * var md = require('markdown-it')();
   *
   * md.renderer.rules.strong_open  = function () { return '<b>'; };
   * md.renderer.rules.strong_close = function () { return '</b>'; };
   *
   * var result = md.renderInline(...);
   * ```
   *
   * Each rule is called as independed static function with fixed signature:
   *
   * ```javascript
   * function my_token_render(tokens, idx, options, env, renderer) {
   *   // ...
   *   return renderedHTML;
   * }
   * ```
   *
   * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js)
   * for more details and examples.
   **/
  this.rules = assign({}, rules);
}

/**
 * Renderer.renderInline(tokens, options, env) -> String
 * - tokens (Array): list on block tokens to renter
 * - options (Object): params of parser instance
 * - env (Object): additional data from parsed input (references, for example)
 *
 * The same as [[Renderer.render]], but for single token of `inline` type.
 **/
Renderer.prototype.renderInline = function (tokens, options, env) {
  var result = '',
      _rules = this.rules;

  for (var i = 0, len = tokens.length; i < len; i++) {
    result += _rules[tokens[i].type](tokens, i, options, env, this);
  }

  return result;
};

/** internal
 * Renderer.renderInlineAsText(tokens, options, env) -> String
 * - tokens (Array): list on block tokens to renter
 * - options (Object): params of parser instance
 * - env (Object): additional data from parsed input (references, for example)
 *
 * Special kludge for image `alt` attributes to conform CommonMark spec.
 * Don't try to use it! Spec requires to show `alt` content with stripped markup,
 * instead of simple escaping.
 **/
Renderer.prototype.renderInlineAsText = function (tokens, options, env) {
  var result = '',
      _rules = this.rules;

  for (var i = 0, len = tokens.length; i < len; i++) {
    if (tokens[i].type === 'text') {
      result += _rules.text(tokens, i, options, env, this);
    } else if (tokens[i].type === 'image') {
      result += this.renderInlineAsText(tokens[i].tokens, options, env);
    }
  }

  return result;
};

/**
 * Renderer.render(tokens, options, env) -> String
 * - tokens (Array): list on block tokens to renter
 * - options (Object): params of parser instance
 * - env (Object): additional data from parsed input (references, for example)
 *
 * Takes token stream and generates HTML. Probably, you will never need to call
 * this method directly.
 **/
Renderer.prototype.render = function (tokens, options, env) {
  var i, len,
      result = '',
      _rules = this.rules;

  for (i = 0, len = tokens.length; i < len; i++) {
    if (tokens[i].type === 'inline') {
      result += this.renderInline(tokens[i].children, options, env);
    } else {
      result += _rules[tokens[i].type](tokens, i, options, env, this);
    }
  }

  return result;
};

module.exports = Renderer;
<|endoftext|>


<commit_before>'use strict';

/*
 * RESTBase request dispatcher and general shared per-request state namespace
 */

var P = require('bluebird');
var rbUtil = require('./rbUtil');
var HTTPError = rbUtil.HTTPError;
var preq = require('preq');
var swaggerUI = require('./swaggerUI');

// Create a uniform but shallow request object copy with sane defaults. This
// keeps code dealing with this request monomorphic (good for perf), and
// avoids subtle bugs when requests shared between recursive requests are
// mutated in another control branch. At the very minimum, we are mutating the
// .params property for each sub-request.
function cloneRequest(req) {
    return {
        uri: req.uri || req.url || null,
        method: req.method || 'get',
        headers: req.headers || {},
        query: req.query || {},
        body: req.body || null,
        params: req.params || {}
    };
}

function RESTBase (options, req) {
    if (options && options.constructor === RESTBase) {
        // Child instance
        var par = this._parent = options;
        this.log = par.log;
        this._recursionDepth = par._recursionDepth + 1;
        this._priv = par._priv;
    } else {
        // Brand new instance
        this.log = options.log; // logging method

        // Private
        this._parent = null;
        this._req = null;
        this._recursionDepth = 0;

        options.maxDepth = options.maxDepth || 10;
        // Private state, shared with child instances
        this._priv = {
            options: options,
            router: options.router,
            statsd: options.metrics
        };

    }
}

// Make a child instance
RESTBase.prototype.makeChild = function(req) {
    var child = new RESTBase(this);
    // Remember the request that led to this child instance at each level, so
    // that we can provide nice error reporting and tracing.
    child._req = req;
    return child;
};

// A default listing handler for URIs that end in / and don't have any
// handlers associated with it otherwise.
RESTBase.prototype.defaultListingHandler = function(value, restbase, req) {
    var rq = req.query;
    if (rq.spec !== undefined && value.specRoot) {
        return P.resolve({
            status: 200,
            body: Object.assign({}, value.specRoot, {
                // Set the base path dynamically
                basePath: req.uri.toString().replace(/\/$/, '')
            })
        });
    } else if (rq.doc !== undefined) {
        // Return swagger UI & load spec from /?spec
        if (!req.query.path) {
            req.query.path = '/index.html';
        }
        return swaggerUI(restbase, req);
    } else if (/\btext\/html\b/.test(req.headers.accept)
            && req.uri.path.length <= 2) {
        // Browser request and above api level
        req.query.path = '/index.html';
        var html = '
'
                    + '
Wikimedia REST API
';
        if (req.uri.path.length === 1) {
            html += '
Domains:
'
                    + '

'
                    + req.params._ls.map(function(domain) {
                        return '
' + domain + '
';
                    }).join('\n')
                    + '

';
        } else {
            html += '
APIs:
'
                    + '

'
                    + req.params._ls.filter(function(item) {
                            return item !== 'sys';
                        })
                        .map(function(api) {
                        return '
' + api + '
';
                    }).join('\n')
                    + '
';
        }
        html += "
JSON listing

To retrieve a regular JSON listing, you can either "
            + "omit the Accept header, or send one that does not contain "
            + "text/html.

";

        return swaggerUI(restbase, req)
        .then(function(res) {
            res.body = res.body
                .replace(/window\.swaggerUi\.load/,'')
                .replace(/
/, html);
            return res;
        });
    } else {
        // Plain listing
        return P.resolve({
            status: 200,
            body: {
                items: req.params._ls
            }
        });
    }
};

// Special handling for external web requests
RESTBase.prototype.defaultWebRequestHandler = function(restbase, req) {
    this.log('trace/webrequest', {
        req: req
    });
    // TODO: move this out & only enable it while testing!
    // Can really set up custom handlers for test.local in the config,
    // which should avoid the need to rewrite things here.
    req.uri = req.uri
        .replace(/^http:\/\/en\.wikipedia\.test\.local\//,
                'http://en.wikipedia.org/')
        .replace(/^http:\/\/parsoid-lb\.eqiad\.wikimedia\.org\/v2\/en\.wikipedia\.test\.local\//,
                'http://parsoid-lb.eqiad.wikimedia.org/v2/en.wikipedia.org/')
        .replace(/\/v1\/en\.wikipedia\.test\.local\//,
                '/v1/en.wikipedia.org/')
        .replace(/\/v2\/en\.wikipedia\.test\.local\//,
                '/v2/en.wikipedia.org/');
    // Call P.resolve to make sure we have a bluebird Promise
    return P.resolve(preq(req));
};

// Process one request
RESTBase.prototype.request = function (req) {
    // Protect the sys api from direct access
    // Could consider opening this up with a specific permission later.
    if (this._recursionDepth === 0 &&
            ((req.uri.params && req.uri.params.api === 'sys')
             // TODO: Remove once params.api is reliable
             || (req.uri.path.length > 1 && req.uri.path[1] === 'sys'))) {
        return P.reject(new HTTPError({
            status: 403,
            body: {
                type: 'access_denied#sys',
                title: 'Access to the /{domain}/sys/ hierarchy is restricted to system users.'
            }
        }));
    }

    if (req.method) {
        req.method = req.method.toLowerCase();
    }

    return this._request(req);
};

// Internal request handler
RESTBase.prototype._request = function (req) {
    var self = this;

    // Special handling for https? requests
    if (req.uri.constructor === String && /^https?:\/\//.test(req.uri)) {
        return this.defaultWebRequestHandler(this, req);
    }

    var priv = this._priv;
    if (this._recursionDepth > priv.options.maxDepth) {
        var parents = [];
        var rb = this._parent;
        while (rb) {
            parents.push(rb._req);
            rb = rb._parent;
        }
        return P.resolve({
            status: 500,
            body: {
                type: 'request_recursion_depth_exceeded',
                title: 'RESTBase request recursion depth exceeded.',
                uri: req.uri,
                method: req.method,
                parents: parents,
                depth: this._recursionDepth
            }
        });
    }

    // Make sure we have a sane & uniform request object that doesn't change
    // (at least at the top level) under our feet.
    var childReq = cloneRequest(req);

    var match = priv.router.route(childReq.uri);
    var methods = match && match.value && match.value.methods;
    var handler = methods && (methods[childReq.method] || methods.all);

    if (match && !handler
            && childReq.method === 'get'
            && childReq.uri.path[childReq.uri.path.length - 1] === '') {
        // A GET for an URL that ends with /: return a default listing
        if (!match.value) { match.value = {}; }
        if (!match.value.path) { match.value.path = '_defaultListingHandler'; }
        handler = function (restbase, req) {
            return self.defaultListingHandler(match.value, restbase, req);
        };
    }

    if (handler) {
        // TODO: check ACLs in the match object

        // Remove the /{domain}/ prefix, as it's not very useful in stats
        var statName = match.value.path.replace(/\/[^\/]+\//, '')
            + '.' + req.method.toUpperCase() + '.';
        // Normalize invalid chars
        statName = priv.statsd.normalizeName(statName);

        // start timer
        var startTime = Date.now();

        // Prepare to call the handler with a child restbase instance
        var childRESTBase = this.makeChild(req);
        childReq.params = match.params;

        // Call the handler with P.try to catch synchronous exceptions.
        return P.try(function() {
            return handler(childRESTBase, childReq);
        })
        .then(function(res){
            // Record request metrics & log
            var statusClass = Math.floor(res.status / 100) + 'xx';
            priv.statsd.endTiming([statName + statusClass, statName + 'ALL'], startTime);

            self.log('trace', {
                req: req,
                res: res
            });

            if (!res) {
                throw new HTTPError({
                    status: 500,
                    body: {
                        type: 'empty_response',
                        description: 'Empty response received',
                        req: req
                    }
                });
            } else if (res.status >= 400 && !(res instanceof Error)) {
                var err = new HTTPError(res);
                if (res.body && res.body.stack) { err.stack = res.body.stack; }
                err.innerBody = res.body;
                err.internalReq = childReq;
                throw err;
            } else {
                return res;
            }
        },
        function(err){
            var statusClass = '5xx';
            if (err && err.status) {
                statusClass = Math.floor(err.status / 100) + 'xx';
            }
            priv.statsd.endTiming([statName + statusClass, statName + 'ALL'], startTime);
            throw err;
        });
    } else {
        // No handler found.
        return P.reject(new HTTPError({
            status: 404,
            body: {
                type: 'not_found#proxy_handler',
                title: 'Not found.',
                internalURI: req.uri,
                method: req.method,
                depth: self._recursionDepth
            }
        }));
    }
};

// Generic parameter massaging:
// * If last parameter is an object, it is expected to be the request object.
// * If the first parameter is a string, it's expected to be the URL.
// * If the second parameter is a String or Buffer, it's expected to be a
//   resource body.
function makeRequest (uri, reqOrBody, method) {
    var req;
    if (uri.constructor === Object) {
        // fast path
        req = uri;
        req.method = method;
        return req;
    } else if (reqOrBody && reqOrBody.constructor === Object) {
        req = reqOrBody;
        req.uri = uri;
        req.method = method;
    } else {
        req = {
            uri: uri,
            method: method,
            body: reqOrBody
        };
    }
    return req;
}

// Convenience wrappers
var methods = ['get', 'post', 'put', 'delete', 'head', 'options',
    'trace', 'connect', 'copy', 'move', 'purge', 'search'];
methods.forEach(function(method) {
    RESTBase.prototype[method] = function(uri, req) {
        return this._request(makeRequest(uri, req, method));
    };
});

module.exports = RESTBase;
<commit_msg>Make metrics a public restbase member<commit_after>'use strict';

/*
 * RESTBase request dispatcher and general shared per-request state namespace
 */

var P = require('bluebird');
var rbUtil = require('./rbUtil');
var HTTPError = rbUtil.HTTPError;
var preq = require('preq');
var swaggerUI = require('./swaggerUI');

// Create a uniform but shallow request object copy with sane defaults. This
// keeps code dealing with this request monomorphic (good for perf), and
// avoids subtle bugs when requests shared between recursive requests are
// mutated in another control branch. At the very minimum, we are mutating the
// .params property for each sub-request.
function cloneRequest(req) {
    return {
        uri: req.uri || req.url || null,
        method: req.method || 'get',
        headers: req.headers || {},
        query: req.query || {},
        body: req.body || null,
        params: req.params || {}
    };
}

function RESTBase (options, req) {
    if (options && options.constructor === RESTBase) {
        // Child instance
        var par = this._parent = options;
        this.log = par.log;
        this.metrics = par.metrics;
        this._recursionDepth = par._recursionDepth + 1;
        this._priv = par._priv;
    } else {
        // Brand new instance
        this.log = options.log; // logging method
        this.metrics = options.metrics;

        // Private
        this._parent = null;
        this._req = null;
        this._recursionDepth = 0;

        options.maxDepth = options.maxDepth || 10;
        // Private state, shared with child instances
        this._priv = {
            options: options,
            router: options.router,
        };

    }
}

// Make a child instance
RESTBase.prototype.makeChild = function(req) {
    var child = new RESTBase(this);
    // Remember the request that led to this child instance at each level, so
    // that we can provide nice error reporting and tracing.
    child._req = req;
    return child;
};

// A default listing handler for URIs that end in / and don't have any
// handlers associated with it otherwise.
RESTBase.prototype.defaultListingHandler = function(value, restbase, req) {
    var rq = req.query;
    if (rq.spec !== undefined && value.specRoot) {
        return P.resolve({
            status: 200,
            body: Object.assign({}, value.specRoot, {
                // Set the base path dynamically
                basePath: req.uri.toString().replace(/\/$/, '')
            })
        });
    } else if (rq.doc !== undefined) {
        // Return swagger UI & load spec from /?spec
        if (!req.query.path) {
            req.query.path = '/index.html';
        }
        return swaggerUI(restbase, req);
    } else if (/\btext\/html\b/.test(req.headers.accept)
            && req.uri.path.length <= 2) {
        // Browser request and above api level
        req.query.path = '/index.html';
        var html = '
'
                    + '
Wikimedia REST API
';
        if (req.uri.path.length === 1) {
            html += '
Domains:
'
                    + '

'
                    + req.params._ls.map(function(domain) {
                        return '
' + domain + '
';
                    }).join('\n')
                    + '

';
        } else {
            html += '
APIs:
'
                    + '

'
                    + req.params._ls.filter(function(item) {
                            return item !== 'sys';
                        })
                        .map(function(api) {
                        return '
' + api + '
';
                    }).join('\n')
                    + '
';
        }
        html += "
JSON listing

To retrieve a regular JSON listing, you can either "
            + "omit the Accept header, or send one that does not contain "
            + "text/html.

";

        return swaggerUI(restbase, req)
        .then(function(res) {
            res.body = res.body
                .replace(/window\.swaggerUi\.load/,'')
                .replace(/
/, html);
            return res;
        });
    } else {
        // Plain listing
        return P.resolve({
            status: 200,
            body: {
                items: req.params._ls
            }
        });
    }
};

// Special handling for external web requests
RESTBase.prototype.defaultWebRequestHandler = function(restbase, req) {
    this.log('trace/webrequest', {
        req: req
    });
    // TODO: move this out & only enable it while testing!
    // Can really set up custom handlers for test.local in the config,
    // which should avoid the need to rewrite things here.
    req.uri = req.uri
        .replace(/^http:\/\/en\.wikipedia\.test\.local\//,
                'http://en.wikipedia.org/')
        .replace(/^http:\/\/parsoid-lb\.eqiad\.wikimedia\.org\/v2\/en\.wikipedia\.test\.local\//,
                'http://parsoid-lb.eqiad.wikimedia.org/v2/en.wikipedia.org/')
        .replace(/\/v1\/en\.wikipedia\.test\.local\//,
                '/v1/en.wikipedia.org/')
        .replace(/\/v2\/en\.wikipedia\.test\.local\//,
                '/v2/en.wikipedia.org/');
    // Call P.resolve to make sure we have a bluebird Promise
    return P.resolve(preq(req));
};

// Process one request
RESTBase.prototype.request = function (req) {
    // Protect the sys api from direct access
    // Could consider opening this up with a specific permission later.
    if (this._recursionDepth === 0 &&
            ((req.uri.params && req.uri.params.api === 'sys')
             // TODO: Remove once params.api is reliable
             || (req.uri.path.length > 1 && req.uri.path[1] === 'sys'))) {
        return P.reject(new HTTPError({
            status: 403,
            body: {
                type: 'access_denied#sys',
                title: 'Access to the /{domain}/sys/ hierarchy is restricted to system users.'
            }
        }));
    }

    if (req.method) {
        req.method = req.method.toLowerCase();
    }

    return this._request(req);
};

// Internal request handler
RESTBase.prototype._request = function (req) {
    var self = this;

    // Special handling for https? requests
    if (req.uri.constructor === String && /^https?:\/\//.test(req.uri)) {
        return this.defaultWebRequestHandler(this, req);
    }

    var priv = this._priv;
    if (this._recursionDepth > priv.options.maxDepth) {
        var parents = [];
        var rb = this._parent;
        while (rb) {
            parents.push(rb._req);
            rb = rb._parent;
        }
        return P.resolve({
            status: 500,
            body: {
                type: 'request_recursion_depth_exceeded',
                title: 'RESTBase request recursion depth exceeded.',
                uri: req.uri,
                method: req.method,
                parents: parents,
                depth: this._recursionDepth
            }
        });
    }

    // Make sure we have a sane & uniform request object that doesn't change
    // (at least at the top level) under our feet.
    var childReq = cloneRequest(req);

    var match = priv.router.route(childReq.uri);
    var methods = match && match.value && match.value.methods;
    var handler = methods && (methods[childReq.method] || methods.all);

    if (match && !handler
            && childReq.method === 'get'
            && childReq.uri.path[childReq.uri.path.length - 1] === '') {
        // A GET for an URL that ends with /: return a default listing
        if (!match.value) { match.value = {}; }
        if (!match.value.path) { match.value.path = '_defaultListingHandler'; }
        handler = function (restbase, req) {
            return self.defaultListingHandler(match.value, restbase, req);
        };
    }

    if (handler) {
        // TODO: check ACLs in the match object

        // Remove the /{domain}/ prefix, as it's not very useful in stats
        var statName = match.value.path.replace(/\/[^\/]+\//, '')
            + '.' + req.method.toUpperCase() + '.';
        // Normalize invalid chars
        statName = self.metrics.normalizeName(statName);

        // start timer
        var startTime = Date.now();

        // Prepare to call the handler with a child restbase instance
        var childRESTBase = this.makeChild(req);
        childReq.params = match.params;

        // Call the handler with P.try to catch synchronous exceptions.
        return P.try(function() {
            return handler(childRESTBase, childReq);
        })
        .then(function(res){
            // Record request metrics & log
            var statusClass = Math.floor(res.status / 100) + 'xx';
            self.metrics.endTiming([statName + statusClass, statName + 'ALL'], startTime);

            self.log('trace', {
                req: req,
                res: res
            });

            if (!res) {
                throw new HTTPError({
                    status: 500,
                    body: {
                        type: 'empty_response',
                        description: 'Empty response received',
                        req: req
                    }
                });
            } else if (res.status >= 400 && !(res instanceof Error)) {
                var err = new HTTPError(res);
                if (res.body && res.body.stack) { err.stack = res.body.stack; }
                err.innerBody = res.body;
                err.internalReq = childReq;
                throw err;
            } else {
                return res;
            }
        },
        function(err){
            var statusClass = '5xx';
            if (err && err.status) {
                statusClass = Math.floor(err.status / 100) + 'xx';
            }
            self.metrics.endTiming([statName + statusClass, statName + 'ALL'], startTime);
            throw err;
        });
    } else {
        // No handler found.
        return P.reject(new HTTPError({
            status: 404,
            body: {
                type: 'not_found#proxy_handler',
                title: 'Not found.',
                internalURI: req.uri,
                method: req.method,
                depth: self._recursionDepth
            }
        }));
    }
};

// Generic parameter massaging:
// * If last parameter is an object, it is expected to be the request object.
// * If the first parameter is a string, it's expected to be the URL.
// * If the second parameter is a String or Buffer, it's expected to be a
//   resource body.
function makeRequest (uri, reqOrBody, method) {
    var req;
    if (uri.constructor === Object) {
        // fast path
        req = uri;
        req.method = method;
        return req;
    } else if (reqOrBody && reqOrBody.constructor === Object) {
        req = reqOrBody;
        req.uri = uri;
        req.method = method;
    } else {
        req = {
            uri: uri,
            method: method,
            body: reqOrBody
        };
    }
    return req;
}

// Convenience wrappers
var methods = ['get', 'post', 'put', 'delete', 'head', 'options',
    'trace', 'connect', 'copy', 'move', 'purge', 'search'];
methods.forEach(function(method) {
    RESTBase.prototype[method] = function(uri, req) {
        return this._request(makeRequest(uri, req, method));
    };
});

module.exports = RESTBase;
<|endoftext|>


<commit_before>const url = require('url');
const fs = require('fs');
const http = require('http');
const axios = require('axios');
const _ = require('lodash');
const boom = require('boom');

const Router = require('./router');
const Mapping = require('./mapping');
const Parser = require('./parser');

/** A request redirection handler. */
class Rosalina {
  static get version() { return '0.1.0'; }

  static writeResponse(res, statusCode, headers, body) {
    res.writeHead(statusCode, headers);
    if(_.isObject(body)) {
      body = JSON.stringify(body);
    }
    res.end(body);
  }

  /**
   * Creates a new Rosalina instance
   * @param {string} remote - the base url where requests will be redirected to.
                              must be an absolute URL.
   */

  constructor(remote) {
    this.router = new Router;
    this.headerMappings = {};
    this.remote = remote;
  }

  /**
   * Returns a mapping based on the path defined. If a mapping with
   * the defined path doesn't exists, then a new mapping is created.
   * @see Mapping
   * @param {string} path - the path where the application will listen to requests.
   * @returns The Mapping object instance.
   */
  from(path) {
    let route = this.router.path(path);
    if(!route.mapping) {
      route.mapping = new Mapping;
    }
    return route.mapping;
  }

  /**
   * Assigns which headers will be put on all remote requests
   * @param {object} headers - an object which represents a from-to assignation.
   */

  map_headers(headers={}) {
    this.headerMappings = _.assign({}, this.headerMappings, headers);
  }

  /**
   * Builds the request redirection logic.
   */

   logic() {
     let {router, headers, remote} = this;

     return (function (req, res) {
       let writeResponse = Rosalina.writeResponse.bind(null, res);
       let responseHeaders = {
         'X-Powered-By': `Rosalina ${Rosalina.version}`
       }

       let pathinfo = url.parse(req.url);
       let method = req.method.toLowerCase();
       let route = router.match(pathinfo.pathname);

       if(!route) {
         let error = boom.notFound().output;
         writeResponse(error.statusCode, responseHeaders, error.payload.error);
         return;
       }

       let {endpoint, parameters} = route;

       if(!(method in endpoint.mapping.actions)) {
         let error = boom.methodNotAllowed().output;
         writeResponse(error.statusCode, responseHeaders, error.payload.error);
         return;
       }

       let action = endpoint.mapping.actions[method];

       Parser.parse(req).then((form) => {
         let sendingFields = _.transform(action.parameters,
           (result, value, key) => {
             if(!(key in form.fields) || !(key in form.files)) { return; }
             if(!(value in result)) {
               if(key in form.fields) {
                 result[value] = form.fields[key];
               } else {
                 result[value] = fs.createReadStream(form.files[key].path);
               }
             }
           });

         let sendingHeaders = _.transform(headers,
           (result, value, key) => {
             if(!(key in req.headers)) { return; }
             if(_.isFunction(value)) {
               _.assign(result, value(req.headers[key]));
             } else {
               result[value] = req.headers[key];
             }
           });

         return axios({
           method: method,
           data: sendingFields,
           headers: sendingHeaders,
           baseURL: remote,
           url: endpoint.mapping._to
         });

       }).then((remote) => {
         responseHeaders = _.assign({}, responseHeaders, remote.headers);
         writeResponse(remote.status, responseHeaders, remote.data);
       }).catch((err) => {
         if(!err.response) {
           let internalError = boom.badImplementation(
             'Rosalina found an internal error while processing your request.',
             {
               ROSALINA_ERROR_INTERNAL: true,
               ROSALINA_ERROR_CODE: err.code,
               ROSALINA_ERROR_TRACEBACK: err.stack
            });
          console.log(internalError);
           writeResponse(internalError.output.statusCode,
             responseHeaders,
             internalError.output.message);
         } else {
           let remote = err.response;
           responseHeaders = _.assign({}, responseHeaders, remote.headers);
           writeResponse(remote.status, responseHeaders, remote.data);
         }
       });
     });
   }

   listen(port, hostname='127.0.0.1') {
     let server = http.createServer(this.logic());
     server.listen(port, hostname);
     return server;
   }
}

module.exports = Rosalina;
<commit_msg>Fixed bug where all redirected requests were empty. Also, added debug statements and an error response writer helper.<commit_after>const url = require('url');
const fs = require('fs');
const http = require('http');
const axios = require('axios');
const _ = require('lodash');
const boom = require('boom');

const Router = require('./router');
const Mapping = require('./mapping');
const Parser = require('./parser');
const debug = require('debug')('rosalina:app');

/** A request redirection handler. */
class Rosalina {
  static get version() { return '0.1.0'; }

  static writeResponse(res, statusCode, headers, body) {
    res.writeHead(statusCode, headers);
    debug('sending %o', body);
    if(headers['content-type'].includes('application/json')) {
      body = JSON.stringify(body);
    }
    res.write(body);
    res.end();
  }

  static writeError(res, headers, error) {
    res.writeHead(error.output.statusCode, headers);
    res.write("An error was found during while processing the request.\n");
    res.write(error.stack);
    res.end("\n- Rosalina");
  }

  /**
  * Creates a new Rosalina instance
  * @param {string} remote - the base url where requests will be redirected to, must be an absolute URL.
  */

  constructor(remote) {
    debug('new Rosalina instance with remote host as %s', remote);
    this.router = new Router;
    this.headerMappings = {};
    this.remote = remote;
  }

  /**
  * Returns a mapping based on the path defined. If a mapping with
  * the defined path doesn't exists, then a new mapping is created.
  * @see Mapping
  * @param {string} path - the path where the application will listen to requests.
  * @returns The Mapping object instance.
  */
  from(path) {
    let route = this.router.path(path);
    if(!route.mapping) {
      debug('mapping not defined, creating new one');
      route.mapping = new Mapping;
    }
    debug('returning related mapping');
    return route.mapping;
  }

  /**
  * Assigns which headers will be put on all remote requests
  * @param {object} headers - an object which represents a from-to assignation.
  */

  map_headers(headers={}) {
    this.headerMappings = _.assign({}, this.headerMappings, headers);
  }

  /**
  * Builds the request redirection logic.
  */

  logic() {
    let {router, headers, remote} = this;

    return (function (req, res) {
      debug('received %s request with URL %s', req.method, req.url);
      let responseHeaders = {
        'X-Powered-By': `Rosalina ${Rosalina.version}`
      };

      let writeResponse = Rosalina.writeResponse.bind(null, res);
      let writeError = Rosalina.writeError.bind(null, res, responseHeaders);

      let pathinfo = url.parse(req.url);
      let method = req.method.toLowerCase();
      let route = router.match(pathinfo.pathname);

      if(!route) {
        debug('path not found, sending error');
        let error = boom.notFound().output;
        writeResponse(error.statusCode, responseHeaders, error.payload.error);
        return;
      }

      let {endpoint, parameters} = route;

      if(!(method in endpoint.mapping.actions)) {
        debug('method not allowed, sending error');
        let error = boom.methodNotAllowed().output;
        writeResponse(error.statusCode, responseHeaders, error.payload.error);
        return;
      }

      let action = endpoint.mapping.actions[method];

      Parser.parse(req).then((form) => {
        debug('fields after parsing: %o', form.fields);
        debug('files after parsing: %o', _.keys(form.files));

        let sendingFields = _.transform(action.parameters,
          (result, value, key) => {
            if(!(key in form.fields) && !(key in form.files)) { return; }
            if(!(value in result)) {
              if(key in form.fields) {
                result[value] = form.fields[key];
              } else {
                result[value] = fs.createReadStream(form.files[key].path);
              }
            }
        });

        debug('retrieved fields: %o', sendingFields);

        let sendingHeaders = _.transform(headers, (result, value, key) => {
          if(!(key in req.headers)) { return; }
          if(_.isFunction(value)) {
            _.assign(result, value(req.headers[key]));
          } else {
            result[value] = req.headers[key];
          }
        });

        debug('retrieved headers: %o', sendingHeaders);
        debug('sending request');
        return axios({
          method: method,
          data: sendingFields,
          headers: sendingHeaders,
          baseURL: remote,
          url: endpoint.mapping._to
        });

      }).then((remote) => {
        debug('received response %o, %o, %o', remote.data, remote.headers, remote.status);
        responseHeaders = _.assign({}, responseHeaders, remote.headers);
        writeResponse(remote.status, responseHeaders, remote.data);
      }).catch((err) => {
        debug('found error on execution');
        if(!err.response) {
          debug('assuming internal error because no response from request');
          writeError(boom.wrap(err, 500));
        } else {
          debug('connection problem error: %d %o', err.response.status, err.response);
          let remote = err.response;
          responseHeaders = _.assign({}, responseHeaders, remote.headers);
          writeError(boom.wrap(err, remote.status));
        }
      });
    });
  }

  listen(port, hostname='127.0.0.1') {
    let server = http.createServer(this.logic());
    server.listen(port, hostname);
    debug('HTTP server with Rosalina running at %s:%d', hostname, port);
    return server;
  }
}

module.exports = Rosalina;
<|endoftext|>