<commit_before>var fs = require('graceful-fs');
var path = require('path');
var deepExtend = require('deep-extend');
var isAsset = require('./util/isAsset');
var isComponent = require('./util/isComponent');
var createError = require('./util/createError');

var possibleJsons = ['bower.json', 'component.json', '.bower.json'];

function read(file, options, callback) {
    if (typeof options === 'function') {
        callback = options;
        options = {};
    }

    // Check if file is a directory
    fs.stat(file, function (err, stat) {
        if (err) {
            return callback(err);
        }

        // It's a directory, so we find the json inside it
        if (stat.isDirectory()) {
            return find(file, function (err, file) {
                if (err) {
                    return callback(err);
                }

                read(file, options, callback);
            });
        }

        // Otherwise read it
        fs.readFile(file, function (err, contents) {
            var json;

            if (err) {
                return callback(err);
            }

            try {
                json = JSON.parse(contents.toString());
            } catch (err) {
                err.file = path.resolve(file);
                err.code = 'EMALFORMED';
                return callback(err);
            }

            // Parse it
            try {
                json = parse(json, options);
            } catch (err) {
                err.file = path.resolve(file);
                return callback(err);
            }

            callback(null, json, file);
        });
    });
}

function parse(json, options) {
    options = deepExtend({
        normalize: false,
        validate: true,
        clone: false
    }, options || {});

    // Clone
    if (options.clone) {
        json = deepExtend({}, json);
    }

    // Validate
    if (options.validate) {
        validate(json);
    }

    // Normalize
    if (options.normalize) {
        normalize(json);
    }

    return json;
}

function validate(json, options) {
    
    options = deepExtend({
        enforceNameExists: true
    }, options || {});

    if(options.enforceNameExists || json.name) {
        validatePackageName(json.name);
    }

    if (json.description && json.description.length > 140) {
        throw createError('The description is too long. 140 characters should be more than enough', 'EINVALID');
    }

    if (json.main !== undefined) {
        var main = json.main;
        if (typeof main === 'string') {
            main = [main];
        }
        if (!(main instanceof Array)) {
            throw createError('The "main" field has to be either an Array or a String', 'EINVALID');
        }
        var ext2files = {};
        main.forEach(function (filename) {
            if (typeof filename !== 'string') {
                throw createError('The "main" Array has to contain only Strings', 'EINVALID');
            }
            if (/[*]/.test(filename)) {
                throw createError('The "main" field cannot contain globs (example: "*.js")', 'EINVALID');
            }
            if (/[.]min[.][^/]+$/.test(filename)) {
                throw createError('The "main" field cannot contain minified files', 'EINVALID');
            }
            if (isAsset(filename)) {
                throw createError('The "main" field cannot contain font, image, audio, or video files', 'EINVALID');
            }
            var ext = path.extname(filename);
            if (ext.length >= 2) {
                var files = ext2files[ext];
                if (!files) {
                    files = ext2files[ext] = [];
                }
                files.push(filename);
            }
        });
        Object.keys(ext2files).forEach(function (ext) {
            var files = ext2files[ext];
            if (files.length > 1) {
                throw createError('The "main" field has to contain only 1 file per filetype; found multiple ' + ext + ' files: ' + JSON.stringify(files), 'EINVALID');
            }
        });
    }

    // TODO https://github.com/bower/bower.json-spec

    return json;
}

function validatePackageName(name) {
    if (!name) {
        throw createError('No name property set', 'EINVALID');
    }

    if (name.length > 50) {
        throw createError('The name is too long. 50 characters should be more than enough', 'EINVALID');
    }

    if (/[A-Z]/.test(name)) {
        throw createError('The name contains upper case letters', 'EINVALID');
    }

    if (!/^[a-z]/.test(name)) {
        throw createError('The name has to start with a lower case character from a to z', 'EINVALID');
    }

    if (!/[a-z]$/.test(name)) {
        throw createError('The name has to end with a lower case character from a to z', 'EINVALID');
    }
}

