| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 | 
							- var fs = require('fs');
 
- var path = require('path');
 
- var http = require('http');
 
- var https = require('https');
 
- var url = require('url');
 
- var rewriteUrls = require('../urls/rewrite');
 
- var split = require('../utils/split');
 
- var override = require('../utils/object.js').override;
 
- var MAP_MARKER = /\/\*# sourceMappingURL=(\S+) \*\//;
 
- var REMOTE_RESOURCE = /^(https?:)?\/\//;
 
- var NO_PROTOCOL_RESOURCE = /^\/\//;
 
- function ImportInliner (context) {
 
-   this.outerContext = context;
 
- }
 
- ImportInliner.prototype.process = function (data, context) {
 
-   var root = this.outerContext.options.root;
 
-   context = override(context, {
 
-     baseRelativeTo: this.outerContext.options.relativeTo || root,
 
-     debug: this.outerContext.options.debug,
 
-     done: [],
 
-     errors: this.outerContext.errors,
 
-     left: [],
 
-     inliner: this.outerContext.options.inliner,
 
-     rebase: this.outerContext.options.rebase,
 
-     relativeTo: this.outerContext.options.relativeTo || root,
 
-     root: root,
 
-     sourceReader: this.outerContext.sourceReader,
 
-     sourceTracker: this.outerContext.sourceTracker,
 
-     warnings: this.outerContext.warnings,
 
-     visited: []
 
-   });
 
-   return importFrom(data, context);
 
- };
 
- function importFrom(data, context) {
 
-   if (context.shallow) {
 
-     context.shallow = false;
 
-     context.done.push(data);
 
-     return processNext(context);
 
-   }
 
-   var nextStart = 0;
 
-   var nextEnd = 0;
 
-   var cursor = 0;
 
-   var isComment = commentScanner(data);
 
-   for (; nextEnd < data.length;) {
 
-     nextStart = nextImportAt(data, cursor);
 
-     if (nextStart == -1)
 
-       break;
 
-     if (isComment(nextStart)) {
 
-       cursor = nextStart + 1;
 
-       continue;
 
-     }
 
-     nextEnd = data.indexOf(';', nextStart);
 
-     if (nextEnd == -1) {
 
-       cursor = data.length;
 
-       data = '';
 
-       break;
 
-     }
 
-     var noImportPart = data.substring(0, nextStart);
 
-     context.done.push(noImportPart);
 
-     context.left.unshift([data.substring(nextEnd + 1), override(context, { shallow: false })]);
 
-     context.afterContent = hasContent(noImportPart);
 
-     return inline(data, nextStart, nextEnd, context);
 
-   }
 
-   // no @import matched in current data
 
-   context.done.push(data);
 
-   return processNext(context);
 
- }
 
- function rebaseMap(data, source) {
 
-   return data.replace(MAP_MARKER, function (match, sourceMapUrl) {
 
-     return REMOTE_RESOURCE.test(sourceMapUrl) ?
 
-       match :
 
-       match.replace(sourceMapUrl, url.resolve(source, sourceMapUrl));
 
-   });
 
- }
 
- function nextImportAt(data, cursor) {
 
-   var nextLowerCase = data.indexOf('@import', cursor);
 
-   var nextUpperCase = data.indexOf('@IMPORT', cursor);
 
-   if (nextLowerCase > -1 && nextUpperCase == -1)
 
-     return nextLowerCase;
 
-   else if (nextLowerCase == -1 && nextUpperCase > -1)
 
-     return nextUpperCase;
 
-   else
 
-     return Math.min(nextLowerCase, nextUpperCase);
 
- }
 
- function processNext(context) {
 
-   return context.left.length > 0 ?
 
-     importFrom.apply(null, context.left.shift()) :
 
-     context.whenDone(context.done.join(''));
 
- }
 
- function commentScanner(data) {
 
-   var commentRegex = /(\/\*(?!\*\/)[\s\S]*?\*\/)/;
 
-   var lastStartIndex = 0;
 
-   var lastEndIndex = 0;
 
-   var noComments = false;
 
-   // test whether an index is located within a comment
 
-   return function scanner(idx) {
 
-     var comment;
 
-     var localStartIndex = 0;
 
-     var localEndIndex = 0;
 
-     var globalStartIndex = 0;
 
-     var globalEndIndex = 0;
 
-     // return if we know there are no more comments
 
-     if (noComments)
 
-       return false;
 
-     do {
 
-       // idx can be still within last matched comment (many @import statements inside one comment)
 
-       if (idx > lastStartIndex && idx < lastEndIndex)
 
-         return true;
 
-       comment = data.match(commentRegex);
 
-       if (!comment) {
 
-         noComments = true;
 
-         return false;
 
-       }
 
-       // get the indexes relative to the current data chunk
 
-       lastStartIndex = localStartIndex = comment.index;
 
-       localEndIndex = localStartIndex + comment[0].length;
 
-       // calculate the indexes relative to the full original data
 
-       globalEndIndex = localEndIndex + lastEndIndex;
 
-       globalStartIndex = globalEndIndex - comment[0].length;
 
-       // chop off data up to and including current comment block
 
-       data = data.substring(localEndIndex);
 
-       lastEndIndex = globalEndIndex;
 
-     } while (globalEndIndex < idx);
 
-     return globalEndIndex > idx && idx > globalStartIndex;
 
-   };
 
- }
 
