<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2010, Ajax.org B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

define(function(require, exports, module) {
"use strict";
var HashHandler = require("ace/keyboard/hash_handler").HashHandler;
var Editor = require("ace/editor").Editor;
var snippetManager = require("ace/snippets").snippetManager;
var Range = require("ace/range").Range;
var emmet, emmetPath;

/**
 * Implementation of {@link IEmmetEditor} interface for Ace
 */
function AceEmmetEditor() {}

AceEmmetEditor.prototype = {
    setupContext: function(editor) {
        this.ace = editor;
        this.indentation = editor.session.getTabString();
        if (!emmet)
            emmet = window.emmet;
        emmet.require("resources").setVariable("indentation", this.indentation);
        this.$syntax = null;
        this.$syntax = this.getSyntax();
    },
    /**
     * Returns character indexes of selected text: object with start
     * and end properties. If there's no selection, should return
     * object with start and end properties referring
     * to current caret position
     * @return {Object}
     * @example
     * var selection = editor.getSelectionRange();
     * alert(selection.start + ', ' + selection.end);
     */
    getSelectionRange: function() {
        // TODO should start be caret position instead?
        var range = this.ace.getSelectionRange();
        var doc = this.ace.session.doc;
        return {
            start: doc.positionToIndex(range.start),
            end: doc.positionToIndex(range.end)
        };
    },

    /**
     * Creates selection from start to end character
     * indexes. If end is ommited, this method should place caret
     * and start index
     * @param {Number} start
     * @param {Number} [end]
     * @example
     * editor.createSelection(10, 40);
     *
     * //move caret to 15th character
     * editor.createSelection(15);
     */
    createSelection: function(start, end) {
        var doc = this.ace.session.doc;
        this.ace.selection.setRange({
            start: doc.indexToPosition(start),
            end: doc.indexToPosition(end)
        });
    },

    /**
     * Returns current line's start and end indexes as object with start
     * and end properties
     * @return {Object}
     * @example
     * var range = editor.getCurrentLineRange();
     * alert(range.start + ', ' + range.end);
     */
    getCurrentLineRange: function() {
        var ace = this.ace;
        var row = ace.getCursorPosition().row;
        var lineLength = ace.session.getLine(row).length;
        var index = ace.session.doc.positionToIndex({row: row, column: 0});
        return {
            start: index,
            end: index + lineLength
        };
    },

    /**
     * Returns current caret position
     * @return {Number|null}
     */
    getCaretPos: function(){
        var pos = this.ace.getCursorPosition();
        return this.ace.session.doc.positionToIndex(pos);
    },

    /**
     * Set new caret position
     * @param {Number} index Caret position
     */
    setCaretPos: function(index){
        var pos = this.ace.session.doc.indexToPosition(index);
        this.ace.selection.moveToPosition(pos);
    },

    /**
     * Returns content of current line
     * @return {String}
     */
    getCurrentLine: function() {
        var row = this.ace.getCursorPosition().row;
        return this.ace.session.getLine(row);
    },

    /**
     * Replace editor's content or it's part (from start to
     * end index). If value contains
     * caret_placeholder, the editor will put caret into
     * this position. If you skip start and end
     * arguments, the whole target's content will be replaced with
     * value.
     *
     * If you pass start argument only,
     * the value will be placed at start string
     * index of current content.
     *
     * If you pass start and end arguments,
     * the corresponding substring of current target's content will be
     * replaced with value.
     * @param {String} value Content you want to paste
     * @param {Number} [start] Start index of editor's content
     * @param {Number} [end] End index of editor's content
     * @param {Boolean} [noIndent] Do not auto indent value
     */
    replaceContent: function(value, start, end, noIndent) {
        if (end == null)
            end = start == null ? this.getContent().length : start;
        if (start == null)
            start = 0;

        var editor = this.ace;
        var doc = editor.session.doc;
        var range = Range.fromPoints(doc.indexToPosition(start), doc.indexToPosition(end));
        editor.session.remove(range);

        range.end = range.start;
        //editor.selection.setRange(range);

        value = this.$updateTabstops(value);
        snippetManager.insertSnippet(editor, value);
    },

    /**
     * Returns editor's content
     * @return {String}
     */
    getContent: function(){
        return this.ace.getValue();
    },

    /**
     * Returns current editor's syntax mode
     * @return {String}
     */
    getSyntax: function() {
        if (this.$syntax)
            return this.$syntax;
        var syntax = this.ace.session.$modeId.split("/").pop();
        if (syntax == "html" || syntax == "php") {
            var cursor = this.ace.getCursorPosition();
            var state = this.ace.session.getState(cursor.row);
            if (typeof state != "string")
                state = state[0];
            if (state) {
                state = state.split("-");
                if (state.length > 1)
                    syntax = state[0];
                else if (syntax == "php")
                    syntax = "html";
            }
        }
        return syntax;
    },

    /**
     * Returns current output profile name (@see emmet#setupProfile)
     * @return {String}
     */
    getProfileName: function() {
        switch(this.getSyntax()) {
          case "css": return "css";
          case "xml":
          case "xsl":
            return "xml";
          case "html":
            var profile = emmet.require("resources").getVariable("profile");
            // no forced profile, guess from content html or xhtml?
            if (!profile)
                profile = this.ace.session.getLines(0,2).join("").search(/<!DOCTYPE[^>]+XHTML/i) != -1 ? "xhtml": "html";
            return profile;
        }
        return "xhtml";
    },

    /**
     * Ask user to enter something
     * @param {String} title Dialog title
     * @return {String} Entered data
     * @since 0.65
     */
    prompt: function(title) {
        return prompt(title);
    },

    /**
     * Returns current selection
     * @return {String}
     * @since 0.65
     */
    getSelection: function() {
        return this.ace.session.getTextRange();
    },

    /**
     * Returns current editor's file path
     * @return {String}
     * @since 0.65
     */
    getFilePath: function() {
        return "";
    },

    // update tabstops: make sure all caret placeholders are unique
    // by default, abbreviation parser generates all unlinked (un-mirrored)
    // tabstops as ${0}, so we have upgrade all caret tabstops with unique
    // positions but make sure that all other tabstops are not linked accidentally
    // based on https://github.com/sergeche/emmet-sublime/blob/master/editor.js#L119-L171
    $updateTabstops: function(value) {
        var base = 1000;
        var zeroBase = 0;
        var lastZero = null;
        var range = emmet.require('range');
        var ts = emmet.require('tabStops');
        var settings = emmet.require('resources').getVocabulary("user");
        var tabstopOptions = {
            tabstop: function(data) {
                var group = parseInt(data.group, 10);
                var isZero = group === 0;
                if (isZero)
                    group = ++zeroBase;
                else
                    group += base;

                var placeholder = data.placeholder;
                if (placeholder) {
                    // recursively update nested tabstops
                    placeholder = ts.processText(placeholder, tabstopOptions);
                }

                var result = '${' + group + (placeholder ? ':' + placeholder : '') + '}';

                if (isZero) {
                    lastZero = range.create(data.start, result);
                }

                return result;
            },
            escape: function(ch) {
                if (ch == '$') return '\\$';
                if (ch == '\\') return '\\\\';
                return ch;
            }
        };

        value = ts.processText(value, tabstopOptions);

        if (settings.variables['insert_final_tabstop'] && !/\$\{0\}$/.test(value)) {
            value += '${0}';
        } else if (lastZero) {
            value = emmet.require('utils').replaceSubstring(value, '${0}', lastZero);
        }

        return value;
    }
};

var keymap = {
    expand_abbreviation: {"mac": "ctrl+alt+e", "win": "alt+e"},
    match_pair_outward: {"mac": "ctrl+d", "win": "ctrl+,"},
    match_pair_inward: {"mac": "ctrl+j", "win": "ctrl+shift+0"},
    matching_pair: {"mac": "ctrl+alt+j", "win": "alt+j"},
    next_edit_point: "alt+right",
    prev_edit_point: "alt+left",
    toggle_comment: {"mac": "command+/", "win": "ctrl+/"},
    split_join_tag: {"mac": "shift+command+'", "win": "shift+ctrl+`"},
    remove_tag: {"mac": "command+'", "win": "shift+ctrl+;"},
    evaluate_math_expression: {"mac": "shift+command+y", "win": "shift+ctrl+y"},
    increment_number_by_1: "ctrl+up",
    decrement_number_by_1: "ctrl+down",
    increment_number_by_01: "alt+up",
    decrement_number_by_01: "alt+down",
    increment_number_by_10: {"mac": "alt+command+up", "win": "shift+alt+up"},
    decrement_number_by_10: {"mac": "alt+command+down", "win": "shift+alt+down"},
    select_next_item: {"mac": "shift+command+.", "win": "shift+ctrl+."},
    select_previous_item: {"mac": "shift+command+,", "win": "shift+ctrl+,"},
    reflect_css_value: {"mac": "shift+command+r", "win": "shift+ctrl+r"},

    encode_decode_data_url: {"mac": "shift+ctrl+d", "win": "ctrl+'"},
    // update_image_size: {"mac": "shift+ctrl+i", "win": "ctrl+u"},
    // expand_as_you_type: "ctrl+alt+enter",
    // wrap_as_you_type: {"mac": "shift+ctrl+g", "win": "shift+ctrl+g"},
    expand_abbreviation_with_tab: "Tab",
    wrap_with_abbreviation: {"mac": "shift+ctrl+a", "win": "shift+ctrl+a"}
};

var editorProxy = new AceEmmetEditor();
exports.commands = new HashHandler();
exports.runEmmetCommand = function(editor) {
    editorProxy.setupContext(editor);
    if (editorProxy.getSyntax() == "php")
        return false;
    var actions = emmet.require("actions");

    if (this.action == "expand_abbreviation_with_tab") {
        if (!editor.selection.isEmpty())
            return false;
    }

    if (this.action == "wrap_with_abbreviation") {
        // without setTimeout prompt doesn't work on firefox
        return setTimeout(function() {
            actions.run("wrap_with_abbreviation", editorProxy);
        }, 0);
    }

    var pos = editor.selection.lead;
    var token = editor.session.getTokenAt(pos.row, pos.column);
    if (token && /\btag\b/.test(token.type))
        return false;

    try {
        var result = actions.run(this.action, editorProxy);
    } catch(e) {
        editor._signal("changeStatus", typeof e == "string" ? e : e.message);
        console.log(e);
        result = false;
    }
    return result;
};

for (var command in keymap) {
    exports.commands.addCommand({
        name: "emmet:" + command,
        action: command,
        bindKey: keymap[command],
        exec: exports.runEmmetCommand,
        multiSelectAction: "forEach"
    });
}

exports.updateCommands = function(editor, enabled) {
    if (enabled) {
        editor.keyBinding.addKeyboardHandler(exports.commands);
    } else {
        editor.keyBinding.removeKeyboardHandler(exports.commands);
    }
};

exports.isSupportedMode = function(modeId) {
    return modeId && /css|less|scss|sass|stylus|html|php|twig/.test(modeId);
};

var onChangeMode = function(e, target) {
    var editor = target;
    if (!editor)
        return;
    var enabled = exports.isSupportedMode(editor.session.$modeId);
    if (e.enableEmmet === false)
        enabled = false;
    if (enabled) {
        if (typeof emmetPath == "string") {
            require("ace/config").loadModule(emmetPath, function() {

            });
            emmetPath = null;
        }
    }
    exports.updateCommands(editor, enabled);
};

exports.AceEmmetEditor = AceEmmetEditor;
require("ace/config").defineOptions(Editor.prototype, "editor", {
    enableEmmet: {
        set: function(val) {
            this[val ? "on" : "removeListener"]("changeMode", onChangeMode);
            onChangeMode({enableEmmet: !!val}, this);
        },
        value: true
    }
});

exports.setCore = function(e) {
    if (typeof e == "string")
       emmetPath = e;
    else
       emmet = e;
};
});