function normalize(json) {
    if (typeof json.main === 'string') {
        json.main = [json.main];
    }<commit_msg>validate dependency names prevent directory traversing<commit_after>var fs = require('graceful-fs');
var path = require('path');
var deepExtend = require('deep-extend');
var isAsset = require('./util/isAsset');
var isComponent = require('./util/isComponent');
var createError = require('./util/createError');

var possibleJsons = ['bower.json', 'component.json', '.bower.json'];

function read(file, options, callback) {
    if (typeof options === 'function') {
        callback = options;
        options = {};
    }

    // Check if file is a directory
    fs.stat(file, function (err, stat) {
        if (err) {
            return callback(err);
        }

        // It's a directory, so we find the json inside it
        if (stat.isDirectory()) {
            return find(file, function (err, file) {
                if (err) {
                    return callback(err);
                }

                read(file, options, callback);
            });
        }

        // Otherwise read it
        fs.readFile(file, function (err, contents) {
            var json;

            if (err) {
                return callback(err);
            }

            try {
                json = JSON.parse(contents.toString());
            } catch (err) {
                err.file = path.resolve(file);
                err.code = 'EMALFORMED';
                return callback(err);
            }

            // Parse it
            try {
                json = parse(json, options);
            } catch (err) {
                err.file = path.resolve(file);
                return callback(err);
            }

            callback(null, json, file);
        });
    });
}

function parse(json, options) {
    options = deepExtend({
        normalize: false,
        validate: true,
        clone: false
    }, options || {});

    // Clone
    if (options.clone) {
        json = deepExtend({}, json);
    }

    // Validate
    if (options.validate) {
        validate(json);
    }

    // Normalize
    if (options.normalize) {
        normalize(json);
    }

    return json;
}

function validate(json, options) {
    
    options = deepExtend({
        enforceNameExists: true
    }, options || {});

    if(options.enforceNameExists || json.name) {
        validatePackageName(json.name);
    }

    if (json.description && json.description.length > 140) {
        throw createError('The description is too long. 140 characters should be more than enough', 'EINVALID');
    }

    if (json.main !== undefined) {
        var main = json.main;
        if (typeof main === 'string') {
            main = [main];
        }
        if (!(main instanceof Array)) {
            throw createError('The "main" field has to be either an Array or a String', 'EINVALID');
        }
        var ext2files = {};
        main.forEach(function (filename) {
            if (typeof filename !== 'string') {
                throw createError('The "main" Array has to contain only Strings', 'EINVALID');
            }
            if (/[*]/.test(filename)) {
                throw createError('The "main" field cannot contain globs (example: "*.js")', 'EINVALID');
            }
            if (/[.]min[.][^/]+$/.test(filename)) {
                throw createError('The "main" field cannot contain minified files', 'EINVALID');
            }
            if (isAsset(filename)) {
                throw createError('The "main" field cannot contain font, image, audio, or video files', 'EINVALID');
            }
            var ext = path.extname(filename);
            if (ext.length >= 2) {
                var files = ext2files[ext];
                if (!files) {
                    files = ext2files[ext] = [];
                }
                files.push(filename);
            }
        });
        Object.keys(ext2files).forEach(function (ext) {
            var files = ext2files[ext];
            if (files.length > 1) {
                throw createError('The "main" field has to contain only 1 file per filetype; found multiple ' + ext + ' files: ' + JSON.stringify(files), 'EINVALID');
            }
        });
    }

    if(json.dependencies instanceof Object) {

        Object.keys(json.dependencies).forEach(function(name) {
            var resource = json.dependencies[name];

            validatePackageName(name);

            if(/\.\./.test(resource)) {
                throw createError('Directory traversing in dependency paths is not allowed', 'EINVALID');
            }

        });

    }

    // TODO https://github.com/bower/bower.json-spec

    return json;
}

function validatePackageName(name) {
    if (!name) {
        throw createError('No name property set', 'EINVALID');
    }

    if (name.length > 50) {
        throw createError('The name is too long. 50 characters should be more than enough', 'EINVALID');
    }

    if (/[A-Z]/.test(name)) {
        throw createError('The name contains upper case letters', 'EINVALID');
    }

    if (!/^[a-z]/.test(name)) {
        throw createError('The name has to start with a lower case character from a to z', 'EINVALID');
    }

    if (!/[a-z]$/.test(name)) {
        throw createError('The name has to end with a lower case character from a to z', 'EINVALID');
    }
}

function normalize(json) {
    if (typeof json.main === 'string') {
        json.main = [json.main];
    }
{'fraction_non_alphanumeric': 0.13458162668227033, 'fraction_numerical': 0.003510825043885313, 'mean_word_length': 1.743445692883895, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1112443', 'n_tokens_mistral': 2888, 'n_tokens_neox': 2750, 'n_words': 788}