@warren-bank/hls-proxy 3.5.1 → 3.5.2

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/README.md CHANGED
@@ -132,7 +132,8 @@ options:
132
132
  --cache-storage <adapter>
133
133
  --cache-storage-fs-dirpath <dirpath>
134
134
  -v <number>
135
- --acl-whitelist <ip_address_list>
135
+ --acl-ip <ip_address_list>
136
+ --acl-pass <password_list>
136
137
  --http-proxy <http[s]://[user:pass@]hostname:port>
137
138
  --tls-cert <filepath>
138
139
  --tls-key <filepath>
@@ -363,8 +364,10 @@ options:
363
364
  * show an enhanced technical trace (useful while debugging unexpected behavior)
364
365
  * `4`:
365
366
  * show the content of .m3u8 files (both before and after URLs are modified)
366
- * _--acl-whitelist_ restricts proxy server access to clients at IP addresses in whitelist
367
+ * _--acl-ip_ restricts proxy server access to clients at IP addresses in whitelist
367
368
  * ex: `"192.168.1.100,192.168.1.101,192.168.1.102"`
369
+ * _--acl-pass_ restricts proxy server access to requests that include a `password` querystring parameter having a value in whitelist
370
+ * ex: `"1111,2222,3333,4444,5555"`
368
371
  * --http-proxy enables all outbound HTTP and HTTPS requests from HLS-Proxy to be tunnelled through an additional external web proxy server
369
372
  * SOCKS proxies are not supported
370
373
  * ex: `http://myusername:mypassword@myproxy.example.com:1234`