<commit_msg>make sure missing emmet can't break tab key<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2010, Ajax.org B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

define(function(require, exports, module) {
"use strict";
var HashHandler = require("ace/keyboard/hash_handler").HashHandler;
var Editor = require("ace/editor").Editor;
var snippetManager = require("ace/snippets").snippetManager;
var Range = require("ace/range").Range;
var emmet, emmetPath;

/**
 * Implementation of {@link IEmmetEditor} interface for Ace
 */
function AceEmmetEditor() {}

AceEmmetEditor.prototype = {
    setupContext: function(editor) {
        this.ace = editor;
        this.indentation = editor.session.getTabString();
        if (!emmet)
            emmet = window.emmet;
        emmet.require("resources").setVariable("indentation", this.indentation);
        this.$syntax = null;
        this.$syntax = this.getSyntax();
    },
    /**
     * Returns character indexes of selected text: object with start
     * and end properties. If there's no selection, should return
     * object with start and end properties referring
     * to current caret position
     * @return {Object}
     * @example
     * var selection = editor.getSelectionRange();
     * alert(selection.start + ', ' + selection.end);
     */
    getSelectionRange: function() {
        // TODO should start be caret position instead?
        var range = this.ace.getSelectionRange();
        var doc = this.ace.session.doc;
        return {
            start: doc.positionToIndex(range.start),
            end: doc.positionToIndex(range.end)
        };
    },

    /**
     * Creates selection from start to end character
     * indexes. If end is ommited, this method should place caret
     * and start index
     * @param {Number} start
     * @param {Number} [end]
     * @example
     * editor.createSelection(10, 40);
     *
     * //move caret to 15th character
     * editor.createSelection(15);
     */
    createSelection: function(start, end) {
        var doc = this.ace.session.doc;
        this.ace.selection.setRange({
            start: doc.indexToPosition(start),
            end: doc.indexToPosition(end)
        });
    },

    /**
     * Returns current line's start and end indexes as object with start
     * and end properties
     * @return {Object}
     * @example
     * var range = editor.getCurrentLineRange();
     * alert(range.start + ', ' + range.end);
     */
    getCurrentLineRange: function() {
        var ace = this.ace;
        var row = ace.getCursorPosition().row;
        var lineLength = ace.session.getLine(row).length;
        var index = ace.session.doc.positionToIndex({row: row, column: 0});
        return {
            start: index,
            end: index + lineLength
        };
    },

    /**
     * Returns current caret position
     * @return {Number|null}
     */
    getCaretPos: function(){
        var pos = this.ace.getCursorPosition();
        return this.ace.session.doc.positionToIndex(pos);
    },

    /**
     * Set new caret position
     * @param {Number} index Caret position
     */
    setCaretPos: function(index){
        var pos = this.ace.session.doc.indexToPosition(index);
        this.ace.selection.moveToPosition(pos);
    },

    /**
     * Returns content of current line
     * @return {String}
     */
    getCurrentLine: function() {
        var row = this.ace.getCursorPosition().row;
        return this.ace.session.getLine(row);
    },

    /**
     * Replace editor's content or it's part (from start to
     * end index). If value contains
     * caret_placeholder, the editor will put caret into
     * this position. If you skip start and end
     * arguments, the whole target's content will be replaced with
     * value.
     *
     * If you pass start argument only,
     * the value will be placed at start string
     * index of current content.
     *
     * If you pass start and end arguments,
     * the corresponding substring of current target's content will be
     * replaced with value.
     * @param {String} value Content you want to paste
     * @param {Number} [start] Start index of editor's content
     * @param {Number} [end] End index of editor's content
     * @param {Boolean} [noIndent] Do not auto indent value
     */
    replaceContent: function(value, start, end, noIndent) {
        if (end == null)
            end = start == null ? this.getContent().length : start;
        if (start == null)
            start = 0;

        var editor = this.ace;
        var doc = editor.session.doc;
        var range = Range.fromPoints(doc.indexToPosition(start), doc.indexToPosition(end));
        editor.session.remove(range);

        range.end = range.start;
        //editor.selection.setRange(range);

        value = this.$updateTabstops(value);
        snippetManager.insertSnippet(editor, value);
    },

    /**
     * Returns editor's content
     * @return {String}
     */
    getContent: function(){
        return this.ace.getValue();
    },

    /**
     * Returns current editor's syntax mode
     * @return {String}
     */
    getSyntax: function() {
        if (this.$syntax)
            return this.$syntax;
        var syntax = this.ace.session.$modeId.split("/").pop();
        if (syntax == "html" || syntax == "php") {
            var cursor = this.ace.getCursorPosition();
            var state = this.ace.session.getState(cursor.row);
            if (typeof state != "string")
                state = state[0];
            if (state) {
                state = state.split("-");
                if (state.length > 1)
                    syntax = state[0];
                else if (syntax == "php")
                    syntax = "html";
            }
        }
        return syntax;
    },

    /**
     * Returns current output profile name (@see emmet#setupProfile)
     * @return {String}
     */
    getProfileName: function() {
        switch(this.getSyntax()) {
          case "css": return "css";
          case "xml":
          case "xsl":
            return "xml";
          case "html":
            var profile = emmet.require("resources").getVariable("profile");
            // no forced profile, guess from content html or xhtml?
            if (!profile)
                profile = this.ace.session.getLines(0,2).join("").search(/<!DOCTYPE[^>]+XHTML/i) != -1 ? "xhtml": "html";
            return profile;
        }
        return "xhtml";
    },

    /**
     * Ask user to enter something
     * @param {String} title Dialog title
     * @return {String} Entered data
     * @since 0.65
     */
    prompt: function(title) {
        return prompt(title);
    },

    /**
     * Returns current selection
     * @return {String}
     * @since 0.65
     */
    getSelection: function() {
        return this.ace.session.getTextRange();
    },

    /**
     * Returns current editor's file path
     * @return {String}
     * @since 0.65
     */
    getFilePath: function() {
        return "";
    },

    // update tabstops: make sure all caret placeholders are unique
    // by default, abbreviation parser generates all unlinked (un-mirrored)
    // tabstops as ${0}, so we have upgrade all caret tabstops with unique
    // positions but make sure that all other tabstops are not linked accidentally
    // based on https://github.com/sergeche/emmet-sublime/blob/master/editor.js#L119-L171
    $updateTabstops: function(value) {
        var base = 1000;
        var zeroBase = 0;
        var lastZero = null;
        var range = emmet.require('range');
        var ts = emmet.require('tabStops');
        var settings = emmet.require('resources').getVocabulary("user");
        var tabstopOptions = {
            tabstop: function(data) {
                var group = parseInt(data.group, 10);
                var isZero = group === 0;
                if (isZero)
                    group = ++zeroBase;
                else
                    group += base;

                var placeholder = data.placeholder;
                if (placeholder) {
                    // recursively update nested tabstops
                    placeholder = ts.processText(placeholder, tabstopOptions);
                }

                var result = '${' + group + (placeholder ? ':' + placeholder : '') + '}';

                if (isZero) {
                    lastZero = range.create(data.start, result);
                }

                return result;
            },
            escape: function(ch) {
                if (ch == '$') return '\\$';
                if (ch == '\\') return '\\\\';
                return ch;
            }
        };

        value = ts.processText(value, tabstopOptions);

        if (settings.variables['insert_final_tabstop'] && !/\$\{0\}$/.test(value)) {
            value += '${0}';
        } else if (lastZero) {
            value = emmet.require('utils').replaceSubstring(value, '${0}', lastZero);
        }

        return value;
    }
};

var keymap = {
    expand_abbreviation: {"mac": "ctrl+alt+e", "win": "alt+e"},
    match_pair_outward: {"mac": "ctrl+d", "win": "ctrl+,"},
    match_pair_inward: {"mac": "ctrl+j", "win": "ctrl+shift+0"},
    matching_pair: {"mac": "ctrl+alt+j", "win": "alt+j"},
    next_edit_point: "alt+right",
    prev_edit_point: "alt+left",
    toggle_comment: {"mac": "command+/", "win": "ctrl+/"},
    split_join_tag: {"mac": "shift+command+'", "win": "shift+ctrl+`"},
    remove_tag: {"mac": "command+'", "win": "shift+ctrl+;"},
    evaluate_math_expression: {"mac": "shift+command+y", "win": "shift+ctrl+y"},
    increment_number_by_1: "ctrl+up",
    decrement_number_by_1: "ctrl+down",
    increment_number_by_01: "alt+up",
    decrement_number_by_01: "alt+down",
    increment_number_by_10: {"mac": "alt+command+up", "win": "shift+alt+up"},
    decrement_number_by_10: {"mac": "alt+command+down", "win": "shift+alt+down"},
    select_next_item: {"mac": "shift+command+.", "win": "shift+ctrl+."},
    select_previous_item: {"mac": "shift+command+,", "win": "shift+ctrl+,"},
    reflect_css_value: {"mac": "shift+command+r", "win": "shift+ctrl+r"},

    encode_decode_data_url: {"mac": "shift+ctrl+d", "win": "ctrl+'"},
    // update_image_size: {"mac": "shift+ctrl+i", "win": "ctrl+u"},
    // expand_as_you_type: "ctrl+alt+enter",
    // wrap_as_you_type: {"mac": "shift+ctrl+g", "win": "shift+ctrl+g"},
    expand_abbreviation_with_tab: "Tab",
    wrap_with_abbreviation: {"mac": "shift+ctrl+a", "win": "shift+ctrl+a"}
};

var editorProxy = new AceEmmetEditor();
exports.commands = new HashHandler();
exports.runEmmetCommand = function(editor) {
    try {
        editorProxy.setupContext(editor);
        if (editorProxy.getSyntax() == "php")
            return false;
        var actions = emmet.require("actions");

        if (this.action == "expand_abbreviation_with_tab") {
            if (!editor.selection.isEmpty())
                return false;
        }

        if (this.action == "wrap_with_abbreviation") {
            // without setTimeout prompt doesn't work on firefox
            return setTimeout(function() {
                actions.run("wrap_with_abbreviation", editorProxy);
            }, 0);
        }

        var pos = editor.selection.lead;
        var token = editor.session.getTokenAt(pos.row, pos.column);
        if (token && /\btag\b/.test(token.type))
            return false;

        var result = actions.run(this.action, editorProxy);
    } catch(e) {
        editor._signal("changeStatus", typeof e == "string" ? e : e.message);
        console.log(e);
        result = false;
    }
    return result;
};

for (var command in keymap) {
    exports.commands.addCommand({
        name: "emmet:" + command,
        action: command,
        bindKey: keymap[command],
        exec: exports.runEmmetCommand,
        multiSelectAction: "forEach"
    });
}

exports.updateCommands = function(editor, enabled) {
    if (enabled) {
        editor.keyBinding.addKeyboardHandler(exports.commands);
    } else {
        editor.keyBinding.removeKeyboardHandler(exports.commands);
    }
};

exports.isSupportedMode = function(modeId) {
    return modeId && /css|less|scss|sass|stylus|html|php|twig|ejs/.test(modeId);
};

var onChangeMode = function(e, target) {
    var editor = target;
    if (!editor)
        return;
    var enabled = exports.isSupportedMode(editor.session.$modeId);
    if (e.enableEmmet === false)
        enabled = false;
    if (enabled) {
        if (typeof emmetPath == "string") {
            require("ace/config").loadModule(emmetPath, function() {
                emmetPath = null;
            });
        }
    }
    exports.updateCommands(editor, enabled);
};

exports.AceEmmetEditor = AceEmmetEditor;
require("ace/config").defineOptions(Editor.prototype, "editor", {
    enableEmmet: {
        set: function(val) {
            this[val ? "on" : "removeListener"]("changeMode", onChangeMode);
            onChangeMode({enableEmmet: !!val}, this);
        },
        value: true
    }
});

exports.setCore = function(e) {
    if (typeof e == "string")
       emmetPath = e;
    else
       emmet = e;
};
});