- function hasContent(data) {
 
-   var isComment = commentScanner(data);
 
-   var firstContentIdx = -1;
 
-   while (true) {
 
-     firstContentIdx = data.indexOf('{', firstContentIdx + 1);
 
-     if (firstContentIdx == -1 || !isComment(firstContentIdx))
 
-       break;
 
-   }
 
-   return firstContentIdx > -1;
 
- }
 
- function inline(data, nextStart, nextEnd, context) {
 
-   context.shallow = data.indexOf('@shallow') > 0;
 
-   var importDeclaration = data
 
-     .substring(nextImportAt(data, nextStart) + '@import'.length + 1, nextEnd)
 
-     .replace(/@shallow\)$/, ')')
 
-     .trim();
 
-   var viaUrl = importDeclaration.indexOf('url(') === 0;
 
-   var urlStartsAt = viaUrl ? 4 : 0;
 
-   var isQuoted = /^['"]/.exec(importDeclaration.substring(urlStartsAt, urlStartsAt + 2));
 
-   var urlEndsAt = isQuoted ?
 
-     importDeclaration.indexOf(isQuoted[0], urlStartsAt + 1) :
 
-     split(importDeclaration, ' ')[0].length - (viaUrl ? 1 : 0);
 
-   var importedFile = importDeclaration
 
-     .substring(urlStartsAt, urlEndsAt)
 
-     .replace(/['"]/g, '')
 
-     .replace(/\)$/, '')
 
-     .trim();
 
-   var mediaQuery = importDeclaration
 
-     .substring(urlEndsAt + 1)
 
-     .replace(/^\)/, '')
 
-     .trim();
 
-   var isRemote = context.isRemote || REMOTE_RESOURCE.test(importedFile);
 
-   if (isRemote && (context.localOnly || !allowedResource(importedFile, true, context.imports))) {
 
-     if (context.afterContent || hasContent(context.done.join('')))
 
-       context.warnings.push('Ignoring remote @import of "' + importedFile + '" as no callback given.');
 
-     else
 
-       restoreImport(importedFile, mediaQuery, context);
 
-     return processNext(context);
 
-   }
 
-   if (!isRemote && !allowedResource(importedFile, false, context.imports)) {
 
-     if (context.afterImport)
 
-       context.warnings.push('Ignoring local @import of "' + importedFile + '" as after other inlined content.');
 
-     else
 
-       restoreImport(importedFile, mediaQuery, context);
 
-     return processNext(context);
 
-   }
 
-   if (!isRemote && context.afterContent) {
 
-     context.warnings.push('Ignoring local @import of "' + importedFile + '" as after other CSS content.');
 
-     return processNext(context);
 
-   }
 
-   var method = isRemote ? inlineRemoteResource : inlineLocalResource;
 
-   return method(importedFile, mediaQuery, context);
 
- }
 
- function allowedResource(importedFile, isRemote, rules) {
 
-   if (rules.length === 0)
 
-     return false;
 
-   if (isRemote && NO_PROTOCOL_RESOURCE.test(importedFile))
 
-     importedFile = 'http:' + importedFile;
 
-   var match = isRemote ?
 
-     url.parse(importedFile).host :
 
-     importedFile;
 
-   var allowed = true;
 
-   for (var i = 0; i < rules.length; i++) {
 
-     var rule = rules[i];
 
-     if (rule == 'all')
 
-       allowed = true;
 
-     else if (isRemote && rule == 'local')
 
-       allowed = false;
 
-     else if (isRemote && rule == 'remote')
 
-       allowed = true;
 
-     else if (!isRemote && rule == 'remote')
 
-       allowed = false;
 
-     else if (!isRemote && rule == 'local')
 
-       allowed = true;
 
-     else if (rule[0] == '!' && rule.substring(1) === match)
 
-       allowed = false;
 
-   }
 
-   return allowed;
 
- }
 
