@warren-bank/hls-proxy 3.5.3 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.gitattributes CHANGED
@@ -1,3 +1,4 @@
1
+ # ------------------------------------------------
1
2
  # https://github.com/github/linguist
2
3
  # https://github.com/github/linguist#vendored-code
3
4
  # https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
@@ -6,3 +7,13 @@
6
7
  .related/* linguist-vendored=true
7
8
 
8
9
  * linguist-language=JavaScript
10
+
11
+ # ------------------------------------------------
12
+
13
+ * text=auto
14
+
15
+ *.cmd text eol=crlf
16
+ *.bat text eol=crlf
17
+ *.sh text eol=lf
18
+
19
+ # ------------------------------------------------
package/README.md CHANGED
@@ -57,6 +57,19 @@
57
57
  const hls_proxy_url = `${proxy_url}/${ btoa(`${video_url}|${referer_url}`) }${file_extension}`
58
58
  }
59
59
  ```
60
+ * [example Javascript]: construction of URL to _HLS Proxy_ for video stream w/ "Referer" and additional request headers
61
+ ```javascript
62
+ {
63
+ const proxy_url = 'http://127.0.0.1:8080'
64
+ const video_url = 'https://example.com/video/master.m3u8'
65
+ const referer_url = 'https://example.com/videos.html'
66
+ const file_extension = '.m3u8'
67
+ const headers = {"Cookie": "foo=bar"}
68
+
69
+ const querystring = `?headers=${ encodeURIComponent( btoa( JSON.stringify(headers) ) ) }`
70
+ const hls_proxy_url = `${proxy_url}/${ btoa(`${video_url}|${referer_url}`) }${file_extension}${querystring}`
71
+ }
72
+ ```
60
73
  * [example Bash]: construction of URL to _HLS Proxy_ for video stream
61
74
  ```bash
62
75
  proxy_url='http://127.0.0.1:8080'
@@ -74,6 +87,17 @@
74
87
 
75
88
  hls_proxy_url="${proxy_url}/"$(echo -n "${video_url}|${referer_url}" | base64 --wrap=0)"$file_extension"
76
89
  ```
90
+ * [example Bash]: construction of URL to _HLS Proxy_ for video stream w/ "Referer" and additional request headers
91
+ ```bash
92
+ proxy_url='http://127.0.0.1:8080'
93
+ video_url='https://example.com/video/master.m3u8'
94
+ referer_url='https://example.com/videos.html'
95
+ file_extension='.m3u8'
96
+ headers_json='{"Cookie": "foo=bar"}'
97
+
98
+ querystring='?headers='$(echo -n "$headers_json" | base64 --wrap=0)
99
+ hls_proxy_url="${proxy_url}/"$(echo -n "${video_url}|${referer_url}" | base64 --wrap=0)"${file_extension}${querystring}"
100
+ ```
77
101
 
78
102
  ##### notes:
79
103
 
@@ -1,3 +1,5 @@
1
+ const {URL} = require('./url')
2
+
1
3
  const get_full_req_url = function(req) {
2
4
  return req.originalUrl || req.url
3
5
  }
@@ -6,6 +8,10 @@ const has_req_param = function(req, key) {
6
8
  return (req.params && (typeof req.params === 'object') && req.params[key])
7
9
  }
8
10
 
11
+ const has_req_query = function(req, key) {
12
+ return (req.query && (typeof req.query === 'object') && req.query[key])
13
+ }
14
+
9
15
  const get_proxy_req_url = function(req) {
10
16
  const key = "0"
11
17
  return has_req_param(req, key)
@@ -13,6 +19,18 @@ const get_proxy_req_url = function(req) {
13
19
  : req.url
14
20
  }
15
21
 
22
+ const get_proxy_req_query = function(req, key) {
23
+ if (has_req_param(req, key))
24
+ return req.params[key]
25
+
26
+ if (has_req_query(req, key))
27
+ return req.query[key]
28
+
29
+ const url = new URL(req.url)
30
+ const qs = url.searchParams
31
+ return qs.get(key)
32
+ }
33
+
16
34
  const get_base_req_url = function(req) {
17
35
  let base_url = ''
18
36
  const key = "0"
@@ -28,5 +46,6 @@ const get_base_req_url = function(req) {
28
46
  module.exports = {
29
47
  get_full_req_url,
30
48
  get_proxy_req_url,
49
+ get_proxy_req_query,
31
50
  get_base_req_url
32
51
  }
@@ -112,12 +112,13 @@ const parse_HHMMSS_to_seconds = function(str) {
112
112
  // prefetch_urls: [],
113
113
  // modified_m3u8: ''
114
114
  // }
115
- const parse_manifest = function(m3u8_content, m3u8_url, referer_url, hooks, cache_segments, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url, manifest_extension, segment_extension, qs_password) {
115
+ const parse_manifest = function(m3u8_content, m3u8_url, referer_url, querystring_req_headers, hooks, cache_segments, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url, manifest_extension, segment_extension, qs_password) {
116
116
  const m3u8_lines = m3u8_content.split(regexs.m3u8_line_separator)
117
117
  m3u8_content = null
118
118
 
119
119
  const meta_data = {}
120
120
  const embedded_urls = extract_embedded_urls(m3u8_lines, m3u8_url, referer_url, (cache_segments ? meta_data : null))
121
+ const qs_headers = !!querystring_req_headers ? utils.base64_encode(JSON.stringify(querystring_req_headers)) : null
121
122
  const prefetch_urls = []
122
123
 
123
124
  if (embedded_urls && Array.isArray(embedded_urls) && embedded_urls.length) {
@@ -125,7 +126,7 @@ const parse_manifest = function(m3u8_content, m3u8_url, referer_url, hooks, cach
125
126
  redirect_embedded_url(embedded_url, hooks, m3u8_url, debug)
126
127
  if (validate_embedded_url(embedded_url)) {
127
128
  finalize_embedded_url(embedded_url, vod_start_at_ms, debug)
128
- encode_embedded_url(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension, qs_password)
129
+ encode_embedded_url(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension, qs_headers, qs_password)
129
130
  get_prefetch_url(embedded_url, should_prefetch_url, prefetch_urls)
130
131
  modify_m3u8_line(embedded_url, m3u8_lines)
131
132
  }
@@ -332,7 +333,7 @@ const finalize_embedded_url = function(embedded_url, vod_start_at_ms, debug) {
332
333
  }
333
334
  }
334
335
 
335
- const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension, qs_password) {
336
+ const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension, qs_headers, qs_password) {
336
337
  if (embedded_url.unencoded_url) {
337
338
  let file_extension = embedded_url.url_type
338
339
  if (file_extension) {
@@ -344,9 +345,6 @@ const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, d
344
345
 
345
346
  embedded_url.encoded_url = `${redirected_base_url}/${ utils.base64_encode(embedded_url.unencoded_url) }.${file_extension || 'other'}`
346
347
 
347
- if (qs_password)
348
- embedded_url.encoded_url += `?password=${qs_password}`
349
-
350
348
  debug(3, 'redirecting (proxied):', embedded_url.encoded_url)
351
349
 
352
350
  if (hooks && (hooks instanceof Object) && hooks.redirect_final && (typeof hooks.redirect_final === 'function')) {
@@ -354,6 +352,18 @@ const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, d
354
352
 
355
353
  debug(3, 'redirecting (proxied, post-hook):', embedded_url.encoded_url)
356
354
  }
355
+
356
+ let qs_pairs = []
357
+ if (qs_headers)
358
+ qs_pairs.push(['headers', qs_headers])
359
+ if (qs_password)
360
+ qs_pairs.push(['password', qs_password])
361
+ if (qs_pairs.length) {
362
+ qs_pairs = qs_pairs.map(pair => `${pair[0]}=${encodeURIComponent(pair[1])}`)
363
+
364
+ embedded_url.encoded_url += '?' + qs_pairs.join('&')
365
+ debug(3, 'redirecting (proxied, with querystring):', embedded_url.encoded_url)
366
+ }
357
367
  }
358
368
  else {
359
369
  embedded_url.encoded_url = ''
@@ -382,7 +392,7 @@ const modify_m3u8_line = function(embedded_url, m3u8_lines) {
382
392
  }
383
393
  }
384
394
 
385
- const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_url, referer_url, inbound_req_headers, redirected_base_url, qs_password) {
395
+ const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_url, referer_url, querystring_req_headers, inbound_req_headers, redirected_base_url, qs_password) {
386
396
  const {hooks, cache_segments, max_segments, debug_level, manifest_extension, segment_extension} = params
387
397
 
388
398
  const {has_cache, get_time_since_last_access, is_expired, prefetch_segment} = segment_cache
@@ -424,13 +434,13 @@ const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_u
424
434
  const matching_url = urls[0]
425
435
  urls[0] = undefined
426
436
 
427
- promise = prefetch_segment(m3u8_url, matching_url, referer_url, inbound_req_headers, dont_touch_access)
437
+ promise = prefetch_segment(m3u8_url, matching_url, referer_url, querystring_req_headers, inbound_req_headers, dont_touch_access)
428
438
  }
429
439
 
430
440
  promise.then(() => {
431
441
  urls.forEach((matching_url, index) => {
432
442
  if (matching_url) {
433
- prefetch_segment(m3u8_url, matching_url, referer_url, inbound_req_headers, dont_touch_access)
443
+ prefetch_segment(m3u8_url, matching_url, referer_url, querystring_req_headers, inbound_req_headers, dont_touch_access)
434
444
 
435
445
  urls[index] = undefined
436
446
  }
@@ -440,7 +450,7 @@ const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_u
440
450
  : null
441
451
 
442
452
  {
443
- const parsed_manifest = parse_manifest(m3u8_content, m3u8_url, referer_url, hooks, cache_segments, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url, manifest_extension, segment_extension, qs_password)
453
+ const parsed_manifest = parse_manifest(m3u8_content, m3u8_url, referer_url, querystring_req_headers, hooks, cache_segments, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url, manifest_extension, segment_extension, qs_password)
444
454
  is_vod = !!parsed_manifest.meta_data.is_vod // default: false => hls live stream
445
455
  seg_duration_ms = parsed_manifest.meta_data.seg_duration_ms || 10000 // default: 10 seconds in ms
446
456
  prefetch_urls = parsed_manifest.prefetch_urls
@@ -47,7 +47,7 @@ const get_middleware = function(params) {
47
47
 
48
48
  utils.add_CORS_headers(res)
49
49
 
50
- const {redirected_base_url, url_type, url, referer_url} = parse_req_url(req)
50
+ const {redirected_base_url, url_type, url, referer_url, querystring_req_headers} = parse_req_url(req)
51
51
 
52
52
  if (!url) {
53
53
  res.writeHead(400)
@@ -79,7 +79,7 @@ const get_middleware = function(params) {
79
79
  }
80
80
  }
81
81
 
82
- const options = get_request_options(url, is_m3u8, referer_url, req.headers)
82
+ const options = get_request_options(url, is_m3u8, referer_url, querystring_req_headers, req.headers)
83
83
  debug(1, 'proxying:', url)
84
84
  debug(3, 'm3u8:', (is_m3u8 ? 'true' : 'false'))
85
85
 
@@ -107,7 +107,7 @@ const get_middleware = function(params) {
107
107
  : url
108
108
 
109
109
  res.writeHead(200, { "content-type": "application/x-mpegURL" })
110
- res.end( modify_m3u8_content(response.toString().trim(), m3u8_url, referer_url, req.headers, redirected_base_url, qs_password) )
110
+ res.end( modify_m3u8_content(response.toString().trim(), m3u8_url, referer_url, querystring_req_headers, req.headers, redirected_base_url, qs_password) )
111
111
  }
112
112
  })
113
113
  .catch((e) => {
@@ -161,7 +161,7 @@ module.exports = function(params) {
161
161
  }
162
162
  }
163
163
 
164
- const prefetch_segment = function(m3u8_url, url, referer_url, inbound_req_headers, dont_touch_access) {
164
+ const prefetch_segment = function(m3u8_url, url, referer_url, querystring_req_headers, inbound_req_headers, dont_touch_access) {
165
165
  let promise = Promise.resolve()
166
166
 
167
167
  if (cache[m3u8_url] === undefined) {
@@ -184,7 +184,7 @@ module.exports = function(params) {
184
184
  index = ts.length
185
185
  ts[index] = {key: get_privatekey_from_url(url), has: false, cb: [], type: null, state: {}}
186
186
 
187
- let options = get_request_options(url, /* is_m3u8= */ false, referer_url, inbound_req_headers)
187
+ let options = get_request_options(url, /* is_m3u8= */ false, referer_url, querystring_req_headers, inbound_req_headers)
188
188
  promise = request(options, '', {binary: true, stream: false, cookieJar: cookies.getCookieJar()})