<|endoftext|>


<commit_before>var helpers = require('./helpers'),
	underscore = require('underscore');

// Utility method for creating the necessary methods on the
// resource class for calling through to the API.
//
// - @param {Mixed} apiCallDefinition - Either a string if the
// action method name is the same as the action path component
// on the underlying API call or a hash if they differ.
// - @param {APIRequest} apiRequest - The request
module.exports.create = function (apiCallDefinition, apiRequest) {
	var fnName;

	if (underscore.isString(apiCallDefinition)) {
		fnName = 'get' + helpers.capitalize(apiCallDefinition);
	} else {
		fnName = apiCallDefinition.methodName;
		apiCallDefinition = apiCallDefinition.apiCall;
	}

	return function (params, callback) {
		var self = this;

		this.doGetRequest(apiCallDefinition, params,
			underscore.bind(self.parseResponse, self, callback));
	};
};
<commit_msg>Removed unused actionfactory<commit_after><|endoftext|>


<commit_before>/**
 * XadillaX created at 2016-08-08 17:04:49 With ♥
 *
 * Copyright (c) 2016 Souche.com, all rights
 * reserved.
 */
"use strict";

const debug = require("debug")("toshihiko:adapter:base");
const EventEmitter = require("eventemitter2").EventEmitter2;

const common = require("../../util/common");

class Adapter extends EventEmitter {
    constructor(parent, options) {
        super();

        Object.defineProperties(this, {
            parent: {
                value: parent,
                writable: false,
                configurable: false,
                enumerable: false
            },
            options: {
                value: common.extend({}, options || {}),
                writable: true,
                configurable: false,
                enumerable: true
            }
        });

        debug("created.", this);
    }

