text: 
  /**
   * body.js
   *
   * Body interface provides common methods for Request and Response
   */
  
  var convert = require('encoding').convert;
  var bodyStream = require('is-stream');
  var PassThrough = require('stream').PassThrough;
  var FetchError = require('./fetch-error');
  
  module.exports = Body;
  
  /**
   * Body class
   *
   * @param   Stream  body  Readable stream
   * @param   Object  opts  Response options
   * @return  Void
   */
  function Body(body, opts) {
  
  	opts = opts || {};
  
  	this.body = body;
  	this.bodyUsed = false;
  	this.size = opts.size || 0;
  	this.timeout = opts.timeout || 0;
  	this._raw = [];
  	this._abort = false;
  
  }
  
  /**
   * Decode response as json
   *
   * @return  Promise
   */
  Body.prototype.json = function() {
  
  	// for 204 No Content response, buffer will be empty, parsing it will throw error
  	if (this.status === 204) {
  		return Body.Promise.resolve({});
  	}
  
  	return this._decode().then(function(buffer) {
  		return JSON.parse(buffer.toString());
  	});
  
  };
  
  /**
   * Decode response as text
   *
   * @return  Promise
   */
  Body.prototype.text = function() {
  
  	return this._decode().then(function(buffer) {
  		return buffer.toString();
  	});
  
  };
  
  /**
   * Decode response as buffer (non-spec api)
   *
   * @return  Promise
   */
  Body.prototype.buffer = function() {
  
  	return this._decode();
  
  };
  
  /**
   * Decode buffers into utf-8 string
   *
   * @return  Promise
   */
  Body.prototype._decode = function() {
  
  	var self = this;
  
  	if (this.bodyUsed) {
  		return Body.Promise.reject(new Error('body used already for: ' + this.url));
  	}
  
  	this.bodyUsed = true;
  	this._bytes = 0;
  	this._abort = false;
  	this._raw = [];
  
  	return new Body.Promise(function(resolve, reject) {
  		var resTimeout;
  
  		// body is string
  		if (typeof self.body === 'string') {
  			self._bytes = self.body.length;
  			self._raw = [new Buffer(self.body)];
  			return resolve(self._convert());
  		}
  
  		// body is buffer
  		if (self.body instanceof Buffer) {
  			self._bytes = self.body.length;
  			self._raw = [self.body];
  			return resolve(self._convert());
  		}
  
  		// allow timeout on slow response body
  		if (self.timeout) {
  			resTimeout = setTimeout(function() {
  				self._abort = true;
  				reject(new FetchError('response timeout at ' + self.url + ' over limit: ' + self.timeout, 'body-timeout'));
  			}, self.timeout);
  		}
  
  		// handle stream error, such as incorrect content-encoding
  		self.body.on('error', function(err) {
  			reject(new FetchError('invalid response body at: ' + self.url + ' reason: ' + err.message, 'system', err));
  		});
  
  		// body is stream
  		self.body.on('data', function(chunk) {
  			if (self._abort || chunk === null) {
  				return;
  			}
  
  			if (self.size && self._bytes + chunk.length > self.size) {
  				self._abort = true;
  				reject(new FetchError('content size at ' + self.url + ' over limit: ' + self.size, 'max-size'));
  				return;
  			}
  
  			self._bytes += chunk.length;
  			self._raw.push(chunk);
  		});
  
  		self.body.on('end', function() {
  			if (self._abort) {
  				return;
  			}
  
  			clearTimeout(resTimeout);
  			resolve(self._convert());
  		});
  	});
  
  };
  
  /**
   * Detect buffer encoding and convert to target encoding
   * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
   *
   * @param   String  encoding  Target encoding
   * @return  String
   */
  Body.prototype._convert = function(encoding) {
  
  	encoding = encoding || 'utf-8';
  
  	var ct = this.headers.get('content-type');
  	var charset = 'utf-8';
  	var res, str;
  
  	// header
  	if (ct) {
  		// skip encoding detection altogether if not html/xml/plain text
  		if (!/text\/html|text\/plain|\+xml|\/xml/i.test(ct)) {
  			return Buffer.concat(this._raw);
  		}
  
  		res = /charset=([^;]*)/i.exec(ct);
  	}
  
  	// no charset in content type, peek at response body for at most 1024 bytes
  	if (!res && this._raw.length > 0) {
  		for (var i = 0; i < this._raw.length; i++) {
  			str += this._raw[i].toString()
  			if (str.length > 1024) {
  				break;
  			}
  		}
  		str = str.substr(0, 1024);
  	}
  
  	// html5
  	if (!res && str) {
  		res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str);
  	}
  
  	// html4
  	if (!res && str) {
  		res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str);
  
  		if (res) {
  			res = /charset=(.*)/i.exec(res.pop());
  		}
  	}
  
  	// xml
  	if (!res && str) {
  		res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str);
  	}
  
  	// found charset
  	if (res) {
  		charset = res.pop();
  
  		// prevent decode issues when sites use incorrect encoding
  		// ref: https://hsivonen.fi/encoding-menu/
  		if (charset === 'gb2312' || charset === 'gbk') {
  			charset = 'gb18030';
  		}
  	}
  
  	// turn raw buffers into a single utf-8 buffer
  	return convert(
  		Buffer.concat(this._raw)
  		, encoding
  		, charset
  	);
  
  };
  
  /**
   * Clone body given Res/Req instance
   *
   * @param   Mixed  instance  Response or Request instance
   * @return  Mixed
   */
  Body.prototype._clone = function(instance) {
  	var p1, p2;
  	var body = instance.body;
  
  	// don't allow cloning a used body
  	if (instance.bodyUsed) {
  		throw new Error('cannot clone body after it is used');
  	}
  
  	// check that body is a stream and not form-data object
  	// note: we can't clone the form-data object without having it as a dependency
  	if (bodyStream(body) && typeof body.getBoundary !== 'function') {
  		// tee instance body
  		p1 = new PassThrough();
  		p2 = new PassThrough();
  		body.pipe(p1);
  		body.pipe(p2);
  		// set instance body to teed body and return the other teed body
  		instance.body = p1;
  		body = p2;
  	}
  
  	return body;
  }
  
  // expose Promise
  Body.Promise = global.Promise;
  