Coverage

91%
93
85
8

expand.js

91%
93
85
8
LineHitsSource
1/*!
2 * urlexpand - index.js
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * MIT Licensed
5 */
6
71"use strict";
8
9/**
10 * Module dependencies.
11 */
12
131var http = require('http');
141var https = require('https');
151var urlutil = require('url');
161var charset = require('charset');
171var iconv = require('iconv-lite');
18
19
201function handleCallback(err, url, callback) {
2111 if (callback.__called) {
220 return;
23 }
2411 callback.__called = true;
2511 callback(err, {
26 url: url,
27 title: callback.__title,
28 count: callback.__redirectCounter,
29 tracks: callback.__tracks,
30 });
31}
32
331var TITLE_RE = /<title>([^<]+)</i;
34
351function getTitle(data, cs) {
366 cs = iconv.encodings[cs] ? cs : 'utf8';
376 var text = iconv.decode(data, cs);
386 var m = TITLE_RE.exec(text);
396 return m ? m[1].trim() : null;
40}
41
42/**
43 * Expand a shorten url, return the original url and the redirect histories.
44 *
45 * @param {String} url, the url you want to expand.
46 * @param {Object} [options]
47 * - {Number} [redirects], max redirect times, default is `5`.
48 * - {Boolean} [title], get title or not, default is `true`.
49 * - {Number} [timeout], request timeout, default is `10000` ms.
50 * @param {Function(err, data)} callback
51 * - {Object} data {
52 * {String} url: the last status 200 url.
53 * {String} title: the last status 200 html page title, maybe empty.
54 * {Number} count: need redirect times.
55 * {Array} tracks: the handle tracks. `[{ url: $url, headers: $headers, statusCode: 301 }, ... ]`
56 * }
57 */
581function expand(url, options, callback) {
5918 if (typeof options === 'function') {
608 callback = options;
618 options = null;
62 }
6318 options = options || {};
6418 options.redirects = options.redirects || 5;
6518 if (options.title === undefined) {
6610 options.title = true;
67 }
6818 options.timeout = options.timeout || 10000;
6918 var info = urlutil.parse(url || '');
7018 if (!info.hostname) {
710 return callback();
72 }
7318 var reqOptions = {
74 hostname: info.hostname,
75 path: info.path,
76 method: 'GET'
77 };
7818 if (info.port) {
795 reqOptions.port = info.port;
80 }
8118 if (callback.__redirectCounter === undefined) {
8211 callback.__redirectCounter = 0;
8311 callback.__tracks = [];
84 }
8518 var request = http.request;
8618 if (info.protocol === 'https:') {
872 request = https.request;
88 }
8918 var req = request(reqOptions);
9018 var timer = null;
9118 req.on('response', function (res) {
9215 callback.__tracks.push({
93 url: url,
94 headers: res.headers,
95 statusCode: res.statusCode
96 });
9715 if (res.statusCode === 302 || res.statusCode === 301) {
988 clearTimeout(timer);
998 callback.__redirectCounter++;
1008 var location = urlutil.resolve(url, res.headers.location);
1018 if (callback.__redirectCounter > options.redirects) {
1021 return handleCallback(null, location, callback);
103 }
1047 return expand(location, options, callback);
105 }
106
1077 if (!options.title) {
1081 clearTimeout(timer);
1091 res.destroy();
1101 return handleCallback(null, url, callback);
111 }
112
113 // get the title
1146 var buffers = [];
1156 var size = 0;
1166 res.on('data', function (chunk) {
117224 buffers.push(chunk);
118224 size += chunk.length;
119 });
1206 res.on('end', function () {
1216 clearTimeout(timer);
1226 var data = Buffer.concat(buffers, size);
1236 var cs = charset(res.headers, data) || 'utf8';
1246 var title = getTitle(data, cs);
1256 callback.__title = title;
1266 handleCallback(null, url, callback);
127 });
128 });
12918 req.on('error', function (err) {
1303 callback.__tracks.push({
131 url: url,
132 error: req.isTimeout ? 'request timeout' : err.message
133 });
1343 handleCallback(err, url, callback);
135 });
13618 req.end();
13718 timer = setTimeout(function () {
1381 req.isTimeout = true;
1391 req.abort();
140 }, options.timeout);
141}
142
1431module.exports = expand;
144
145/**
146 * Let Buffer support concat. node < 0.8
147 *
148 * https://github.com/joyent/node/blob/master/lib/buffer.js#L504
149 */
150
1511if (!Buffer.concat) {
1521 Buffer.concat = function (list, length) {
1536 if (!Array.isArray(list)) {
1540 throw new Error('Usage: Buffer.concat(list, [length])');
155 }
156
1576 if (list.length === 0) {
1580 return new Buffer(0);
1596 } else if (list.length === 1) {
1601 return list[0];
161 }
162
1635 if (typeof length !== 'number') {
1640 length = 0;
1650 for (var i = 0; i < list.length; i++) {
1660 var buf = list[i];
1670 length += buf.length;
168 }
169 }
170
1715 var buffer = new Buffer(length);
1725 var pos = 0;
1735 for (var i = 0; i < list.length; i++) {
174223 var buf = list[i];
175223 buf.copy(buffer, pos);
176223 pos += buf.length;
177 }
1785 return buffer;
179 };
180}