    execute(param, callback) {
        debug("this adapter instance's execute is not implemented.");
        process.nextTick(callback);
    }

    find(query, callback, options) {
        options = null;
        process.nextTick(function() {
            callback(new Error("this adapter's find function is not implemented yet."));
        });
    }

    count(query, callback) {
        process.nextTick(function() {
            callback(new Error("this adapter's count function is not implemented yet."));
        });
    }

    updateByQuery(query, callback) {
        process.nextTick(function() {
            callback(new Error("this adapter's updateByQuery function is not implemented yet."));
        });
    }

    deleteByQuery(query, callback) {
        process.nextTick(function() {
            callback(new Error("this adapter's deleteByQuery function is not implemented yet."));
        });
    }

    insert(model, data, callback) {
        process.nextTick(function() {
            callback(new Error("this adapter's insert function is not implemented yet."));
        });
    }

    update(model, pk, data, callback) {
        process.nextTick(function() {
            callback(new Error("this adapter's update function is not implemented yet."));
        });
    }

    getDBName() {
        return "";
    }
}

module.exports = Adapter;
<commit_msg>fix: update base adapter's execute<commit_after>/**
 * XadillaX created at 2016-08-08 17:04:49 With ♥
 *
 * Copyright (c) 2016 Souche.com, all rights
 * reserved.
 */
"use strict";

const debug = require("debug")("toshihiko:adapter:base");
const EventEmitter = require("eventemitter2").EventEmitter2;

const common = require("../../util/common");

class Adapter extends EventEmitter {
    constructor(parent, options) {
        super();

        Object.defineProperties(this, {
            parent: {
                value: parent,
                writable: false,
                configurable: false,
                enumerable: false
            },
            options: {
                value: common.extend({}, options || {}),
                writable: true,
                configurable: false,
                enumerable: true
            }
        });

        debug("created.", this);
    }

    find(query, callback, options) {
        options = null;
        process.nextTick(function() {
            callback(new Error("this adapter's find function is not implemented yet."));
        });
    }

    count(query, callback) {
        process.nextTick(function() {
            callback(new Error("this adapter's count function is not implemented yet."));
        });
    }

    updateByQuery(query, callback) {
        process.nextTick(function() {
            callback(new Error("this adapter's updateByQuery function is not implemented yet."));
        });
    }

    deleteByQuery(query, callback) {
        process.nextTick(function() {
            callback(new Error("this adapter's deleteByQuery function is not implemented yet."));
        });
    }

    insert(model, data, callback) {
        process.nextTick(function() {
            callback(new Error("this adapter's insert function is not implemented yet."));
        });
    }

    update(model, pk, data, callback) {
        process.nextTick(function() {
            callback(new Error("this adapter's update function is not implemented yet."));
        });
    }

    execute() {
        const callback = arguments[arguments.length - 1];
        process.nextTick(function() {
            callback(new Error("this adapter's execute function is not implemented yet."));
        });
    }

    getDBName() {
        return "";
    }
}

module.exports = Adapter;
<|endoftext|>


<commit_before>contract("Standard_Token", function(accounts) {

/*CREATION*/

    it("creation: should create an initial balance of 10000 for the creator", function(done) {
        Standard_Token.new(10000, {from: accounts[0]}).then(function(ctr) {
            return ctr.balanceOf.call(accounts[0]);
    }).then(function (result) {
        assert.strictEqual(result.c[0], 10000);
        done();
        }).catch(done);
    });

    it("creation: should succeed in creating over 2^256 - 1 (max) tokens", function(done) {
        //2^256 - 1
        Standard_Token.new('115792089237316195423570985008687907853269984665640564039457584007913129639935', {from: accounts[0]}).then(function(ctr) {
            return ctr.totalSupply();
    }).then(function (result) {
        var match = result.equals('1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77');
        assert.isTrue(match);
        done();
        }).catch(done);
    });

/*TRANSERS*/
//normal transfers without approvals.

    it("transfers: should transfer 10000 to accounts[1] with accounts[0] having 10000", function(done) {
        var ctr;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.transfer(accounts[1], 10000, {from: accounts[0]});
        }).then(function (result) {
            return ctr.balanceOf.call(accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 10000);
            done();
        }).catch(done);
    });

    it("transfers: should fail when trying to transfer 10001 to accounts[1] with accounts[0] having 10000", function(done) {
        var ctr;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.transfer.call(accounts[1], 10001, {from: accounts[0]});
        }).then(function (result) {
            assert.isFalse(result);
            done();
        }).catch(done);
    });

    it("transfers: should fail when trying to transfer zero.", function(done) {
        var ctr;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.transfer.call(accounts[1], 0, {from: accounts[0]});
        }).then(function (result) {
            assert.isFalse(result);
            done();
        }).catch(done);
    });

    //NOTE: testing uint256 wrapping is impossible in this standard token since you can't supply > 2^256 -1.

    //todo: transfer max amounts.

/*APPROVALS*/

    it("approvals: msg.sender should approve 100 to accounts[1]", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1], 100, {from: accounts[0]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 100);
            done();
        }).catch(done);
    });

    //bit overkill. But is for testing a bug
    it("approvals: msg.sender approves accounts[1] of 100 & withdraws 20 once.", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.balanceOf.call(accounts[0]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 10000);
            return ctr.approve(accounts[1], 100, {from: accounts[0]});
        }).then(function (result) {
            return ctr.balanceOf.call(accounts[2]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 0);
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 100);
            return ctr.transferFrom.call(accounts[0], accounts[2], 20, {from: accounts[1]});
        }).then(function (result) {
            return ctr.transferFrom(accounts[0], accounts[2], 20, {from: accounts[1]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 80);
            return ctr.balanceOf.call(accounts[2]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 20);
            return ctr.balanceOf.call(accounts[0]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 9980);
            done();
        }).catch(done);
    });

    //should approve 100 of msg.sender & withdraw 50, twice. (should succeed)
    it("approvals: msg.sender approves accounts[1] of 100 & withdraws 20 twice.", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1], 100, {from: accounts[0]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 100);
            return ctr.transferFrom(accounts[0], accounts[2], 20, {from: accounts[1]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 80);
            return ctr.balanceOf.call(accounts[2]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 20);
            return ctr.balanceOf.call(accounts[0]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 9980);
            //FIRST tx done.
            //onto next.
            return ctr.transferFrom(accounts[0], accounts[2], 20, {from: accounts[1]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 60);
            return ctr.balanceOf.call(accounts[2]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 40);
            return ctr.balanceOf.call(accounts[0]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 9960);
            done();
        }).catch(done);
    });

    //should approve 100 of msg.sender & withdraw 50 & 60 (should fail).
    it("approvals: msg.sender approves accounts[1] of 100 & withdraws 50 & 60 (2nd tx should fail)", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1], 100, {from: accounts[0]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 100);
            return ctr.transferFrom(accounts[0], accounts[2], 50, {from: accounts[1]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 50);
            return ctr.balanceOf.call(accounts[2]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 50);
            return ctr.balanceOf.call(accounts[0]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 9950);
            //FIRST tx done.
            //onto next.
            return ctr.transferFrom.call(accounts[0], accounts[2], 60, {from: accounts[1]});
        }).then(function (result) {
            assert.isFalse(result);
            done();
        }).catch(done);
    });

    it("approvals: attempt withdrawal from acconut with no allowance (should fail)", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.transferFrom.call(accounts[0], accounts[2], 60, {from: accounts[1]});
        }).then(function (result) {
              assert.isFalse(result);
              done();
        }).catch(done);
    });

    it("approvals: allow accounts[1] 100 to withdraw from accounts[0]. Withdraw 60 and then unapprove.", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1], 100, {from: accounts[0]});
        }).then(function (result) {
            return ctr.transferFrom(accounts[0], accounts[2], 60, {from: accounts[1]});
        }).then(function (result) {
            return ctr.unapprove(accounts[1], {from: accounts[0]});
        }).then(function (result) {
            return ctr.transferFrom.call(accounts[0], accounts[2], 10, {from: accounts[1]});
        }).then(function (result) {
              assert.isFalse(result);
              done();
        }).catch(done);
    });

    it("approvals: approve max (2^256 - 1)", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1],'115792089237316195423570985008687907853269984665640564039457584007913129639935' , {from: accounts[0]});
        }).then(function (result) {
            return ctr.allowance(accounts[0], accounts[1]);
        }).then(function (result) {
            var match = result.equals('1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77');
            assert.isTrue(match);
            done();
        }).catch(done);
    });

    it("approvals: approve max (2^256 - 1) & try to approve more (should fail)", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1],'115792089237316195423570985008687907853269984665640564039457584007913129639935' , {from: accounts[0]});
        }).then(function (result) {
            return ctr.allowance(accounts[0], accounts[1]);
        }).then(function (result) {
            var match = result.equals('1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77');
            assert.isTrue(match);
            return ctr.approve.call(accounts[1], 1, {from: accounts[0]});
        }).then(function (result) {
            assert.isFalse(result);
            done();
        }).catch(done);

    });

    //todo, max approvals.

});
<commit_msg>test description embellished<commit_after>contract("Standard_Token", function(accounts) {

/*CREATION*/

    it("creation: should create an initial balance of 10000 for the creator", function(done) {
        Standard_Token.new(10000, {from: accounts[0]}).then(function(ctr) {
            return ctr.balanceOf.call(accounts[0]);
    }).then(function (result) {
        assert.strictEqual(result.c[0], 10000);
        done();
        }).catch(done);
    });

    it("creation: should succeed in creating over 2^256 - 1 (max) tokens", function(done) {
        //2^256 - 1
        Standard_Token.new('115792089237316195423570985008687907853269984665640564039457584007913129639935', {from: accounts[0]}).then(function(ctr) {
            return ctr.totalSupply();
    }).then(function (result) {
        var match = result.equals('1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77');
        assert.isTrue(match);
        done();
        }).catch(done);
    });

/*TRANSERS*/
//normal transfers without approvals.

    it("transfers: should transfer 10000 to accounts[1] with accounts[0] having 10000", function(done) {
        var ctr;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.transfer(accounts[1], 10000, {from: accounts[0]});
        }).then(function (result) {
            return ctr.balanceOf.call(accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 10000);
            done();
        }).catch(done);
    });

    it("transfers: should fail when trying to transfer 10001 to accounts[1] with accounts[0] having 10000", function(done) {
        var ctr;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.transfer.call(accounts[1], 10001, {from: accounts[0]});
        }).then(function (result) {
            assert.isFalse(result);
            done();
        }).catch(done);
    });

    it("transfers: should fail when trying to transfer zero.", function(done) {
        var ctr;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.transfer.call(accounts[1], 0, {from: accounts[0]});
        }).then(function (result) {
            assert.isFalse(result);
            done();
        }).catch(done);
    });

    //NOTE: testing uint256 wrapping is impossible in this standard token since you can't supply > 2^256 -1.

    //todo: transfer max amounts.

