index.js
  1  /*!
  2   * range-parser
  3   * Copyright(c) 2012-2014 TJ Holowaychuk
  4   * Copyright(c) 2015-2016 Douglas Christopher Wilson
  5   * MIT Licensed
  6   */
  7  
  8  'use strict'
  9  
 10  /**
 11   * Module exports.
 12   * @public
 13   */
 14  
 15  module.exports = rangeParser
 16  
 17  /**
 18   * Parse "Range" header `str` relative to the given file `size`.
 19   *
 20   * @param {Number} size
 21   * @param {String} str
 22   * @param {Object} [options]
 23   * @return {Array}
 24   * @public
 25   */
 26  
 27  function rangeParser (size, str, options) {
 28    if (typeof str !== 'string') {
 29      throw new TypeError('argument str must be a string')
 30    }
 31  
 32    var index = str.indexOf('=')
 33  
 34    if (index === -1) {
 35      return -2
 36    }
 37  
 38    // split the range string
 39    var arr = str.slice(index + 1).split(',')
 40    var ranges = []
 41  
 42    // add ranges type
 43    ranges.type = str.slice(0, index)
 44  
 45    // parse all ranges
 46    for (var i = 0; i < arr.length; i++) {
 47      var range = arr[i].split('-')
 48      var start = parseInt(range[0], 10)
 49      var end = parseInt(range[1], 10)
 50  
 51      // -nnn
 52      if (isNaN(start)) {
 53        start = size - end
 54        end = size - 1
 55      // nnn-
 56      } else if (isNaN(end)) {
 57        end = size - 1
 58      }
 59  
 60      // limit last-byte-pos to current length
 61      if (end > size - 1) {
 62        end = size - 1
 63      }
 64  
 65      // invalid or unsatisifiable
 66      if (isNaN(start) || isNaN(end) || start > end || start < 0) {
 67        continue
 68      }
 69  
 70      // add range
 71      ranges.push({
 72        start: start,
 73        end: end
 74      })
 75    }
 76  
 77    if (ranges.length < 1) {
 78      // unsatisifiable
 79      return -1
 80    }
 81  
 82    return options && options.combine
 83      ? combineRanges(ranges)
 84      : ranges
 85  }
 86  
 87  /**
 88   * Combine overlapping & adjacent ranges.
 89   * @private
 90   */
 91  
 92  function combineRanges (ranges) {
 93    var ordered = ranges.map(mapWithIndex).sort(sortByRangeStart)
 94  
 95    for (var j = 0, i = 1; i < ordered.length; i++) {
 96      var range = ordered[i]
 97      var current = ordered[j]
 98  
 99      if (range.start > current.end + 1) {
100        // next range
101        ordered[++j] = range
102      } else if (range.end > current.end) {
103        // extend range
104        current.end = range.end
105        current.index = Math.min(current.index, range.index)
106      }
107    }
108  
109    // trim ordered array
110    ordered.length = j + 1
111  
112    // generate combined range
113    var combined = ordered.sort(sortByRangeIndex).map(mapWithoutIndex)
114  
115    // copy ranges type
116    combined.type = ranges.type
117  
118    return combined
119  }
120  
121  /**
122   * Map function to add index value to ranges.
123   * @private
124   */
125  
126  function mapWithIndex (range, index) {
127    return {
128      start: range.start,
129      end: range.end,
130      index: index
131    }
132  }
133  
134  /**
135   * Map function to remove index value from ranges.
136   * @private
137   */
138  
139  function mapWithoutIndex (range) {
140    return {
141      start: range.start,
142      end: range.end
143    }
144  }
145  
146  /**
147   * Sort function to sort ranges by index.
148   * @private
149   */
150  
151  function sortByRangeIndex (a, b) {
152    return a.index - b.index
153  }
154  
155  /**
156   * Sort function to sort ranges by start position.
157   * @private
158   */
159  
160  function sortByRangeStart (a, b) {
161    return a.start - b.start
162  }