扩展Bundle支持动态Bundle和javascript混淆

来源:互联网 发布:mac怎么重新安装系统 编辑:程序博客网 时间:2024/06/18 16:30

扩展Bundle支持动态Bundle和javascript混淆


两个目的:

  1. 支持动态页面上的Bundle,而不必每次在Global中添加Bundle。
  2. 支持Javascript混淆

ASP.NET MVC自带Bundle


通常ASP.NET MVC 4.0以后自带的Bundle如下:
BundleConfig.RegisterBundles(BundleTable.Bundles);public class BundleConfig    {        // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725        public static void RegisterBundles(BundleCollection bundles)        {            // Use the development version of Modernizr to develop with and learn from. Then, when you're            // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.            bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(                        "~/Scripts/modernizr-*"));            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(                        "~/Scripts/jquery-{version}.js"));#if !DEBUG            BundleTable.EnableOptimizations = true;#endif        }    }

页面上的使用:
@Scripts.Render("~/bundles/jquery")

新动态Bundle


对于有些不是很重要的页面,觉得不必为此页面特意创建一个Bundle,此时在页面上动态创建Bundle会更方便一些。如,在页面上使用:
@Html.Style("~/Scripts/JQueryUI2/themes/smoothness/jquery.ui.theme.css", "~/Scripts/JQueryUI2/themes/smoothness/jquery.ui.menu.css")        @Html.Script("~/Scripts/JQueryUI2/ui/jquery.ui.core.js", "~/Scripts/JQueryUI2/ui/jquery.ui.position.js", "~/Scripts/JQueryUI2/ui/jquery.ui.widget.js", "~/Scripts/JQueryUI2/ui/jquery.ui.menu.js")

当然上面Html.Style是自己创建的扩展,下面贴出扩展的源码:
public static class Extension    {        public static IHtmlString Script(this HtmlHelper helper, params string[] urls)        {            var bundleDirectory = "~/bundles/" + MakeBundleName("js", urls);            var bundle = BundleTable.Bundles.GetBundleFor(bundleDirectory);            if (bundle == null)            {                var transform = new JavascriptObfuscator();                bundle = new ScriptBundle(bundleDirectory).Include(urls);                bundle.Transforms.Add(transform);                BundleTable.Bundles.Add(bundle);            }            return Scripts.Render(bundleDirectory);        }        public static IHtmlString Style(this HtmlHelper helper, params string[] urls)        {            var bundleDirectory = "~/bundles/" + MakeBundleName("css", urls);            var bundle=BundleTable.Bundles.GetBundleFor(bundleDirectory);            if (bundle == null)            {                bundle = new StyleBundle(bundleDirectory).Include(urls);                BundleTable.Bundles.Add(bundle);            }            return Styles.Render(bundleDirectory);        }        private static string MakeBundleName(string type, params string[] urls)        {            var array =                urls.SelectMany(url => url.Split('/'))                    .SelectMany(url => url.Split('.'))                    .Distinct()                    .Except(new[] {"~", type});            return string.Join("-", array);        }    }

相信上面的代码简简易懂,不需要多作解释。上面只是一个思路,人们可以随意发挥你的创作。只是有一点要说的是,我添加了一个Javascript混淆器,为了生成像如下一样的代码:
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromChar

混淆器代码是网上找的,如下:
/// <summary>    /// Packs a javascript file into a smaller area, removing unnecessary characters from the output.    /// </summary>    public class ECMAScriptPacker : IHttpHandler    {        /// <summary>        /// The encoding level to use. See http://dean.edwards.name/packer/usage/ for more info.        /// </summary>        public enum PackerEncoding { None = 0, Numeric = 10, Mid = 36, Normal = 62, HighAscii = 95 };        private PackerEncoding encoding = PackerEncoding.Normal;        private bool fastDecode = true;        private bool specialChars = false;        private bool enabled = true;        string IGNORE = "$1";        /// <summary>        /// The encoding level for this instance        /// </summary>        public PackerEncoding Encoding        {            get { return encoding; }            set { encoding = value; }        }        /// <summary>        /// Adds a subroutine to the output to speed up decoding        /// </summary>        public bool FastDecode        {            get { return fastDecode; }            set { fastDecode = value; }        }        /// <summary>        /// Replaces special characters        /// </summary>        public bool SpecialChars        {            get { return specialChars; }            set { specialChars = value; }        }        /// <summary>        /// Packer enabled        /// </summary>        public bool Enabled        {            get { return enabled; }            set { enabled = value; }        }        public ECMAScriptPacker()        {            Encoding = PackerEncoding.Normal;            FastDecode = true;            SpecialChars = false;        }        /// <summary>        /// Constructor        /// </summary>        /// <param name="encoding">The encoding level for this instance</param>        /// <param name="fastDecode">Adds a subroutine to the output to speed up decoding</param>        /// <param name="specialChars">Replaces special characters</param>        public ECMAScriptPacker(PackerEncoding encoding, bool fastDecode, bool specialChars)        {            Encoding = encoding;            FastDecode = fastDecode;            SpecialChars = specialChars;        }        /// <summary>        /// Packs the script        /// </summary>        /// <param name="script">the script to pack</param>        /// <returns>the packed script</returns>        public string Pack(string script)        {            if (enabled)            {                script += "\n";                script = basicCompression(script);                if (SpecialChars)                    script = encodeSpecialChars(script);                if (Encoding != PackerEncoding.None)                    script = encodeKeywords(script);            }            return script;        }        //zero encoding - just removal of whitespace and comments        private string basicCompression(string script)        {            ParseMaster parser = new ParseMaster();            // make safe            parser.EscapeChar = '\\';            // protect strings            parser.Add("'[^'\\n\\r]*'", IGNORE);            parser.Add("\"[^\"\\n\\r]*\"", IGNORE);            // remove comments            parser.Add("\\/\\/[^\\n\\r]*[\\n\\r]");            parser.Add("\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/");            // protect regular expressions            parser.Add("\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)", "$2");            parser.Add("[^\\w\\$\\/'\"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?", IGNORE);            // remove: ;;; doSomething();            if (specialChars)                parser.Add(";;[^\\n\\r]+[\\n\\r]");            // remove redundant semi-colons            parser.Add(";+\\s*([};])", "$2");            // remove white-space            parser.Add("(\\b|\\$)\\s+(\\b|\\$)", "$2 $3");            parser.Add("([+\\-])\\s+([+\\-])", "$2 $3");            parser.Add("\\s+");            // done            return parser.Exec(script);        }        WordList encodingLookup;        private string encodeSpecialChars(string script)        {            ParseMaster parser = new ParseMaster();            // replace: $name -> n, $$name -> na            parser.Add("((\\$+)([a-zA-Z\\$_]+))(\\d*)",                new ParseMaster.MatchGroupEvaluator(encodeLocalVars));            // replace: _name -> _0, double-underscore (__name) is ignored            Regex regex = new Regex("\\b_[A-Za-z\\d]\\w*");            // build the word list            encodingLookup = analyze(script, regex, new EncodeMethod(encodePrivate));            parser.Add("\\b_[A-Za-z\\d]\\w*", new ParseMaster.MatchGroupEvaluator(encodeWithLookup));            script = parser.Exec(script);            return script;        }        private string encodeKeywords(string script)        {            // escape high-ascii values already in the script (i.e. in strings)            if (Encoding == PackerEncoding.HighAscii) script = escape95(script);            // create the parser            ParseMaster parser = new ParseMaster();            EncodeMethod encode = getEncoder(Encoding);            // for high-ascii, don't encode single character low-ascii            Regex regex = new Regex(                    (Encoding == PackerEncoding.HighAscii) ? "\\w\\w+" : "\\w+"                );            // build the word list            encodingLookup = analyze(script, regex, encode);            // encode            parser.Add((Encoding == PackerEncoding.HighAscii) ? "\\w\\w+" : "\\w+",                new ParseMaster.MatchGroupEvaluator(encodeWithLookup));            // if encoded, wrap the script in a decoding function            return (script == string.Empty) ? "" : bootStrap(parser.Exec(script), encodingLookup);        }        private string bootStrap(string packed, WordList keywords)        {            // packed: the packed script            packed = "'" + escape(packed) + "'";            // ascii: base for encoding            int ascii = Math.Min(keywords.Sorted.Count, (int)Encoding);            if (ascii == 0)                ascii = 1;            // count: number of words contained in the script            int count = keywords.Sorted.Count;            // keywords: list of words contained in the script            foreach (object key in keywords.Protected.Keys)            {                keywords.Sorted[(int)key] = "";            }            // convert from a string to an array            StringBuilder sbKeywords = new StringBuilder("'");            foreach (string word in keywords.Sorted)                sbKeywords.Append(word + "|");            sbKeywords.Remove(sbKeywords.Length - 1, 1);            string keywordsout = sbKeywords.ToString() + "'.split('|')";            string encode;            string inline = "c";            switch (Encoding)            {                case PackerEncoding.Mid:                    encode = "function(c){return c.toString(36)}";                    inline += ".toString(a)";                    break;                case PackerEncoding.Normal:                    encode = "function(c){return(c<a?\"\":e(parseInt(c/a)))+" +                        "((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))}";                    inline += ".toString(a)";                    break;                case PackerEncoding.HighAscii:                    encode = "function(c){return(c<a?\"\":e(c/a))+" +                        "String.fromCharCode(c%a+161)}";                    inline += ".toString(a)";                    break;                default:                    encode = "function(c){return c}";                    break;            }            // decode: code snippet to speed up decoding            string decode = "";            if (fastDecode)            {                decode = "if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\\\w+'};c=1;}";                if (Encoding == PackerEncoding.HighAscii)                    decode = decode.Replace("\\\\w", "[\\xa1-\\xff]");                else if (Encoding == PackerEncoding.Numeric)                    decode = decode.Replace("e(c)", inline);                if (count == 0)                    decode = decode.Replace("c=1", "c=0");            }            // boot function            string unpack = "function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p;}";            Regex r;            if (fastDecode)            {                //insert the decoder                r = new Regex("\\{");                unpack = r.Replace(unpack, "{" + decode + ";", 1);            }            if (Encoding == PackerEncoding.HighAscii)            {                // get rid of the word-boundries for regexp matches                r = new Regex("'\\\\\\\\b'\\s*\\+|\\+\\s*'\\\\\\\\b'");                unpack = r.Replace(unpack, "");            }            if (Encoding == PackerEncoding.HighAscii || ascii > (int)PackerEncoding.Normal || fastDecode)            {                // insert the encode function                r = new Regex("\\{");                unpack = r.Replace(unpack, "{e=" + encode + ";", 1);            }            else            {                r = new Regex("e\\(c\\)");                unpack = r.Replace(unpack, inline);            }            // no need to pack the boot function since i've already done it            string _params = "" + packed + "," + ascii + "," + count + "," + keywordsout;            if (fastDecode)            {                //insert placeholders for the decoder                _params += ",0,{}";            }            // the whole thing            return "eval(" + unpack + "(" + _params + "))\n";        }        private string escape(string input)        {            Regex r = new Regex("([\\\\'])");            return r.Replace(input, "\\$1");        }        private EncodeMethod getEncoder(PackerEncoding encoding)        {            switch (encoding)            {                case PackerEncoding.Mid:                    return new EncodeMethod(encode36);                case PackerEncoding.Normal:                    return new EncodeMethod(encode62);                case PackerEncoding.HighAscii:                    return new EncodeMethod(encode95);                default:                    return new EncodeMethod(encode10);            }        }        private string encode10(int code)        {            return code.ToString();        }        //lookups seemed like the easiest way to do this since         // I don't know of an equivalent to .toString(36)        private static string lookup36 = "0123456789abcdefghijklmnopqrstuvwxyz";        private string encode36(int code)        {            string encoded = "";            int i = 0;            do            {                int digit = (code / (int)Math.Pow(36, i)) % 36;                encoded = lookup36[digit] + encoded;                code -= digit * (int)Math.Pow(36, i++);            } while (code > 0);            return encoded;        }        private static string lookup62 = lookup36 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ";        private string encode62(int code)        {            string encoded = "";            int i = 0;            do            {                int digit = (code / (int)Math.Pow(62, i)) % 62;                encoded = lookup62[digit] + encoded;                code -= digit * (int)Math.Pow(62, i++);            } while (code > 0);            return encoded;        }        private static string lookup95 = "¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";        private string encode95(int code)        {            string encoded = "";            int i = 0;            do            {                int digit = (code / (int)Math.Pow(95, i)) % 95;                encoded = lookup95[digit] + encoded;                code -= digit * (int)Math.Pow(95, i++);            } while (code > 0);            return encoded;        }        private string escape95(string input)        {            Regex r = new Regex("[\xa1-\xff]");            return r.Replace(input, new MatchEvaluator(escape95Eval));        }        private string escape95Eval(Match match)        {            return "\\x" + ((int)match.Value[0]).ToString("x"); //return hexadecimal value        }        private string encodeLocalVars(Match match, int offset)        {            int length = match.Groups[offset + 2].Length;            int start = length - Math.Max(length - match.Groups[offset + 3].Length, 0);            return match.Groups[offset + 1].Value.Substring(start, length) +                match.Groups[offset + 4].Value;        }        private string encodeWithLookup(Match match, int offset)        {            return (string)encodingLookup.Encoded[match.Groups[offset].Value];        }        private delegate string EncodeMethod(int code);        private string encodePrivate(int code)        {            return "_" + code;        }        private WordList analyze(string input, Regex regex, EncodeMethod encodeMethod)        {            // analyse            // retreive all words in the script            MatchCollection all = regex.Matches(input);            WordList rtrn;            rtrn.Sorted = new StringCollection(); // list of words sorted by frequency            rtrn.Protected = new HybridDictionary(); // dictionary of word->encoding            rtrn.Encoded = new HybridDictionary(); // instances of "protected" words            if (all.Count > 0)            {                StringCollection unsorted = new StringCollection(); // same list, not sorted                HybridDictionary Protected = new HybridDictionary(); // "protected" words (dictionary of word->"word")                HybridDictionary values = new HybridDictionary(); // dictionary of charCode->encoding (eg. 256->ff)                HybridDictionary count = new HybridDictionary(); // word->count                int i = all.Count, j = 0;                string word;                // count the occurrences - used for sorting later                do                {                    word = "$" + all[--i].Value;                    if (count[word] == null)                    {                        count[word] = 0;                        unsorted.Add(word);                        // make a dictionary of all of the protected words in this script                        //  these are words that might be mistaken for encoding                        Protected["$" + (values[j] = encodeMethod(j))] = j++;                    }                    // increment the word counter                    count[word] = (int)count[word] + 1;                } while (i > 0);                /* prepare to sort the word list, first we must protect                    words that are also used as codes. we assign them a code                    equivalent to the word itself.                   e.g. if "do" falls within our encoding range                        then we store keywords["do"] = "do";                   this avoids problems when decoding */                i = unsorted.Count;                string[] sortedarr = new string[unsorted.Count];                do                {                    word = unsorted[--i];                    if (Protected[word] != null)                    {                        sortedarr[(int)Protected[word]] = word.Substring(1);                        rtrn.Protected[(int)Protected[word]] = true;                        count[word] = 0;                    }                } while (i > 0);                string[] unsortedarr = new string[unsorted.Count];                unsorted.CopyTo(unsortedarr, 0);                // sort the words by frequency                Array.Sort(unsortedarr, (IComparer)new CountComparer(count));                j = 0;                /*because there are "protected" words in the list                  we must add the sorted words around them */                do                {                    if (sortedarr[i] == null)                        sortedarr[i] = unsortedarr[j++].Substring(1);                    rtrn.Encoded[sortedarr[i]] = values[i];                } while (++i < unsortedarr.Length);                rtrn.Sorted.AddRange(sortedarr);            }            return rtrn;        }        private struct WordList        {            public StringCollection Sorted;            public HybridDictionary Encoded;            public HybridDictionary Protected;        }        private class CountComparer : IComparer        {            HybridDictionary count;            public CountComparer(HybridDictionary count)            {                this.count = count;            }            #region IComparer Members            public int Compare(object x, object y)            {                return (int)count[y] - (int)count[x];            }            #endregion        }        #region IHttpHandler Members        public void ProcessRequest(HttpContext context)        {            // try and read settings from config file            if (System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker") != null)            {                NameValueCollection cfg =                    (NameValueCollection)                    System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker");                if (cfg["Encoding"] != null)                {                    switch (cfg["Encoding"].ToLower())                    {                        case "none":                            Encoding = PackerEncoding.None;                            break;                        case "numeric":                            Encoding = PackerEncoding.Numeric;                            break;                        case "mid":                            Encoding = PackerEncoding.Mid;                            break;                        case "normal":                            Encoding = PackerEncoding.Normal;                            break;                        case "highascii":                        case "high":                            Encoding = PackerEncoding.HighAscii;                            break;                    }                }                if (cfg["FastDecode"] != null)                {                    if (cfg["FastDecode"].ToLower() == "true")                        FastDecode = true;                    else                        FastDecode = false;                }                if (cfg["SpecialChars"] != null)                {                    if (cfg["SpecialChars"].ToLower() == "true")                        SpecialChars = true;                    else                        SpecialChars = false;                }                if (cfg["Enabled"] != null)                {                    if (cfg["Enabled"].ToLower() == "true")                        Enabled = true;                    else                        Enabled = false;                }            }            // try and read settings from URL            if (context.Request.QueryString["Encoding"] != null)            {                switch (context.Request.QueryString["Encoding"].ToLower())                {                    case "none":                        Encoding = PackerEncoding.None;                        break;                    case "numeric":                        Encoding = PackerEncoding.Numeric;                        break;                    case "mid":                        Encoding = PackerEncoding.Mid;                        break;                    case "normal":                        Encoding = PackerEncoding.Normal;                        break;                    case "highascii":                    case "high":                        Encoding = PackerEncoding.HighAscii;                        break;                }            }            if (context.Request.QueryString["FastDecode"] != null)            {                if (context.Request.QueryString["FastDecode"].ToLower() == "true")                    FastDecode = true;                else                    FastDecode = false;            }            if (context.Request.QueryString["SpecialChars"] != null)            {                if (context.Request.QueryString["SpecialChars"].ToLower() == "true")                    SpecialChars = true;                else                    SpecialChars = false;            }            if (context.Request.QueryString["Enabled"] != null)            {                if (context.Request.QueryString["Enabled"].ToLower() == "true")                    Enabled = true;                else                    Enabled = false;            }            //handle the request            TextReader r = new StreamReader(context.Request.PhysicalPath);            string jscontent = r.ReadToEnd();            r.Close();            context.Response.ContentType = "text/javascript";            context.Response.Output.Write(Pack(jscontent));        }        public bool IsReusable        {            get            {                if (System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker") != null)                {                    NameValueCollection cfg =                        (NameValueCollection)                        System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker");                    if (cfg["IsReusable"] != null)                        if (cfg["IsReusable"].ToLower() == "true")                            return true;                }                return false;            }        }        #endregion    }

上面代码中有一些作者的信息。


为了使用它,创建一个BundleTransform:
public class JavascriptObfuscator : IBundleTransform    {        public void Process(BundleContext context, BundleResponse response)        {            var p = new ECMAScriptPacker(ECMAScriptPacker.PackerEncoding.Normal, true, false);            response.Content = p.Pack(response.Content);        }    }

当然这个混淆器可以做为一个Handler使用,不多做说明。

总结


上面代码全部连接起来,就是我想要用的两个功能。这只是为自己的一点点私欲创建的一个小工具,希望对有些人会有帮助。另外提一下,网上有现成的强大的Bundle插件,如Bundle Transformer: YUI 1.8.0。有兴趣的可以去看看。


1 0
原创粉丝点击