/*APPROVALS*/

    it("approvals: msg.sender should approve 100 to accounts[1]", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1], 100, {from: accounts[0]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 100);
            done();
        }).catch(done);
    });

    //bit overkill. But is for testing a bug
    it("approvals: msg.sender approves accounts[1] of 100 & withdraws 20 once.", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.balanceOf.call(accounts[0]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 10000);
            return ctr.approve(accounts[1], 100, {from: accounts[0]});
        }).then(function (result) {
            return ctr.balanceOf.call(accounts[2]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 0);
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 100);
            return ctr.transferFrom.call(accounts[0], accounts[2], 20, {from: accounts[1]});
        }).then(function (result) {
            return ctr.transferFrom(accounts[0], accounts[2], 20, {from: accounts[1]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 80);
            return ctr.balanceOf.call(accounts[2]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 20);
            return ctr.balanceOf.call(accounts[0]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 9980);
            done();
        }).catch(done);
    });

    //should approve 100 of msg.sender & withdraw 50, twice. (should succeed)
    it("approvals: msg.sender approves accounts[1] of 100 & withdraws 20 twice.", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1], 100, {from: accounts[0]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 100);
            return ctr.transferFrom(accounts[0], accounts[2], 20, {from: accounts[1]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 80);
            return ctr.balanceOf.call(accounts[2]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 20);
            return ctr.balanceOf.call(accounts[0]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 9980);
            //FIRST tx done.
            //onto next.
            return ctr.transferFrom(accounts[0], accounts[2], 20, {from: accounts[1]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 60);
            return ctr.balanceOf.call(accounts[2]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 40);
            return ctr.balanceOf.call(accounts[0]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 9960);
            done();
        }).catch(done);
    });

    //should approve 100 of msg.sender & withdraw 50 & 60 (should fail).
    it("approvals: msg.sender approves accounts[1] of 100 & withdraws 50 & 60 (2nd tx should fail)", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1], 100, {from: accounts[0]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 100);
            return ctr.transferFrom(accounts[0], accounts[2], 50, {from: accounts[1]});
        }).then(function (result) {
            return ctr.allowance.call(accounts[0], accounts[1]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 50);
            return ctr.balanceOf.call(accounts[2]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 50);
            return ctr.balanceOf.call(accounts[0]);
        }).then(function (result) {
            assert.strictEqual(result.c[0], 9950);
            //FIRST tx done.
            //onto next.
            return ctr.transferFrom.call(accounts[0], accounts[2], 60, {from: accounts[1]});
        }).then(function (result) {
            assert.isFalse(result);
            done();
        }).catch(done);
    });

    it("approvals: attempt withdrawal from acconut with no allowance (should fail)", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.transferFrom.call(accounts[0], accounts[2], 60, {from: accounts[1]});
        }).then(function (result) {
              assert.isFalse(result);
              done();
        }).catch(done);
    });

    it("approvals: allow accounts[1] 100 to withdraw from accounts[0]. Withdraw 60 and then unapprove.", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1], 100, {from: accounts[0]});
        }).then(function (result) {
            return ctr.transferFrom(accounts[0], accounts[2], 60, {from: accounts[1]});
        }).then(function (result) {
            return ctr.unapprove(accounts[1], {from: accounts[0]});
        }).then(function (result) {
            return ctr.transferFrom.call(accounts[0], accounts[2], 10, {from: accounts[1]});
        }).then(function (result) {
              assert.isFalse(result);
              done();
        }).catch(done);
    });

    it("approvals: approve max (2^256 - 1)", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1],'115792089237316195423570985008687907853269984665640564039457584007913129639935' , {from: accounts[0]});
        }).then(function (result) {
            return ctr.allowance(accounts[0], accounts[1]);
        }).then(function (result) {
            var match = result.equals('1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77');
            assert.isTrue(match);
            done();
        }).catch(done);
    });

    it("approvals: approve max (2^256 - 1) & try to approve 1 more (should fail)", function(done) {
        var ctr = null;
        Standard_Token.new(10000, {from: accounts[0]}).then(function(result) {
            ctr = result;
            return ctr.approve(accounts[1],'115792089237316195423570985008687907853269984665640564039457584007913129639935' , {from: accounts[0]});
        }).then(function (result) {
            return ctr.allowance(accounts[0], accounts[1]);
        }).then(function (result) {
            var match = result.equals('1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77');
            assert.isTrue(match);
            return ctr.approve.call(accounts[1], 1, {from: accounts[0]});
        }).then(function (result) {
            assert.isFalse(result);
            done();
        }).catch(done);

    });

    //todo, max approvals.

});
<|endoftext|>


<commit_before>'use strict';

var ono = require('ono');
var File = require('./File');
var assign = require('../util/assign');
var __internal = require('../util/internal');

module.exports = FileArray;

/**
 * An array of {@link File} objects, with some helper methods.
 *
 * @param {Schema} schema - The JSON Schema that these files are part of
 *
 * @class
 * @extends Array
 */
function FileArray (schema) {
  var fileArray = [];

  /**
   * Internal stuff. Use at your own risk!
   *
   * @private
   */
  fileArray[__internal] = {
    /**
     * A reference to the {@link Schema} object
     */
    schema: schema,
  };

  // Return an array that "inherits" from FileArray
  return assign(fileArray, FileArray.prototype);
}

/**
 * Determines whether a given file is in the array.
 *
 * @param {string|File} url
 * An absolute URL, or a relative URL (relative to the schema's root file), or a {@link File} object
 *
 * @returns {boolean}
 */
FileArray.prototype.exists = function exists (url) {
  if (this.length === 0) {
    return false;
  }

  // Get the absolute URL
  var absoluteURL = resolveURL(url, this[__internal].schema);

  // Try to find a file with this URL
  for (var i = 0; i < this.length; i++) {
    var file = this[i];
    if (file.url === absoluteURL) {
      return true;
    }
  }

  // If we get here, thne no files matched the URL
  return false;
};

