├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── example ├── tor-request.js └── tor.js ├── index.js ├── lib └── Agent.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8.1" 5 | - "6.11" 6 | 7 | script: make test 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: index.js lib/*.js node_modules 2 | ./node_modules/.bin/mocha \ 3 | --reporter dot \ 4 | --check-leaks \ 5 | --ui tdd 6 | 7 | node_modules: package.json 8 | npm install 9 | touch $@ 10 | 11 | .PHONY: test 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SOCKS5 HTTPS Client # 2 | 3 | [![Build Status](https://travis-ci.org/mattcg/socks5-https-client.png?branch=master)](https://travis-ci.org/mattcg/socks5-https-client) 4 | 5 | SOCKS v5 HTTPS client implementation in JavaScript for Node.js. 6 | 7 | ```js 8 | var shttps = require('socks5-https-client'); 9 | 10 | shttps.get({ 11 | hostname: 'encrypted.google.com', 12 | path: '/', 13 | rejectUnauthorized: true // This is the default. 14 | }, function(res) { 15 | res.setEncoding('utf8'); 16 | res.on('readable', function() { 17 | console.log(res.read()); // Log response to console. 18 | }); 19 | }); 20 | ``` 21 | 22 | Specify the `socksHost` and `socksPort` options if your SOCKS server isn't running on `localhost:1080`. Tor runs its SOCKS server on port `9050` by default, for example. 23 | 24 | Username and password authentication is supported with the `socksUsername` and `socksPassword` options. 25 | 26 | You may also pass a URL as the first argument to `get` or `request`, which will be parsed using `url.parse`. 27 | 28 | ## Using with Tor ## 29 | 30 | Works great for making HTTPS requests through [Tor](https://www.torproject.org/). 31 | 32 | Make sure a Tor server is running locally and run `node example/tor https://check.torproject.org/` to test. 33 | 34 | ## Using with Request ## 35 | 36 | To use with [Request](https://github.com/mikeal/request), just pass a reference to the `Agent` constructor.. 37 | 38 | ```js 39 | var Agent = require('socks5-https-client/lib/Agent'); 40 | 41 | request({ 42 | url: 'https://encrypted.google.com/', 43 | strictSSL: true, 44 | agentClass: Agent, 45 | agentOptions: { 46 | socksHost: 'my-tor-proxy-host', // Defaults to 'localhost'. 47 | socksPort: 9050, // Defaults to 1080. 48 | 49 | // Optional credentials 50 | socksUsername: 'proxyuser', 51 | socksPassword: 'p@ssw0rd', 52 | } 53 | }, function(err, res) { 54 | console.log(err || res.body); 55 | }); 56 | ``` 57 | 58 | ## HTTP ## 59 | 60 | This client only provides support for making HTTPS requests. See [socks5-http-client](https://github.com/mattcg/socks5-http-client) for an HTTP implementation. 61 | 62 | ## License ## 63 | 64 | Copyright © 2013 [Matthew Caruana Galizia](http://twitter.com/mcaruanagalizia), licensed under an [MIT license](http://mattcg.mit-license.org/). 65 | -------------------------------------------------------------------------------- /example/tor-request.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*jshint node:true*/ 4 | 5 | var request = require('request'); 6 | 7 | var Agent = require('../lib/Agent'); 8 | 9 | request({ 10 | url: process.argv[2], 11 | agentClass: Agent, 12 | agentOptions: { 13 | socksPort: 9050 // Defaults to 1080. 14 | } 15 | }, function(err, res) { 16 | console.log(res.body); 17 | }); 18 | -------------------------------------------------------------------------------- /example/tor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*jshint node:true*/ 4 | 5 | var url = require('url'); 6 | var shttps = require('../'); 7 | 8 | var options = url.parse(process.argv[2]); 9 | 10 | options.socksPort = 9050; // Tor default port. 11 | 12 | var req = shttps.get(options, function(res) { 13 | res.setEncoding('utf8'); 14 | 15 | res.on('readable', function() { 16 | var data = res.read(); 17 | 18 | // Check for the end of stream signal. 19 | if (null === data) { 20 | process.stdout.write('\n'); 21 | return; 22 | } 23 | 24 | process.stdout.write(data); 25 | }); 26 | }); 27 | 28 | req.on('error', function(e) { 29 | console.error('Problem with request: ' + e.message); 30 | }); 31 | 32 | // GET request, so end without sending any data. 33 | req.end(); 34 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @overview 3 | * @author Matthew Caruana Galizia 4 | * @license MIT 5 | * @copyright Copyright (c) 2013, Matthew Caruana Galizia 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /*jshint node:true*/ 11 | 12 | var https = require('https'); 13 | var url = require('url'); 14 | 15 | var Agent = require('./lib/Agent'); 16 | 17 | exports.request = function(options, cb) { 18 | var agent, version; 19 | 20 | if (typeof options === 'string') { 21 | options = url.parse(options); 22 | } 23 | 24 | options.protocol = 'https:'; 25 | 26 | // Node v0.12.0 requires the port to be specified. 27 | if (!options.port && options.host) { 28 | options.port = options.host.split(':')[1]; 29 | } 30 | 31 | if (!options.port) { 32 | options.port = 443; 33 | } 34 | 35 | agent = new Agent(options); 36 | options.agent = agent; 37 | 38 | return https.request(options, cb); 39 | }; 40 | 41 | exports.Agent = Agent; 42 | 43 | exports.get = function(options, cb) { 44 | var req = exports.request(options, cb); 45 | 46 | req.end(); 47 | 48 | return req; 49 | }; 50 | -------------------------------------------------------------------------------- /lib/Agent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @overview 3 | * @author Matthew Caruana Galizia 4 | * @license MIT 5 | * @copyright Copyright (c) 2013, Matthew Caruana Galizia 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /*jshint node:true*/ 11 | 12 | var tls = require('tls'); 13 | var https = require('https'); 14 | var inherits = require('util').inherits; 15 | 16 | var socksClient = require('socks5-client'); 17 | 18 | function createConnection(options) { 19 | var socksSocket, onProxied; 20 | 21 | socksSocket = socksClient.createConnection(options); 22 | 23 | onProxied = socksSocket.onProxied; 24 | socksSocket.onProxied = function() { 25 | options.socket = socksSocket.socket; 26 | 27 | if (options.hostname) { 28 | options.servername = options.hostname; 29 | } else if (options.host) { 30 | options.servername = options.host.split(':')[0]; 31 | } 32 | 33 | socksSocket.socket = tls.connect(options, function() { 34 | 35 | // Set the 'authorized flag for clients that check it. 36 | socksSocket.authorized = socksSocket.socket.authorized; 37 | onProxied.call(socksSocket); 38 | }); 39 | 40 | socksSocket.socket.on('error', function(err) { 41 | socksSocket.emit('error', err); 42 | }); 43 | }; 44 | 45 | return socksSocket; 46 | } 47 | 48 | function Agent(options) { 49 | https.Agent.call(this, options); 50 | 51 | this.socksHost = options.socksHost || 'localhost'; 52 | this.socksPort = options.socksPort || 1080; 53 | 54 | this.createConnection = createConnection; 55 | } 56 | 57 | inherits(Agent, https.Agent); 58 | 59 | module.exports = Agent; 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socks5-https-client", 3 | "description": "SOCKS v5 HTTPS client.", 4 | "version": "1.2.1", 5 | "main": "index.js", 6 | "homepage": "https://github.com/mattcg/socks5-https-client", 7 | "implements": ["CommonJS/Modules/1.0"], 8 | "author": { 9 | "name": "Matthew Caruana Galizia", 10 | "email": "mattcg@gmail.com" 11 | }, 12 | "keywords": [ 13 | "socks5", "socksv5", "socks", "v5", "https", "ssl", "tls", "tor", "client" 14 | ], 15 | "bugs": { 16 | "url": "https://github.com/mattcg/socks5-https-client/issues" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/mattcg/socks5-https-client.git" 21 | }, 22 | "dependencies": { 23 | "socks5-client": "~1.2.3" 24 | }, 25 | "devDependencies": { 26 | "mocha": "~3.1.2", 27 | "node-socks": "~0.1.0", 28 | "request": "~2.72.0" 29 | }, 30 | "scripts": { 31 | "test": "make test" 32 | }, 33 | "engines": { 34 | "node": ">= 6.4.0" 35 | }, 36 | "license": "MIT" 37 | } 38 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @overview 3 | * @author Matthew Caruana Galizia 4 | * @copyright Copyright (c) 2013, Matthew Caruana Galizia 5 | * @license MIT 6 | * @preserve 7 | */ 8 | 9 | 'use strict'; 10 | 11 | /*jshint node:true*/ 12 | /*global test, suite, setup*/ 13 | 14 | var assert = require('assert'); 15 | var net = require('net'); 16 | var request = require('request'); 17 | var socks = require('node-socks/socks.js'); 18 | var https = require('../'); 19 | var Agent = require('../lib/Agent'); 20 | 21 | suite('socks5-https-client tests', function() { 22 | var server; 23 | 24 | this.timeout(5000); 25 | 26 | suiteSetup(function(done) { 27 | server = socks.createServer(function(socket, port, address, proxyReady) { 28 | var proxy; 29 | 30 | proxy = net.createConnection(port, address, proxyReady); 31 | 32 | proxy.on('data', function(data) { 33 | socket.write(data); 34 | }); 35 | 36 | socket.on('data', function(data) { 37 | proxy.write(data); 38 | }); 39 | 40 | proxy.on('close', function() { 41 | socket.end(); 42 | }); 43 | 44 | socket.on('close', function() { 45 | proxy.removeAllListeners('data'); 46 | proxy.end(); 47 | }); 48 | }); 49 | 50 | server.listen(1080, 'localhost', 511, function() { 51 | done(); 52 | }); 53 | 54 | server.on('error', function(err) { 55 | throw err; 56 | }); 57 | }); 58 | 59 | test('simple request', function(done) { 60 | var req; 61 | 62 | req = https.request('https://en.wikipedia.org/wiki/Main_Page', function(res, err) { 63 | var data = ''; 64 | 65 | assert.ifError(err); 66 | assert.equal(res.statusCode, 200); 67 | 68 | res.setEncoding('utf8'); 69 | res.on('readable', function() { 70 | data += res.read(); 71 | }); 72 | 73 | res.on('end', function() { 74 | assert(-1 !== data.indexOf('')); 76 | 77 | done(); 78 | }); 79 | }); 80 | 81 | req.on('error', function(err) { 82 | assert.fail(err); 83 | }); 84 | 85 | // GET request, so end without sending any data. 86 | req.end(); 87 | }); 88 | 89 | test('using request', function(done) { 90 | var req; 91 | 92 | request({ 93 | url: 'https://encrypted.google.com/', 94 | agentClass: Agent, 95 | strictSSL: true 96 | }, function(err, res, data) { 97 | assert.ifError(err); 98 | assert.equal(res.statusCode, 200); 99 | 100 | assert(-1 !== data.indexOf('')); 102 | 103 | done(); 104 | }); 105 | }); 106 | }); 107 | --------------------------------------------------------------------------------