189
189
  .then(({redirects, response}) => {
190
190
  debug(1, `prefetch (complete, ${response.length} bytes):`, debug_url)
@@ -11,7 +11,7 @@ const initialize_timers = function(params) {
11
11
  const get_request_options = utils.get_request_options.bind(null, params)
12
12
 
13
13
  const request_wrapper = function(url, POST_data, user_config) {
14
- const options = get_request_options(url, /* is_m3u8= */ false, /* referer_url= */ null, /* inbound_req_headers= */ null)
14
+ const options = get_request_options(url, /* is_m3u8= */ false, /* referer_url= */ null, /* querystring_req_headers= */ null, /* inbound_req_headers= */ null)
15
15
  const config = Object.assign(
16
16
  {},
17
17
  (user_config || {}),
@@ -19,7 +19,7 @@ const base64_decode = function(str) {
19
19
  const parse_req_url = function(params, req) {
20
20
  const {is_secure, host, manifest_extension, segment_extension, hooks} = params
21
21
 
22
- const result = {redirected_base_url: '', url_type: '', url: '', referer_url: ''}
22
+ const result = {redirected_base_url: '', url_type: '', url: '', referer_url: '', querystring_req_headers: null}
23
23
 
24
24
  const matches = regexs.req_url.exec( expressjs.get_proxy_req_url(req) )
25
25
 
@@ -56,6 +56,18 @@ const parse_req_url = function(params, req) {
56
56
 
57
57
  result.url = url
58
58
  }
59
+
60
+ let qs_headers = expressjs.get_proxy_req_query(req, 'headers')
61
+ if (qs_headers) {
62
+ try {
63
+ qs_headers = base64_decode( decodeURIComponent( qs_headers ) ).trim()
64
+ qs_headers = JSON.parse(qs_headers)
65
+
66
+ if (qs_headers && (qs_headers instanceof Object))
67
+ result.querystring_req_headers = qs_headers
68
+ }
69
+ catch(e) {}
70
+ }
59
71
  }
60
72
 
61
73
  return result
@@ -120,21 +132,31 @@ const debug = function() {
120
132
  }
121
133
  }
122
134
 
123
- const normalize_req_headers = function(req_headers) {
135
+ const normalize_req_headers = function(req_headers, blacklist) {
124
136
  const normalized = {}
125
137
 
138
+ if (blacklist && !Array.isArray(blacklist))
139
+ blacklist = null
140
+ if (blacklist)
141
+ blacklist = blacklist.filter(val => val && (typeof val === 'string')).map(val => val.toLowerCase())
142
+ if (blacklist && !blacklist.length)
143
+ blacklist = null
144
+
126
145
  for (let name in req_headers) {
127
- normalized[ name.toLowerCase() ] = req_headers[name]
146
+ const lc_name = name.toLowerCase()
147
+
148
+ if (!blacklist || (blacklist.indexOf(lc_name) === -1))
149
+ normalized[lc_name] = req_headers[name]
128
150
  }
129
151
 
130
152
  return normalized
131
153
  }
132
154
 
133
- const get_request_options = function(params, url, is_m3u8, referer_url, inbound_req_headers) {
155
+ const get_request_options = function(params, url, is_m3u8, referer_url, querystring_req_headers, inbound_req_headers) {
134
156
  const {copy_req_headers, req_headers, req_options, hooks, http_proxy} = params
135
157
 
136
158
  const copied_req_headers = (copy_req_headers && inbound_req_headers && (inbound_req_headers instanceof Object))
137
- ? normalize_req_headers(inbound_req_headers)
159
+ ? normalize_req_headers(inbound_req_headers, ['host'])
138
160
  : null
139
161
 
140
162
  const additional_req_options = (hooks && (hooks instanceof Object) && hooks.add_request_options && (typeof hooks.add_request_options === 'function'))
@@ -145,7 +167,7 @@ const get_request_options = function(params, url, is_m3u8, referer_url, inbound_
145
167
  ? hooks.add_request_headers(url, is_m3u8)
146
168
  : null
147
169
 
148
- if (!req_options && !http_proxy && !additional_req_options && !copied_req_headers && !req_headers && !additional_req_headers && !referer_url) return url
170
+ if (!req_options && !http_proxy && !additional_req_options && !copied_req_headers && !req_headers && !additional_req_headers && !referer_url && !querystring_req_headers) return url
149
171
 
150
172
  const request_options = Object.assign(
151
173
  {},
@@ -161,7 +183,8 @@ const get_request_options = function(params, url, is_m3u8, referer_url, inbound_
161
183
  ((additional_req_options && additional_req_options.headers) ? additional_req_options.headers : {}),
162
184
  (req_headers || {}),
163
185
  (additional_req_headers || {}),
164
- (referer_url ? {"referer": referer_url, "origin": referer_url.replace(regexs.origin, '$1')} : {})
186
+ (referer_url ? {"referer": referer_url, "origin": referer_url.replace(regexs.origin, '$1')} : {}),
187
+ (querystring_req_headers || {})
165
188
  )
166
189
 
167
190
  // normalize
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@warren-bank/hls-proxy",
3
3
  "description": "Node.js server to proxy HLS video streams",
4
- "version": "3.5.3",
4
+ "version": "3.6.0",
5
5
  "scripts": {
6
6
  "start": "node hls-proxy/bin/hlsd.js",
7
7
  "sudo": "sudo node hls-proxy/bin/hlsd.js"