/**
 * Returns the given file in the array. Throws an error if not found.
 *
 * @param {string|File} url
 * An absolute URL, or a relative URL (relative to the schema's root file), or a {@link File} object
 *
 * @returns {File}
 */
FileArray.prototype.get = function get (url) {
  if (this.length === 0) {
    throw ono('Unable to get %s. \nThe schema is empty.', url);
  }

  // Get the absolute URL
  var absoluteURL = resolveURL(url, this[__internal].schema);

  // Try to find a file with this URL
  for (var i = 0; i < this.length; i++) {
    var file = this[i];
    if (file.url === absoluteURL) {
      return file;
    }
  }

  // If we get here, then no files matched the URL
  throw ono('Unable to get %s. \nThe schema does not include this file.', absoluteURL);
};

/**
 * Resolves the given URL to an absolute URL.
 *
 * @param {string|File} url
 * An absolute URL, or a relative URL (relative to the schema's root file), or a {@link File} object
 *
 * @param {Schema} schema
 * @returns {boolean}
 */
function resolveURL (url, schema) {
  if (url instanceof File) {
    // The URL is already absolute
    return url.url;
  }

  return schema.plugins.resolveURL({ from: schema.rootURL, to: url });
}
<commit_msg>Refactored the `FileArray.exists()` and `FileArray.get()` methods<commit_after>'use strict';

var ono = require('ono');
var File = require('./File');
var assign = require('../util/assign');
var __internal = require('../util/internal');

module.exports = FileArray;

/**
 * An array of {@link File} objects, with some helper methods.
 *
 * @param {Schema} schema - The JSON Schema that these files are part of
 *
 * @class
 * @extends Array
 */
function FileArray (schema) {
  var fileArray = [];

  /**
   * Internal stuff. Use at your own risk!
   *
   * @private
   */
  fileArray[__internal] = {
    /**
     * A reference to the {@link Schema} object
     */
    schema: schema,
  };

  // Return an array that "inherits" from FileArray
  return assign(fileArray, FileArray.prototype);
}

/**
 * Determines whether a given file is in the array.
 *
 * @param {string|File} url
 * An absolute URL, or a relative URL (relative to the schema's root file), or a {@link File} object
 *
 * @returns {boolean}
 */
FileArray.prototype.exists = function exists (url) {
  var file = findFile(this[__internal].schema, url);
  return !!file;
};

/**
 * Returns the given file in the array. Throws an error if not found.
 *
 * @param {string|File} url
 * An absolute URL, or a relative URL (relative to the schema's root file), or a {@link File} object
 *
 * @returns {File}
 */
FileArray.prototype.get = function get (url) {
  var file = findFile(this[__internal].schema, url);

  if (file) {
    return file;
  }

  if (this.length === 0) {
    throw ono('Unable to get %s. \nThe schema is empty.', url);
  }
  else {
    throw ono('Unable to get %s. \nThe schema does not include this file.', url);
  }
};

/**
 * Finds the given file in the schema
 *
 * @param {Schema} schema
 * @param {string|File} url
 * An absolute URL, or a relative URL (relative to the schema's root file), or a {@link File} object
 *
 * @returns {?File}
 */
function findFile (schema, url) {
  if (schema.files.length === 0) {
    return null;
  }

  if (url instanceof File) {
    // Short-circuit behavior for File obejcts
    return findByURL(schema.files, url.url);
  }

  // Try to find an exact URL match
  var file = findByURL(schema.files, url);

  if (!file) {
    // Resolve the URL and see if we can find a match
    var absoluteURL = schema.plugins.resolveURL({ from: schema.rootURL, to: url });
    file = findByURL(schema.files, absoluteURL);
  }

  return file;
}

/**
 * Finds a file by its exact URL
 *
 * @param {FileArray} files
 * @param {string} url
 * @returns {?File}
 */
function findByURL (files, url) {
  for (var i = 0; i < files.length; i++) {
    var file = files[i];
    if (file.url === url) {
      return file;
    }
  }
}
<|endoftext|>


<commit_before>const { errors, s3middleware } = require('arsenal');
const { parseRange } = require('arsenal/lib/network/http/utils');

const { data } = require('../data/wrapper');

const { decodeVersionId } = require('./apiUtils/object/versioning');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const collectResponseHeaders = require('../utilities/collectResponseHeaders');
const { pushMetric } = require('../utapi/utilities');
const { getVersionIdResHeader } = require('./apiUtils/object/versioning');
const setPartRanges = require('./apiUtils/object/setPartRanges');
const locationHeaderCheck =
    require('./apiUtils/object/locationHeaderCheck');
const getReplicationBackendDataLocator =
    require('./apiUtils/object/getReplicationBackendDataLocator');
const checkReadLocation = require('./apiUtils/object/checkReadLocation');

const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const { config } = require('../Config');
const monitoring = require('../utilities/monitoringHandler');
const { getPartCountFromMd5 } = require('./apiUtils/object/partInfo');

const validateHeaders = s3middleware.validateConditionalHeaders;

/**
 * GET Object - Get an object
 * @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
 * @param {object} request - normalized request object
 * @param {boolean} returnTagCount - returns the x-amz-tagging-count header
 * @param {object} log - Werelogs instance
 * @param {function} callback - callback to function in route
 * @return {undefined}
 */