@@ -0,0 +1,25 @@
1
+ const expressjs = require('./expressjs_utils')
2
+ const {URL} = require('./url')
3
+
4
+ const get_encoded_qs_password = function(req) {
5
+ const req_url = new URL( expressjs.get_full_req_url(req) )
6
+
7
+ return req_url.searchParams.get('password') || ''
8
+ }
9
+
10
+ const is_allowed = function(params, req) {
11
+ const {acl_pass} = params
12
+
13
+ if (acl_pass && Array.isArray(acl_pass) && acl_pass.length) {
14
+ const password = decodeURIComponent( get_encoded_qs_password(req) )
15
+
16
+ return (acl_pass.indexOf(password) >= 0)
17
+ }
18
+
19
+ return true
20
+ }
21
+
22
+ module.exports = {
23
+ get_encoded_qs_password,
24
+ is_allowed
25
+ }
@@ -44,7 +44,8 @@ const middleware = require('../proxy')({
44
44
  cache_storage: argv_vals["--cache-storage"],
45
45
  cache_storage_fs_dirpath: argv_vals["--cache-storage-fs-dirpath"],
46
46
  debug_level: argv_vals["-v"],
47
- acl_whitelist: argv_vals["--acl-whitelist"],
47
+ acl_ip: argv_vals["--acl-ip"],
48
+ acl_pass: argv_vals["--acl-pass"],
48
49
  http_proxy: argv_vals["--http-proxy"],
49
50
  manifest_extension: argv_vals["--manifest-extension"],
50
51
  segment_extension: argv_vals["--segment-extension"]
@@ -27,7 +27,8 @@ options:
27
27
  --cache-storage <adapter>
28
28
  --cache-storage-fs-dirpath <dirpath>
29
29
  -v <number>
30
- --acl-whitelist <ip_address_list>
30
+ --acl-ip <ip_address_list>
31
+ --acl-pass <password_list>
31
32
  --http-proxy <http[s]://[user:pass@]hostname:port>
32
33
  --tls-cert <filepath>
33
34
  --tls-key <filepath>
@@ -33,7 +33,8 @@ const argv_flags = {
33
33
  "--cache-storage-fs-dirpath": {file: "path-exists"},
34
34
 
35
35
  "-v": {num: "int"},
36
- "--acl-whitelist": {},
36
+ "--acl-ip": {},
37
+ "--acl-pass": {},
37
38
  "--http-proxy": {},
38
39
 
39
40
  "--tls-cert": {file: "path-exists"},
@@ -46,6 +47,7 @@ const argv_flags = {
46
47
 
47
48
  const argv_flag_aliases = {
48
49
  "--help": ["-h"],
50
+ "--acl-ip": ["--acl-whitelist"],
49
51
  "--http-proxy": ["--https-proxy", "--proxy"]
50
52
  }
51
53
 
@@ -169,6 +171,14 @@ if (typeof argv_vals["--cache-key"] !== 'number')
169
171
  if (typeof argv_vals["-v"] !== 'number')
170
172
  argv_vals["-v"] = 0
171
173
 
174
+ if (argv_vals["--acl-ip"]) {
175
+ argv_vals["--acl-ip"] = argv_vals["--acl-ip"].trim().toLowerCase().split(/\s*,\s*/g)
176
+ }
177
+
178
+ if (argv_vals["--acl-pass"]) {
179
+ argv_vals["--acl-pass"] = argv_vals["--acl-pass"].trim().split(/\s*,\s*/g)
180
+ }
181
+
172
182
  if (argv_vals["--http-proxy"]) {
173
183
  const proxy_options = {
174
184
  keepAlive: true,
@@ -112,7 +112,7 @@ 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) {
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) {
116
116
  const m3u8_lines = m3u8_content.split(regexs.m3u8_line_separator)
117
117
  m3u8_content = null
118
118
 
@@ -125,7 +125,7 @@ const parse_manifest = function(m3u8_content, m3u8_url, referer_url, hooks, cach
125
125
  redirect_embedded_url(embedded_url, hooks, m3u8_url, debug)
126
126
  if (validate_embedded_url(embedded_url)) {
127
127
  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)
128
+ encode_embedded_url(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension, qs_password)
129
129
  get_prefetch_url(embedded_url, should_prefetch_url, prefetch_urls)
130
130
  modify_m3u8_line(embedded_url, m3u8_lines)
131
131
  }
@@ -332,7 +332,7 @@ const finalize_embedded_url = function(embedded_url, vod_start_at_ms, debug) {
332
332
  }
333
333
  }
334
334
 
335
- const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension) {
335
+ const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension, qs_password) {
336
336
  if (embedded_url.unencoded_url) {
337
337
  let file_extension = embedded_url.url_type
338
338
  if (file_extension) {
@@ -344,6 +344,9 @@ const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, d
344
344
 
345
345
  embedded_url.encoded_url = `${redirected_base_url}/${ utils.base64_encode(embedded_url.unencoded_url) }.${file_extension || 'other'}`
346
346
 
347
+ if (qs_password)
348
+ embedded_url.encoded_url += `?password=${qs_password}`
349
+
347
350
  debug(3, 'redirecting (proxied):', embedded_url.encoded_url)
348
351
 
349
352
  if (hooks && (hooks instanceof Object) && hooks.redirect_final && (typeof hooks.redirect_final === 'function')) {
@@ -379,7 +382,7 @@ const modify_m3u8_line = function(embedded_url, m3u8_lines) {
379
382
  }
380
383
  }
381
384
 
382
- const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_url, referer_url, redirected_base_url) {
385
+ const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_url, referer_url, redirected_base_url, qs_password) {
383
386
  const {hooks, cache_segments, max_segments, debug_level, manifest_extension, segment_extension} = params
384
387
 
385
388
  const {has_cache, get_time_since_last_access, is_expired, prefetch_segment} = segment_cache
@@ -437,7 +440,7 @@ const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_u
437
440
  : null
438
441
 
439
442
  {
440
- 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)
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)
441
444
  is_vod = !!parsed_manifest.meta_data.is_vod // default: false => hls live stream
442
445
  seg_duration_ms = parsed_manifest.meta_data.seg_duration_ms || 10000 // default: 10 seconds in ms
443
446
  prefetch_urls = parsed_manifest.prefetch_urls
@@ -1,16 +1,18 @@
1
- const request = require('@warren-bank/node-request').request
2
- const cookies = require('./cookies')
3
- const parser = require('./manifest_parser')
4
- const timers = require('./timers')
5
- const utils = require('./utils')
1
+ const request = require('@warren-bank/node-request').request
2
+ const acl_pass = require('./acl_pass')
3
+ const cookies = require('./cookies')
4
+ const parser = require('./manifest_parser')
5
+ const timers = require('./timers')
6
+ const utils = require('./utils')
6
7
 
7
8
  const get_middleware = function(params) {
8
9
  const {cache_segments} = params
9
- let {acl_whitelist} = params
10
+ let {acl_ip} = params
10
11
 
11
12
  const segment_cache = require('./segment_cache')(params)
12
13
  const {get_segment, add_listener} = segment_cache
13
14
 
15
+ const is_acl_pass_allowed = acl_pass.is_allowed.bind(null, params)
14
16
  const debug = utils.debug.bind(null, params)
15
17
  const parse_req_url = utils.parse_req_url.bind(null, params)
16
18
  const get_request_options = utils.get_request_options.bind(null, params)
@@ -19,16 +21,14 @@ const get_middleware = function(params) {
19
21
  const middleware = {}
20
22
 
21
23
  // Access Control
22
- if (acl_whitelist) {
23
- acl_whitelist = acl_whitelist.trim().toLowerCase().split(/\s*,\s*/g)
24
-
24
+ if (acl_ip && Array.isArray(acl_ip) && acl_ip.length) {
25
25
  middleware.connection = (socket) => {
26
26
  if (socket && socket.remoteAddress) {
27
- let remoteIP = socket.remoteAddress.toLowerCase().replace(/^::?ffff:/, '')
27
+ const remote_ip = socket.remoteAddress.toLowerCase().replace(/^::?ffff:/, '')
28
28
 
29
- if (acl_whitelist.indexOf(remoteIP) === -1) {
29
+ if (acl_ip.indexOf(remote_ip) === -1) {
30
30
  socket.destroy()
31
- debug(2, socket.remoteFamily, 'connection blocked by ACL whitelist:', remoteIP)
31
+ debug(2, socket.remoteFamily, 'connection blocked by ACL IP whitelist:', remote_ip)
32
32
  }
33
33
  }
34
34
  }
@@ -36,6 +36,13 @@ const get_middleware = function(params) {
36
36
 
37
37
  // Create an HTTP tunneling proxy
38
38
  middleware.request = async (req, res) => {
39
+ if (!is_acl_pass_allowed(req)) {
40
+ res.writeHead(401)
41
+ res.end()
42
+ debug(2, 'request blocked by ACL password whitelist:', req.url)
43
+ return
44
+ }
45
+
39
46
  debug(3, 'proxying (raw):', req.url)
40
47
 
41
48
  utils.add_CORS_headers(res)
@@ -48,7 +55,8 @@ const get_middleware = function(params) {
48
55
  return
49
56
  }
50
57
 
51
- const is_m3u8 = (url_type === 'm3u8')
58
+ const qs_password = acl_pass.get_encoded_qs_password(req)
59
+ const is_m3u8 = (url_type === 'm3u8')
52
60
 
53
61
  const send_cache_segment = function(segment, type) {
54
62
  if (!type)
@@ -99,7 +107,7 @@ const get_middleware = function(params) {
99
107
  : url
100
108
 
101
109
  res.writeHead(200, { "content-type": "application/x-mpegURL" })
102
- res.end( modify_m3u8_content(response.toString().trim(), m3u8_url, referer_url, redirected_base_url) )
110
+ res.end( modify_m3u8_content(response.toString().trim(), m3u8_url, referer_url, redirected_base_url, qs_password) )
103
111
  }
104
112
  })
105
113
  .catch((e) => {
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.1",
4
+ "version": "3.5.2",
5
5
  "scripts": {
6
6
  "start": "node hls-proxy/bin/hlsd.js",
7
7
  "sudo": "sudo node hls-proxy/bin/hlsd.js"