<commit_before>var CloudCommander;
var CloudFunc;

/* Key constants*/
CloudCommander.KEY = {
    TAB         : 9,
    ENTER       : 13,

    PAGE_DOWN   : 34,
    END         : 35,
    HOME        : 36,
    UP          : 38,
    DOWN        : 40,

    D           : 68,
    Q           : 81,
    R           : 82,
    S           : 83,

    F2          : 113,
    F3          : 114,
    F4          : 115,
    F10         : 121
};

CloudCommander.keyBinding = (function(){
    "use strict";

    var Util = new CloudCommander.Util();

    /* saving state of tabs varibles */
    var lTabPanel   = {
        left        : 0,
        right       : 0

    };

    var lKEY = CloudCommander.KEY;

    var key_event = function(event){
            var lCurrentFile,
                lName, i;
            /* если клавиши можно обрабатывать*/
            if(CloudCommander.keyBinded && event){
                /* если нажали таб:
                 * переносим курсор на
                 * правую панель, если
                 * мы были на левой и
                 * наоборот
                 */
                if(event.keyCode === lKEY.TAB){
                    console.log('Tab pressed');

                    try{
                        lCurrentFile = Util.getCurrentFile();

                        /* changing parent panel of curent-file */
                        var lPanel = Util.getPanel();
                        var lId = lPanel.id;

                        lTabPanel[lId] = lCurrentFile;

                        lPanel = Util.getPanel({active:false});
                        lId = lPanel.id;

                        if(lTabPanel[lId])
                            Util.setCurrentFile(lTabPanel[lId]);
                        else{
                            var lFirstFileOnList =
                                lPanel.getElementsByTagName('li')[2];

                            Util.setCurrentFile(lFirstFileOnList);
                        }

                    }catch(error){console.log(error);}

                    event.preventDefault();//запрет на дальнейшее действие
                }
                /* if f2 pressed */
                else if(event.keyCode === lKEY.F2){

                }

                /* if f3 pressed */
                else if(event.keyCode === lKEY.F3){
                    if (typeof CloudCommander.Viewer === 'function')
                        CloudCommander.Viewer();

                    event.preventDefault();//запрет на дальнейшее действие
                }

                /* if alt+f3 pressed */
                else if(event.keyCode === lKEY.F3 &&
                    event.altKey){
                        if (typeof CloudCommander.Terminal === 'function')
                            CloudCommander.Terminal();

                        event.preventDefault();//запрет на дальнейшее действие
                }

                 /* if f4 pressed */
                else if(event.keyCode === lKEY.F4) {
                    Util.Images.showLoad();

                    if (typeof CloudCommander.Editor === 'function')
                        CloudCommander.Editor();

                    event.preventDefault();//запрет на дальнейшее действие
                 }
                 else  if(event.keyCode === CloudCommander.KEY.F10 &&
                        event.shiftKey){
                            if (typeof CloudCommander.Menu === 'function')
                                CloudCommander.Menu();

                            event.preventDefault();//запрет на дальнейшее действие
                            event.returnValue = false;
                }
                /* навигация по таблице файлов*/
                /* если нажали клавишу вверх*/
                else if(event.keyCode === lKEY.UP){
                    /* получаем выдленный файл*/
                    lCurrentFile = Util.getCurrentFile();

                    /* если ненайдены выделенные файлы - выходим*/
                    if(!lCurrentFile) return;

                    /* если это строка существет и
                     * если она не заголовок
                     * файловой таблицы
                     */
                    if(lCurrentFile.previousSibling &&
                       lCurrentFile.previousSibling.className!=='fm_header' ){

                        /* выделяем предыдущую строку*/
                        Util.setCurrentFile(lCurrentFile.previousSibling);

                        /* scrolling to current file*/
                        if(lCurrentFile.previousSibling)
                            lCurrentFile.previousSibling
                                .scrollIntoViewIfNeeded();
                    }

                    event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали клавишу в низ*/
                else if(event.keyCode === lKEY.DOWN){
                    /* получаем выдленный файл*/
                    lCurrentFile = Util.getCurrentFile();

                    /* если ненайдены выделенные файлы - выходим*/
                    if(!lCurrentFile)return;

                    /* если это не последняя строка */
                    if(lCurrentFile.nextSibling){
                        /* выделяем следующую строку*/
                    Util.setCurrentFile(lCurrentFile.nextSibling);

                        /* scrolling to current file*/
                        lCurrentFile.nextSibling.scrollIntoViewIfNeeded();

                    event.preventDefault();//запрет на дальнейшее действие
                    }
                }

                /* если нажали клавишу Home
                 * переходим к самому верхнему
                 * элементу
                 */
                else if(event.keyCode === lKEY.HOME){
                        lCurrentFile = Util.getCurrentFile();
                        /* получаем первый элемент
                         * пропускаем путь и заголовки столбиков
                         * выделяем верхий файл
                         */
                        Util.setCurrentFile(lCurrentFile
                            .parentElement.firstElementChild
                                .nextElementSibling.nextElementSibling);

                        /* move scrollbar to top */
                        Util.getPanel().scrollByLines(-100000000000000);

                event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали клавишу End
                 * выделяем последний элемент
                 */
                else if( event.keyCode === lKEY.END){
                        lCurrentFile = Util.getCurrentFile();
                        /* выделяем самый нижний файл */
                        Util.setCurrentFile(lCurrentFile
                            .parentElement.lastElementChild);

                        /* move scrollbar to bottom*/
                        Util.getPanel().scrollByLines(100000000000000);

                event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали клавишу page down
                 * проматываем экран
                 */
                else if(event.keyCode === lKEY.PAGE_DOWN){
                    Util.getPanel().scrollByPages(1);

                    lCurrentFile = Util.getCurrentFile();
                    for(i=0; i<30; i++){
                        if(!lCurrentFile.nextSibling) break;

                        lCurrentFile = lCurrentFile.nextSibling;
                    }
                    Util.setCurrentFile(lCurrentFile);

                    event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали клавишу page up
                 * проматываем экран
                 */
                else if(event.keyCode === 33){
                     Util.getPanel().scrollByPages(-1);

                    lCurrentFile = Util.getCurrentFile();
                    for(i=0; i<30; i++){
                        if(!lCurrentFile.previousSibling) break;
                        else try{
                            lCurrentFile
                                .previousSibling
                                    .previousSibling
                                        .previousSibling
                                            .previousSibling;
                            }
                            catch(pError){
                                break;
                            }
                        lCurrentFile = lCurrentFile.previousSibling;
                    }
                    Util.setCurrentFile(lCurrentFile);

                event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали Enter - открываем папку*/
                else if(event.keyCode === 13){
                    lCurrentFile = Util.getCurrentFile();
                    /* если ненайдены выделенные файлы - выходим*/
                    if(!lCurrentFile)return;

                    /* из него достаём спан с именем файла*/
                    lName = lCurrentFile.getElementsByClassName('name');
                    /* если нету (что вряд ли) - выходим*/
                    if(!lName)return false;
                    /* достаём все ссылки*/
                    var lATag = lName[0].getElementsByTagName('a');
                    /* если нету - выходим */
                    if(!lATag)return false;

                    /* вызываем ajaxload привязанный через changelinks
                     * пробуем нажать на ссылку, если не получиться
                     * (opera, ie), вызываем событие onclick,
                     */

                     if(lCurrentFile.onclick)lCurrentFile.onclick(true);
                     else try{
                         lATag[0].click();
                     }
                     catch(error){
                         console.log(error);
                     }

                    event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали <ctr>+r
                 * обновляем страницу,
                 * загружаем содержимое каталога
                 * при этом данные берём всегда с
                 * сервера, а не из кэша
                 * (обновляем кэш)
                 */
                else if(event.keyCode === lKEY.R &&
                    event.ctrlKey){
                    console.log('<ctrl>+r pressed');
                    console.log('reloading page...');
                    console.log('press <alt>+q to remove all key-handlers');
                    /* Программно нажимаем на кнопку перезагрузки
                     * содержимого каталога
                     */
                    var lRefreshIcon        = Util.getRefreshButton();
                    if(lRefreshIcon){
                        /* находим файл который сейчас выделен */
                         lCurrentFile       = Util.getCurrentFile();

                         /* получаем название файла*/
                         var lSelectedName  = lCurrentFile
                            .getElementsByTagName('a')[0].textContent;
                         /* если нашли элемент нажимаем него
                          * а если не можем - нажимаем на
                          * ссылку, на которую повешен eventHandler
                          * onclick
                          */
                        if(lRefreshIcon.click)
                            lRefreshIcon.parentElement.click();
                        else
                            lRefreshIcon.parentElement.onclick();

                        CloudCommander._currentToParent(lSelectedName);

                        event.preventDefault();//запрет на дальнейшее действие
                    }
                }

                /* если нажали <ctrl>+d чистим кэш */
                else if(event.keyCode === lKEY.D &&
                    event.ctrlKey){
                        console.log('<ctrl>+d pressed');
                        console.log('clearing cache...');
                        console.log('press <alt>+q to remove all key-handlers');

                        var lClearCache = Util.getById('clear-cache');
                        if(lClearCache && lClearCache.onclick)
                            lClearCache.onclick();

                        event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали <alt>+q
                 * убираем все обработчики
                 * нажатий клавиш
                 */
                else if(event.keyCode === lKEY.Q &&
                    event.altKey){
                        //document.removeEventListener('keydown', key_event,false);
                        console.log('<alt>+q pressed');
                        console.log('<ctrl>+r reload key-handerl - removed');
                        console.log('<ctrl>+s clear cache key-handler - removed');
                        console.log('press <alt>+s to to set them');

                        /* обработчик нажатий клавиш снят*/
                        CloudCommander.keyBinded = false;

                        event.preventDefault();//запрет на дальнейшее действие
                }
            }

            /* если нажали <alt>+s
             * устанавливаем все обработчики
             * нажатий клавиш
             */
            else if(event && event.keyCode === lKEY.S &&
                event.altKey){
                    /* обрабатываем нажатия на клавиши*/
                    CloudCommander.keyBinded = true;

                    console.log('<alt>+s pressed');
                    console.log('<ctrl>+r reload key-handerl - set');
                    console.log('<ctrl>+s clear cache key-handler - set');
                    console.log('press <alt>+q to remove them');

                    event.preventDefault();//запрет на дальнейшее действие
            }

        return false;
    };

    /* добавляем обработчик клавишь */
    if(document.addEventListener)
        document.addEventListener('keydown', key_event,false);
    else document.onkeydown = key_event;

    /* клавиши назначены*/
    CloudCommander.keyBinded=true;
});<commit_msg>minor changes<commit_after>var CloudCommander;
var CloudFunc;

/* Key constants*/
CloudCommander.KEY = {
    TAB         : 9,
    ENTER       : 13,

    PAGE_DOWN   : 34,
    END         : 35,
    HOME        : 36,
    UP          : 38,
    DOWN        : 40,

    D           : 68,
    Q           : 81,
    R           : 82,
    S           : 83,

    F2          : 113,
    F3          : 114,
    F4          : 115,
    F10         : 121
};

CloudCommander.keyBinding = (function(){
    "use strict";

    var Util = new CloudCommander.Util();

    /* saving state of tabs varibles */
    var lTabPanel   = {
        left        : 0,
        right       : 0

    };

    var lKEY = CloudCommander.KEY;

    var key_event = function(event){
            var lCurrentFile,
                lName, i;
            /* если клавиши можно обрабатывать*/
            if(CloudCommander.keyBinded && event){
                /* если нажали таб:
                 * переносим курсор на
                 * правую панель, если
                 * мы были на левой и
                 * наоборот
                 */
                if(event.keyCode === lKEY.TAB){
                    console.log('Tab pressed');

                    try{
                        lCurrentFile = Util.getCurrentFile();

                        /* changing parent panel of curent-file */
                        var lPanel = Util.getPanel();
                        var lId = lPanel.id;

                        lTabPanel[lId] = lCurrentFile;

                        lPanel = Util.getPanel({active:false});
                        lId = lPanel.id;

                        if(lTabPanel[lId])
                            Util.setCurrentFile(lTabPanel[lId]);
                        else{
                            var lFirstFileOnList =
                                lPanel.getElementsByTagName('li')[2];

                            Util.setCurrentFile(lFirstFileOnList);
                        }

                    }catch(error){console.log(error);}

                    event.preventDefault();//запрет на дальнейшее действие
                }
                /* if f2 pressed */
                else if(event.keyCode === lKEY.F2){

                }

                /* if f3 pressed */
                else if(event.keyCode === lKEY.F3){
                    if (typeof CloudCommander.Viewer === 'function')
                        CloudCommander.Viewer();

                    event.preventDefault();//запрет на дальнейшее действие
                }

                /* if alt+f3 pressed */
                else if(event.keyCode === lKEY.F3 &&
                    event.altKey){
                        if (typeof CloudCommander.Terminal === 'function')
                            CloudCommander.Terminal();

                        event.preventDefault();//запрет на дальнейшее действие
                }

                 /* if f4 pressed */
                else if(event.keyCode === lKEY.F4) {
                    Util.Images.showLoad();

                    if (typeof CloudCommander.Editor === 'function')
                        CloudCommander.Editor();

                    event.preventDefault();//запрет на дальнейшее действие
                 }
                 else  if(event.keyCode === CloudCommander.KEY.F10 &&
                        event.shiftKey){
                            if (typeof CloudCommander.Menu === 'function')
                                CloudCommander.Menu();

                            event.preventDefault();//запрет на дальнейшее действие
                            event.returnValue = false;
                }
                /* навигация по таблице файлов*/
                /* если нажали клавишу вверх*/
                else if(event.keyCode === lKEY.UP){
                    /* получаем выдленный файл*/
                    lCurrentFile = Util.getCurrentFile();

                    /* если ненайдены выделенные файлы - выходим*/
                    if(!lCurrentFile) return;

                    /* если это строка существет и
                     * если она не заголовок
                     * файловой таблицы
                     */
                    if(lCurrentFile.previousSibling &&
                       lCurrentFile.previousSibling.className!=='fm_header' ){

                        /* выделяем предыдущую строку*/
                        Util.setCurrentFile(lCurrentFile.previousSibling);

                        /* scrolling to current file*/
                        if(lCurrentFile.previousSibling)
                            lCurrentFile.previousSibling
                                .scrollIntoViewIfNeeded();
                    }

                    event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали клавишу в низ*/
                else if(event.keyCode === lKEY.DOWN){
                    /* получаем выдленный файл*/
                    lCurrentFile = Util.getCurrentFile();

                    /* если ненайдены выделенные файлы - выходим*/
                    if(!lCurrentFile)return;

                    /* если это не последняя строка */
                    if(lCurrentFile.nextSibling){
                        /* выделяем следующую строку*/
                    Util.setCurrentFile(lCurrentFile.nextSibling);

                        /* scrolling to current file*/
                        lCurrentFile.nextSibling.scrollIntoViewIfNeeded();

                    event.preventDefault();//запрет на дальнейшее действие
                    }
                }

                /* если нажали клавишу Home
                 * переходим к самому верхнему
                 * элементу
                 */
                else if(event.keyCode === lKEY.HOME){
                        lCurrentFile = Util.getCurrentFile();
                        /* получаем первый элемент
                         * пропускаем путь и заголовки столбиков
                         * выделяем верхий файл
                         */
                        Util.setCurrentFile(lCurrentFile
                            .parentElement.firstElementChild
                                .nextElementSibling.nextElementSibling);

                        /* move scrollbar to top */
                        Util.getPanel().scrollByLines(-100000000000000);

                event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали клавишу End
                 * выделяем последний элемент
                 */
                else if( event.keyCode === lKEY.END){
                        lCurrentFile = Util.getCurrentFile();
                        /* выделяем самый нижний файл */
                        Util.setCurrentFile(lCurrentFile
                            .parentElement.lastElementChild);

                        /* move scrollbar to bottom*/
                        Util.getPanel().scrollByLines(100000000000000);

                event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали клавишу page down
                 * проматываем экран
                 */
                else if(event.keyCode === lKEY.PAGE_DOWN){
                    Util.getPanel().scrollByPages(1);

                    lCurrentFile = Util.getCurrentFile();
                    for(i=0; i<30; i++){
                        if(!lCurrentFile.nextSibling) break;

                        lCurrentFile = lCurrentFile.nextSibling;
                    }
                    Util.setCurrentFile(lCurrentFile);

                    event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали клавишу page up
                 * проматываем экран
                 */
                else if(event.keyCode === 33){
                     Util.getPanel().scrollByPages(-1);

                    lCurrentFile = Util.getCurrentFile();
                    for(i=0; i<30; i++){
                        if(!lCurrentFile.previousSibling) break;
                        else try{
                            lCurrentFile
                                .previousSibling
                                    .previousSibling
                                        .previousSibling
                                            .previousSibling;
                            }
                            catch(pError){
                                break;
                            }
                        lCurrentFile = lCurrentFile.previousSibling;
                    }
                    Util.setCurrentFile(lCurrentFile);

                event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали Enter - открываем папку*/
                else if(event.keyCode === 13){
                    lCurrentFile = Util.getCurrentFile();
                    /* если ненайдены выделенные файлы - выходим*/
                    if(!lCurrentFile)return;

                    /* из него достаём спан с именем файла*/
                    lName = lCurrentFile.getElementsByClassName('name');
                    /* если нету (что вряд ли) - выходим*/
                    if(!lName)return false;
                    /* достаём все ссылки*/
                    var lATag = lName[0].getElementsByTagName('a');
                    /* если нету - выходим */
                    if(!lATag)return false;

                    /* вызываем ajaxload привязанный через changelinks
                     * пробуем нажать на ссылку, если не получиться
                     * (opera, ie), вызываем событие onclick,
                     */

                     if(lCurrentFile.onclick)lCurrentFile.onclick(true);
                     else try{
                         lATag[0].click();
                     }
                     catch(error){
                         console.log(error);
                     }

                    event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали <ctr>+r
                 * обновляем страницу,
                 * загружаем содержимое каталога
                 * при этом данные берём всегда с
                 * сервера, а не из кэша
                 * (обновляем кэш)
                 */
                else if(event.keyCode === lKEY.R &&
                    event.ctrlKey){
                    console.log('<ctrl>+r pressed');
                    console.log('reloading page...');
                    console.log('press <alt>+q to remove all key-handlers');
                    /* Программно нажимаем на кнопку перезагрузки
                     * содержимого каталога
                     */
                    var lRefreshIcon        = Util.getRefreshButton();
                    if(lRefreshIcon){
                        /* находим файл который сейчас выделен */
                         lCurrentFile       = Util.getCurrentFile();

                         /* получаем название файла*/
                         var lSelectedName  = lCurrentFile
                            .getElementsByTagName('a')[0].textContent;
                         /* если нашли элемент нажимаем него
                          * а если не можем - нажимаем на
                          * ссылку, на которую повешен eventHandler
                          * onclick
                          */
                        //if(lRefreshIcon.click)
                          //  lRefreshIcon.parentElement.click();
                        //else
                            lRefreshIcon.parentElement.onclick();

                        CloudCommander._currentToParent(lSelectedName);

                        event.preventDefault();//запрет на дальнейшее действие
                    }
                }

                /* если нажали <ctrl>+d чистим кэш */
                else if(event.keyCode === lKEY.D &&
                    event.ctrlKey){
                        console.log('<ctrl>+d pressed');
                        console.log('clearing cache...');
                        console.log('press <alt>+q to remove all key-handlers');

                        var lClearCache = Util.getById('clear-cache');
                        if(lClearCache && lClearCache.onclick)
                            lClearCache.onclick();

                        event.preventDefault();//запрет на дальнейшее действие
                }

                /* если нажали <alt>+q
                 * убираем все обработчики
                 * нажатий клавиш
                 */
                else if(event.keyCode === lKEY.Q &&
                    event.altKey){
                        //document.removeEventListener('keydown', key_event,false);
                        console.log('<alt>+q pressed');
                        console.log('<ctrl>+r reload key-handerl - removed');
                        console.log('<ctrl>+s clear cache key-handler - removed');
                        console.log('press <alt>+s to to set them');

                        /* обработчик нажатий клавиш снят*/
                        CloudCommander.keyBinded = false;

                        event.preventDefault();//запрет на дальнейшее действие
                }
            }

            /* если нажали <alt>+s
             * устанавливаем все обработчики
             * нажатий клавиш
             */
            else if(event && event.keyCode === lKEY.S &&
                event.altKey){
                    /* обрабатываем нажатия на клавиши*/
                    CloudCommander.keyBinded = true;

                    console.log('<alt>+s pressed');
                    console.log('<ctrl>+r reload key-handerl - set');
                    console.log('<ctrl>+s clear cache key-handler - set');
                    console.log('press <alt>+q to remove them');

                    event.preventDefault();//запрет на дальнейшее действие
            }

        return false;
    };

    /* добавляем обработчик клавишь */
    if(document.addEventListener)
        document.addEventListener('keydown', key_event,false);
    else document.onkeydown = key_event;

    /* клавиши назначены*/
    CloudCommander.keyBinded=true;
});<|endoftext|>


<commit_before>'use strict';

const url = require('url');
const ldap = require('ldapjs');

const defaultPageSize = 1000; // should probably be a property of ad
let ad;
let log;
let parseRangeAttributes;

/**
 * Factory to create the LDAP client object.
 *
 * @private
 * @param {String} url The url to use when creating the LDAP client.
 * @param {object} opts The optional LDAP client options.
 */
function createClient(url, opts) {
  // Attempt to get Url from this instance.
  const _url = url || ad.url || ad.opts.url;
  if (!_url) {
    throw 'No url specified for ActiveDirectory client.';
  }
  log.trace('createClient(%s)', _url);

  const ldapOpts = getLdapClientOpts(Object.assign({ url: _url }, opts));
  log.debug('Creating ldapjs client for %s. Opts: %j', opts.url, ldapOpts);
  const client = ldap.createClient(opts);
  return client;
}

/**
 * From the list of options, retrieves the ldapjs client specific options.
 *
 * @param {Object} opts The opts to parse.
 * @returns {Object} The ldapjs opts.
 */
function getLdapClientOpts(opts) {
  const clientOpts = [
    'url',
    'host', 'port', 'secure', 'tlsOptions',
    'socketPath', 'log', 'timeout', 'idleTimeout',
    'reconnect', 'queue', 'queueSize', 'queueTimeout',
    'queueDisable', 'bindDN', 'bindCredentials',
    'maxConnections'
  ];

  const options = {};
  clientOpts.forEach((opt) => {
    if (opts.hasOwnProperty(opt)) {
      options[opt] = opts[opt];
    }
  });

  return options;
}

function isReferralAllowed(referralUri) {
  if (ad.defaultReferrals.enabled === false || !referralUri) {
    return false;
  }

  let result = true;
  for (let excludePattern of ad.defaultReferrals.exclude) {
    const regex = new RegExp(excludePattern, 'i');
    if (regex.test(referralUri)) {
      result = false;
      break;
    }
  }

  return result;
}

/**
 * @property {string} baseDN Where in the tree to start searches.
 * @property {function} callback A function to invoke when the search has
 * completed. This method must accept an error and a result, in that order.
 * @property {object} All of the options relevant to an {@link ActiveDirectory}
 * instance. It must include an `opts` property that represents that is
 * an {@link LDAPQueryParameters}.
 * @typedef {object} SearcherOptions
 */

/**
 * An interface for performing searches against an Active Directory database.
 * It handles ranged results, finding deleted items, and following referrals.
 *
 * @param {SearcherOptions} opts
 * @constructor
 */
function Searcher(opts) { // jshint -W071
  this.baseDN = opts.baseDN;
  this.callback = opts.callback;
  this.opts = opts;
  this.ldapOpts = getLdapClientOpts(this.opts);
  this.query = this.opts.opts;

  this.results = [];
  this.pendingReferrals = new Set();
  this.pendingRangeRetrievals = 0;
  this.searchComplete = false;

  this.client = createClient(ad.url || ad.opts.url, this.ldapOpts);

  this.controls = this.opts.controls || [];

  // Add paging results control by default if not already added.
  const pagedControls = this.controls.filter(
    (control) => control instanceof ldap.PagedResultsControl
  );
  if (pagedControls.length === 0) {
    log.debug('Adding PagedResultControl to search (%s) with filter "%s" for %j',
              this.baseDN,
              this.query.filter,
              (this.query.attributes) ? this.opts.attributes : '[*]'
    );
    this.controls.push(new ldap.PagedResultsControl({
      value: { size: defaultPageSize }
    }));
  }

  if (this.opts.includeDeleted) {
    const deletedControls = this.controls.filter(
      (control) => control.type === '1.2.840.113556.1.4.417'
    );
    if (deletedControls.length === 0) {
      log.debug('Adding ShowDeletedOidControl(1.2.840.113556.1.4.417) to search (%s) with filter "%s" for %j',
                this.baseDN,
                this.query.filter,
                (this.query.attributes) ? this.query.attributes : '[*]'
      );
      this.controls.push(new ldap.Control({
        type: '1.2.840.113556.1.4.417',
        criticality: true
      }));
    }
  }
}

/**
 * The only method you should need to invoke. It uses the information parsed
 * during construction to construct the query and submit it to the server. Once
 * the query has completed, or an error occurs, the callback you specified
 * during construction will be invoked.
 */
Searcher.prototype.search = function search() {
  log.debug('Querying active directory (%s) with filter "%s" for %j',
            this.baseDN,
            this.query.filter,
            (this.query.attributes) ? this.query.attributes : '[*]'
  );

  this.client.on('error', (err) => {
    // to handle connection errors
    this.callback(err);
  });

  this.client.search(this.baseDN, this.query, this.controls, (err, res) => {
    if (err) {
      if (this.callback) {
        this.callback(err);
      }
      return;
    }

    const errCallback = (err) => {
      if (err.name === 'SizeLimitExceededError') {
        this.onSearchEnd(res);
        return;
      }

      this.client.unbind();
      log.error(err,
                '[%s] An error occurred performing the requested LDAP search on %s (%j)',
                err.errno || 'UNKNOWN',
                this.baseDN,
                this.opts
      );
      if (this.callback) {
        this.callback(err);
      }
    };

    res.on('searchEntry', this.onSearchEntry.bind(this));
    res.on('searchReference', this.onReferralChase.bind(this));
    res.on('error', errCallback);
    res.on('end', (err) => {
      this.searchComplete = true;
      this.onSearchEnd();
    });
  });
};

/**
 * If set via the options of the query or the {@link ActiveDirectory}
 * instance, run the entry through the function. Otherwise, just feed it to
 * the parser callback.
 *
 * @param {object} entry The search entry object.
 * @param {object} raw The raw search entry object as returned from ldap.js.
 * @param {function} callback The callback to execute when complete.
 */
Searcher.prototype.entryParser = function entryParser(entry, raw, callback) {
  if (this.opts.entryParser) {
    return this.opts.entryParser(entry, raw, callback);
  } else if (ad.opts.entryParser) {
    return ad.opts.entryParser(entry, raw, callback);
  }

  return callback(entry);
};

/**
 * Invoked when the ldap.js client is returning a search entry result.
 *
 * @param {object} entry The search entry as returned by ldap.js.
 */
Searcher.prototype.onSearchEntry = function onSearchEntry(entry) {
  log.trace('onSearchEntry(%j)', entry);
  var result = entry.object;
  delete result.controls;

  // Some attributes can have range attributes (paging). Execute the query
  // again to get additional items.
  const parserCallback = (result) => {
    if (result) {
      this.results.push(result);
    }
    if (this.pendingRangeRetrievals === 0 && this.searchComplete) {
      this.onSearchEnd();
    }
  };
  this.pendingRangeRetrievals += 1;
  parseRangeAttributes(result, this, (err, item) => {
    this.pendingRangeRetrievals -= 1;

    let _item = item;
    if (err) {
      _item = entry.object;
    }
    this.entryParser(_item, entry.raw, parserCallback);
  });
};

/**
 * Used to handle referrals, if they are enabled.
 *
 * @param {object} referral A referral object that has a `uris` property.
 */
Searcher.prototype.onReferralChase = function onReferralChase(referral) {
  referral.uris.forEach((uri) => {
    if (!isReferralAllowed(uri)) {
      return;
    }

    log.debug('Following LDAP referral chase at %s', uri);
    const referral = url.parse(uri);
    const referralBaseDn = (referral.pathname || '/').substring(1);
    const refSearcher = new Searcher({
      baseDN: referralBaseDn,
      opts: this.opts,
      callback: (err, res) => {
        if (err) {
          log.error(
            err,
            '[%s] An error occurred chasing the LDAP referral on %s (%j)',
            err.errno,
            referralBaseDn,
            this.opts
          );
        }
        this.removeReferral(refSearcher);
      }
    });
    this.pendingReferrals.add(refSearcher);

    refSearcher.search();
  });
};

/**
 * Invoked when the main search has completed, including any referrals.
 */
Searcher.prototype.onSearchEnd = function onSearchEnd() {
  if (this.pendingRangeRetrievals === 0 && this.pendingReferrals.size === 0) {
    this.client.unbind();
    log.info('Active directory search (%s) for "%s" returned %d entries.',
              this.baseDN,
              this.opts.filter,
              this.results.length
    );
    if (this.callback) {
      this.callback(null, this.results);
    }
  }
};

/**
 * Dequeues a referral chase client.
 *
 * @param {object} referral An instance of {@link Searcher} being used to chase
 * a referral.
 */
Searcher.prototype.removeReferral = function removeReferral(referral) {
  if (!referral) {
    return;
  }

  referral.client.unbind();
  this.pendingReferrals.delete(referral);
};

/**
 * @property {string} [scope] The type of search to perform: base, one, or sub.
 * Default: base.
 * @property {string} [filter] The LDAP filter to use.
 * Default: '(objectclass=*)'
 * @property {array} [attributes] A list of entry attributes to include in the
 * result. Defaults to all attributes.
 * @property {int} [sizeLimit] The maximum number of entries to return.
 * Default: 0 (infinite)
 * @property {int} [timeLimit] The maximum number of seconds to wait for a
 * server response. Default: 10.
 * @typedef {object} LDAPQueryParameters
 */

/**
 * Wraps an instance of {@link Searcher} so as to be backward compatible with
 * the original search function.
 *
 * @private
 * @param {string} [baseDN] The optional base directory where the LDAP query is
 * to originate from. If not specified, then starts at the root.
 * @param {LDAPQueryParameters} [opts] LDAP query string parameters to execute.
 * @param {function} callback The callback to execure when completed.
 * The call back is a standard Node style callback with an error parameter and
 * a result parameter.
 */
function search(baseDN, opts, callback) { // jshint -W071
  const options = Object.assign({}, ad.opts);

  if (typeof baseDN === 'function') {
    options.baseDN = ad.baseDN;
    options.callback = baseDN;
    options.opts = {};
  } else if (typeof baseDN === 'object') {
    options.baseDN = ad.baseDN;
    options.callback = opts;
    options.opts = baseDN;
  } else if (typeof baseDN === 'string') {
    options.baseDN = baseDN;
    if (typeof opts === 'function') {
      options.callback = opts;
      options.opts = {};
    } else {
      options.callback = callback;
      options.opts = opts;
    }
  }

  const searcher = new Searcher(options);
  return searcher.search();
}

module.exports = function($ad, $log) {
  ad = $ad;
  log = $log;
  parseRangeAttributes = require('./parseRangeAttributes')($ad, $log);
  return search;
};<commit_msg>Cleanup some stuff in search.js<commit_after>'use strict';

const url = require('url');
const ldap = require('ldapjs');

const defaultPageSize = 1000; // should probably be a property of ad
let ad;
let log;
let parseRangeAttributes;

/**
 * Factory to create the LDAP client object.
 *
 * @private
 * @param {String} url The url to use when creating the LDAP client.
 * @param {object} opts The optional LDAP client options.
 */
function createClient(url, opts) {
  // Attempt to get Url from this instance.
  const _url = url || ad.url || ad.opts.url;
  if (!_url) {
    throw 'No url specified for ActiveDirectory client.';
  }
  log.trace('createClient(%s)', _url);

  const ldapOpts = getLdapClientOpts(Object.assign({ url: _url }, opts));
  log.debug('Creating ldapjs client for %s. Opts: %j', opts.url, ldapOpts);
  return ldap.createClient(opts);
}

/**
 * From the list of options, retrieves the ldapjs client specific options.
 *
 * @private
 * @param {Object} opts The opts to parse.
 * @returns {Object} The ldapjs opts.
 */
function getLdapClientOpts(opts) {
  const clientOpts = [
    'url',
    'host', 'port', 'secure', 'tlsOptions',
    'socketPath', 'log', 'timeout', 'idleTimeout',
    'reconnect', 'queue', 'queueSize', 'queueTimeout',
    'queueDisable', 'bindDN', 'bindCredentials',
    'maxConnections'
  ];

  const options = {};
  clientOpts.forEach((opt) => {
    if (opts.hasOwnProperty(opt)) {
      options[opt] = opts[opt];
    }
  });

  return options;
}

function isReferralAllowed(referralUri) {
  if (ad.defaultReferrals.enabled === false || !referralUri) {
    return false;
  }

  let result = true;
  for (let excludePattern of ad.defaultReferrals.exclude) {
    const regex = new RegExp(excludePattern, 'i');
    if (regex.test(referralUri)) {
      result = false;
      break;
    }
  }

  return result;
}

/**
 * @property {string} baseDN Where in the tree to start searches.
 * @property {function} callback A function to invoke when the search has
 * completed. This method must accept an error and a result, in that order.
 * @property {object} opts All of the options relevant to an
 * {@link ActiveDirectory} instance. It must include an `opts` property that
 * represents that is an {@link LDAPQueryParameters}.
 * @private
 * @typedef {object} SearcherOptions
 */

/**
 * An interface for performing searches against an Active Directory database.
 * It handles ranged results, finding deleted items, and following referrals.
 *
 * @private
 * @param {SearcherOptions} opts
 * @constructor
 */
function Searcher(opts) { // jshint -W071
  this.baseDN = opts.baseDN;
  this.callback = opts.callback;
  this.opts = opts;
  this.ldapOpts = getLdapClientOpts(this.opts);
  this.query = this.opts.opts;

  this.results = [];
  this.pendingReferrals = new Set();
  this.pendingRangeRetrievals = 0;
  this.searchComplete = false;

  this.client = createClient(ad.url || ad.opts.url, this.ldapOpts);

  this.controls = this.opts.controls || [];

  // Add paging results control by default if not already added.
  const pagedControls = this.controls.filter(
    (control) => control instanceof ldap.PagedResultsControl
  );
  if (pagedControls.length === 0) {
    log.debug('Adding PagedResultControl to search (%s) with filter "%s" for %j',
              this.baseDN,
              this.query.filter,
              (this.query.attributes) ? this.opts.attributes : '[*]'
    );
    this.controls.push(new ldap.PagedResultsControl({
      value: { size: defaultPageSize }
    }));
  }

  if (this.opts.includeDeleted) {
    const deletedControls = this.controls.filter(
      (control) => control.type === '1.2.840.113556.1.4.417'
    );
    if (deletedControls.length === 0) {
      log.debug('Adding ShowDeletedOidControl(1.2.840.113556.1.4.417) to search (%s) with filter "%s" for %j',
                this.baseDN,
                this.query.filter,
                (this.query.attributes) ? this.query.attributes : '[*]'
      );
      this.controls.push(new ldap.Control({
        type: '1.2.840.113556.1.4.417',
        criticality: true
      }));
    }
  }
}

/**
 * The only method you should need to invoke. It uses the information parsed
 * during construction to construct the query and submit it to the server. Once
 * the query has completed, or an error occurs, the callback you specified
 * during construction will be invoked.
 */
Searcher.prototype.search = function search() {
  log.debug('Querying active directory (%s) with filter "%s" for %j',
            this.baseDN,
            this.query.filter,
            (this.query.attributes) ? this.query.attributes : '[*]'
  );

  this.client.on('error', (err) => {
    // to handle connection errors
    this.callback(err);
  });

  this.client.search(this.baseDN, this.query, this.controls, (err, res) => {
    if (err) {
      if (this.callback) {
        this.callback(err);
      }
      return;
    }

    const errCallback = (err) => {
      if (err.name === 'SizeLimitExceededError') {
        this.onSearchEnd(res);
        return;
      }

      this.client.unbind();
      log.error(err,
                '[%s] An error occurred performing the requested LDAP search on %s (%j)',
                err.errno || 'UNKNOWN',
                this.baseDN,
                this.opts
      );
      if (this.callback) {
        this.callback(err);
      }
    };

    res.on('searchEntry', this.onSearchEntry.bind(this));
    res.on('searchReference', this.onReferralChase.bind(this));
    res.on('error', errCallback);
    res.on('end', (err) => {
      this.searchComplete = true;
      this.onSearchEnd();
    });
  });
};

/**
 * If set via the options of the query or the {@link ActiveDirectory}
 * instance, run the entry through the function. Otherwise, just feed it to
 * the parser callback.
 *
 * @param {object} entry The search entry object.
 * @param {object} raw The raw search entry object as returned from ldap.js.
 * @param {function} callback The callback to execute when complete.
 */
Searcher.prototype.entryParser = function entryParser(entry, raw, callback) {
  if (this.opts.entryParser) {
    return this.opts.entryParser(entry, raw, callback);
  } else if (ad.opts.entryParser) {
    return ad.opts.entryParser(entry, raw, callback);
  }

  return callback(entry);
};

/**
 * Invoked when the ldap.js client is returning a search entry result.
 *
 * @param {object} entry The search entry as returned by ldap.js.
 */
Searcher.prototype.onSearchEntry = function onSearchEntry(entry) {
  log.trace('onSearchEntry(%j)', entry);
  var result = entry.object;
  delete result.controls;

  // Some attributes can have range attributes (paging). Execute the query
  // again to get additional items.
  const parserCallback = (result) => {
    if (result) {
      this.results.push(result);
    }
    if (this.pendingRangeRetrievals === 0 && this.searchComplete) {
      this.onSearchEnd();
    }
  };
  this.pendingRangeRetrievals += 1;
  parseRangeAttributes(result, this, (err, item) => {
    this.pendingRangeRetrievals -= 1;

    let _item = item;
    if (err) {
      _item = entry.object;
    }
    this.entryParser(_item, entry.raw, parserCallback);
  });
};

/**
 * Used to handle referrals, if they are enabled.
 *
 * @param {object} referral A referral object that has a `uris` property.
 */
Searcher.prototype.onReferralChase = function onReferralChase(referral) {
  referral.uris.forEach((uri) => {
    if (!isReferralAllowed(uri)) {
      return;
    }

    log.debug('Following LDAP referral chase at %s', uri);
    const referral = url.parse(uri);
    const referralBaseDn = (referral.pathname || '/').substring(1);
    const refSearcher = new Searcher({
      baseDN: referralBaseDn,
      opts: this.opts,
      callback: (err, res) => {
        if (err) {
          log.error(
            err,
            '[%s] An error occurred chasing the LDAP referral on %s (%j)',
            err.errno,
            referralBaseDn,
            this.opts
          );
        }
        this.removeReferral(refSearcher);
      }
    });
    this.pendingReferrals.add(refSearcher);

    refSearcher.search();
  });
};

/**
 * Invoked when the main search has completed, including any referrals.
 */
Searcher.prototype.onSearchEnd = function onSearchEnd() {
  if (this.pendingRangeRetrievals === 0 && this.pendingReferrals.size === 0) {
    this.client.unbind();
    log.info('Active directory search (%s) for "%s" returned %d entries.',
              this.baseDN,
              this.ldapOpts.filter,
              this.results.length
    );
    if (this.callback) {
      this.callback(null, this.results);
    }
  }
};

/**
 * Dequeues a referral chase client.
 *
 * @param {object} referral An instance of {@link Searcher} being used to chase
 * a referral.
 */
Searcher.prototype.removeReferral = function removeReferral(referral) {
  if (!referral) {
    return;
  }

  referral.client.unbind();
  this.pendingReferrals.delete(referral);
};

/**
 * @property {string} [scope] The type of search to perform: base, one, or sub.
 * Default: base.
 * @property {string} [filter] The LDAP filter to use.
 * Default: '(objectclass=*)'
 * @property {array} [attributes] A list of entry attributes to include in the
 * result. Defaults to all attributes.
 * @property {int} [sizeLimit] The maximum number of entries to return.
 * Default: 0 (infinite)
 * @property {int} [timeLimit] The maximum number of seconds to wait for a
 * server response. Default: 10.
 * @typedef {object} LDAPQueryParameters
 */

/**
 * Wraps an instance of {@link Searcher} so as to be backward compatible with
 * the original search function.
 *
 * @private
 * @param {string} [baseDN] The optional base directory where the LDAP query is
 * to originate from. If not specified, then starts at the root.
 * @param {LDAPQueryParameters} [opts] LDAP query string parameters to execute.
 * @param {function} callback The callback to execure when completed.
 * The call back is a standard Node style callback with an error parameter and
 * a result parameter.
 */
function search(baseDN, opts, callback) { // jshint -W071
  const options = Object.assign({}, ad.opts);

  if (typeof baseDN === 'function') {
    options.baseDN = ad.baseDN;
    options.callback = baseDN;
    options.opts = {};
  } else if (typeof baseDN === 'object') {
    options.baseDN = ad.baseDN;
    options.callback = opts;
    options.opts = baseDN;
  } else if (typeof baseDN === 'string') {
    options.baseDN = baseDN;
    if (typeof opts === 'function') {
      options.callback = opts;
      options.opts = {};
    } else {
      options.callback = callback;
      options.opts = opts;
    }
  }

  const searcher = new Searcher(options);
  return searcher.search();
}

module.exports = function($ad, $log) {
  ad = $ad;
  log = $log;
  parseRangeAttributes = require('./parseRangeAttributes')($ad, $log);
  return search;
};<|endoftext|>


<commit_before>/*!
 * connect-memcached
 * MIT Licensed
 */

var Memcached = require('memcached');
var oneDay = 86400;
function ensureCallback(fn) {
    return function() {
        fn && fn.apply(null, arguments);
    };
}

/**
 * Return the `MemcachedStore` extending `connect`'s session Store.
 *
 * @param {object} session
 * @return {Function}
 * @api public
 */
module.exports = function(session) {
    var Store = session.Store;

    /**
     * Initialize MemcachedStore with the given `options`.
     *
     * @param {Object} options
     * @api public
     */
    function MemcachedStore(options) {
        options = options || {};
        Store.call(this, options);

        this.prefix = options.prefix || '';
        if (!options.client) {
            if (!options.hosts) {
                options.hosts = '127.0.0.1:11211';
            }
            if (options.secret) {
                this.crypto = require('crypto'),
                this.secret = options.secret;
            }
            if (options.algorithm) {
                this.algorithm = options.algorithm;
            }

            options.client = new Memcached(options.hosts, options);
        }

        this.client = options.client;
    }

    MemcachedStore.prototype.__proto__ = Store.prototype;

    /**
     * Translates the given `sid` into a memcached key, optionally with prefix.
     *
     * @param {String} sid
     * @api private
     */
    MemcachedStore.prototype.getKey = function getKey(sid) {
        return this.prefix + sid;
    };

    /**
     * Attempt to fetch session by the given `sid`.
     *
     * @param {String} sid
     * @param {Function} fn
     * @api public
     */
    MemcachedStore.prototype.get = function(sid, fn) {
    secret = this.secret, self = this, sid = this.getKey(sid);

        this.client.get(sid, function(err, data) {
            if (err) {
                return fn(err, {});
            }
            try {
                if (!data) {
                    return fn();
                }
                if (secret) {
                    parseable_string = decryptData.call(self, data.toString());
                }
                else {
                    parseable_string = data.toString();
                }

                fn(null, JSON.parse(parseable_string));
            } catch (e) {
                fn(e);
            }
        });
    };

    /**
     * Commit the given `sess` object associated with the given `sid`.
     *
     * @param {String} sid
     * @param {Session} sess
     * @param {Function} fn
     * @api public
     */
    MemcachedStore.prototype.set = function(sid, sess, fn) {
        sid = this.getKey(sid);

        try {
            var maxAge = sess.cookie.maxAge;
            var ttl = 'number' == typeof maxAge ? maxAge / 1000 | 0 : oneDay;
            var sess = JSON.stringify((this.secret) ?
                                encryptData.call(this, JSON.stringify(sess),
                                                 this.secret, this.algorithm) :
                                sess);

            this.client.set(sid, sess, ttl, ensureCallback(fn));
        } catch (err) {
            fn && fn(err);
        }
    };

    /**
     * Destroy the session associated with the given `sid`.
     *
     * @param {String} sid
     * @param {Function} fn
     * @api public
     */
    MemcachedStore.prototype.destroy = function(sid, fn) {
        sid = this.getKey(sid);
        this.client.del(sid, ensureCallback(fn));
    };

    /**
     * Fetch number of sessions.
     *
     * @param {Function} fn
     * @api public
     */
    MemcachedStore.prototype.length = function(fn) {
        this.client.items(ensureCallback(fn));
    };

    /**
     * Clear all sessions.
     *
     * @param {Function} fn
     * @api public
     */
    MemcachedStore.prototype.clear = function(fn) {
        this.client.flush(ensureCallback(fn));
    };

    function encryptData(plaintext){
        var pt = encrypt.call(this, this.secret, plaintext, this.algo)
            , hmac = digest.call(this, this.secret, pt);

        return {
            ct: pt,
            mac: hmac
        };
    }

    function decryptData(ciphertext){
        ciphertext = JSON.parse(ciphertext)
        var hmac = digest.call(this, this.secret, ciphertext.ct);

        if (hmac != ciphertext.mac) {
            throw 'Encrypted session was tampered with!';
        }

        return decrypt.call(this, this.secret, ciphertext.ct, this.algo);
    }

    function digest(key, obj) {
        var hmac = this.crypto.createHmac('sha512', key);
        hmac.setEncoding('hex');
        hmac.write(obj);
        hmac.end();
        return hmac.read();
    }

    function encrypt(key, pt, algo) {
        algo = algo || 'aes-256-ctr';
        pt = (Buffer.isBuffer(pt)) ? pt : new Buffer(pt);

        var cipher = this.crypto.createCipher(algo, key), ct = [];
        ct.push(cipher.update(pt, 'buffer', 'hex'));
        ct.push(cipher.final('hex'));

        return ct.join('');
    }

    function decrypt(key, ct, algo) {
        algo = algo || 'aes-256-ctr';
        var cipher = this.crypto.createDecipher(algo, key), pt = [];

        pt.push(cipher.update(ct, 'hex', 'utf8'));
        pt.push(cipher.final('utf8'));

        return pt.join('');
    }

    return MemcachedStore;
};
<commit_msg>support a default TTL without having a maxAge provided by dwolfand<commit_after>/*!
 * connect-memcached
 * MIT Licensed
 */

var Memcached = require('memcached');
var oneDay = 86400;
function ensureCallback(fn) {
    return function() {
        fn && fn.apply(null, arguments);
    };
}

/**
 * Return the `MemcachedStore` extending `connect`'s session Store.
 *
 * @param {object} session
 * @return {Function}
 * @api public
 */
module.exports = function(session) {
    var Store = session.Store;

    /**
     * Initialize MemcachedStore with the given `options`.
     *
     * @param {Object} options
     * @api public
     */
    function MemcachedStore(options) {
        options = options || {};
        Store.call(this, options);

        this.prefix = options.prefix || '';
        this.ttl = options.ttl;
        if (!options.client) {
            if (!options.hosts) {
                options.hosts = '127.0.0.1:11211';
            }
            if (options.secret) {
                this.crypto = require('crypto'),
                this.secret = options.secret;
            }
            if (options.algorithm) {
                this.algorithm = options.algorithm;
            }

            options.client = new Memcached(options.hosts, options);
        }

        this.client = options.client;
    }

    MemcachedStore.prototype.__proto__ = Store.prototype;

    /**
     * Translates the given `sid` into a memcached key, optionally with prefix.
     *
     * @param {String} sid
     * @api private
     */
    MemcachedStore.prototype.getKey = function getKey(sid) {
        return this.prefix + sid;
    };

    /**
     * Attempt to fetch session by the given `sid`.
     *
     * @param {String} sid
     * @param {Function} fn
     * @api public
     */
    MemcachedStore.prototype.get = function(sid, fn) {
    secret = this.secret, self = this, sid = this.getKey(sid);

        this.client.get(sid, function(err, data) {
            if (err) {
                return fn(err, {});
            }
            try {
                if (!data) {
                    return fn();
                }
                if (secret) {
                    parseable_string = decryptData.call(self, data.toString());
                }
                else {
                    parseable_string = data.toString();
                }

                fn(null, JSON.parse(parseable_string));
            } catch (e) {
                fn(e);
            }
        });
    };

    /**
     * Commit the given `sess` object associated with the given `sid`.
     *
     * @param {String} sid
     * @param {Session} sess
     * @param {Function} fn
     * @api public
     */
    MemcachedStore.prototype.set = function(sid, sess, fn) {
        sid = this.getKey(sid);

        try {
            var maxAge = sess.cookie.maxAge;
            var ttl = this.ttl || ('number' == typeof maxAge ? maxAge / 1000 | 0 : oneDay);
            var sess = JSON.stringify((this.secret) ?
                                encryptData.call(this, JSON.stringify(sess),
                                                 this.secret, this.algorithm) :
                                sess);

            this.client.set(sid, sess, ttl, ensureCallback(fn));
        } catch (err) {
            fn && fn(err);
        }
    };

    /**
     * Destroy the session associated with the given `sid`.
     *
     * @param {String} sid
     * @param {Function} fn
     * @api public
     */
    MemcachedStore.prototype.destroy = function(sid, fn) {
        sid = this.getKey(sid);
        this.client.del(sid, ensureCallback(fn));
    };

    /**
     * Fetch number of sessions.
     *
     * @param {Function} fn
     * @api public
     */
    MemcachedStore.prototype.length = function(fn) {
        this.client.items(ensureCallback(fn));
    };

    /**
     * Clear all sessions.
     *
     * @param {Function} fn
     * @api public
     */
    MemcachedStore.prototype.clear = function(fn) {
        this.client.flush(ensureCallback(fn));
    };

    function encryptData(plaintext){
        var pt = encrypt.call(this, this.secret, plaintext, this.algo)
            , hmac = digest.call(this, this.secret, pt);

        return {
            ct: pt,
            mac: hmac
        };
    }

    function decryptData(ciphertext){
        ciphertext = JSON.parse(ciphertext)
        var hmac = digest.call(this, this.secret, ciphertext.ct);

        if (hmac != ciphertext.mac) {
            throw 'Encrypted session was tampered with!';
        }

        return decrypt.call(this, this.secret, ciphertext.ct, this.algo);
    }

    function digest(key, obj) {
        var hmac = this.crypto.createHmac('sha512', key);
        hmac.setEncoding('hex');
        hmac.write(obj);
        hmac.end();
        return hmac.read();
    }

    function encrypt(key, pt, algo) {
        algo = algo || 'aes-256-ctr';
        pt = (Buffer.isBuffer(pt)) ? pt : new Buffer(pt);

        var cipher = this.crypto.createCipher(algo, key), ct = [];
        ct.push(cipher.update(pt, 'buffer', 'hex'));
        ct.push(cipher.final('hex'));

        return ct.join('');
    }

    function decrypt(key, ct, algo) {
        algo = algo || 'aes-256-ctr';
        var cipher = this.crypto.createDecipher(algo, key), pt = [];

        pt.push(cipher.update(ct, 'hex', 'utf8'));
        pt.push(cipher.final('utf8'));

        return pt.join('');
    }

    return MemcachedStore;
};
<|endoftext|>


<commit_before>'use strict';

var querystring = require('querystring');

var cip = require('../cip-methods.js');
var cip_categories = require('../cip-categories.js');
var asset_mapping = require('../asset-mapping.js');
var asset_player = require('../asset-player.js');
var license_mapping = require('../license-mapping.js');

var helpers = {};

helpers.license_tag = function license_tag(license_id) {
    return license_mapping.licenses[license_id];
};

helpers.magic360_options = function magic360_options(image_rotation_set) {
    var small_images = image_rotation_set.small;
    var large_images = image_rotation_set.large;
    var options = {
        "magnifier-shape": "circle",
        "magnifier-width": "100%",
        "columns": small_images.length,
        "images": small_images.join(' '),
        "large-images": large_images.join(' '),
    };
    var result = "";
    for(var o in options) {
        result += o + ": " +options[o]+ "; ";
    }
    return result;
};

helpers.category_by_tag = function category_by_tag(categories, tag) {
    for(var i=0; i < categories.length; i++) {
        for(var j=0; j < categories[i].length; j++) {
            if(categories[i][j].name === tag) {
                return categories[i];
            }
        }
    }

    return [];
};

exports.index = function(req, res) {
    var url = req.url;
    var catalog_alias = req.params.catalog;
    var id = req.params.id;

    cip.init_session().then(function(nm) {
            cip.get_asset(nm, catalog_alias, id, function(items) {
                if(items === null || items.length === 0) {
                    res.status(404);
                    res.render('404.jade', {'req': req});
                    return;
                }

                var asset = items[0];

                var id_str = catalog_alias + '-' + id;
                var formatted_item = asset_mapping.format_result(asset.fields);

                var player = asset_player.determine_player( formatted_item );

                /* Only entries tagged as public or public with id can be shown */
                if(formatted_item.review_state == null || (formatted_item.review_state.id != 3 && formatted_item.review_state.id != 4)) {
                    res.status(403);
                    res.render('404.jade', {'req': req});
                    return;
                }
                var category_storage = req.app.get('cip_categories')[catalog_alias];
                var formatted_categories = cip_categories.format_categories(category_storage, formatted_item.categories);
                var catalog = cip.find_catalog(req.app.get('cip_catalogs'), catalog_alias);
                var item_title = formatted_item.short_title || 'Ingen titel';
                item_title = item_title.replace(/(\r\n|\n|\r)/gm,"");
                var dashed_title = item_title.replace(/ /gi, '-').replace('/' ,'-');
                var encoded_title = querystring.escape(dashed_title);
                var filename = formatted_item.filename;

                var render_parameters = {
                    'id': id_str,
                    'item': formatted_item,
                    'categories': formatted_categories,
                    'catalog': catalog,
                    'player': player,
                    'helpers': helpers,
                    'req': req
                };

                if(player === "image-rotation") {
                    cip.get_related_assets(asset, 'isalternatemaster')
                        .then(function(relations) {
                            asset_player.generate_sources(req, {
                                url: url,
                                player: player,
                                asset_id: formatted_item.id,
                                filename: filename,
                                catalog_alias: catalog_alias,
                                related_assets_ids: relations.ids
                            }).then(function(sources) {
                                render_parameters['sources'] = sources;
                                res.render('asset', render_parameters);
                            });
                        });
                } else {
                    asset_player.generate_sources(req, {
                        url: url,
                        player: player,
                        filename: filename,
                        encoded_title: encoded_title
                    }).then(function(sources) {
                        render_parameters['sources'] = sources;
                        res.render('asset', render_parameters);
                    });
                }

            });
        }, function(nm) {
            console.log("Session not available");
            res.status(500);
            res.render('error.jade', {'req': req});
            return;
        });
};
<commit_msg>Fixing indention in the asset.js controller<commit_after>'use strict';

var querystring = require('querystring');

var cip = require('../cip-methods.js');
var cip_categories = require('../cip-categories.js');
var asset_mapping = require('../asset-mapping.js');
var asset_player = require('../asset-player.js');
var license_mapping = require('../license-mapping.js');

var helpers = {};

helpers.license_tag = function license_tag(license_id) {
    return license_mapping.licenses[license_id];
};

helpers.magic360_options = function magic360_options(image_rotation_set) {
    var small_images = image_rotation_set.small;
    var large_images = image_rotation_set.large;
    var options = {
        "magnifier-shape": "circle",
        "magnifier-width": "100%",
        "columns": small_images.length,
        "images": small_images.join(' '),
        "large-images": large_images.join(' '),
    };
    var result = "";
    for(var o in options) {
        result += o + ": " +options[o]+ "; ";
    }
    return result;
};

helpers.category_by_tag = function category_by_tag(categories, tag) {
    for(var i=0; i < categories.length; i++) {
        for(var j=0; j < categories[i].length; j++) {
            if(categories[i][j].name === tag) {
                return categories[i];
            }
        }
    }

    return [];
};

exports.index = function(req, res) {
    var url = req.url;
    var catalog_alias = req.params.catalog;
    var id = req.params.id;

    cip.init_session().then(function(nm) {
        cip.get_asset(nm, catalog_alias, id, function(items) {
            if(items === null || items.length === 0) {
                res.status(404);
                res.render('404.jade', {'req': req});
                return;
            }

            var asset = items[0];

            var id_str = catalog_alias + '-' + id;
            var formatted_item = asset_mapping.format_result(asset.fields);

            var player = asset_player.determine_player( formatted_item );

            /* Only entries tagged as public or public with id can be shown */
            if(formatted_item.review_state == null || (formatted_item.review_state.id != 3 && formatted_item.review_state.id != 4)) {
                res.status(403);
                res.render('404.jade', {'req': req});
                return;
            }
            var category_storage = req.app.get('cip_categories')[catalog_alias];
            var formatted_categories = cip_categories.format_categories(category_storage, formatted_item.categories);
            var catalog = cip.find_catalog(req.app.get('cip_catalogs'), catalog_alias);
            var item_title = formatted_item.short_title || 'Ingen titel';
            item_title = item_title.replace(/(\r\n|\n|\r)/gm,"");
            var dashed_title = item_title.replace(/ /gi, '-').replace('/' ,'-');
            var encoded_title = querystring.escape(dashed_title);
            var filename = formatted_item.filename;

            var render_parameters = {
                'id': id_str,
                'item': formatted_item,
                'categories': formatted_categories,
                'catalog': catalog,
                'player': player,
                'helpers': helpers,
                'req': req
            };

            if(player === "image-rotation") {
                cip.get_related_assets(asset, 'isalternatemaster')
                    .then(function(relations) {
                        asset_player.generate_sources(req, {
                            url: url,
                            player: player,
                            asset_id: formatted_item.id,
                            filename: filename,
                            catalog_alias: catalog_alias,
                            related_assets_ids: relations.ids
                        }).then(function(sources) {
                            render_parameters['sources'] = sources;
                            res.render('asset', render_parameters);
                        });
                    });
            } else {
                asset_player.generate_sources(req, {
                    url: url,
                    player: player,
                    filename: filename,
                    encoded_title: encoded_title
                }).then(function(sources) {
                    render_parameters['sources'] = sources;
                    res.render('asset', render_parameters);
                });
            }

        });
    }, function(nm) {
        console.log("Session not available");
        res.status(500);
        res.render('error.jade', {'req': req});
        return;
    });
};
<|endoftext|>


<commit_before>'use strict';

const log = require('intel').getLogger('browsertime');
const merge = require('lodash.merge');
const StorageManager = require('../../support/storageManager');
const FirefoxDelegate = require('../../firefox/webdriver/firefoxDelegate');
const ChromeDelegate = require('../../chrome/webdriver/chromeDelegate');
const { addConnectivity, removeConnectivity } = require('../../connectivity');
const util = require('../../support/util');
const harUtil = require('../../support/har/');
const XVFB = require('../../support/xvfb');
const ExtensionServer = require('../../extensionserver/');
const Iteration = require('./iteration');
const Collector = require('./collector');
const run = require('./run');
const engineUtils = require('../../support/engineUtils');

const defaults = {
  scripts: [],
  iterations: 3,
  delay: 0,
  videoParams: {}
};

const delay = ms => new Promise(res => setTimeout(res, ms));

function shouldDelay(index, total, delay) {
  const isLast = total - index <= 1;
  return delay > 0 && !isLast;
}

function addExtraFieldsToHar(totalResults, har, options) {
  if (har) {
    for (let pageNumber = 0; pageNumber < totalResults.length; pageNumber++) {
      for (let iteration = 0; iteration < options.iterations; iteration++) {
        const page = har.log.pages[iteration + pageNumber];
        const visualMetric = totalResults[pageNumber].visualMetrics[iteration];
        const browserScript =
          totalResults[pageNumber].browserScripts[iteration];
        const cpu = totalResults[pageNumber].cpu[iteration];
        harUtil.addExtrasToHAR(
          pageNumber,
          page,
          visualMetric,
          browserScript.timings,
          cpu,
          totalResults[pageNumber].info.url,
          options
        );
      }
    }
  }
}

/**
 * Create a new Browsertime Engine.
 * @class
 */
class Engine {
  constructor(options) {
    this.options = merge({}, defaults, options);
    if (log.isEnabledFor(log.DEBUG)) {
      log.debug('Running with options: %:2j', this.options);
    }
    this.myXVFB = new XVFB(this.options);
    this.myExtensionServer = new ExtensionServer(this.options);
  }

  /**
   * Start the engine. Will prepare everything before you will start your run:
   * * Start XVFB (if it is configured)
   * * Set connectivity
   * * Start the extension server
   */
  async start() {
    return Promise.all([
      this.myXVFB.start(),
      addConnectivity(this.options),
      this.myExtensionServer.start()
    ]);
  }

  async runByScript(
    navigationScript,
    name,
    scriptsByCategory,
    asyncScriptsByCategory
  ) {
    const options = this.options;
    const storageManager = new StorageManager(name, options);
    const engineDelegate =
      this.options.browser === 'firefox'
        ? new FirefoxDelegate(storageManager.directory, this.options)
        : new ChromeDelegate(storageManager, this.options);
    const iteration = new Iteration(
      storageManager,
      this.myExtensionServer,
      engineDelegate,
      scriptsByCategory,
      asyncScriptsByCategory,
      options
    );

    await storageManager.createDataDir();
    await engineDelegate.onStart(name, options);
    const collector = new Collector(name, storageManager, options);

    log.info(
      'Running tests using %s - %s iteration(s)',
      `${options.browser[0].toUpperCase()}${options.browser.slice(1)}`,
      options.iterations
    );

    for (let index = 1; index < options.iterations + 1; index++) {
      const data = await iteration.run(navigationScript, index);
      await collector.perIteration(data, index);
      if (shouldDelay(index, options.iterations, options.delay)) {
        await delay(options.delay);
      }
    }

    const totalResult = collector.getResults();
    const extras = await engineDelegate.onStop();
    // Add extra fields to the HAR
    // to make the HAR files better when we use them in
    // compare.sitespeed.io
    if (extras.har) {
      addExtraFieldsToHar(totalResult, extras.har, options);
      totalResult.har = extras.har;
    }
    util.logResultLogLine(totalResult);
    return totalResult;
  }

  async run(url, scriptsByCategory, asyncScriptsByCategory) {
    return this.runByScript(
      run([url]),
      url,
      scriptsByCategory,
      asyncScriptsByCategory
    );
  }

  async runMultiple(urlOrFiles, scriptsByCategory, asyncScriptsByCategory) {
    const scripts = [];
    let name;
    for (let urlOrFile of urlOrFiles) {
      if (urlOrFile.indexOf('http') > -1) {
        scripts.push(urlOrFile);
      } else {
        scripts.push(engineUtils.loadScript(urlOrFile));
      }
      if (!name) {
        name = urlOrFile;
      }
    }
    return this.runByScript(
      run(scripts),
      name,
      scriptsByCategory,
      asyncScriptsByCategory
    );
  }

  /**
   * Stop the engine. Will stop everything started in start().
   * * Stop XVFB (if it is configured)
   * * Remove connectivity
   * * Stop the extension server
   */
  async stop() {
    return Promise.all([
      this.myXVFB.stop(),
      removeConnectivity(this.options),
      this.myExtensionServer.stop()
    ]);
  }
}

module.exports = Engine;
<commit_msg>add metrics at correct page for HAR (#695)<commit_after>'use strict';

const log = require('intel').getLogger('browsertime');
const merge = require('lodash.merge');
const StorageManager = require('../../support/storageManager');
const FirefoxDelegate = require('../../firefox/webdriver/firefoxDelegate');
const ChromeDelegate = require('../../chrome/webdriver/chromeDelegate');
const { addConnectivity, removeConnectivity } = require('../../connectivity');
const util = require('../../support/util');
const harUtil = require('../../support/har/');
const XVFB = require('../../support/xvfb');
const ExtensionServer = require('../../extensionserver/');
const Iteration = require('./iteration');
const Collector = require('./collector');
const run = require('./run');
const engineUtils = require('../../support/engineUtils');

const defaults = {
  scripts: [],
  iterations: 3,
  delay: 0,
  videoParams: {}
};

const delay = ms => new Promise(res => setTimeout(res, ms));

function shouldDelay(index, total, delay) {
  const isLast = total - index <= 1;
  return delay > 0 && !isLast;
}

function addExtraFieldsToHar(totalResults, har, options) {
  if (har) {
    let harPageNumber = 0;
    for (let harPage of har.log.pages) {
      let pageNumber = harPageNumber % totalResults.length;
      for (let iteration = 0; iteration < options.iterations; iteration++) {
        const visualMetric = totalResults[pageNumber].visualMetrics[iteration];
        const browserScript =
          totalResults[pageNumber].browserScripts[iteration];
        const cpu = totalResults[pageNumber].cpu[iteration];
        harUtil.addExtrasToHAR(
          iteration,
          harPage,
          visualMetric,
          browserScript.timings,
          cpu,
          totalResults[pageNumber].info.url,
          options
        );
      }
      harPageNumber++;
    }
  }
}

/**
 * Create a new Browsertime Engine.
 * @class
 */
class Engine {
  constructor(options) {
    this.options = merge({}, defaults, options);
    if (log.isEnabledFor(log.DEBUG)) {
      log.debug('Running with options: %:2j', this.options);
    }
    this.myXVFB = new XVFB(this.options);
    this.myExtensionServer = new ExtensionServer(this.options);
  }

  /**
   * Start the engine. Will prepare everything before you will start your run:
   * * Start XVFB (if it is configured)
   * * Set connectivity
   * * Start the extension server
   */
  async start() {
    return Promise.all([
      this.myXVFB.start(),
      addConnectivity(this.options),
      this.myExtensionServer.start()
    ]);
  }

  async runByScript(
    navigationScript,
    name,
    scriptsByCategory,
    asyncScriptsByCategory
  ) {
    const options = this.options;
    const storageManager = new StorageManager(name, options);
    const engineDelegate =
      this.options.browser === 'firefox'
        ? new FirefoxDelegate(storageManager.directory, this.options)
        : new ChromeDelegate(storageManager, this.options);
    const iteration = new Iteration(
      storageManager,
      this.myExtensionServer,
      engineDelegate,
      scriptsByCategory,
      asyncScriptsByCategory,
      options
    );

    await storageManager.createDataDir();
    await engineDelegate.onStart(name, options);
    const collector = new Collector(name, storageManager, options);

    log.info(
      'Running tests using %s - %s iteration(s)',
      `${options.browser[0].toUpperCase()}${options.browser.slice(1)}`,
      options.iterations
    );

    for (let index = 1; index < options.iterations + 1; index++) {
      const data = await iteration.run(navigationScript, index);
      await collector.perIteration(data, index);
      if (shouldDelay(index, options.iterations, options.delay)) {
        await delay(options.delay);
      }
    }

    const totalResult = collector.getResults();
    const extras = await engineDelegate.onStop();
    // Add extra fields to the HAR
    // to make the HAR files better when we use them in
    // compare.sitespeed.io
    if (extras.har) {
      addExtraFieldsToHar(totalResult, extras.har, options);
      totalResult.har = extras.har;
    }
    util.logResultLogLine(totalResult);
    return totalResult;
  }

  async run(url, scriptsByCategory, asyncScriptsByCategory) {
    return this.runByScript(
      run([url]),
      url,
      scriptsByCategory,
      asyncScriptsByCategory
    );
  }

  async runMultiple(urlOrFiles, scriptsByCategory, asyncScriptsByCategory) {
    const scripts = [];
    let name;
    for (let urlOrFile of urlOrFiles) {
      if (urlOrFile.indexOf('http') > -1) {
        scripts.push(urlOrFile);
      } else {
        scripts.push(engineUtils.loadScript(urlOrFile));
      }
      if (!name) {
        name = urlOrFile;
      }
    }
    return this.runByScript(
      run(scripts),
      name,
      scriptsByCategory,
      asyncScriptsByCategory
    );
  }

  /**
   * Stop the engine. Will stop everything started in start().
   * * Stop XVFB (if it is configured)
   * * Remove connectivity
   * * Stop the extension server
   */
  async stop() {
    return Promise.all([
      this.myXVFB.stop(),
      removeConnectivity(this.options),
      this.myExtensionServer.stop()
    ]);
  }
}

module.exports = Engine;
<|endoftext|>


<commit_before>const dockerRegistryClient = require('docker-registry-client');
const got = require('got');
const URL = require('url');
const parseLinkHeader = require('parse-link-header');
const wwwAuthenticate = require('www-authenticate');
const { isVersion, sortVersions } = require('../versioning/docker');
const endpoints = require('../util/endpoints');

module.exports = {
  getDigest,
  getPkgReleases,
};

function massageRegistry(input) {
  let registry = input;
  if (!registry || registry === 'docker.io') {
    registry = 'index.docker.io'; // eslint-disable-line no-param-reassign
  }
  if (!registry.match('$https?://')) {
    registry = `https://${registry}`; // eslint-disable-line no-param-reassign
  }
  return registry;
}

function getRepository(pkgName, registry) {
  // The implicit prefix only applies to Docker Hub, not other registries
  if (!registry || registry === 'docker.io') {
    return pkgName.includes('/') ? pkgName : `library/${pkgName}`;
  }

  return pkgName;
}

async function getAuthHeaders(registry, repository) {
  // istanbul ignore if
  try {
    const apiCheckUrl = `${registry}/v2/`;
    const apiCheckResponse = await got(apiCheckUrl, { throwHttpErrors: false });
    if (apiCheckResponse.headers['www-authenticate'] === undefined) {
      return {};
    }
    const authenticateHeader = new wwwAuthenticate.parsers.WWW_Authenticate(
      apiCheckResponse.headers['www-authenticate']
    );

    // prettier-ignore
    const authUrl = `${authenticateHeader.parms.realm}?service=${authenticateHeader.parms.service}&scope=repository:${repository}:pull`;
    const { host } = URL.parse(registry);
    const opts = endpoints.find({ platform: 'docker', host }, { json: true });
    if (opts.username && opts.password) {
      const auth = Buffer.from(`${opts.username}:${opts.password}`).toString(
        'base64'
      );
      opts.headers = { Authorization: `Basic ${auth}` };
    }
    logger.debug(
      `Obtaining docker registry token for ${repository} using url ${authUrl}`
    );
    const { token } = (await got(authUrl, opts)).body;
    // istanbul ignore if
    if (!token) {
      logger.warn('Failed to obtain docker registry token');
      return null;
    }
    return {
      Authorization: `Bearer ${token}`,
      Accept: 'application/vnd.docker.distribution.manifest.v2+json',
    };
  } catch (err) /* istanbul ignore next */ {
    if (err.statusCode === 401) {
      logger.info({ registry, repository }, 'Unauthorized docker lookup');
      logger.debug({ err });
      return null;
    }
    if (err.statusCode === 429 && registry.endsWith('docker.io')) {
      logger.warn({ err }, 'docker registry failure: too many requests');
      throw new Error('registry-failure');
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn({ err }, 'docker registry failure: internal error');
      throw new Error('registry-failure');
    }
    logger.warn({ registry, repository, err }, 'Error obtaining docker token');
    return null;
  }
}

function extractDigestFromResponse(manifestResponse) {
  if (manifestResponse.headers['docker-content-digest'] === undefined) {
    return dockerRegistryClient.digestFromManifestStr(manifestResponse.body);
  }
  return manifestResponse.headers['docker-content-digest'];
}

async function getDigest(config, newValue) {
  const { dockerRegistry, depName, tagSuffix } = config;
  logger.debug(`getDigest(${dockerRegistry}, ${depName}, ${newValue})`);
  const massagedRegistry = massageRegistry(dockerRegistry);
  const repository = getRepository(depName, dockerRegistry);
  let newTag = newValue;
  if (tagSuffix) {
    newTag += `-${tagSuffix}`;
  }
  try {
    const url = `${massagedRegistry}/v2/${repository}/manifests/${newTag}`;
    const cacheNamespace = 'datasource-docker-digest';
    const cachedResult = await renovateCache.get(cacheNamespace, url);
    if (cachedResult) {
      return cachedResult;
    }
    const headers = await getAuthHeaders(massagedRegistry, repository);
    if (!headers) {
      logger.info('No docker auth found - returning');
      return null;
    }
    headers.accept = 'application/vnd.docker.distribution.manifest.v2+json';
    const manifestResponse = await got(url, {
      headers,
      timeout: 10000,
    });
    const digest = extractDigestFromResponse(manifestResponse);
    logger.debug({ digest }, 'Got docker digest');
    const cacheMinutes = 30;
    await renovateCache.set(cacheNamespace, url, digest, cacheMinutes);
    return digest;
  } catch (err) /* istanbul ignore next */ {
    if (err.message === 'registry-failure') {
      throw err;
    }
    if (err.statusCode === 401) {
      logger.info({ dockerRegistry, repository }, 'Unauthorized docker lookup');
      logger.debug({ err });
      return null;
    }
    if (err.statusCode === 404) {
      logger.info(
        {
          err,
          depName,
          newTag,
        },
        'Docker Manifest is unknown'
      );
      return null;
    }
    if (err.statusCode === 429 && massagedRegistry.endsWith('docker.io')) {
      logger.warn({ err }, 'docker registry failure: too many requests');
      throw new Error('registry-failure');
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn(
        {
          err,
          depName,
          newTag,
        },
        'docker registry failure: internal error'
      );
      throw new Error('registry-failure');
    }
    if (err.code === 'ETIMEDOUT') {
      logger.info(
        { massagedRegistry },
        'Timeout when attempting to connect to docker registry'
      );
      logger.debug({ err });
      return null;
    }
    logger.info(
      {
        err,
        depName,
        newTag,
      },
      'Unknown Error looking up docker image digest'
    );
    return null;
  }
}

async function getTags(registry, repository) {
  let tags = [];
  try {
    const cacheNamespace = 'datasource-docker-tags';
    const cacheKey = `${registry}:${repository}`;
    const cachedResult = await renovateCache.get(cacheNamespace, cacheKey);
    if (cachedResult) {
      return cachedResult;
    }
    let url = `${registry}/v2/${repository}/tags/list?n=10000`;
    const headers = await getAuthHeaders(registry, repository);
    if (!headers) {
      return null;
    }
    let page = 1;
    do {
      const res = await got(url, { json: true, headers, timeout: 10000 });
      tags = tags.concat(res.body.tags);
      const linkHeader = parseLinkHeader(res.headers.link);
      url =
        linkHeader && linkHeader.next
          ? URL.resolve(url, linkHeader.next.url)
          : null;
      page += 1;
    } while (url && page < 20);
    logger.debug({ length: tags.length }, 'Got docker tags');
    logger.trace({ tags });
    const cacheMinutes = 30;
    await renovateCache.set(cacheNamespace, cacheKey, tags, cacheMinutes);
    return tags;
  } catch (err) /* istanbul ignore next */ {
    logger.debug(
      {
        err,
      },
      'docker.getTags() error'
    );
    if (err.statusCode === 401) {
      logger.info(
        { registry, repository, err },
        'Not authorised to look up docker tags'
      );
      return null;
    }
    if (err.statusCode === 429 && registry.endsWith('docker.io')) {
      logger.warn(
        { registry, repository, err },
        'docker registry failure: too many requests'
      );
      throw new Error('registry-failure');
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn(
        { registry, repository, err },
        'docker registry failure: internal error'
      );
      throw new Error('registry-failure');
    }
    if (err.code === 'ETIMEDOUT') {
      logger.info(
        { registry },
        'Timeout when attempting to connect to docker registry'
      );
      return null;
    }
    logger.warn(
      { registry, repository, err },
      'Error getting docker image tags'
    );
    return null;
  }
}

async function getPkgReleases(purl) {
  const { fullname, qualifiers } = purl;
  const { registry, suffix } = qualifiers;
  logger.debug({ fullname, registry, suffix }, 'docker.getDependencies()');
  const massagedRegistry = massageRegistry(registry);
  const repository = getRepository(fullname, registry);
  const tags = await getTags(massagedRegistry, repository);
  if (!tags) {
    return null;
  }
  const releases = tags
    .filter(tag => !suffix || tag.endsWith(`-${suffix}`))
    .map(tag => (suffix ? tag.replace(new RegExp(`-${suffix}$`), '') : tag))
    .filter(isVersion)
    .sort(sortVersions)
    .map(version => ({ version }));
  return { releases };
}
<commit_msg>refactor: don’t use repository for docker logs<commit_after>const dockerRegistryClient = require('docker-registry-client');
const got = require('got');
const URL = require('url');
const parseLinkHeader = require('parse-link-header');
const wwwAuthenticate = require('www-authenticate');
const { isVersion, sortVersions } = require('../versioning/docker');
const endpoints = require('../util/endpoints');

module.exports = {
  getDigest,
  getPkgReleases,
};

function massageRegistry(input) {
  let registry = input;
  if (!registry || registry === 'docker.io') {
    registry = 'index.docker.io'; // eslint-disable-line no-param-reassign
  }
  if (!registry.match('$https?://')) {
    registry = `https://${registry}`; // eslint-disable-line no-param-reassign
  }
  return registry;
}

function getRepository(pkgName, registry) {
  // The implicit prefix only applies to Docker Hub, not other registries
  if (!registry || registry === 'docker.io') {
    return pkgName.includes('/') ? pkgName : `library/${pkgName}`;
  }

  return pkgName;
}

async function getAuthHeaders(registry, repository) {
  // istanbul ignore if
  try {
    const apiCheckUrl = `${registry}/v2/`;
    const apiCheckResponse = await got(apiCheckUrl, { throwHttpErrors: false });
    if (apiCheckResponse.headers['www-authenticate'] === undefined) {
      return {};
    }
    const authenticateHeader = new wwwAuthenticate.parsers.WWW_Authenticate(
      apiCheckResponse.headers['www-authenticate']
    );

    // prettier-ignore
    const authUrl = `${authenticateHeader.parms.realm}?service=${authenticateHeader.parms.service}&scope=repository:${repository}:pull`;
    const { host } = URL.parse(registry);
    const opts = endpoints.find({ platform: 'docker', host }, { json: true });
    if (opts.username && opts.password) {
      const auth = Buffer.from(`${opts.username}:${opts.password}`).toString(
        'base64'
      );
      opts.headers = { Authorization: `Basic ${auth}` };
    }
    logger.debug(
      `Obtaining docker registry token for ${repository} using url ${authUrl}`
    );
    const { token } = (await got(authUrl, opts)).body;
    // istanbul ignore if
    if (!token) {
      logger.warn('Failed to obtain docker registry token');
      return null;
    }
    return {
      Authorization: `Bearer ${token}`,
      Accept: 'application/vnd.docker.distribution.manifest.v2+json',
    };
  } catch (err) /* istanbul ignore next */ {
    if (err.statusCode === 401) {
      logger.info(
        { registry, dockerRepository: repository },
        'Unauthorized docker lookup'
      );
      logger.debug({ err });
      return null;
    }
    if (err.statusCode === 429 && registry.endsWith('docker.io')) {
      logger.warn({ err }, 'docker registry failure: too many requests');
      throw new Error('registry-failure');
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn({ err }, 'docker registry failure: internal error');
      throw new Error('registry-failure');
    }
    logger.warn(
      { registry, dockerRepository: repository, err },
      'Error obtaining docker token'
    );
    return null;
  }
}

function extractDigestFromResponse(manifestResponse) {
  if (manifestResponse.headers['docker-content-digest'] === undefined) {
    return dockerRegistryClient.digestFromManifestStr(manifestResponse.body);
  }
  return manifestResponse.headers['docker-content-digest'];
}

async function getDigest(config, newValue) {
  const { dockerRegistry, depName, tagSuffix } = config;
  logger.debug(`getDigest(${dockerRegistry}, ${depName}, ${newValue})`);
  const massagedRegistry = massageRegistry(dockerRegistry);
  const repository = getRepository(depName, dockerRegistry);
  let newTag = newValue;
  if (tagSuffix) {
    newTag += `-${tagSuffix}`;
  }
  try {
    const url = `${massagedRegistry}/v2/${repository}/manifests/${newTag}`;
    const cacheNamespace = 'datasource-docker-digest';
    const cachedResult = await renovateCache.get(cacheNamespace, url);
    if (cachedResult) {
      return cachedResult;
    }
    const headers = await getAuthHeaders(massagedRegistry, repository);
    if (!headers) {
      logger.info('No docker auth found - returning');
      return null;
    }
    headers.accept = 'application/vnd.docker.distribution.manifest.v2+json';
    const manifestResponse = await got(url, {
      headers,
      timeout: 10000,
    });
    const digest = extractDigestFromResponse(manifestResponse);
    logger.debug({ digest }, 'Got docker digest');
    const cacheMinutes = 30;
    await renovateCache.set(cacheNamespace, url, digest, cacheMinutes);
    return digest;
  } catch (err) /* istanbul ignore next */ {
    if (err.message === 'registry-failure') {
      throw err;
    }
    if (err.statusCode === 401) {
      logger.info(
        { dockerRegistry, dockerRepository: repository },
        'Unauthorized docker lookup'
      );
      logger.debug({ err });
      return null;
    }
    if (err.statusCode === 404) {
      logger.info(
        {
          err,
          depName,
          newTag,
        },
        'Docker Manifest is unknown'
      );
      return null;
    }
    if (err.statusCode === 429 && massagedRegistry.endsWith('docker.io')) {
      logger.warn({ err }, 'docker registry failure: too many requests');
      throw new Error('registry-failure');
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn(
        {
          err,
          depName,
          newTag,
        },
        'docker registry failure: internal error'
      );
      throw new Error('registry-failure');
    }
    if (err.code === 'ETIMEDOUT') {
      logger.info(
        { massagedRegistry },
        'Timeout when attempting to connect to docker registry'
      );
      logger.debug({ err });
      return null;
    }
    logger.info(
      {
        err,
        depName,
        newTag,
      },
      'Unknown Error looking up docker image digest'
    );
    return null;
  }
}

async function getTags(registry, repository) {
  let tags = [];
  try {
    const cacheNamespace = 'datasource-docker-tags';
    const cacheKey = `${registry}:${repository}`;
    const cachedResult = await renovateCache.get(cacheNamespace, cacheKey);
    if (cachedResult) {
      return cachedResult;
    }
    let url = `${registry}/v2/${repository}/tags/list?n=10000`;
    const headers = await getAuthHeaders(registry, repository);
    if (!headers) {
      return null;
    }
    let page = 1;
    do {
      const res = await got(url, { json: true, headers, timeout: 10000 });
      tags = tags.concat(res.body.tags);
      const linkHeader = parseLinkHeader(res.headers.link);
      url =
        linkHeader && linkHeader.next
          ? URL.resolve(url, linkHeader.next.url)
          : null;
      page += 1;
    } while (url && page < 20);
    logger.debug({ length: tags.length }, 'Got docker tags');
    logger.trace({ tags });
    const cacheMinutes = 30;
    await renovateCache.set(cacheNamespace, cacheKey, tags, cacheMinutes);
    return tags;
  } catch (err) /* istanbul ignore next */ {
    logger.debug(
      {
        err,
      },
      'docker.getTags() error'
    );
    if (err.statusCode === 401) {
      logger.info(
        { registry, dockerRepository: repository, err },
        'Not authorised to look up docker tags'
      );
      return null;
    }
    if (err.statusCode === 429 && registry.endsWith('docker.io')) {
      logger.warn(
        { registry, dockerRepository: repository, err },
        'docker registry failure: too many requests'
      );
      throw new Error('registry-failure');
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      logger.warn(
        { registry, dockerRepository: repository, err },
        'docker registry failure: internal error'
      );
      throw new Error('registry-failure');
    }
    if (err.code === 'ETIMEDOUT') {
      logger.info(
        { registry },
        'Timeout when attempting to connect to docker registry'
      );
      return null;
    }
    logger.warn(
      { registry, dockerRepository: repository, err },
      'Error getting docker image tags'
    );
    return null;
  }
}

async function getPkgReleases(purl) {
  const { fullname, qualifiers } = purl;
  const { registry, suffix } = qualifiers;
  logger.debug({ fullname, registry, suffix }, 'docker.getDependencies()');
  const massagedRegistry = massageRegistry(registry);
  const repository = getRepository(fullname, registry);
  const tags = await getTags(massagedRegistry, repository);
  if (!tags) {
    return null;
  }
  const releases = tags
    .filter(tag => !suffix || tag.endsWith(`-${suffix}`))
    .map(tag => (suffix ? tag.replace(new RegExp(`-${suffix}$`), '') : tag))
    .filter(isVersion)
    .sort(sortVersions)
    .map(version => ({ version }));
  return { releases };
}
<|endoftext|>