function objectGet(authInfo, request, returnTagCount, log, callback) {
    log.debug('processing request', { method: 'objectGet' });
    const bucketName = request.bucketName;
    const objectKey = request.objectKey;

    // returns name of location to get from and key if successful
    const locCheckResult =
        locationHeaderCheck(request.headers, objectKey, bucketName);
    if (locCheckResult instanceof Error) {
        log.trace('invalid location constraint to get from', {
            location: request.headers['x-amz-location-constraint'],
            error: locCheckResult,
        });
        return callback(locCheckResult);
    }

    const decodedVidResult = decodeVersionId(request.query);
    if (decodedVidResult instanceof Error) {
        log.trace('invalid versionId query', {
            versionId: request.query.versionId,
            error: decodedVidResult,
        });
        return callback(decodedVidResult);
    }
    const versionId = decodedVidResult;

    const mdValParams = {
        authInfo,
        bucketName,
        objectKey,
        versionId,
        requestType: 'objectGet',
    };

    return metadataValidateBucketAndObj(mdValParams, log,
    (err, bucket, objMD) => {
        const corsHeaders = collectCorsHeaders(request.headers.origin,
            request.method, bucket);
        if (err) {
            log.debug('error processing request', {
                error: err,
                method: 'metadataValidateBucketAndObj',
            });
            monitoring.promMetrics(
                'GET', bucketName, err.code, 'getObject');
            return callback(err, null, corsHeaders);
        }
        if (!objMD) {
            const err = versionId ? errors.NoSuchVersion : errors.NoSuchKey;
            monitoring.promMetrics(
                'GET', bucketName, err.code, 'getObject');
            return callback(err, null, corsHeaders);
        }
        const verCfg = bucket.getVersioningConfiguration();
        if (objMD.isDeleteMarker) {
            const responseMetaHeaders = Object.assign({},
                { 'x-amz-delete-marker': true }, corsHeaders);
            if (!versionId) {
                monitoring.promMetrics(
                    'GET', bucketName, 404, 'getObject');
                return callback(errors.NoSuchKey, null, responseMetaHeaders);
            }
            // return MethodNotAllowed if requesting a specific
            // version that has a delete marker
            responseMetaHeaders['x-amz-version-id'] =
                getVersionIdResHeader(verCfg, objMD);
            monitoring.promMetrics(
                'GET', bucketName, 405, 'getObject');
            return callback(errors.MethodNotAllowed, null,
                responseMetaHeaders);
        }
        const headerValResult = validateHeaders(request.headers,
            objMD['last-modified'], objMD['content-md5']);
        if (headerValResult.error) {
            return callback(headerValResult.error, null, corsHeaders);
        }
        const responseMetaHeaders = collectResponseHeaders(objMD,
            corsHeaders, verCfg, returnTagCount);

        const objLength = (objMD.location === null ?
                           0 : parseInt(objMD['content-length'], 10));
        let byteRange;
        const streamingParams = {};
        if (request.headers.range) {
            const { range, error } = parseRange(request.headers.range,
                                                objLength);
            if (error) {
                monitoring.promMetrics(
                    'GET', bucketName, 400, 'getObject');
                return callback(error, null, corsHeaders);
            }
            responseMetaHeaders['Accept-Ranges'] = 'bytes';
            if (range) {
                byteRange = range;
                // End of range should be included so + 1
                responseMetaHeaders['Content-Length'] =
                    range[1] - range[0] + 1;
                responseMetaHeaders['Content-Range'] =
                    `bytes ${range[0]}-${range[1]}/${objLength}`;
                streamingParams.rangeStart = range[0] ?
                range[0].toString() : undefined;
                streamingParams.rangeEnd = range[1] ?
                range[1].toString() : undefined;
            }
        }
        let dataLocator = null;
        if (objMD.location !== null) {
            // To provide for backwards compatibility before
            // md-model-version 2, need to handle cases where
            // objMD.location is just a string
            dataLocator = Array.isArray(objMD.location) ?
                objMD.location : [{ key: objMD.location }];

            const repConf = bucket.getReplicationConfiguration();
            const prefReadLocation = repConf && repConf.preferredReadLocation;
            const prefReadDataLocator = checkReadLocation(config,
                prefReadLocation, objectKey, bucketName);
            const targetLocation = locCheckResult || prefReadDataLocator ||
                null;

            if (targetLocation &&
                targetLocation.location !== objMD.dataStoreName) {
                const repBackendResult = getReplicationBackendDataLocator(
                    targetLocation, objMD.replicationInfo);
                if (repBackendResult.error) {
                    log.error('Error with location constraint header', {
                        bucketName, objectKey, versionId,
                        error: repBackendResult.error,
                        status: repBackendResult.status,
                    });
                    return callback(repBackendResult.error, null, corsHeaders);
                }
                const targetDataLocator = repBackendResult.dataLocator;
                if (targetDataLocator) {
                    dataLocator = targetDataLocator;
                } else {
                    log.debug('using source location as preferred read ' +
                              'is unavailable', {
                                  bucketName, objectKey, versionId,
                                  reason: repBackendResult.reason,
                              });
                }
            }
            // if the data backend is azure, there will only ever be at
            // most one item in the dataLocator array
            if (dataLocator[0] && dataLocator[0].dataStoreType === 'azure') {
                dataLocator[0].azureStreamingOptions = streamingParams;
            }

            let partNumber = null;
            if (request.query && request.query.partNumber !== undefined) {
                if (byteRange) {
                    const error = errors.InvalidRequest
                        .customizeDescription('Cannot specify both Range ' +
                            'header and partNumber query parameter.');
                    monitoring.promMetrics(
                        'GET', bucketName, 400, 'getObject');
                    return callback(error, null, corsHeaders);
                }
                partNumber = Number.parseInt(request.query.partNumber, 10);
                if (Number.isNaN(partNumber)) {
                    const error = errors.InvalidArgument
                        .customizeDescription('Part number must be a number.');
                    monitoring.promMetrics(
                        'GET', bucketName, 400, 'getObject');
                    return callback(error, null, corsHeaders);
                }
                if (partNumber < 1 || partNumber > 10000) {
                    const error = errors.InvalidArgument
                        .customizeDescription('Part number must be an ' +
                            'integer between 1 and 10000, inclusive.');
                    monitoring.promMetrics(
                        'GET', bucketName, 400, 'getObject');
                    return callback(error, null, corsHeaders);
                }
            }
            // If have a data model before version 2, cannot support
            // get range for objects with multiple parts
            if (byteRange && dataLocator.length > 1 &&
                dataLocator[0].start === undefined) {
                monitoring.promMetrics(
                    'GET', bucketName, 501, 'getObject');
                return callback(errors.NotImplemented, null, corsHeaders);
            }
            if (objMD['x-amz-server-side-encryption']) {
                for (let i = 0; i < dataLocator.length; i++) {
                    dataLocator[i].masterKeyId =
                        objMD['x-amz-server-side-encryption-aws-kms-key-id'];
                    dataLocator[i].algorithm =
                        objMD['x-amz-server-side-encryption'];
                }
            }
            if (partNumber) {
                const locations = [];
                let locationPartNumber;
                for (let i = 0; i < objMD.location.length; i++) {
                    const { dataStoreETag } = objMD.location[i];

                    if (dataStoreETag) {
                        locationPartNumber =
                            Number.parseInt(dataStoreETag.split(':')[0], 10);
                    } else {
                        /**
                         * Location objects prior to GA7.1 do not include the
                         * dataStoreETag field so we cannot find the part range,
                         * the objects are treated as if they only have 1 part
                         */
                        locationPartNumber = 1;
                    }

                    // Get all parts that belong to the requested part number
                    if (partNumber === locationPartNumber) {
                        locations.push(objMD.location[i]);
                    } else if (locationPartNumber > partNumber) {
                        break;
                    }
                }
                if (locations.length === 0) {
                    monitoring.promMetrics(
                        'GET', bucketName, 400, 'getObject');
                    return callback(errors.InvalidPartNumber, null,
                        corsHeaders);
                }
                const { start } = locations[0];
                const endLocation = locations[locations.length - 1];
                const end = endLocation.start + endLocation.size - 1;
                responseMetaHeaders['Content-Length'] = end - start + 1;
                const partByteRange = [start, end];
                dataLocator = setPartRanges(dataLocator, partByteRange);
                const partsCount = getPartCountFromMd5(objMD);
                if (partsCount) {
                    responseMetaHeaders['x-amz-mp-parts-count'] =
                        partsCount;
                }
            } else {
                dataLocator = setPartRanges(dataLocator, byteRange);
            }
        }
        return data.head(dataLocator, log, err => {
            if (err) {
                if (!err.LocationNotFound) {
                    log.error('error from external backend checking for ' +
                              'object existence', { error: err });
                }
                monitoring.promMetrics(
                    'GET', bucketName, err.code, 'getObject');
                return callback(err);
            }
            pushMetric('getObject', log, {
                authInfo,
                bucket: bucketName,
                keys: [objectKey],
                newByteLength:
                    Number.parseInt(responseMetaHeaders['Content-Length'], 10),
                versionId: objMD.versionId,
                location: objMD.dataStoreName,
            });
            monitoring.promMetrics('GET', bucketName, '200', 'getObject',
                Number.parseInt(responseMetaHeaders['Content-Length'], 10));
            return callback(null, dataLocator, responseMetaHeaders,
                byteRange);
        });
    });
}

module.exports = objectGet;
<commit_msg>bugfix: ZENKO-3241: considering range start 0 as valid<commit_after>const { errors, s3middleware } = require('arsenal');
const { parseRange } = require('arsenal/lib/network/http/utils');

const { data } = require('../data/wrapper');

const { decodeVersionId } = require('./apiUtils/object/versioning');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const collectResponseHeaders = require('../utilities/collectResponseHeaders');
const { pushMetric } = require('../utapi/utilities');
const { getVersionIdResHeader } = require('./apiUtils/object/versioning');
const setPartRanges = require('./apiUtils/object/setPartRanges');
const locationHeaderCheck =
    require('./apiUtils/object/locationHeaderCheck');
const getReplicationBackendDataLocator =
    require('./apiUtils/object/getReplicationBackendDataLocator');
const checkReadLocation = require('./apiUtils/object/checkReadLocation');

const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const { config } = require('../Config');
const monitoring = require('../utilities/monitoringHandler');
const { getPartCountFromMd5 } = require('./apiUtils/object/partInfo');

const validateHeaders = s3middleware.validateConditionalHeaders;

/**
 * GET Object - Get an object
 * @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
 * @param {object} request - normalized request object
 * @param {boolean} returnTagCount - returns the x-amz-tagging-count header
 * @param {object} log - Werelogs instance
 * @param {function} callback - callback to function in route
 * @return {undefined}
 */
