tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

node-properties.js (19363B)


      1 /**
      2 * The MIT License (MIT)
      3 *
      4 * Copyright (c) 2014 Gabriel Llamas
      5 *
      6 * Permission is hereby granted, free of charge, to any person obtaining a copy
      7 * of this software and associated documentation files (the "Software"), to deal
      8 * in the Software without restriction, including without limitation the rights
      9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     10 * copies of the Software, and to permit persons to whom the Software is
     11 * furnished to do so, subject to the following conditions:
     12 *
     13 * The above copyright notice and this permission notice shall be included in
     14 * all copies or substantial portions of the Software.
     15 *
     16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     22 * THE SOFTWARE.
     23 *
     24 */
     25 
     26 "use strict";
     27 
     28 var hex = function (c){
     29  switch (c){
     30    case "0": return 0;
     31    case "1": return 1;
     32    case "2": return 2;
     33    case "3": return 3;
     34    case "4": return 4;
     35    case "5": return 5;
     36    case "6": return 6;
     37    case "7": return 7;
     38    case "8": return 8;
     39    case "9": return 9;
     40    case "a": case "A": return 10;
     41    case "b": case "B": return 11;
     42    case "c": case "C": return 12;
     43    case "d": case "D": return 13;
     44    case "e": case "E": return 14;
     45    case "f": case "F": return 15;
     46  }
     47 };
     48 
     49 var parse = function (data, options, handlers, control){
     50  var c;
     51  var code;
     52  var escape;
     53  var skipSpace = true;
     54  var isCommentLine;
     55  var isSectionLine;
     56  var newLine = true;
     57  var multiLine;
     58  var isKey = true;
     59  var key = "";
     60  var value = "";
     61  var section;
     62  var unicode;
     63  var unicodeRemaining;
     64  var escapingUnicode;
     65  var keySpace;
     66  var sep;
     67  var ignoreLine;
     68 
     69  var line = function (){
     70    if (key || value || sep){
     71      handlers.line (key, value);
     72      key = "";
     73      value = "";
     74      sep = false;
     75    }
     76  };
     77 
     78  var escapeString = function (key, c, code){
     79    if (escapingUnicode && unicodeRemaining){
     80      unicode = (unicode << 4) + hex (c);
     81      if (--unicodeRemaining) return key;
     82      escape = false;
     83      escapingUnicode = false;
     84      return key + String.fromCharCode (unicode);
     85    }
     86 
     87    //code 117: u
     88    if (code === 117){
     89      unicode = 0;
     90      escapingUnicode = true;
     91      unicodeRemaining = 4;
     92      return key;
     93    }
     94 
     95    escape = false;
     96 
     97    //code 116: t
     98    //code 114: r
     99    //code 110: n
    100    //code 102: f
    101    if (code === 116) return key + "\t";
    102    else if (code === 114) return key + "\r";
    103    else if (code === 110) return key + "\n";
    104    else if (code === 102) return key + "\f";
    105 
    106    return key + c;
    107  };
    108 
    109  var isComment;
    110  var isSeparator;
    111 
    112  if (options._strict){
    113    isComment = function (c, code, options){
    114      return options._comments[c];
    115    };
    116 
    117    isSeparator = function (c, code, options){
    118      return options._separators[c];
    119    };
    120  }else{
    121    isComment = function (c, code, options){
    122      //code 35: #
    123      //code 33: !
    124      return code === 35 || code === 33 || options._comments[c];
    125    };
    126 
    127    isSeparator = function (c, code, options){
    128      //code 61: =
    129      //code 58: :
    130      return code === 61 || code === 58 || options._separators[c];
    131    };
    132  }
    133 
    134  for (var i=~~control.resume; i<data.length; i++){
    135    if (control.abort) return;
    136    if (control.pause){
    137      //The next index is always the start of a new line, it's a like a fresh
    138      //start, there's no need to save the current state
    139      control.resume = i;
    140      return;
    141    }
    142 
    143    c = data[i];
    144    code = data.charCodeAt (i);
    145 
    146    //code 13: \r
    147    if (code === 13) continue;
    148 
    149    if (isCommentLine){
    150      //code 10: \n
    151      if (code === 10){
    152        isCommentLine = false;
    153        newLine = true;
    154        skipSpace = true;
    155      }
    156      continue;
    157    }
    158 
    159    //code 93: ]
    160    if (isSectionLine && code === 93){
    161      handlers.section (section);
    162      //Ignore chars after the section in the same line
    163      ignoreLine = true;
    164      continue;
    165    }
    166 
    167    if (skipSpace){
    168      //code 32: " " (space)
    169      //code 9: \t
    170      //code 12: \f
    171      if (code === 32 || code === 9 || code === 12){
    172        continue;
    173      }
    174      //code 10: \n
    175      if (!multiLine && code === 10){
    176        //Empty line or key w/ separator and w/o value
    177        isKey = true;
    178        keySpace = false;
    179        newLine = true;
    180        line ();
    181        continue;
    182      }
    183      skipSpace = false;
    184      multiLine = false;
    185    }
    186 
    187    if (newLine){
    188      newLine = false;
    189      if (isComment (c, code, options)){
    190        isCommentLine = true;
    191        continue;
    192      }
    193      //code 91: [
    194      if (options.sections && code === 91){
    195        section = "";
    196        isSectionLine = true;
    197        control.skipSection = false;
    198        continue;
    199      }
    200    }
    201 
    202    //code 10: \n
    203    if (code !== 10){
    204      if (control.skipSection || ignoreLine) continue;
    205 
    206      if (!isSectionLine){
    207        if (!escape && isKey && isSeparator (c, code, options)){
    208          //sep is needed to detect empty key and empty value with a
    209          //non-whitespace separator
    210          sep = true;
    211          isKey = false;
    212          keySpace = false;
    213          //Skip whitespace between separator and value
    214          skipSpace = true;
    215          continue;
    216        }
    217      }
    218 
    219      //code 92: "\" (backslash)
    220      if (code === 92){
    221        if (escape){
    222          if (escapingUnicode) continue;
    223 
    224          if (keySpace){
    225            //Line with whitespace separator
    226            keySpace = false;
    227            isKey = false;
    228          }
    229 
    230          if (isSectionLine) section += "\\";
    231          else if (isKey) key += "\\";
    232          else value += "\\";
    233        }
    234        escape = !escape;
    235      }else{
    236        if (keySpace){
    237          //Line with whitespace separator
    238          keySpace = false;
    239          isKey = false;
    240        }
    241 
    242        if (isSectionLine){
    243          if (escape) section = escapeString (section, c, code);
    244          else section += c;
    245        }else if (isKey){
    246          if (escape){
    247            key = escapeString (key, c, code);
    248          }else{
    249            //code 32: " " (space)
    250            //code 9: \t
    251            //code 12: \f
    252            if (code === 32 || code === 9 || code === 12){
    253              keySpace = true;
    254              //Skip whitespace between key and separator
    255              skipSpace = true;
    256              continue;
    257            }
    258            key += c;
    259          }
    260        }else{
    261          if (escape) value = escapeString (value, c, code);
    262          else value += c;
    263        }
    264      }
    265    }else{
    266      if (escape){
    267        if (!escapingUnicode){
    268          escape = false;
    269        }
    270        skipSpace = true;
    271        multiLine = true;
    272      }else{
    273        if (isSectionLine){
    274          isSectionLine = false;
    275          if (!ignoreLine){
    276            //The section doesn't end with ], it's a key
    277            control.error = new Error ("The section line \"" + section +
    278                "\" must end with \"]\"");
    279            return;
    280          }
    281          ignoreLine = false;
    282        }
    283        newLine = true;
    284        skipSpace = true;
    285        isKey = true;
    286 
    287        line ();
    288      }
    289    }
    290  }
    291 
    292  control.parsed = true;
    293 
    294  if (isSectionLine && !ignoreLine){
    295    //The section doesn't end with ], it's a key
    296    control.error = new Error ("The section line \"" + section + "\" must end" +
    297        "with \"]\"");
    298    return;
    299  }
    300  line ();
    301 };
    302 
    303 var INCLUDE_KEY = "include";
    304 var INDEX_FILE = "index.properties";
    305 
    306 var cast = function (value){
    307  if (value === null || value === "null") return null;
    308  if (value === "undefined") return undefined;
    309  if (value === "true") return true;
    310  if (value === "false") return false;
    311  var v = Number (value);
    312  return isNaN (v) ? value : v;
    313 };
    314 
    315 var expand = function  (o, str, options, cb){
    316  if (!options.variables || !str) return cb (null, str);
    317 
    318  var stack = [];
    319  var c;
    320  var cp;
    321  var key = "";
    322  var section = null;
    323  var v;
    324  var holder;
    325  var t;
    326  var n;
    327 
    328  for (var i=0; i<str.length; i++){
    329    c = str[i];
    330 
    331    if (cp === "$" && c === "{"){
    332      key = key.substring (0, key.length - 1);
    333      stack.push ({
    334        key: key,
    335        section: section
    336      });
    337      key = "";
    338      section = null;
    339      continue;
    340    }else if (stack.length){
    341      if (options.sections && c === "|"){
    342        section = key;
    343        key = "";
    344        continue;
    345      }else if (c === "}"){
    346        holder = section !== null ? searchValue (o, section, true) : o;
    347        if (!holder){
    348          return cb (new Error ("The section \"" + section + "\" does not " +
    349              "exist"));
    350        }
    351 
    352        v = options.namespaces ? searchValue (holder, key) : holder[key];
    353        if (v === undefined){
    354          //Read the external vars
    355          v = options.namespaces
    356              ? searchValue (options._vars, key)
    357              : options._vars[key]
    358 
    359          if (v === undefined){
    360            return cb (new Error ("The property \"" + key + "\" does not " +
    361                "exist"));
    362          }
    363        }
    364 
    365        t = stack.pop ();
    366        section = t.section;
    367        key = t.key + (v === null ? "" : v);
    368        continue;
    369      }
    370    }
    371 
    372    cp = c;
    373    key += c;
    374  }
    375 
    376  if (stack.length !== 0){
    377    return cb (new Error ("Malformed variable: " + str));
    378  }
    379 
    380  cb (null, key);
    381 };
    382 
    383 var searchValue = function (o, chain, section){
    384  var n = chain.split (".");
    385  var str;
    386 
    387  for (var i=0; i<n.length-1; i++){
    388    str = n[i];
    389    if (o[str] === undefined) return;
    390    o = o[str];
    391  }
    392 
    393  var v = o[n[n.length - 1]];
    394  if (section){
    395    if (typeof v !== "object") return;
    396    return v;
    397  }else{
    398    if (typeof v === "object") return;
    399    return v;
    400  }
    401 };
    402 
    403 var namespaceKey = function (o, key, value){
    404  var n = key.split (".");
    405  var str;
    406 
    407  for (var i=0; i<n.length-1; i++){
    408    str = n[i];
    409    if (o[str] === undefined){
    410      o[str] = {};
    411    }else if (typeof o[str] !== "object"){
    412      throw new Error ("Invalid namespace chain in the property name '" +
    413          key + "' ('" + str + "' has already a value)");
    414    }
    415    o = o[str];
    416  }
    417 
    418  o[n[n.length - 1]] = value;
    419 };
    420 
    421 var namespaceSection = function (o, section){
    422  var n = section.split (".");
    423  var str;
    424 
    425  for (var i=0; i<n.length; i++){
    426    str = n[i];
    427    if (o[str] === undefined){
    428      o[str] = {};
    429    }else if (typeof o[str] !== "object"){
    430      throw new Error ("Invalid namespace chain in the section name '" +
    431          section + "' ('" + str + "' has already a value)");
    432    }
    433    o = o[str];
    434  }
    435 
    436  return o;
    437 };
    438 
    439 var merge = function (o1, o2){
    440  for (var p in o2){
    441    try{
    442      if (o1[p].constructor === Object){
    443        o1[p] = merge (o1[p], o2[p]);
    444      }else{
    445        o1[p] = o2[p];
    446      }
    447    }catch (e){
    448      o1[p] = o2[p];
    449    }
    450  }
    451  return o1;
    452 }
    453 
    454 var build = function (data, options, dirname, cb){
    455  var o = {};
    456 
    457  if (options.namespaces){
    458    var n = {};
    459  }
    460 
    461  var control = {
    462    abort: false,
    463    skipSection: false
    464  };
    465 
    466  if (options.include){
    467    var remainingIncluded = 0;
    468 
    469    var include = function (value){
    470      if (currentSection !== null){
    471        return abort (new Error ("Cannot include files from inside a " +
    472            "section: " + currentSection));
    473      }
    474 
    475      var p = path.resolve (dirname, value);
    476      if (options._included[p]) return;
    477 
    478      options._included[p] = true;
    479      remainingIncluded++;
    480      control.pause = true;
    481 
    482      read (p, options, function (error, included){
    483        if (error) return abort (error);
    484 
    485        remainingIncluded--;
    486        merge (options.namespaces ? n : o, included);
    487        control.pause = false;
    488 
    489        if (!control.parsed){
    490          parse (data, options, handlers, control);
    491          if (control.error) return abort (control.error);
    492        }
    493 
    494        if (!remainingIncluded) cb (null, options.namespaces ? n : o);
    495      });
    496    };
    497  }
    498 
    499  if (!data){
    500    if (cb) return cb (null, o);
    501    return o;
    502  }
    503 
    504  var currentSection = null;
    505  var currentSectionStr = null;
    506 
    507  var abort = function (error){
    508    control.abort = true;
    509    if (cb) return cb (error);
    510    throw error;
    511  };
    512 
    513  var handlers = {};
    514  var reviver = {
    515    assert: function (){
    516      return this.isProperty ? reviverLine.value : true;
    517    }
    518  };
    519  var reviverLine = {};
    520 
    521  //Line handler
    522  //For speed reasons, if "namespaces" is enabled, the old object is still
    523  //populated, e.g.: ${a.b} reads the "a.b" property from { "a.b": 1 }, instead
    524  //of having a unique object { a: { b: 1 } } which is slower to search for
    525  //the "a.b" value
    526  //If "a.b" is not found, then the external vars are read. If "namespaces" is
    527  //enabled, the var "a.b" is split and it searches the a.b value. If it is not
    528  //enabled, then the var "a.b" searches the "a.b" value
    529 
    530  var line;
    531  var error;
    532 
    533  if (options.reviver){
    534    if (options.sections){
    535      line = function (key, value){
    536        if (options.include && key === INCLUDE_KEY) return include (value);
    537 
    538        reviverLine.value = value;
    539        reviver.isProperty = true;
    540        reviver.isSection = false;
    541 
    542        value = options.reviver.call (reviver, key, value, currentSectionStr);
    543        if (value !== undefined){
    544          if (options.namespaces){
    545            try{
    546              namespaceKey (currentSection === null ? n : currentSection,
    547                  key, value);
    548            }catch (error){
    549              abort (error);
    550            }
    551          }else{
    552            if (currentSection === null) o[key] = value;
    553            else currentSection[key] = value;
    554          }
    555        }
    556      };
    557    }else{
    558      line = function (key, value){
    559        if (options.include && key === INCLUDE_KEY) return include (value);
    560 
    561        reviverLine.value = value;
    562        reviver.isProperty = true;
    563        reviver.isSection = false;
    564 
    565        value = options.reviver.call (reviver, key, value);
    566        if (value !== undefined){
    567          if (options.namespaces){
    568            try{
    569              namespaceKey (n, key, value);
    570            }catch (error){
    571              abort (error);
    572            }
    573          }else{
    574            o[key] = value;
    575          }
    576        }
    577      };
    578    }
    579  }else{
    580    if (options.sections){
    581      line = function (key, value){
    582        if (options.include && key === INCLUDE_KEY) return include (value);
    583 
    584        if (options.namespaces){
    585          try{
    586            namespaceKey (currentSection === null ? n : currentSection, key,
    587                value);
    588          }catch (error){
    589            abort (error);
    590          }
    591        }else{
    592          if (currentSection === null) o[key] = value;
    593          else currentSection[key] = value;
    594        }
    595      };
    596    }else{
    597      line = function (key, value){
    598        if (options.include && key === INCLUDE_KEY) return include (value);
    599 
    600        if (options.namespaces){
    601          try{
    602            namespaceKey (n, key, value);
    603          }catch (error){
    604            abort (error);
    605          }
    606        }else{
    607          o[key] = value;
    608        }
    609      };
    610    }
    611  }
    612 
    613  //Section handler
    614  var section;
    615  if (options.sections){
    616    if (options.reviver){
    617      section = function (section){
    618        currentSectionStr = section;
    619        reviverLine.section = section;
    620        reviver.isProperty = false;
    621        reviver.isSection = true;
    622 
    623        var add = options.reviver.call (reviver, null, null, section);
    624        if (add){
    625          if (options.namespaces){
    626            try{
    627              currentSection = namespaceSection (n, section);
    628            }catch (error){
    629              abort (error);
    630            }
    631          }else{
    632            currentSection = o[section] = {};
    633          }
    634        }else{
    635          control.skipSection = true;
    636        }
    637      };
    638    }else{
    639      section = function (section){
    640        currentSectionStr = section;
    641        if (options.namespaces){
    642          try{
    643            currentSection = namespaceSection (n, section);
    644          }catch (error){
    645            abort (error);
    646          }
    647        }else{
    648          currentSection = o[section] = {};
    649        }
    650      };
    651    }
    652  }
    653 
    654  //Variables
    655  if (options.variables){
    656    handlers.line = function (key, value){
    657      expand (options.namespaces ? n : o, key, options, function (error, key){
    658        if (error) return abort (error);
    659 
    660        expand (options.namespaces ? n : o, value, options,
    661            function (error, value){
    662          if (error) return abort (error);
    663 
    664          line (key, cast (value || null));
    665        });
    666      });
    667    };
    668 
    669    if (options.sections){
    670      handlers.section = function (s){
    671        expand (options.namespaces ? n : o, s, options, function (error, s){
    672          if (error) return abort (error);
    673 
    674          section (s);
    675        });
    676      };
    677    }
    678  }else{
    679    handlers.line = function (key, value){
    680      line (key, cast (value || null));
    681    };
    682 
    683    if (options.sections){
    684      handlers.section = section;
    685    }
    686  }
    687 
    688  parse (data, options, handlers, control);
    689  if (control.error) return abort (control.error);
    690 
    691  if (control.abort || control.pause) return;
    692 
    693  if (cb) return cb (null, options.namespaces ? n : o);
    694  return options.namespaces ? n : o;
    695 };
    696 
    697 var read = function (f, options, cb){
    698  fs.stat (f, function (error, stats){
    699    if (error) return cb (error);
    700 
    701    var dirname;
    702 
    703    if (stats.isDirectory ()){
    704      dirname = f;
    705      f = path.join (f, INDEX_FILE);
    706    }else{
    707      dirname = path.dirname (f);
    708    }
    709 
    710    fs.readFile (f, { encoding: "utf8" }, function (error, data){
    711      if (error) return cb (error);
    712      build (data, options, dirname, cb);
    713    });
    714  });
    715 };
    716 
    717 module.exports = function (data, options, cb){
    718  if (typeof options === "function"){
    719    cb = options;
    720    options = {};
    721  }
    722 
    723  options = options || {};
    724  var code;
    725 
    726  if (options.include){
    727    if (!cb) throw new Error ("A callback must be passed if the 'include' " +
    728        "option is enabled");
    729    options._included = {};
    730  }
    731 
    732  options = options || {};
    733  options._strict = options.strict && (options.comments || options.separators);
    734  options._vars = options.vars || {};
    735 
    736  var comments = options.comments || [];
    737  if (!Array.isArray (comments)) comments = [comments];
    738  var c = {};
    739  comments.forEach (function (comment){
    740    code = comment.charCodeAt (0);
    741    if (comment.length > 1 || code < 33 || code > 126){
    742      throw new Error ("The comment token must be a single printable ASCII " +
    743          "character");
    744    }
    745    c[comment] = true;
    746  });
    747  options._comments = c;
    748 
    749  var separators = options.separators || [];
    750  if (!Array.isArray (separators)) separators = [separators];
    751  var s = {};
    752  separators.forEach (function (separator){
    753    code = separator.charCodeAt (0);
    754    if (separator.length > 1 || code < 33 || code > 126){
    755      throw new Error ("The separator token must be a single printable ASCII " +
    756          "character");
    757    }
    758    s[separator] = true;
    759  });
    760  options._separators = s;
    761 
    762  if (options.path){
    763    if (!cb) throw new Error ("A callback must be passed if the 'path' " +
    764        "option is enabled");
    765    if (options.include){
    766      read (data, options, cb);
    767    }else{
    768      fs.readFile (data, { encoding: "utf8" }, function (error, data){
    769        if (error) return cb (error);
    770        build (data, options, ".", cb);
    771      });
    772    }
    773  }else{
    774    return build (data, options, ".", cb);
    775  }
    776 };