* We add the additional requirement that the first character must be a letter or number

* (there's a security implication to allowing a name like "..", because the name is

/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, node: true, nomen: true,
indent: 4, maxerr: 50, regexp: true */

"use strict";

var unzip   = require("unzip"),
    semver  = require("semver"),
    path    = require("path"),
    http    = require("http"),
    request = require("request"),
    os      = require("os"),
    fs      = require("fs-extra");

var Errors = {
    NOT_FOUND_ERR: "NOT_FOUND_ERR",                       // {0} is path where ZIP file was expected
    INVALID_ZIP_FILE: "INVALID_ZIP_FILE",                 // {0} is path to ZIP file
    INVALID_PACKAGE_JSON: "INVALID_PACKAGE_JSON",         // {0} is JSON parse error, {1} is path to ZIP file
    MISSING_PACKAGE_NAME: "MISSING_PACKAGE_NAME",         // {0} is path to ZIP file
    BAD_PACKAGE_NAME: "BAD_PACKAGE_NAME",                 // {0} is the name
    MISSING_PACKAGE_VERSION: "MISSING_PACKAGE_VERSION",   // {0} is path to ZIP file
    INVALID_VERSION_NUMBER: "INVALID_VERSION_NUMBER",     // {0} is version string in JSON, {1} is path to ZIP file
    MISSING_MAIN: "MISSING_MAIN",                         // {0} is path to ZIP file
    MISSING_PACKAGE_JSON: "MISSING_PACKAGE_JSON",         // {0} is path to ZIP file
    INVALID_BRACKETS_VERSION: "INVALID_BRACKETS_VERSION", // {0} is the version string in JSON, {1} is the path to the zip file,
    DISALLOWED_WORDS: "DISALLOWED_WORDS"                  // {0} is the field with the word, {1} is a string list of words that were in violation, {2} is the path to the zip file
};

/*
 * Directories to ignore when computing the common prefix among the entries of
 * a zip file.
 */
var ignoredPrefixes = {
    "__MACOSX": true
};

/**
 * Returns true if the name presented is acceptable as a package name. This enforces the
 * requirement as presented in the CommonJS spec: http://wiki.commonjs.org/wiki/Packages/1.0
 *
 * @param {string} name to test
 * @return {boolean} true if the name is valid
 */
function validateName(name) {
    // "This must be a unique, lowercase alpha-numeric name without spaces. It may include "." or "_" or "-" characters."
    if (/^[a-z0-9._\-]+$/.exec(name)) {
        return true;
    }
    return false;
}

// Parses strings of the form "name <email> (url)" where email and url are optional
var _personRegex = /^([^<\(]+)(?:\s+<([^>]+)>)?(?:\s+\(([^\)]+)\))?$/;

/**
 * Normalizes person fields from package.json.
 *
 * These fields can be an object with name, email and url properties or a
 * string of the form "name <email> <url>". This does a tolerant parsing of
 * the data to try to return an object with name and optional email and url.
 * If the string does not match the format, the string is returned as the
 * name on the resulting object.
 *
 * If an object other than a string is passed in, it's returned as is.
 *
 * @param <String|Object> obj to normalize
 * @return {Object} person object with name and optional email and url
 */
function parsePersonString(obj) {
    if (typeof (obj) === "string") {
        var parts = _personRegex.exec(obj);

        // No regex match, so we just synthesize an object with an opaque name string
        if (!parts) {
            return {
                name: obj
            };
        } else {
            var result = {
                name: parts[1]
            };
            if (parts[2]) {
                result.email = parts[2];
            }
            if (parts[3]) {
                result.url = parts[3];
            }
            return result;
        }
    } else {
        // obj is not a string, so return as is
        return obj;
    }
}

/**
 * Determines if any of the words in wordlist appear in str.
 *
 * @param {String[]} wordlist list of words to check
 * @param {String} str to check for words
 * @return {String[]} words that matched
 */
function containsWords(wordlist, str) {
    var i;
    var matches = [];
    for (i = 0; i < wordlist.length; i++) {
        var re = new RegExp("\\b" + wordlist[i] + "\\b", "i");
        if (re.exec(str)) {
            matches.push(wordlist[i]);
        }
    }
    return matches;
}

/**
 * Implements the "validate" command in the "extensions" domain.
 * Validates the zipped package at path.
 *
 * The "err" parameter of the callback is only set if there was an
 * unexpected error. Otherwise, errors are reported in the result.
 *
 * The result object has an "errors" property. It is an array of
 * arrays of strings. Each array in the array is a set of parameters
 * that can be passed to StringUtils.format for internationalization.
 * The array will be empty if there are no errors.
 *
 * The result will have a "metadata" property if the metadata was
 * read successfully from package.json in the zip file.
 *
 * @param {string} path Absolute path to the package zip file
 * @param {{requirePackageJSON: ?boolean, disallowedWords: ?Array.<string>}} options for validation
 * @param {function} callback (err, result)
 */
function validate(path, options, callback) {
    options = options || {};
    fs.exists(path, function (doesExist) {
        if (!doesExist) {
            callback(null, {
                errors: [[Errors.NOT_FOUND_ERR, path]]
            });
            return;
        }
        var callbackCalled = false;
        var metadata;
        var foundMainIn = null;
        var errors = [];
        var commonPrefix = null;

        var readStream = fs.createReadStream(path);

        readStream
            .pipe(unzip.Parse())
            .on("error", function (exception) {
                // General error to report for problems reading the file
                errors.push([Errors.INVALID_ZIP_FILE, path]);
                callback(null, {
                    errors: errors
                });
                callbackCalled = true;
                readStream.destroy();
            })
            .on("entry", function (entry) {
                // look for the metadata
                var fileName = entry.path;

                var slash = fileName.indexOf("/");
                if (slash > -1) {
                    var prefix = fileName.substring(0, slash);
                    if (!ignoredPrefixes.hasOwnProperty(prefix)) {
                        if (commonPrefix === null) {
                            commonPrefix = prefix;
                        } else if (prefix !== commonPrefix) {
                            commonPrefix = "";
                        }
                        if (commonPrefix) {
                            fileName = fileName.substring(commonPrefix.length + 1);
                        }
                    }
                } else {
                    commonPrefix = "";
                }

                if (fileName === "package.json") {
                    // This handles an edge case where we found a package.json in a
                    // nested directory that we thought was a commonPrefix but
                    // actually wasn't. We reset as if we never read that first
                    // package.json
                    if (metadata) {
                        metadata = undefined;
                        errors = [];
                    }

                    var packageJSON = "";
                    entry
                        .on("data", function (data) {
                            // We're assuming utf8 encoding here, which is pretty safe
                            // Note that I found that .setEncoding on the stream
                            // would fail, so I convert the buffer to a string here.
                            packageJSON += data.toString("utf8");
                        })
                        .on("error", function (exception) {
                            // general exception handler. It is unknown what kinds of
                            // errors we can get here.
                            callback(exception, null);
                            callbackCalled = true;
                            readStream.destroy();
                        })
                        .on("end", function () {
                            // attempt to parse the metadata
                            try {
                                metadata = JSON.parse(packageJSON);
                            } catch (e) {
                                errors.push([Errors.INVALID_PACKAGE_JSON, e.toString(), path]);
                                return;
                            }

                            // confirm required fields in the metadata
                            if (!metadata.name) {
                                errors.push([Errors.MISSING_PACKAGE_NAME, path]);
                            } else if (!validateName(metadata.name)) {
                                errors.push([Errors.BAD_PACKAGE_NAME, metadata.name]);
                            }
                            if (!metadata.version) {
                                errors.push([Errors.MISSING_PACKAGE_VERSION, path]);
                            } else if (!semver.valid(metadata.version)) {
                                errors.push([Errors.INVALID_VERSION_NUMBER, metadata.version, path]);
                            }

                            // normalize the author
                            if (metadata.author) {
                                metadata.author = parsePersonString(metadata.author);
                            }

                            // contributors should be an array of people.
                            // normalize each entry.
                            if (metadata.contributors) {
                                if (metadata.contributors.map) {
                                    metadata.contributors = metadata.contributors.map(function (person) {
                                        return parsePersonString(person);
                                    });
                                } else {
                                    metadata.contributors = [
                                        parsePersonString(metadata.contributors)
                                    ];
                                }
                            }

                            if (metadata.engines && metadata.engines.brackets) {
                                var range = metadata.engines.brackets;
                                if (!semver.validRange(range)) {
                                    errors.push([Errors.INVALID_BRACKETS_VERSION, range, path]);
                                }
                            }

                            if (options.disallowedWords) {
                                ["title", "description", "name"].forEach(function (field) {
                                    var words = containsWords(options.disallowedWords, metadata[field]);
                                    if (words.length > 0) {
                                        errors.push([Errors.DISALLOWED_WORDS, field, words.toString(), path]);
                                    }
                                });
                            }
                        });
                } else if (fileName === "main.js") {
                    foundMainIn = commonPrefix;
                }
            })
            .on("end", function () {
                // Reached the end of the zipfile
                // Report results

                // generally, if we hit an exception, we've already called the callback
                if (callbackCalled) {
                    return;
                }

                if (foundMainIn === null || foundMainIn !== commonPrefix) {
                    errors.push([Errors.MISSING_MAIN, path]);
                }

                // No errors and no metadata means that we never found the metadata
                if (errors.length === 0 && !metadata) {
                    metadata = null;
                }

                if (metadata === null && options.requirePackageJSON) {
                    errors.push([Errors.MISSING_PACKAGE_JSON, path]);
                }

                callback(null, {
                    errors: errors,
                    metadata: metadata,
                    commonPrefix: commonPrefix
                });
            });
    });
}

// exported for unit testing
exports._parsePersonString = parsePersonString;

exports.errors = Errors;
exports.validate = validate;


/*!
 * Connect
 * Copyright(c) 2010 Sencha Inc.
 * Copyright(c) 2011 TJ Holowaychuk
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var EventEmitter = require('events').EventEmitter
  , proto = require('./proto')
  , utils = require('./utils')
  , path = require('path')
  , basename = path.basename
  , fs = require('fs');

// node patches

require('./patch');

// expose createServer() as the module

exports = module.exports = createServer;

/**
 * Framework version.
 */

exports.version = '2.7.2';

/**
 * Expose mime module.
 */

exports.mime = require('./middleware/static').mime;

/**
 * Expose the prototype.
 */

exports.proto = proto;

/**
 * Auto-load middleware getters.
 */

exports.middleware = {};

/**
 * Expose utilities.
 */

exports.utils = utils;

/**
 * Create a new connect server.
 *
 * @return {Function}
 * @api public
 */

function createServer() {
  function app(req, res, next){ app.handle(req, res, next); }
  utils.merge(app, proto);
  utils.merge(app, EventEmitter.prototype);
  app.route = '/';
  app.stack = [];
  for (var i = 0; i < arguments.length; ++i) {
    app.use(arguments[i]);
  }
  return app;
};

/**
 * Support old `.createServer()` method.
 */

createServer.createServer = createServer;

/**
 * Auto-load bundled middleware with getters.
 */

fs.readdirSync(__dirname + '/middleware').forEach(function(filename){
  if (!/\.js$/.test(filename)) return;
  var name = basename(filename, '.js');
  function load(){ return require('./middleware/' + name); }
  exports.middleware.__defineGetter__(name, load);
  exports.__defineGetter__(name, load);
});


/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, $ */

define(function (require, exports, module) {
    "use strict";

    // Load dependent modules
    var AppInit             = brackets.getModule("utils/AppInit"),
        CodeHintManager     = brackets.getModule("editor/CodeHintManager"),
        ExtensionUtils      = brackets.getModule("utils/ExtensionUtils"),
        HTMLUtils           = brackets.getModule("language/HTMLUtils"),
        HtmlSpecialChars    = require("text!SpecialChars.json"),
        specialChars;

    /**
     * Encodes the special Char value given.
     *
     * @param {String} value
     * The value to encode
     *
     * @return {String}
     * The encoded string
     */
    function _encodeValue(value) {
        return value.replace("&", "&").replace("#", "#");
    }

    /**
     * Decodes the special Char value given.
     *
     * @param {String} value
     * The value to decode
     *
     * @return {String}
     * The decoded string
     */
    function _decodeValue(value) {
        return value.replace("&", "&").replace("#", "#");
    }

    /**
     * @constructor
     */
    function SpecialCharHints() {
        this.primaryTriggerKeys = "&ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#0123456789";
        this.currentQuery = "";
    }

    /**
     * Determines whether HtmlSpecialChar hints are available in the current editor
     * context.
     *
     * @param {Editor} editor
     * A non-null editor object for the active window.
     *
     * @param {String} implicitChar
     * Either null, if the hinting request was explicit, or a single character
     * that represents the last insertion and that indicates an implicit
     * hinting request.
     *
     * @return {Boolean}
     * Determines whether the current provider is able to provide hints for
     * the given editor context and, in case implicitChar is non- null,
     * whether it is appropriate to do so.
     */
    SpecialCharHints.prototype.hasHints = function (editor, implicitChar) {
        this.editor = editor;

        return this._getQuery() !== null;
    };

    /**
     * Returns a list of avaliable HtmlSpecialChar hints if possible for the current
     * editor context.
     *
     * @param {String} implicitChar
     * Either null, if the hinting request was explicit, or a single character
     * that represents the last insertion and that indicates an implicit
     * hinting request.
     *
     * @return {Object<hints: Array<(String + jQuery.Obj)>, match: String, selectInitial: Boolean>}
     * Null if the provider wishes to end the hinting session. Otherwise, a
     * response object that provides 1. a sorted array hints that consists
     * of strings; 2. a string match that is used by the manager to emphasize
     * matching substrings when rendering the hint list; and 3. a boolean that
     * indicates whether the first result, if one exists, should be selected
     * by default in the hint list window.
     */
    SpecialCharHints.prototype.getHints = function (implicitChar) {
        var query,
            result;

        if (implicitChar === null || this.primaryTriggerKeys.indexOf(implicitChar) !== -1) {
            this.currentQuery = query = this._getQuery();
            result = $.map(specialChars, function (value, index) {
                if (value.indexOf(query) === 0) {
                    var shownValue = _encodeValue(value);
                    return shownValue  + "; " + value + ";";
                }
            }).sort(this._internalSort);

            if (query !== null) {
                query = _encodeValue(query);
            }

            return {
                hints: result,
                match: query,
                selectInitial: true
            };
        }

        return null;
    };

    /**
     * Sort function used internally when sorting the Hints
     *
     * @param {String} value
     * The value to decode
     *
     * @return {String}
     * The decoded string
     */
    SpecialCharHints.prototype._internalSort = function (a, b) {
        a = _decodeValue(a.slice(0, a.indexOf(" "))).toLowerCase();
        b = _decodeValue(b.slice(0, b.indexOf(" "))).toLowerCase();

        if (a.indexOf("#") !== -1 && b.indexOf("#") !== -1) {
            var num1 = parseInt(a.slice(a.indexOf("#") + 1, a.length - 1), 10),
                num2 = parseInt(b.slice(b.indexOf("#") + 1, b.length - 1), 10);

            return (num1 - num2);
        }

        return a.localeCompare(b);
    };

    /**
     * Returns a query for the Hints
     *
     * @return {String}
     * The Query for which to search
     */
    SpecialCharHints.prototype._getQuery = function () {
        var query,
            lineContentBeforeCursor,
            startChar,
            endChar,
            cursor = this.editor.getCursorPos();

        if (HTMLUtils.getTagInfo(this.editor, cursor).tagName !== "") {
            return null;
        }

        lineContentBeforeCursor = this.editor.document.getRange({
            line: cursor.line,
            ch: 0
        }, cursor);

        startChar = lineContentBeforeCursor.lastIndexOf("&");
        endChar = lineContentBeforeCursor.lastIndexOf(";");

        // If no startChar was found or the endChar is greater than the startChar then it is no entity
        if (startChar === -1 || endChar > startChar) {
            return null;
        }

        query = this.editor.document.getRange({
            line: cursor.line,
            ch: startChar
        }, cursor);

        return query;
    };

    /**
     * Inserts a given HtmlSpecialChar hint into the current editor context.
     *
     * @param {String} completition
     * The hint to be inserted into the editor context.
     *
     * @return {Boolean}
     * Indicates whether the manager should follow hint insertion with an
     * additional explicit hint request.
     */
    SpecialCharHints.prototype.insertHint = function (completion) {
        var start = {line: -1, ch: -1},
            end = {line: -1, ch: -1},
            cursor = this.editor.getCursorPos(),
            match,
            matchSemicolonPos;

        end.line = start.line = cursor.line;
        start.ch = cursor.ch - this.currentQuery.length;
        match = this.editor.document.getLine(cursor.line).slice(cursor.ch);
        matchSemicolonPos = match.indexOf(";");
        end.ch = start.ch + this.currentQuery.length;

        if (matchSemicolonPos !== -1 && /^(#*[0-9]+)|([a-zA-Z]+)$/.test(match.slice(0, matchSemicolonPos))) {
            end.ch = this.editor.document.getLine(cursor.line).indexOf(";", start.ch) + 1;
        }

        completion = completion.slice(0, completion.indexOf(" "));
        completion = _decodeValue(completion);
        if (start.ch !== end.ch) {
            this.editor.document.replaceRange(completion, start, end);
        } else {
            this.editor.document.replaceRange(completion, start);
        }

        return false;
    };

    AppInit.appReady(function () {
        ExtensionUtils.loadStyleSheet(module, "styles.css");
        // Parse JSON files
        specialChars = JSON.parse(HtmlSpecialChars);

        // Register code hint providers
        var specialCharHints = new SpecialCharHints();

        CodeHintManager.registerHintProvider(specialCharHints, ["html"], 1);
    });
});


# Formidable

[![Build Status](https://secure.travis-ci.org/felixge/node-formidable.png?branch=master)](http://travis-ci.org/felixge/node-formidable)

## Purpose

A node.js module for parsing form data, especially file uploads.

## Current status

This module was developed for [Transloadit](http://transloadit.com/), a service focused on uploading
and encoding images and videos. It has been battle-tested against hundreds of GB of file uploads from
a large variety of clients and is considered production-ready.

## Features

* Fast (~500mb/sec), non-buffering multipart parser
* Automatically writing file uploads to disk
* Low memory footprint
* Graceful error handling
* Very high test coverage

## Changelog

### v1.0.9

* Emit progress when content length header parsed (Tim Koschützki)
* Fix Readme syntax due to GitHub changes (goob)
* Replace references to old 'sys' module in Readme with 'util' (Peter Sugihara)

### v1.0.8

* Strip potentially unsafe characters when using `keepExtensions: true`.
* Switch to utest / urun for testing
* Add travis build

### v1.0.7

* Remove file from package that was causing problems when installing on windows. (#102)
* Fix typos in Readme (Jason Davies).

### v1.0.6

* Do not default to the default to the field name for file uploads where
  filename="".

### v1.0.5

* Support filename="" in multipart parts
* Explain unexpected end() errors in parser better

**Note:** Starting with this version, formidable emits 'file' events for empty
file input fields. Previously those were incorrectly emitted as regular file
input fields with value = "".

### v1.0.4

* Detect a good default tmp directory regardless of platform. (#88)

### v1.0.3

* Fix problems with utf8 characters (#84) / semicolons in filenames (#58)
* Small performance improvements
* New test suite and fixture system

### v1.0.2

* Exclude node\_modules folder from git
* Implement new `'aborted'` event
* Fix files in example folder to work with recent node versions
* Make gently a devDependency

[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.1...v1.0.2)

### v1.0.1

* Fix package.json to refer to proper main directory. (#68, Dean Landolt)

[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.0...v1.0.1)

### v1.0.0

* Add support for multipart boundaries that are quoted strings. (Jeff Craig)

This marks the beginning of development on version 2.0 which will include
several architectural improvements.

[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.11...v1.0.0)

### v0.9.11

* Emit `'progress'` event when receiving data, regardless of parsing it. (Tim Koschützki)
* Use [W3C FileAPI Draft](http://dev.w3.org/2006/webapi/FileAPI/) properties for File class

**Important:** The old property names of the File class will be removed in a
future release.

[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.10...v0.9.11)

### Older releases

These releases were done before starting to maintain the above Changelog:

* [v0.9.10](https://github.com/felixge/node-formidable/compare/v0.9.9...v0.9.10)
* [v0.9.9](https://github.com/felixge/node-formidable/compare/v0.9.8...v0.9.9)
* [v0.9.8](https://github.com/felixge/node-formidable/compare/v0.9.7...v0.9.8)
* [v0.9.7](https://github.com/felixge/node-formidable/compare/v0.9.6...v0.9.7)
* [v0.9.6](https://github.com/felixge/node-formidable/compare/v0.9.5...v0.9.6)
* [v0.9.5](https://github.com/felixge/node-formidable/compare/v0.9.4...v0.9.5)
* [v0.9.4](https://github.com/felixge/node-formidable/compare/v0.9.3...v0.9.4)
* [v0.9.3](https://github.com/felixge/node-formidable/compare/v0.9.2...v0.9.3)
* [v0.9.2](https://github.com/felixge/node-formidable/compare/v0.9.1...v0.9.2)
* [v0.9.1](https://github.com/felixge/node-formidable/compare/v0.9.0...v0.9.1)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.1.0](https://github.com/felixge/node-formidable/commits/v0.1.0)

## Installation

Via [npm](http://github.com/isaacs/npm):

    npm install formidable@latest

Manually:

    git clone git://github.com/felixge/node-formidable.git formidable
    vim my.js
    # var formidable = require('./formidable');

Note: Formidable requires [gently](http://github.com/felixge/node-gently) to run the unit tests, but you won't need it for just using the library.

## Example

Parse an incoming file upload.

    var formidable = require('formidable'),
        http = require('http'),

        util = require('util');

    http.createServer(function(req, res) {
      if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
        // parse a file upload
        var form = new formidable.IncomingForm();
        form.parse(req, function(err, fields, files) {
          res.writeHead(200, {'content-type': 'text/plain'});
          res.write('received upload:\n\n');
          res.end(util.inspect({fields: fields, files: files}));
        });
        return;
      }

      // show a file upload form
      res.writeHead(200, {'content-type': 'text/html'});
      res.end(
        '<form action="/upload" enctype="multipart/form-data" method="post">'+
        '<input type="text" name="title">
'+
        '<input type="file" name="upload" multiple="multiple">
'+
        '<input type="submit" value="Upload">'+
        '</form>'
      );
    }).listen(80);

## API

### formidable.IncomingForm

__new formidable.IncomingForm()__

Creates a new incoming form.

__incomingForm.encoding = 'utf-8'__

The encoding to use for incoming form fields.

__incomingForm.uploadDir = process.env.TMP || '/tmp' || process.cwd()__

The directory for placing file uploads in. You can move them later on using
`fs.rename()`. The default directory is picked at module load time depending on
the first existing directory from those listed above.

__incomingForm.keepExtensions = false__

If you want the files written to `incomingForm.uploadDir` to include the extensions of the original files, set this property to `true`.

__incomingForm.type__

Either 'multipart' or 'urlencoded' depending on the incoming request.

__incomingForm.maxFieldsSize = 2 * 1024 * 1024__

Limits the amount of memory a field (not file) can allocate in bytes.
If this value is exceeded, an `'error'` event is emitted. The default
size is 2MB.

__incomingForm.hash = false__

If you want checksums calculated for incoming files, set this to either `'sha1'` or `'md5'`.

__incomingForm.bytesReceived__

The amount of bytes received for this form so far.

__incomingForm.bytesExpected__

The expected number of bytes in this form.

__incomingForm.parse(request, [cb])__

Parses an incoming node.js `request` containing form data. If `cb` is provided, all fields an files are collected and passed to the callback:

    incomingForm.parse(req, function(err, fields, files) {
      // ...
    });

__incomingForm.onPart(part)__

You may overwrite this method if you are interested in directly accessing the multipart stream. Doing so will disable any `'field'` / `'file'` events  processing which would occur otherwise, making you fully responsible for handling the processing.

    incomingForm.onPart = function(part) {
      part.addListener('data', function() {
        // ...
      });
    }

If you want to use formidable to only handle certain parts for you, you can do so:

    incomingForm.onPart = function(part) {
      if (!part.filename) {
        // let formidable handle all non-file parts
        incomingForm.handlePart(part);
      }
    }

Check the code in this method for further inspiration.

__Event: 'progress' (bytesReceived, bytesExpected)__

Emitted after each incoming chunk of data that has been parsed. Can be used to roll your own progress bar.

__Event: 'field' (name, value)__

Emitted whenever a field / value pair has been received.

__Event: 'fileBegin' (name, file)__

Emitted whenever a new file is detected in the upload stream. Use this even if
you want to stream the file to somewhere else while buffering the upload on
the file system.

__Event: 'file' (name, file)__

Emitted whenever a field / file pair has been received. `file` is an instance of `File`.

__Event: 'error' (err)__

Emitted when there is an error processing the incoming form. A request that experiences an error is automatically paused, you will have to manually call `request.resume()` if you want the request to continue firing `'data'` events.

__Event: 'aborted'__

Emitted when the request was aborted by the user. Right now this can be due to a 'timeout' or 'close' event on the socket. In the future there will be a separate 'timeout' event (needs a change in the node core).

__Event: 'end' ()__

Emitted when the entire request has been received, and all contained files have finished flushing to disk. This is a great place for you to send your response.

### formidable.File

__file.size = 0__

The size of the uploaded file in bytes. If the file is still being uploaded (see `'fileBegin'` event), this property says how many bytes of the file have been written to disk yet.

__file.path = null__

The path this file is being written to. You can modify this in the `'fileBegin'` event in
case you are unhappy with the way formidable generates a temporary path for your files.

__file.name = null__

The name this file had according to the uploading client.

__file.type = null__

The mime type of this file, according to the uploading client.

__file.lastModifiedDate = null__

A date object (or `null`) containing the time this file was last written to. Mostly
here for compatibility with the [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/).

__file.hash = null__

If hash calculation was set, you can read the hex digest out of this var.

## License

Formidable is licensed under the MIT license.

## Ports

* [multipart-parser](http://github.com/FooBarWidget/multipart-parser): a C++ parser based on formidable

## Credits

* [Ryan Dahl](http://twitter.com/ryah) for his work on [http-parser](http://github.com/ry/http-parser) which heavily inspired multipart_parser.js


// "This must be a unique, lowercase alpha-numeric name without spaces. It may include "." or "_" or "-" characters."

brackets-extensibility

Used in the management of Brackets extensions


(function( $ ) {

var simulateKeyDownUp = TestHelpers.spinner.simulateKeyDownUp;

module( "spinner: methods" );

test( "destroy", function() {
	expect( 1 );
	domEqual( "#spin", function() {
		$( "#spin" ).spinner().spinner( "destroy" );
	});
});

test( "disable", function() {
	expect( 14 );
	var element = $( "#spin" ).val( 2 ).spinner(),
		wrapper = $( "#spin" ).spinner( "widget" );

	ok( !wrapper.hasClass( "ui-spinner-disabled" ), "before: wrapper does not have ui-spinner-disabled class" );
	ok( !element.is( ":disabled" ), "before: input does not have disabled attribute" );

	element.spinner( "disable" );
	ok( wrapper.hasClass( "ui-spinner-disabled" ), "after: wrapper has ui-spinner-disabled class" );
	ok( element.is( ":disabled"), "after: input has disabled attribute" );

	simulateKeyDownUp( element, $.ui.keyCode.UP );
	equal( 2, element.val(), "keyboard - value does not change on key UP" );

	simulateKeyDownUp( element, $.ui.keyCode.DOWN );
	equal( 2, element.val(), "keyboard - value does not change on key DOWN" );

	simulateKeyDownUp( element, $.ui.keyCode.PAGE_UP );
	equal( 2, element.val(), "keyboard - value does not change on key PGUP" );

	simulateKeyDownUp( element, $.ui.keyCode.PAGE_DOWN );
	equal( 2, element.val(), "keyboard - value does not change on key PGDN" );

	wrapper.find( ".ui-spinner-up" ).trigger( "mousedown" ).trigger( "mouseup" );
	equal( 2, element.val(), "mouse - value does not change on clicking up button" );

	wrapper.find( ".ui-spinner-down" ).trigger( "mousedown" ).trigger( "mouseup" );
	equal( 2, element.val(), "mouse - value does not change on clicking down button" );

	element.spinner( "stepUp", 6 );
	equal( 8, element.val(), "script - stepUp 6 steps changes value");

	element.spinner( "stepDown" );
	equal( 7, element.val(), "script - stepDown 1 step changes value" );

	element.spinner( "pageUp" );
	equal( 17, element.val(), "script - pageUp 1 page changes value" );

	element.spinner( "pageDown" );
	equal( 7, element.val(), "script - pageDown 1 page changes value" );
});

test( "enable", function() {
	expect( 5 );
	var element = $( "#spin" ).val( 1 ).spinner({ disabled: true }),
		wrapper = element.spinner( "widget" );

	ok( wrapper.hasClass( "ui-spinner-disabled" ), "before: wrapper has ui-spinner-disabled class" );
	ok( element.is( ":disabled" ), "before: input has disabled attribute" );

	element.spinner( "enable" );

	ok( !wrapper.hasClass( ".ui-spinner-disabled" ), "after: wrapper does not have ui-spinner-disabled class" );
	ok( !element.is( ":disabled" ), "after: input does not have disabled attribute" );

	simulateKeyDownUp( element, $.ui.keyCode.UP );
	equal( 2, element.val(), "keyboard - value changes on key UP" );
});

test( "pageDown", function() {
	expect( 4 );
	var element = $( "#spin" ).val( -12 ).spinner({
		page: 20,
		min: -100
	});

	element.spinner( "pageDown" );
	equal( element.val(), -32, "pageDown 1 page" );

	element.spinner( "pageDown", 3 );
	equal( element.val(), -92, "pageDown 3 pages" );

	element.spinner( "pageDown" );
	equal( element.val(), -100, "value close to min and pageDown 1 page" );

	element.spinner( "pageDown", 10 );
	equal( element.val(), -100, "value at min and pageDown 10 pages" );
});

test( "pageUp", function() {
	expect( 4 );
	var element = $( "#spin" ).val( 12 ).spinner({
		page: 20,
		max: 100
	});

	element.spinner( "pageUp" );
	equal( element.val(), 32, "pageUp 1 page" );

	element.spinner( "pageUp", 3 );
	equal( element.val(), 92, "pageUp 3 pages" );

	element.spinner( "pageUp" );
	equal( element.val(), 100, "value close to max and pageUp 1 page" );

	element.spinner( "pageUp", 10 );
	equal( element.val(), 100, "value at max and pageUp 10 pages" );
});

test( "stepDown", function() {
	expect( 4 );
	var element = $( "#spin" ).val( 0 ).spinner({
		step: 2,
		min: -15
	});

	element.spinner( "stepDown" );
	equal( element.val(), "-1", "stepDown 1 step" );

	element.spinner( "stepDown", 5 );
	equal( element.val(), "-11", "stepDown 5 steps" );

	element.spinner( "stepDown", 4 );
	equal( element.val(), "-15", "close to min and stepDown 4 steps" );

	element.spinner( "stepDown" );
	equal( element.val(), "-15", "at min and stepDown 1 step" );
});

test( "stepUp", function() {
	expect( 4 );
	var element = $( "#spin" ).val( 0 ).spinner({
		step: 2,
		max: 16
	});

	element.spinner( "stepUp" );
	equal( element.val(), 2, "stepUp 1 step" );

	element.spinner( "stepUp", 5 );
	equal( element.val(), 12, "stepUp 5 steps" );

	element.spinner( "stepUp", 4 );
	equal( element.val(), 16, "close to min and stepUp 4 steps" );

	element.spinner( "stepUp" );
	equal( element.val(), 16, "at max and stepUp 1 step" );
});

test( "value", function() {
	expect( 2 );
	var element = $( "#spin" ).val( 0 ).spinner({
		step: 3
	});

	element.spinner( "value", 10 );
	equal( element.val(), 9, "change value via value method" );

	equal( element.spinner( "value" ), 9, "get value via value method" );
});

})( jQuery );


// Basically just a wrapper around an fs.ReadStream

module.exports = FileReader

var fs = require("graceful-fs")
  , fstream = require("../fstream.js")
  , Reader = fstream.Reader
  , inherits = require("inherits")
  , mkdir = require("mkdirp")
  , Reader = require("./reader.js")
  , EOF = {EOF: true}
  , CLOSE = {CLOSE: true}

inherits(FileReader, Reader)

function FileReader (props) {
  // console.error("    FR create", props.path, props.size, new Error().stack)
  var me = this
  if (!(me instanceof FileReader)) throw new Error(
    "FileReader must be called as constructor.")

  // should already be established as a File type
  // XXX Todo: preserve hardlinks by tracking dev+inode+nlink,
  // with a HardLinkReader class.
  if (!((props.type === "Link" && props.Link) ||
        (props.type === "File" && props.File))) {
    throw new Error("Non-file type "+ props.type)
  }

  me._buffer = []
  me._bytesEmitted = 0
  Reader.call(me, props)
}

FileReader.prototype._getStream = function () {
  var me = this
    , stream = me._stream = fs.createReadStream(me._path, me.props)

  if (me.props.blksize) {
    stream.bufferSize = me.props.blksize
  }

  stream.on("open", me.emit.bind(me, "open"))

  stream.on("data", function (c) {
    // console.error("\t\t%d %s", c.length, me.basename)
    me._bytesEmitted += c.length
    // no point saving empty chunks
    if (!c.length) return
    else if (me._paused || me._buffer.length) {
      me._buffer.push(c)
      me._read()
    } else me.emit("data", c)
  })

  stream.on("end", function () {
    if (me._paused || me._buffer.length) {
      // console.error("FR Buffering End", me._path)
      me._buffer.push(EOF)
      me._read()
    } else {
      me.emit("end")
    }

    if (me._bytesEmitted !== me.props.size) {
      me.error("Didn't get expected byte count\n"+
               "expect: "+me.props.size + "\n" +
               "actual: "+me._bytesEmitted)
    }
  })

  stream.on("close", function () {
    if (me._paused || me._buffer.length) {
      // console.error("FR Buffering Close", me._path)
      me._buffer.push(CLOSE)
      me._read()
    } else {
      // console.error("FR close 1", me._path)
      me.emit("close")
    }
  })

  me._read()
}

FileReader.prototype._read = function () {
  var me = this
  // console.error("FR _read", me._path)
  if (me._paused) {
    // console.error("FR _read paused", me._path)
    return
  }

  if (!me._stream) {
    // console.error("FR _getStream calling", me._path)
    return me._getStream()
  }

  // clear out the buffer, if there is one.
  if (me._buffer.length) {
    // console.error("FR _read has buffer", me._buffer.length, me._path)
    var buf = me._buffer
    for (var i = 0, l = buf.length; i < l; i ++) {
      var c = buf[i]
      if (c === EOF) {
        // console.error("FR Read emitting buffered end", me._path)
        me.emit("end")
      } else if (c === CLOSE) {
        // console.error("FR Read emitting buffered close", me._path)
        me.emit("close")
      } else {
        // console.error("FR Read emitting buffered data", me._path)
        me.emit("data", c)
      }

      if (me._paused) {
        // console.error("FR Read Re-pausing at "+i, me._path)
        me._buffer = buf.slice(i)
        return
      }
    }
    me._buffer.length = 0
  }
  // console.error("FR _read done")
  // that's about all there is to it.
}

FileReader.prototype.pause = function (who) {
  var me = this
  // console.error("FR Pause", me._path)
  if (me._paused) return
  who = who || me
  me._paused = true
  if (me._stream) me._stream.pause()
  me.emit("pause", who)
}

FileReader.prototype.resume = function (who) {
  var me = this
  // console.error("FR Resume", me._path)
  if (!me._paused) return
  who = who || me
  me.emit("resume", who)
  me._paused = false
  if (me._stream) me._stream.resume()
  me._read()
}


},
"devDependencies": {
"rewire": "1.1.x"


Bootstrap v1.4.0
https://github.com/twitter/bootstrap/zipball/v1.4.0
http://twitter.github.com/bootstrap/


[submodule "deps/nodeunit"]
	path = deps/nodeunit
	url = git://github.com/caolan/nodeunit.git
[submodule "deps/UglifyJS"]
	path = deps/UglifyJS
	url = https://github.com/mishoo/UglifyJS.git
[submodule "deps/nodelint"]
	path = deps/nodelint
	url = https://github.com/tav/nodelint.git


var packageValidator = require("../package-validator"),

var testFilesDirectory = path.join(path.dirname(module.filename),

keep keep keep keep keep replace keep keep keep

var rewire           = require("rewire"),


/*!
 * Connect - staticCache
 * Copyright(c) 2011 Sencha Inc.
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var utils = require('../utils')
  , Cache = require('../cache')
  , fresh = require('fresh');

/**
 * Static cache:
 *
 * Enables a memory cache layer on top of
 * the `static()` middleware, serving popular
 * static files.
 *
 * By default a maximum of 128 objects are
 * held in cache, with a max of 256k each,
 * totalling ~32mb.
 *
 * A Least-Recently-Used (LRU) cache algo
 * is implemented through the `Cache` object,
 * simply rotating cache objects as they are
 * hit. This means that increasingly popular
 * objects maintain their positions while
 * others get shoved out of the stack and
 * garbage collected.
 *
 * Benchmarks:
 *
 *     static(): 2700 rps
 *     node-static: 5300 rps
 *     static() + staticCache(): 7500 rps
 *
 * Options:
 *
 *   - `maxObjects`  max cache objects [128]
 *   - `maxLength`  max cache object length 256kb
 *
 * @param {Object} options
 * @return {Function}
 * @api public
 */

module.exports = function staticCache(options){
  var options = options || {}
    , cache = new Cache(options.maxObjects || 128)
    , maxlen = options.maxLength || 1024 * 256;

  console.warn('connect.staticCache() is deprecated and will be removed in 3.0');
  console.warn('use varnish or similar reverse proxy caches.');

  return function staticCache(req, res, next){
    var key = cacheKey(req)
      , ranges = req.headers.range
      , hasCookies = req.headers.cookie
      , hit = cache.get(key);

    // cache static
    // TODO: change from staticCache() -> cache()
    // and make this work for any request
    req.on('static', function(stream){
      var headers = res._headers
        , cc = utils.parseCacheControl(headers['cache-control'] || '')
        , contentLength = headers['content-length']
        , hit;

      // dont cache set-cookie responses
      if (headers['set-cookie']) return hasCookies = true;

      // dont cache when cookies are present
      if (hasCookies) return;

      // ignore larger files
      if (!contentLength || contentLength > maxlen) return;

      // don't cache partial files
      if (headers['content-range']) return;

      // dont cache items we shouldn't be
      // TODO: real support for must-revalidate / no-cache
      if ( cc['no-cache']
        || cc['no-store']
        || cc['private']
        || cc['must-revalidate']) return;

      // if already in cache then validate
      if (hit = cache.get(key)){
        if (headers.etag == hit[0].etag) {
          hit[0].date = new Date;
          return;
        } else {
          cache.remove(key);
        }
      }

      // validation notifiactions don't contain a steam
      if (null == stream) return;

      // add the cache object
      var arr = [];

      // store the chunks
      stream.on('data', function(chunk){
        arr.push(chunk);
      });

      // flag it as complete
      stream.on('end', function(){
        var cacheEntry = cache.add(key);
        delete headers['x-cache']; // Clean up (TODO: others)
        cacheEntry.push(200);
        cacheEntry.push(headers);
        cacheEntry.push.apply(cacheEntry, arr);
      });
    });

    if (req.method == 'GET' || req.method == 'HEAD') {
      if (ranges) {
        next();
      } else if (!hasCookies && hit && !mustRevalidate(req, hit)) {
        res.setHeader('X-Cache', 'HIT');
        respondFromCache(req, res, hit);
      } else {
        res.setHeader('X-Cache', 'MISS');
        next();
      }
    } else {
      next();
    }
  }
};

/**
 * Respond with the provided cached value.
 * TODO: Assume 200 code, that's iffy.
 *
 * @param {Object} req
 * @param {Object} res
 * @param {Object} cacheEntry
 * @return {String}
 * @api private
 */

function respondFromCache(req, res, cacheEntry) {
  var status = cacheEntry[0]
    , headers = utils.merge({}, cacheEntry[1])
    , content = cacheEntry.slice(2);

  headers.age = (new Date - new Date(headers.date)) / 1000 || 0;

  switch (req.method) {
    case 'HEAD':
      res.writeHead(status, headers);
      res.end();
      break;
    case 'GET':
      if (utils.conditionalGET(req) && fresh(req.headers, headers)) {
        headers['content-length'] = 0;
        res.writeHead(304, headers);
        res.end();
      } else {
        res.writeHead(status, headers);

        function write() {
          while (content.length) {
            if (false === res.write(content.shift())) {
              res.once('drain', write);
              return;
            }
          }
          res.end();
        }

        write();
      }
      break;
    default:
      // This should never happen.
      res.writeHead(500, '');
      res.end();
  }
}

/**
 * Determine whether or not a cached value must be revalidated.
 *
 * @param {Object} req
 * @param {Object} cacheEntry
 * @return {String}
 * @api private
 */

function mustRevalidate(req, cacheEntry) {
  var cacheHeaders = cacheEntry[1]
    , reqCC = utils.parseCacheControl(req.headers['cache-control'] || '')
    , cacheCC = utils.parseCacheControl(cacheHeaders['cache-control'] || '')
    , cacheAge = (new Date - new Date(cacheHeaders.date)) / 1000 || 0;

  if ( cacheCC['no-cache']
    || cacheCC['must-revalidate']
    || cacheCC['proxy-revalidate']) return true;

  if (reqCC['no-cache']) return true;

  if (null != reqCC['max-age']) return reqCC['max-age'] < cacheAge;

  if (null != cacheCC['max-age']) return cacheCC['max-age'] < cacheAge;

  return false;
}

/**
 * The key to use in the cache. For now, this is the URL path and query.
 *
 * 'http://example.com?key=value' -> '/?key=value'
 *
 * @param {Object} req
 * @return {String}
 * @api private
 */

function cacheKey(req) {
  return utils.parseUrl(req).path;
}


var path = require('path');
var fs = require('fs');

module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP;

function mkdirP (p, mode, f, made) {
    if (typeof mode === 'function' || mode === undefined) {
        f = mode;
        mode = 0777 & (~process.umask());
    }
    if (!made) made = null;

    var cb = f || function () {};
    if (typeof mode === 'string') mode = parseInt(mode, 8);
    p = path.resolve(p);

    fs.mkdir(p, mode, function (er) {
        if (!er) {
            made = made || p;
            return cb(null, made);
        }
        switch (er.code) {
            case 'ENOENT':
                mkdirP(path.dirname(p), mode, function (er, made) {
                    if (er) cb(er, made);
                    else mkdirP(p, mode, cb, made);
                });
                break;

            // In the case of any other error, just see if there's a dir
            // there already.  If so, then hooray!  If not, then something
            // is borked.
            default:
                fs.stat(p, function (er2, stat) {
                    // if the stat fails, then that's super weird.
                    // let the original error be the failure reason.
                    if (er2 || !stat.isDirectory()) cb(er, made)
                    else cb(null, made);
                });
                break;
        }
    });
}

mkdirP.sync = function sync (p, mode, made) {
    if (mode === undefined) {
        mode = 0777 & (~process.umask());
    }
    if (!made) made = null;

    if (typeof mode === 'string') mode = parseInt(mode, 8);
    p = path.resolve(p);

    try {
        fs.mkdirSync(p, mode);
        made = made || p;
    }
    catch (err0) {
        switch (err0.code) {
            case 'ENOENT' :
                made = sync(path.dirname(p), mode, made);
                sync(p, mode, made);
                break;

            // In the case of any other error, just see if there's a dir
            // there already.  If so, then hooray!  If not, then something
            // is borked.
            default:
                var stat;
                try {
                    stat = fs.statSync(p);
                }
                catch (err1) {
                    throw err0;
                }
                if (!stat.isDirectory()) throw err0;
                break;
        }
    }

    return made;
};


/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50*/
/*global $, define, brackets, FileError */

define(function (require, exports, module) {
    "use strict";

    var Async = require("utils/Async");

    /**
     * @private
     * Map an fs error code to a FileError.
     */
    function _browserErrToFileError(err) {
        if (err === brackets.fs.ERR_NOT_FOUND) {
            return FileError.NOT_FOUND_ERR;
        }

        // All other errors are mapped to the generic "security" error
        return FileError.SECURITY_ERR;
    }

    var liveBrowserOpenedPIDs = [];

    /** openLiveBrowser
     *
     * @param {string} url
     * @return {$.Promise}
     */
    function openLiveBrowser(url, enableRemoteDebugging) {
        var result = new $.Deferred();

        brackets.app.openLiveBrowser(url, enableRemoteDebugging, function onRun(err, pid) {
            if (!err) {
                // Undefined ids never get removed from list, so don't push them on
                if (pid !== undefined) {
                    liveBrowserOpenedPIDs.push(pid);
                }
                result.resolve(pid);
            } else {
                result.reject(_browserErrToFileError(err));
            }
        });

        return result.promise();
    }

    /** closeLiveBrowser
     *
     * @return {$.Promise}
     */
    function closeLiveBrowser(pid) {
        var result = new $.Deferred();

        if (isNaN(pid)) {
            pid = 0;
        }
        brackets.app.closeLiveBrowser(function (err) {
            if (!err) {
                var i = liveBrowserOpenedPIDs.indexOf(pid);
                if (i !== -1) {
                    liveBrowserOpenedPIDs.splice(i, 1);
                }
                result.resolve();
            } else {
                result.reject(_browserErrToFileError(err));
            }
        }, pid);

        return result.promise();
    }

    /** closeAllLiveBrowsers
     * Closes all the browsers that were tracked on open
     * TODO: does not seem to work on Windows
     * @return {$.Promise}
     */
    function closeAllLiveBrowsers() {
        //make a copy incase the array is edited as we iterate
        var closeIDs = liveBrowserOpenedPIDs.concat();
        return Async.doSequentially(closeIDs, closeLiveBrowser, false);
    }

    /**
     * Opens a URL in the system default browser
     */
    function openURLInDefaultBrowser(url) {
        brackets.app.openURLInDefaultBrowser(function (err) {}, url);
    }

    // Define public API
    exports.openLiveBrowser = openLiveBrowser;
    exports.closeLiveBrowser = closeLiveBrowser;
    exports.closeAllLiveBrowsers = closeAllLiveBrowsers;
    exports.openURLInDefaultBrowser = openURLInDefaultBrowser;
});


it("should only allow certain characters in the name", function () {

var validateName = packageValidator.__get__("validateName");

/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, node: true, nomen: true,
indent: 4, maxerr: 50 */
/*global expect, describe, it */

"use strict";

var packageValidator = require("../package-validator"),
    path             = require("path");

var testFilesDirectory = path.join(path.dirname(module.filename),
                                    "..",   // node
                                    "..",   // extensibility
                                    "..",   // src
                                    "..",   // brackets
                                    "test",
                                    "spec",
                                    "extension-test-files");

var basicValidExtension    = path.join(testFilesDirectory, "basic-valid-extension.zip"),
    basicValidExtension2   = path.join(testFilesDirectory, "basic-valid-extension-2.0.zip"),
    missingPackageJSON     = path.join(testFilesDirectory, "missing-package-json.zip"),
    invalidJSON            = path.join(testFilesDirectory, "invalid-json.zip"),
    invalidZip             = path.join(testFilesDirectory, "invalid-zip-file.zip"),
    missingNameVersion     = path.join(testFilesDirectory, "missing-name-version.zip"),
    missingMain            = path.join(testFilesDirectory, "missing-main.zip"),
    oneLevelDown           = path.join(testFilesDirectory, "one-level-extension-master.zip"),
    bogusTopDir            = path.join(testFilesDirectory, "bogus-top-dir.zip"),
    badname                = path.join(testFilesDirectory, "badname.zip"),
    mainInDirectory        = path.join(testFilesDirectory, "main-in-directory.zip"),
    invalidVersion         = path.join(testFilesDirectory, "invalid-version.zip"),
    invalidBracketsVersion = path.join(testFilesDirectory, "invalid-brackets-version.zip");

describe("Package Validation", function () {
    it("should handle a good package", function (done) {
        packageValidator.validate(basicValidExtension, {}, function (err, result) {
            expect(err).toBeNull();
            expect(result.errors.length).toEqual(0);
            var metadata = result.metadata;
            expect(metadata.name).toEqual("basic-valid-extension");
            expect(metadata.version).toEqual("1.0.0");
            expect(metadata.title).toEqual("Basic Valid Extension");
            expect(metadata.author.name).toEqual("Alfred Einstein");
            expect(metadata.author.email).toEqual("alfred_not_albert@thoseeinsteins.org");
            expect(metadata.author.url).toBeUndefined();
            done();
        });
    });

    it("should NOT complain about missing package.json", function (done) {
        packageValidator.validate(missingPackageJSON, {}, function (err, result) {
            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(0);
            expect(result.metadata).toBeNull();
            done();
        });
    });

    it("should complain about missing package.json if you tell it to", function (done) {
        packageValidator.validate(missingPackageJSON, {
            requirePackageJSON: true
        }, function (err, result) {
            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(1);
            expect(errors[0][0]).toEqual("MISSING_PACKAGE_JSON");
            done();
        });
    });

    it("should complain about illegal path", function (done) {
        packageValidator.validate(path.join(testFilesDirectory, "NO_FILE_HERE"), {}, function (err, result) {
            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(1);
            expect(errors[0][0]).toEqual("NOT_FOUND_ERR");
            expect(result.metadata).toBeUndefined();
            done();
        });
    });

    it("should complain about invalid JSON", function (done) {
        packageValidator.validate(invalidJSON, {}, function (err, result) {
            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(1);
            expect(errors[0][0]).toEqual("INVALID_PACKAGE_JSON");
            expect(errors[0][1]).toEqual("SyntaxError: Unexpected token I");
            expect(errors[0][2]).toEqual(invalidJSON);
            expect(result.metadata).toBeUndefined();
            done();
        });
    });

    it("should complain about an invalid zip file", function (done) {
        packageValidator.validate(invalidZip, {}, function (err, result) {
            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(1);
            expect(errors[0][0]).toEqual("INVALID_ZIP_FILE");
            done();
        });
    });

    it("should require name and version in the metadata", function (done) {
        packageValidator.validate(missingNameVersion, {}, function (err, result) {
            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(2);
            expect(errors[0][0]).toEqual("MISSING_PACKAGE_NAME");
            expect(errors[1][0]).toEqual("MISSING_PACKAGE_VERSION");
            done();
        });
    });

    it("should validate the version number", function (done) {
        packageValidator.validate(invalidVersion, {}, function (err, result) {
            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(1);
            expect(errors[0][0]).toEqual("INVALID_VERSION_NUMBER");
            expect(errors[0][1]).toEqual("NOT A VERSION");
            done();
        });
    });

    it("should require a main.js in the zip file", function (done) {
        packageValidator.validate(missingMain, {}, function (err, result) {
            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(1);
            expect(errors[0][0]).toEqual("MISSING_MAIN");
            done();
        });
    });

    it("should determine the common prefix if there is one", function (done) {
        packageValidator.validate(oneLevelDown, {}, function (err, result) {
            expect(err).toBeNull();
            expect(result.errors.length).toEqual(0);
            expect(result.metadata.name).toEqual("one-level-extension");
            expect(result.commonPrefix).toEqual("one-level-extension-master");
            expect(result.metadata.author.name).toEqual("A Person");
            done();
        });
    });

    it("should not be fooled by bogus top directories", function (done) {
        packageValidator.validate(bogusTopDir, {}, function (err, result) {
            expect(err).toBeNull();
            expect(result.errors.length).toEqual(0);
            expect(result.metadata.name).toEqual("bogus-top-dir");
            expect(result.commonPrefix).toEqual("");
            done();
        });
    });

    it("should not allow names that contain disallowed characters", function (done) {
        packageValidator.validate(badname, {}, function (err, result) {
            expect(err).toBeNull();
            expect(result.errors.length).toEqual(1);
            expect(result.errors[0][0]).toEqual("BAD_PACKAGE_NAME");
            done();
        });
    });

    it("should complain about files that don't have main in the top dir", function (done) {
        packageValidator.validate(mainInDirectory, {}, function (err, result) {
            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(1);
            expect(errors[0][0]).toEqual("MISSING_MAIN");
            done();
        });
    });

    it("should handle a variety of person forms", function () {
        var parse = packageValidator._parsePersonString;
        expect(parse("A Person")).toEqual({
            name: "A Person"
        });
        expect(parse({
            name: "A Person"
        })).toEqual({
            name: "A Person"
        });
        expect(parse("A Person (http://foo.bar)")).toEqual({
            name: "A Person",
            url: "http://foo.bar"
        });
        expect(parse("A Person <foo@bar>")).toEqual({
            name: "A Person",
            email: "foo@bar"
        });
        expect(parse("A Person <foo@bar> (http://foo.bar)")).toEqual({
            name: "A Person",
            email: "foo@bar",
            url: "http://foo.bar"
        });
    });

    it("should handle contributors", function (done) {
        packageValidator.validate(basicValidExtension2, {}, function (err, result) {
            expect(err).toBeNull();
            expect(result.errors.length).toEqual(0);
            var contrib = result.metadata.contributors;
            expect(contrib.length).toEqual(3);
            expect(contrib[0]).toEqual({
                name: "Johan Einstein"
            });
            expect(contrib[1]).toEqual({
                name: "Albert Einstein Jr.",
                email: "not_that_albert@thoseeinsteins.org"
            });
            expect(contrib[2]).toEqual({
                name: "Jens Einstein",
                email: "jens@thoseeinsteins.org"
            });
            done();
        });
    });

    it("should validate the Brackets version", function (done) {
        packageValidator.validate(invalidBracketsVersion, {}, function (err, result) {
            expect(err).toBeNull();
            expect(result.errors.length).toEqual(1);
            expect(result.errors[0][0]).toEqual("INVALID_BRACKETS_VERSION");
            expect(result.errors[0][1]).toEqual("foo");
            done();
        });
    });

    it("should reject a package with rejected words in title or description", function (done) {
        packageValidator.validate(basicValidExtension, {
            disallowedWords: ["valid"]
        }, function (err, result) {
            expect(err).toBeNull();
            expect(result.errors.length).toEqual(2);
            expect(result.errors[0][0]).toEqual("DISALLOWED_WORDS");
            expect(result.errors[0][1]).toEqual("title");
            expect(result.errors[0][2]).toEqual("valid");
            expect(result.errors[1][0]).toEqual("DISALLOWED_WORDS");
            expect(result.errors[1][1]).toEqual("name");
            expect(result.errors[1][2]).toEqual("valid");
            done();
        });
    });
});


body {
	font-size: 62.5%;
}

table {
	font-size: 1em;
}

/* Site
   -------------------------------- */

body {
	font-family: "Trebuchet MS", "Helvetica", "Arial",  "Verdana", "sans-serif";
}

/* Layout
   -------------------------------- */

.layout-grid {
	width: 960px;
	border-spacing: 0;
	border-collapse: collapse;
}

.layout-grid td {
	vertical-align: top;
}

.layout-grid td.left-nav {
	width: 140px;
}

.layout-grid td.normal {
	border-left: 1px solid #eee;
	padding: 20px 24px;
	font-family: "Trebuchet MS", "Helvetica", "Arial",  "Verdana", "sans-serif";
}

.layout-grid td.demos {
	background: url('/images/demos_bg.jpg') no-repeat;
	height: 337px;
	overflow: hidden;
}

/* Normal
   -------------------------------- */

.normal h3,
.normal h4 {
	margin: 0;
	font-weight: normal;
}

.normal h3 {
	padding: 0 0 9px;
	font-size: 1.8em;
}

.normal h4 {
	padding-bottom: 21px;
	border-bottom: 1px dashed #999;
	font-size: 1.2em;
	font-weight: bold;
}

.normal p {
	font-size: 1.2em;
}

/* Demos */

.demos-nav, .demos-nav dt, .demos-nav dd, .demos-nav ul, .demos-nav li {
	margin: 0;
	padding: 0
}

.demos-nav {
	float: left;
	width: 170px;
	font-size: 1.3em;
}

.demos-nav dt,
.demos-nav h4 {
	margin: 0;
	padding: 0;
	font: normal 1.1em "Trebuchet MS", "Helvetica", "Arial",  "Verdana", "sans-serif";
	color: #e87b10;
}

.demos-nav dt,
.demos-nav h4 {
	margin-top: 1.5em;
	margin-bottom: 0;
	padding-left: 8px;
	padding-bottom:5px;
	line-height: 1.2em;
	border-bottom: 1px solid #F4F4F4;
}

.demos-nav dd a,
.demos-nav li a {
	border-bottom: 1px solid #F4F4F4;
	display:block;
	padding: 4px 3px 4px 8px;
	font-size: 90%;
	text-decoration: none;
	color: #555 ;
	margin:2px 0;
	height:13px;
}

.demos-nav dd a:hover,
.demos-nav dd a:focus,
.demos-nav dd a:hover,
.demos-nav dd a:focus {
	background: #f3f3f3;
	color:#000;
	-moz-border-radius: 5px; -webkit-border-radius: 5px;
}
 .demos-nav dd a.selected {
	background: #555;
	color:#ffffff;
	-moz-border-radius: 5px; -webkit-border-radius: 5px;
}

/* new styles for demo pages, added by Filament 12.29.08
eventually we should convert the font sizes to ems -- using px for now to minimize style conflicts
*/

.normal h3.demo-header { font-size:32px; padding:0 0 5px; border-bottom:1px solid #eee; text-transform: capitalize; }
.normal h4.demo-subheader { font-size:10px; text-transform: uppercase; color:#999; padding:8px 0 3px; border:0; margin:0; }
#demo-notes a, #demo-link a, #demo-source a { color:#1b75bb; text-decoration:none; }
.normal a:hover,
.normal a:active { color:#0b559b; }

#demo-config { padding:20px 0 0; }

#demo-frame { float:left; width:540px; height:380px; border:1px solid #ddd; overflow: auto; position: relative; }
#demo-frame h3, #demo-frame h4 { padding: 0; font-weight: bold; font-size: 1em; }

#demo-config-menu { float:right; width:180px;  }
#demo-config-menu h4 { font-size:13px; color:#666; font-weight:normal; border:0; padding-left:18px; }

#demo-config-menu ul { list-style: none; padding: 0; margin: 0; }

#demo-config-menu li { font-size:12px; padding:0 0 0 10px; margin:3px 0; zoom: 1; }

#demo-config-menu li a:link,
#demo-config-menu li a:visited { display:block; padding:1px 8px 4px; border-bottom:1px dotted #b3b3b3; }
* html #demo-config-menu li a:link,
* html #demo-config-menu li a:visited { padding:1px 8px 2px; }
#demo-config-menu li a:hover,
#demo-config-menu li a:active { background-color:#f6f6f6; }

#demo-config-menu li.demo-config-on { background: url(images/demo-config-on-tile.gif) repeat-x left center; }

#demo-config-menu li.demo-config-on a:link,
#demo-config-menu li.demo-config-on a:visited,
#demo-config-menu li.demo-config-on a:hover,
#demo-config-menu li.demo-config-on a:active { background: url(images/demo-config-on.gif) no-repeat left; padding-left:18px; color:#fff; border:0; margin-left:-10px; margin-top: 0px; margin-bottom: 0px; }

#demo-source, #demo-notes {
	clear: both;
	padding: 20px 0 0;
	font-size: 1.3em;
}

#demo-notes { width:520px; color:#333; font-size: 1em; }
#demo-notes p code, .demo-description p code { padding: 0; font-weight: bold; }
#demo-source pre, #demo-source code { padding: 0; }
code, pre { padding:8px 0 8px 20px ; font-size: 1.2em; line-height:130%;  }

#demo-source a:link,
#demo-source a:visited,
#demo-source a:hover,
#demo-source a:active { font-size:12px; padding-left:13px; background-position: left center; background-repeat: no-repeat; }

#demo-source a.source-open:link,
#demo-source a.source-open:visited,
#demo-source a.source-open:hover,
#demo-source a.source-open:active { background-image: url(images/demo-spindown-open.gif); }

#demo-source a.source-closed:link,
#demo-source a.source-closed:visited,
#demo-source a.source-closed:hover,
#demo-source a.source-closed:active { background-image: url(images/demo-spindown-closed.gif); }

div.demo {
	padding:12px;
	font-family: "Trebuchet MS", "Arial", "Helvetica", "Verdana", "sans-serif";
}

div.demo h3.docs { clear:left; font-size:12px; font-weight:normal; padding:0 0 1em; margin:0; }

div.demo-description {
	clear:both;
	padding:12px;
	font-family: "Trebuchet MS", "Arial", "Helvetica", "Verdana", "sans-serif";
	font-size: 1.3em;
	line-height: 1.4em;
}

.ui-draggable, .ui-droppable {
	background-position: top left;
}

.left-nav .demos-nav {
	padding-right: 10px;
}

#demo-link { font-size:11px;  padding-top: 6px; clear: both; overflow: hidden; }
#demo-link a span.ui-icon { float:left; margin-right:3px; }

/* Component containers
----------------------------------*/
#widget-docs .ui-widget { font-family: Trebuchet MS,Verdana,Arial,sans-serif; font-size: 1em; }
#widget-docs .ui-widget input, #widget-docs .ui-widget select, #widget-docs .ui-widget textarea, #widget-docs .ui-widget button { font-family: Trebuchet MS,Verdana,Arial,sans-serif; font-size: 1em; }
#widget-docs .ui-widget-header { border: 1px solid #ffffff; background: #464646 url(images/464646_40x100_textures_01_flat_100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
#widget-docs .ui-widget-header a { color: #ffffff; }
#widget-docs .ui-widget-content { border: 1px solid #ffffff; background: #ffffff url(images/ffffff_40x100_textures_01_flat_75.png) 50% 50% repeat-x; color: #222222; }
#widget-docs .ui-widget-content a { color: #222222; }

/* Interaction states
----------------------------------*/
#widget-docs .ui-state-default, #widget-docs .ui-widget-content #widget-docs .ui-state-default { border: 1px solid #666666; background: #555555 url(images/555555_40x100_textures_03_highlight_soft_75.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; outline: none; }
#widget-docs .ui-state-default a { color: #ffffff; text-decoration: none; outline: none; }
#widget-docs .ui-state-hover, #widget-docs .ui-widget-content #widget-docs .ui-state-hover, #widget-docs .ui-state-focus, #widget-docs .ui-widget-content #widget-docs .ui-state-focus { border: 1px solid #666666; background: #444444 url(images/444444_40x100_textures_03_highlight_soft_60.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; outline: none; }
#widget-docs .ui-state-hover a { color: #ffffff; text-decoration: none; outline: none; }
#widget-docs .ui-state-active, #widget-docs .ui-widget-content #widget-docs .ui-state-active { border: 1px solid #666666; background: #ffffff url(images/ffffff_40x100_textures_01_flat_65.png) 50% 50% repeat-x; font-weight: normal; color: #F6921E; outline: none; }
#widget-docs .ui-state-active a { color: #F6921E; outline: none; text-decoration: none; }

/* Interaction Cues
----------------------------------*/
#widget-docs .ui-state-highlight, #widget-docs .ui-widget-content #widget-docs .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/fbf9ee_40x100_textures_02_glass_55.png) 50% 50% repeat-x; color: #363636; }
#widget-docs .ui-state-error, #widget-docs .ui-widget-content #widget-docs .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/fef1ec_40x100_textures_05_inset_soft_95.png) 50% bottom repeat-x; color: #cd0a0a; }
#widget-docs .ui-state-error-text, #widget-docs .ui-widget-content #widget-docs .ui-state-error-text { color: #cd0a0a; }
#widget-docs .ui-state-disabled, #widget-docs .ui-widget-content #widget-docs .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
#widget-docs .ui-priority-primary, #widget-docs .ui-widget-content #widget-docs .ui-priority-primary { font-weight: bold; }
#widget-docs .ui-priority-secondary, #widget-docs .ui-widget-content #widget-docs .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }

/* Icons
----------------------------------*/

/* states and images */
#demo-frame-wrapper .ui-icon, #widget-docs .ui-icon { width: 16px; height: 16px; background-image: url(images/222222_256x240_icons_icons.png); }
#widget-docs .ui-widget-content .ui-icon {background-image: url(images/222222_256x240_icons_icons.png); }
#widget-docs .ui-widget-header .ui-icon {background-image: url(images/222222_256x240_icons_icons.png); }
#widget-docs .ui-state-default .ui-icon { background-image: url(images/888888_256x240_icons_icons.png); }
#widget-docs .ui-state-hover .ui-icon, #widget-docs .ui-state-focus .ui-icon {background-image: url(images/454545_256x240_icons_icons.png); }
#widget-docs .ui-state-active .ui-icon {background-image: url(images/454545_256x240_icons_icons.png); }
#widget-docs .ui-state-highlight .ui-icon {background-image: url(images/2e83ff_256x240_icons_icons.png); }
#widget-docs .ui-state-error .ui-icon, #widget-docs .ui-state-error-text .ui-icon {background-image: url(images/cd0a0a_256x240_icons_icons.png); }

/* Misc visuals
----------------------------------*/

/* Corner radius */
#widget-docs .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; }
#widget-docs .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
#widget-docs .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
#widget-docs .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
#widget-docs .ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
#widget-docs .ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
#widget-docs .ui-corner-right {  -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
#widget-docs .ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
#widget-docs .ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; }

/* Overlays */
#widget-docs .ui-widget-overlay { background: #aaaaaa url(images/aaaaaa_40x100_textures_01_flat_0.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
#widget-docs .ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/aaaaaa_40x100_textures_01_flat_0.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; }

/*
----------------------------------*/

#widget-docs { margin:20px 0 0; border: none; }

#widget-docs h2, #widget-docs h3, #widget-docs h4, #widget-docs p, #widget-docs ul, #widget-docs code { margin:0; padding:0; }
#widget-docs code { display:block; color:#444; font-size:.9em; margin:0 0 1em; }
#widget-docs code strong { color:#000; }
#widget-docs p { margin:0 3em 1.2em 0; }
#widget-docs p.intro { font-size:13px; color:#666; line-height:1.3; }
#widget-docs ul { list-style-type: none; }

#widget-docs h2 { font-size:16px; margin:1.2em 0 .5em; }
#widget-docs h3 { font-size:14px; color:#e6820E; margin:1.5em 0 .5em; }
.normal #widget-docs h4 { font-size:12px; color:#000; border:0; margin:0 0 .5em; }

#docs-overview-main { width:400px; }
#docs-overview-sidebar { float:right; width:200px; }
#docs-overview-sidebar a span { color:#666; }
#widget-docs #docs-overview-main p { margin-right:0; }
#widget-docs #docs-overview-sidebar h4 { padding-left:0; }

.docs-list-header { float:left; width:100%; margin:10px 0 0; border-bottom:1px solid #eee; }
#widget-docs .docs-list-header h2 { float:left; margin:0; }
#widget-docs .docs-list-header p { float:right; margin:5px 0; font-size:11px; }

.docs-list { float:left; width:100%; padding:0 0 10px; }
.docs-list .param-header { float:left; clear:left; width:100%; padding:8px 0; border-top:1px solid #eee; }
#widget-docs .param-header h3, #widget-docs .param-header p { margin:0; float:left; }
#widget-docs .param-header h3 { width:50%; }
#widget-docs .param-header h3 span { background: url(images/demo-spindown-closed.gif) no-repeat left; padding-left:13px; }
#widget-docs .param-open .param-header h3 span { background: url(images/demo-spindown-open.gif) no-repeat left; }
#widget-docs .param-header p { width:24%; }
#widget-docs .param-header p.param-type span { background: url(images/icon-docs-info.gif) no-repeat left; cursor:pointer; border-bottom:1px dashed #ccc; padding-left:15px; }

.param-details { padding-left:13px; }
.param-args { margin:0 0 1.5em; border-top:1px dotted #ccc;}
.param-args td { padding:3px 30px 3px 5px; border-bottom:1px dotted #ccc;  }

/* overrides for ui-tab styles */
#widget-docs ul.ui-tabs-nav { padding:0 0 0 8px; }
#widget-docs .ui-tabs-nav li { margin:5px 5px 0 0; }

#widget-docs .ui-tabs-nav li a:link,
#widget-docs .ui-tabs-nav li a:visited,
#widget-docs .ui-tabs-nav li a:hover,
#widget-docs .ui-tabs-nav li a:active { font-size:14px; padding:4px 1.2em 3px; color:#fff; }

#widget-docs .ui-tabs-nav li.ui-tabs-active a:link,
#widget-docs .ui-tabs-nav li.ui-tabs-active a:visited,
#widget-docs .ui-tabs-nav li.ui-tabs-active a:hover,
#widget-docs .ui-tabs-nav li.ui-tabs-active a:active { color:#e6820E; }

#widget-docs .ui-tabs-panel { padding:20px 9px; font-size:12px; line-height:1.4; color:#000; }

#widget-docs .ui-widget-content a:link,
#widget-docs .ui-widget-content a:visited { color:#1b75bb; text-decoration:none; }
#widget-docs .ui-widget-content a:hover,
#widget-docs .ui-widget-content a:active { color:#0b559b; }