function objectGet(authInfo, request, returnTagCount, log, callback) {
    log.debug('processing request', { method: 'objectGet' });
    const bucketName = request.bucketName;
    const objectKey = request.objectKey;

    // returns name of location to get from and key if successful
    const locCheckResult =
        locationHeaderCheck(request.headers, objectKey, bucketName);
    if (locCheckResult instanceof Error) {
        log.trace('invalid location constraint to get from', {
            location: request.headers['x-amz-location-constraint'],
            error: locCheckResult,
        });
        return callback(locCheckResult);
    }

    const decodedVidResult = decodeVersionId(request.query);
    if (decodedVidResult instanceof Error) {
        log.trace('invalid versionId query', {
            versionId: request.query.versionId,
            error: decodedVidResult,
        });
        return callback(decodedVidResult);
    }
    const versionId = decodedVidResult;

    const mdValParams = {
        authInfo,
        bucketName,
        objectKey,
        versionId,
        requestType: 'objectGet',
    };

    return metadataValidateBucketAndObj(mdValParams, log,
    (err, bucket, objMD) => {
        const corsHeaders = collectCorsHeaders(request.headers.origin,
            request.method, bucket);
        if (err) {
            log.debug('error processing request', {
                error: err,
                method: 'metadataValidateBucketAndObj',
            });
            monitoring.promMetrics(
                'GET', bucketName, err.code, 'getObject');
            return callback(err, null, corsHeaders);
        }
        if (!objMD) {
            const err = versionId ? errors.NoSuchVersion : errors.NoSuchKey;
            monitoring.promMetrics(
                'GET', bucketName, err.code, 'getObject');
            return callback(err, null, corsHeaders);
        }
        const verCfg = bucket.getVersioningConfiguration();
        if (objMD.isDeleteMarker) {
            const responseMetaHeaders = Object.assign({},
                { 'x-amz-delete-marker': true }, corsHeaders);
            if (!versionId) {
                monitoring.promMetrics(
                    'GET', bucketName, 404, 'getObject');
                return callback(errors.NoSuchKey, null, responseMetaHeaders);
            }
            // return MethodNotAllowed if requesting a specific
            // version that has a delete marker
            responseMetaHeaders['x-amz-version-id'] =
                getVersionIdResHeader(verCfg, objMD);
            monitoring.promMetrics(
                'GET', bucketName, 405, 'getObject');
            return callback(errors.MethodNotAllowed, null,
                responseMetaHeaders);
        }
        const headerValResult = validateHeaders(request.headers,
            objMD['last-modified'], objMD['content-md5']);
        if (headerValResult.error) {
            return callback(headerValResult.error, null, corsHeaders);
        }
        const responseMetaHeaders = collectResponseHeaders(objMD,
            corsHeaders, verCfg, returnTagCount);

        const objLength = (objMD.location === null ?
                           0 : parseInt(objMD['content-length'], 10));
        let byteRange;
        const streamingParams = {};
        if (request.headers.range) {
            const { range, error } = parseRange(request.headers.range,
                                                objLength);
            if (error) {
                monitoring.promMetrics(
                    'GET', bucketName, 400, 'getObject');
                return callback(error, null, corsHeaders);
            }
            responseMetaHeaders['Accept-Ranges'] = 'bytes';
            if (range) {
                byteRange = range;
                // End of range should be included so + 1
                responseMetaHeaders['Content-Length'] =
                    range[1] - range[0] + 1;
                responseMetaHeaders['Content-Range'] =
                    `bytes ${range[0]}-${range[1]}/${objLength}`;
                streamingParams.rangeStart = (range[0] || typeof range[0] === 'number')  ?
                range[0].toString() : undefined;
                streamingParams.rangeEnd = range[1] ?
                range[1].toString() : undefined;
            }
        }
        let dataLocator = null;
        if (objMD.location !== null) {
            // To provide for backwards compatibility before
            // md-model-version 2, need to handle cases where
            // objMD.location is just a string
            dataLocator = Array.isArray(objMD.location) ?
                objMD.location : [{ key: objMD.location }];

            const repConf = bucket.getReplicationConfiguration();
            const prefReadLocation = repConf && repConf.preferredReadLocation;
            const prefReadDataLocator = checkReadLocation(config,
                prefReadLocation, objectKey, bucketName);
            const targetLocation = locCheckResult || prefReadDataLocator ||
                null;

            if (targetLocation &&
                targetLocation.location !== objMD.dataStoreName) {
                const repBackendResult = getReplicationBackendDataLocator(
                    targetLocation, objMD.replicationInfo);
                if (repBackendResult.error) {
                    log.error('Error with location constraint header', {
                        bucketName, objectKey, versionId,
                        error: repBackendResult.error,
                        status: repBackendResult.status,
                    });
                    return callback(repBackendResult.error, null, corsHeaders);
                }
                const targetDataLocator = repBackendResult.dataLocator;
                if (targetDataLocator) {
                    dataLocator = targetDataLocator;
                } else {
                    log.debug('using source location as preferred read ' +
                              'is unavailable', {
                                  bucketName, objectKey, versionId,
                                  reason: repBackendResult.reason,
                              });
                }
            }
            // if the data backend is azure, there will only ever be at
            // most one item in the dataLocator array
            if (dataLocator[0] && dataLocator[0].dataStoreType === 'azure') {
                dataLocator[0].azureStreamingOptions = streamingParams;
            }

            let partNumber = null;
            if (request.query && request.query.partNumber !== undefined) {
                if (byteRange) {
                    const error = errors.InvalidRequest
                        .customizeDescription('Cannot specify both Range ' +
                            'header and partNumber query parameter.');
                    monitoring.promMetrics(
                        'GET', bucketName, 400, 'getObject');
                    return callback(error, null, corsHeaders);
                }
                partNumber = Number.parseInt(request.query.partNumber, 10);
                if (Number.isNaN(partNumber)) {
                    const error = errors.InvalidArgument
                        .customizeDescription('Part number must be a number.');
                    monitoring.promMetrics(
                        'GET', bucketName, 400, 'getObject');
                    return callback(error, null, corsHeaders);
                }
                if (partNumber < 1 || partNumber > 10000) {
                    const error = errors.InvalidArgument
                        .customizeDescription('Part number must be an ' +
                            'integer between 1 and 10000, inclusive.');
                    monitoring.promMetrics(
                        'GET', bucketName, 400, 'getObject');
                    return callback(error, null, corsHeaders);
                }
            }
            // If have a data model before version 2, cannot support
            // get range for objects with multiple parts
            if (byteRange && dataLocator.length > 1 &&
                dataLocator[0].start === undefined) {
                monitoring.promMetrics(
                    'GET', bucketName, 501, 'getObject');
                return callback(errors.NotImplemented, null, corsHeaders);
            }
            if (objMD['x-amz-server-side-encryption']) {
                for (let i = 0; i < dataLocator.length; i++) {
                    dataLocator[i].masterKeyId =
                        objMD['x-amz-server-side-encryption-aws-kms-key-id'];
                    dataLocator[i].algorithm =
                        objMD['x-amz-server-side-encryption'];
                }
            }
            if (partNumber) {
                const locations = [];
                let locationPartNumber;
                for (let i = 0; i < objMD.location.length; i++) {
                    const { dataStoreETag } = objMD.location[i];

                    if (dataStoreETag) {
                        locationPartNumber =
                            Number.parseInt(dataStoreETag.split(':')[0], 10);
                    } else {
                        /**
                         * Location objects prior to GA7.1 do not include the
                         * dataStoreETag field so we cannot find the part range,
                         * the objects are treated as if they only have 1 part
                         */
                        locationPartNumber = 1;
                    }

                    // Get all parts that belong to the requested part number
                    if (partNumber === locationPartNumber) {
                        locations.push(objMD.location[i]);
                    } else if (locationPartNumber > partNumber) {
                        break;
                    }
                }
                if (locations.length === 0) {
                    monitoring.promMetrics(
                        'GET', bucketName, 400, 'getObject');
                    return callback(errors.InvalidPartNumber, null,
                        corsHeaders);
                }
                const { start } = locations[0];
                const endLocation = locations[locations.length - 1];
                const end = endLocation.start + endLocation.size - 1;
                responseMetaHeaders['Content-Length'] = end - start + 1;
                const partByteRange = [start, end];
                dataLocator = setPartRanges(dataLocator, partByteRange);
                const partsCount = getPartCountFromMd5(objMD);
                if (partsCount) {
                    responseMetaHeaders['x-amz-mp-parts-count'] =
                        partsCount;
                }
            } else {
                dataLocator = setPartRanges(dataLocator, byteRange);
            }
        }
        return data.head(dataLocator, log, err => {
            if (err) {
                if (!err.LocationNotFound) {
                    log.error('error from external backend checking for ' +
                              'object existence', { error: err });
                }
                monitoring.promMetrics(
                    'GET', bucketName, err.code, 'getObject');
                return callback(err);
            }
            pushMetric('getObject', log, {
                authInfo,
                bucket: bucketName,
                keys: [objectKey],
                newByteLength:
                    Number.parseInt(responseMetaHeaders['Content-Length'], 10),
                versionId: objMD.versionId,
                location: objMD.dataStoreName,
            });
            monitoring.promMetrics('GET', bucketName, '200', 'getObject',
                Number.parseInt(responseMetaHeaders['Content-Length'], 10));
            return callback(null, dataLocator, responseMetaHeaders,
                byteRange);
        });
    });
}

module.exports = objectGet;
<|endoftext|>