- function inlineRemoteResource(importedFile, mediaQuery, context) {
 
-   var importedUrl = REMOTE_RESOURCE.test(importedFile) ?
 
-     importedFile :
 
-     url.resolve(context.relativeTo, importedFile);
 
-   var originalUrl = importedUrl;
 
-   if (NO_PROTOCOL_RESOURCE.test(importedUrl))
 
-     importedUrl = 'http:' + importedUrl;
 
-   if (context.visited.indexOf(importedUrl) > -1)
 
-     return processNext(context);
 
-   if (context.debug)
 
-     console.error('Inlining remote stylesheet: ' + importedUrl);
 
-   context.visited.push(importedUrl);
 
-   var proxyProtocol = context.inliner.request.protocol || context.inliner.request.hostname;
 
-   var get =
 
-     ((proxyProtocol && proxyProtocol.indexOf('https://') !== 0 ) ||
 
-      importedUrl.indexOf('http://') === 0) ?
 
-     http.get :
 
-     https.get;
 
-   var errorHandled = false;
 
-   function handleError(message) {
 
-     if (errorHandled)
 
-       return;
 
-     errorHandled = true;
 
-     context.errors.push('Broken @import declaration of "' + importedUrl + '" - ' + message);
 
-     restoreImport(importedUrl, mediaQuery, context);
 
-     process.nextTick(function () {
 
-       processNext(context);
 
-     });
 
-   }
 
-   var requestOptions = override(url.parse(importedUrl), context.inliner.request);
 
-   if (context.inliner.request.hostname !== undefined) {
 
-     //overwrite as we always expect a http proxy currently
 
-     requestOptions.protocol = context.inliner.request.protocol || 'http:';
 
-     requestOptions.path = requestOptions.href;
 
-   }
 
-   get(requestOptions, function (res) {
 
-     if (res.statusCode < 200 || res.statusCode > 399) {
 
-       return handleError('error ' + res.statusCode);
 
-     } else if (res.statusCode > 299) {
 
-       var movedUrl = url.resolve(importedUrl, res.headers.location);
 
-       return inlineRemoteResource(movedUrl, mediaQuery, context);
 
-     }
 
-     var chunks = [];
 
-     var parsedUrl = url.parse(importedUrl);
 
-     res.on('data', function (chunk) {
 
-       chunks.push(chunk.toString());
 
-     });
 
-     res.on('end', function () {
 
-       var importedData = chunks.join('');
 
-       if (context.rebase)
 
-         importedData = rewriteUrls(importedData, { toBase: originalUrl }, context);
 
-       context.sourceReader.trackSource(importedUrl, importedData);
 
-       importedData = context.sourceTracker.store(importedUrl, importedData);
 
-       importedData = rebaseMap(importedData, importedUrl);
 
-       if (mediaQuery.length > 0)
 
-         importedData = '@media ' + mediaQuery + '{' + importedData + '}';
 
-       context.afterImport = true;
 
-       var newContext = override(context, {
 
-         isRemote: true,
 
-         relativeTo: parsedUrl.protocol + '//' + parsedUrl.host + parsedUrl.pathname
 
-       });
 
-       process.nextTick(function () {
 
-         importFrom(importedData, newContext);
 
-       });
 
-     });
 
-   })
 
-   .on('error', function (res) {
 
-     handleError(res.message);
 
-   })
 
-   .on('timeout', function () {
 
-     handleError('timeout');
 
-   })
 
-   .setTimeout(context.inliner.timeout);
 
- }
 
- function inlineLocalResource(importedFile, mediaQuery, context) {
 
-   var relativeTo = importedFile[0] == '/' ?
 
-     context.root :
 
-     context.relativeTo;
 
-   var fullPath = path.resolve(path.join(relativeTo, importedFile));
 
-   if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
 
-     context.errors.push('Broken @import declaration of "' + importedFile + '"');
 
-     return processNext(context);
 
-   }
 
-   if (context.visited.indexOf(fullPath) > -1)
 
-     return processNext(context);
 
-   if (context.debug)
 
-     console.error('Inlining local stylesheet: ' + fullPath);
 
-   context.visited.push(fullPath);
 
-   var importRelativeTo = path.dirname(fullPath);
 
-   var importedData = fs.readFileSync(fullPath, 'utf8');
 
-   if (context.rebase) {
 
-     var rewriteOptions = {
 
-       relative: true,
 
-       fromBase: importRelativeTo,
 
-       toBase: context.baseRelativeTo
 
-     };
 
-     importedData = rewriteUrls(importedData, rewriteOptions, context);
 
-   }
 
-   var relativePath = path.relative(context.root, fullPath);
 
-   context.sourceReader.trackSource(relativePath, importedData);
 
-   importedData = context.sourceTracker.store(relativePath, importedData);
 
-   if (mediaQuery.length > 0)
 
-     importedData = '@media ' + mediaQuery + '{' + importedData + '}';
 
-   context.afterImport = true;
 
-   var newContext = override(context, {
 
-     relativeTo: importRelativeTo
 
-   });
 
-   return importFrom(importedData, newContext);
 
- }
 
- function restoreImport(importedUrl, mediaQuery, context) {
 
-   var restoredImport = '@import url(' + importedUrl + ')' + (mediaQuery.length > 0 ? ' ' + mediaQuery : '') + ';';
 
-   context.done.push(restoredImport);
 
- }
 
- module.exports = ImportInliner;
 
 
  |