purr 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 00f5cd3df556c392a0350b07a3f301621cbb85e5
4
+ data.tar.gz: 46c2526afaf17d40188a57c6becf4eb44779dc31
5
+ SHA512:
6
+ metadata.gz: 356fbf9104ea61edcda8f7333e35f445b8be80c55db506091707065e2ba52ddc5958294dd40e083a82397d9079e5e83d62b435759f0c887add575c8e3a0a4781
7
+ data.tar.gz: 371811743c189dcdffe3d3e42cc72d544f80077226ecd81309db4d6f8a0802edc0072a1e1d33fd72a14bfa9c95694b87fb19cbe03419584ee765208a34bcbc0b
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,22 @@
1
+ Metrics/PerceivedComplexity:
2
+ Enabled: false
3
+ Metrics/CyclomaticComplexity:
4
+ Enabled: false
5
+ Metrics/AbcSize:
6
+ Enabled: false
7
+ Style/RedundantFreeze:
8
+ Enabled: false
9
+ Style/IndentationConsistency:
10
+ EnforcedStyle: normal
11
+ Metrics/LineLength:
12
+ Max: 140
13
+ Style/HashSyntax:
14
+ EnforcedStyle: hash_rockets
15
+ Documentation:
16
+ Enabled: false
17
+ Metrics/MethodLength:
18
+ Max: 20
19
+ Style/FileName:
20
+ Exclude:
21
+ - lib/surro-gate.rb
22
+ - spec/surro-gate_spec.rb
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.3.1
6
+ before_install:
7
+ - "echo 'gem: --no-ri --no-rdoc --no-document' > ~/.gemrc"
8
+ - "gem install bundler -v 1.13.0"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in purr.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Dávid Halász
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # Purr
2
+
3
+ [![Build Status](https://travis-ci.org/skateman/purr.svg?branch=master)](https://travis-ci.org/skateman/purr)
4
+ [![Dependency Status](https://gemnasium.com/skateman/purr.svg)](https://gemnasium.com/skateman/purr)
5
+ [![Inline docs](http://inch-ci.org/github/skateman/purr.svg?branch=master)](http://inch-ci.org/github/skateman/purr)
6
+ [![Code Climate](https://codeclimate.com/github/skateman/purr/badges/gpa.svg)](https://codeclimate.com/github/skateman/purr)
7
+ [![codecov](https://codecov.io/gh/skateman/purr/branch/master/graph/badge.svg)](https://codecov.io/gh/skateman/purr)
8
+
9
+ Purr is a TCP-over-HTTP solution which consists:
10
+ - a Rack-based web server implemented in Ruby
11
+ - a browser extension implemented in ES6 using Chrome App JavaScript API
12
+
13
+ Using Purr it's possible to "smuggle" any kind of TCP traffic (SSH, VNC, etc.) through an HTTP connection.
14
+
15
+ **Note: this is a highly experimental implementation for demonstration purposes only!**
16
+
17
+ ## How it works
18
+
19
+ **TODO**
20
+
21
+ ## Installation
22
+
23
+ ### Server
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ ```ruby
28
+ gem 'purr'
29
+ ```
30
+
31
+ And then execute:
32
+
33
+ $ bundle
34
+
35
+ Or install it yourself as:
36
+
37
+ $ gem install purr
38
+
39
+ ### Client
40
+
41
+ Currently, the client is available as a Chrome App only and it requires manual installation. It is available under the `contrib/chrome` folder and it needs to be installed manually. Note that the Chrome Apps will be [retired](https://blog.chromium.org/2016/08/from-chrome-apps-to-web.html) on other platforms than ChromeOS and this client might get unsupported in future versions of Chrome. It is planned to implement the client using a different approach in the future.
42
+
43
+ ## Usage
44
+
45
+ ### Server
46
+ The server needs to be wrapped as a Rack application and it's necessary to pass a block that takes one argument. This block should implement the TCP remote endpoint selection based on the **env** variable passed from the Rack context. The endpoint should be in the form of a two element array containing the host as a string and the port as an integer. There is a basic logging support implemented, but it is requires the `Rack::Logger` middleware to be included.
47
+
48
+ ```ruby
49
+ # purr.ru
50
+ require 'purr'
51
+
52
+ # Turn on the optional logging feature
53
+ use Rack::Logger
54
+
55
+ app = Purr.server do |env|
56
+ # Maybe do some database lookup based on the Rack environment
57
+ # ...
58
+ # Return with the remote endpoint
59
+ ['localhost', 22]
60
+ end
61
+
62
+ run app
63
+ ```
64
+
65
+ The application can be started using `rackup`:
66
+ ```sh
67
+ rackup purr.ru
68
+ ```
69
+
70
+ Note that the application requires a web server with [socket hijacking](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking) support, i.e. you can't use WEBrick.
71
+
72
+ ### Client
73
+ The client can be invoked by pointing your browser to an URL in the form: `http://purr/<URL>`
74
+
75
+ Where the `<URL>` is an URL encoded using [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) pointing to the server described above.
76
+
77
+ Because the client catches the URL, it will never appear in the browser's address bar. Therefore, it is not recommended to use `window.open` or `window.location.href` for invoking the client as it will create an empty window. A better solution is to use `window.location.assign` from and existing window with "useful data":
78
+
79
+ ```js
80
+ window.location.assign(`http://purr/${encodeURIComponent('http://example.com/vnc?id=1234')}`)
81
+ ```
82
+
83
+ ### Reverse proxy support
84
+
85
+ - TODO: Describe websocket compatibility mode and how it works with Apache mod_proxy_wstunnel
86
+ - TODO: Test with nginx
87
+
88
+ ## Development
89
+
90
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
91
+
92
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
93
+
94
+ ## Contributing
95
+
96
+ Bug reports and pull requests are welcome on GitHub at https://github.com/skateman/purr.
97
+
98
+
99
+ ## License
100
+
101
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
102
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "purr"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1 @@
1
+ {}
Binary file
Binary file
@@ -0,0 +1,34 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Purr Client</title>
6
+ <link rel="stylesheet" type="text/css" href="styles/main.css"/>
7
+ </head>
8
+ <body>
9
+ <div class="container">
10
+ <h1>Purr Client v<span class="version"></span></h1>
11
+ <div class="info-messages">
12
+ <p class="init">
13
+ Initializing connection, please wait...
14
+ </p>
15
+ <p class="work hidden">
16
+ Listening on: <strong class="address" title="Click to copy!"></strong><br/>
17
+ Connected clients: <span class="clients">0</span>
18
+ </p>
19
+ <p class="error hidden">
20
+ <strong>Error:</strong> <span class="errmsg"></span>
21
+ </p>
22
+ </div>
23
+ <div class="settings">
24
+ <label>Compatibility:</label>
25
+ <select id="websocket">
26
+ <option value="purr">None</option>
27
+ <option value="websocket">Fake WebSocket</option>
28
+ <!-- <option value="">Full WebSocket</option> -->
29
+ </select>
30
+ </div>
31
+ </div>
32
+ <script src="scripts/main.js"></script>
33
+ </body>
34
+ </html>
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "Purr Client",
3
+ "description": "Smuggle TCP connections through HTTP",
4
+ "default_locale": "en",
5
+ "version": "0.1.0",
6
+ "manifest_version": 2,
7
+ "sockets": {
8
+ "tcp": {
9
+ "connect": "*"
10
+ },
11
+ "tcpServer": {
12
+ "listen": "*"
13
+ }
14
+ },
15
+ "url_handlers": {
16
+ "launch_proxy": {
17
+ "matches": [
18
+ "http://purr/*",
19
+ "https://purr/*"
20
+ ],
21
+ "title": "Launch Purr Client"
22
+ }
23
+ },
24
+ "icons": {
25
+ "16": "images/icon-16.png",
26
+ "128": "images/icon-128.png"
27
+ },
28
+ "app": {
29
+ "background": {
30
+ "scripts": [
31
+ "scripts/background.js"
32
+ ]
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ chrome.app.runtime.onLaunched.addListener((data) => {
4
+ // Pass the url for further processing
5
+ window.url = data.url;
6
+ // Create the application window
7
+ chrome.app.window.create('index.html', {
8
+ innerBounds: {
9
+ width: 400,
10
+ height: 160
11
+ }
12
+ }, (win) =>
13
+ // Clean up all sockets if the app window gets closed
14
+ win.onClosed.addListener(() =>
15
+ ['tcp', 'tcpServer'].forEach((provider) =>
16
+ chrome.sockets[provider].getSockets((sockets) =>
17
+ sockets.forEach((socket) => {
18
+ chrome.sockets[provider].disconnect(socket.socketId);
19
+ chrome.sockets[provider].close(socket.socketId);
20
+ })
21
+ )
22
+ )
23
+ )
24
+ );
25
+ });
26
+
@@ -0,0 +1,178 @@
1
+ 'use strict';
2
+
3
+ const showError = (msg) => {
4
+ document.querySelector('.error > .errmsg').textContent = msg;
5
+ showInfo('error');
6
+ };
7
+
8
+ const showInfo = (klass) => {
9
+ if (['init', 'work', 'error'].includes(klass)) {
10
+ document.querySelector(`.info-messages > :not(.hidden)`).classList.add('hidden');
11
+ document.querySelector(`.info-messages .${klass}`).classList.remove('hidden');
12
+ }
13
+ };
14
+
15
+ const copyToClipboard = (e) => {
16
+ var input = document.createElement('textarea');
17
+ document.body.appendChild(input);
18
+ input.value = e.target.textContent;
19
+ input.focus();
20
+ input.select();
21
+ document.execCommand('Copy');
22
+ input.remove();
23
+ };
24
+
25
+ const sendHttpRequest = (sock) => {
26
+ // Build up the HTTP request headers
27
+ const upgrade = document.querySelector('#websocket').value;
28
+
29
+ const req = `
30
+ GET ${window.request.path} HTTP/1.1 \r
31
+ Host: ${window.request.host} \r
32
+ Upgrade: ${upgrade} \r
33
+ Purr-Request: MEOW \r
34
+ Purr-Version: ${chrome.runtime.getManifest().version}\r
35
+ Connection: Upgrade \r
36
+ User-Agent: ${navigator.userAgent} \r
37
+ `.replace(/( {2,})|(^\n)/g, '').replace(/\r\n$/, '\r\n\r\n');
38
+
39
+ let buffer = new ArrayBuffer(req.length);
40
+ let writer = new Uint8Array(buffer);
41
+ // Convert it to the appropriate format
42
+ for (let i=0, len=req.length; i<len; i++) {
43
+ writer[i] = req.charCodeAt(i);
44
+ }
45
+
46
+ // Send it out
47
+ chrome.sockets.tcp.send(sock, buffer, () => null);
48
+ };
49
+
50
+ const windowLoaded = () => new Promise((resolve, reject) =>
51
+ document.addEventListener('DOMContentLoaded', () => {
52
+ document.querySelector('span.version').textContent = chrome.runtime.getManifest().version;
53
+ resolve();
54
+ })
55
+ );
56
+
57
+ const parseBackgroundURL = () => new Promise((resolve, reject) =>
58
+ chrome.runtime.getBackgroundPage((background) => {
59
+ if (background.url) {
60
+ var url = new URL(decodeURIComponent(background.url.replace(/^https?:\/\/purr\// ,'')));
61
+ var m = url.protocol.match(/^http(s?):$/);
62
+
63
+ if (m) {
64
+ window.request = {
65
+ hostname: url.hostname,
66
+ host: url.host,
67
+ port: url.port ? parseInt(url.port) : (m[1] ? 443 : 80),
68
+ path: url.pathname,
69
+ secure: !!m[1]
70
+ };
71
+ resolve();
72
+ } else {
73
+ reject('Invalid URL!');
74
+ }
75
+ } else {
76
+ reject('This application cannot be started separately!');
77
+ }
78
+ })
79
+ );
80
+
81
+ const createServer = () => new Promise((resolve, reject) =>
82
+ chrome.sockets.tcpServer.create({}, (server) =>
83
+ chrome.sockets.tcpServer.listen(server.socketId, '127.0.0.1', 8888, 0, (result) =>
84
+ chrome.sockets.tcpServer.getInfo(server.socketId, (info) => {
85
+ if (result < 0) {
86
+ reject(`tcpServer.listen returned with ${result}`);
87
+ } else {
88
+ document.querySelector('.address').textContent = `127.0.0.1:${info.localPort}`;
89
+ document.querySelector('.address').onclick = copyToClipboard;
90
+ showInfo('work');
91
+ resolve(server.socketId);
92
+ }
93
+ })
94
+ )
95
+ )
96
+ );
97
+
98
+ const setListeners = (promise) => {
99
+ // Set up the proxying
100
+ chrome.sockets.tcp.onReceive.addListener((recv) => {
101
+ let node = window.pairing[recv.socketId];
102
+ if (node.purr) { // Synchronize
103
+ // Convert the response to a readable format
104
+ let response = String.fromCharCode.apply(null, new Uint8Array(recv.data));
105
+
106
+ if (response.match(/^HTTP\/1\.1 101 Switching Protocols/)) {
107
+ // If the upgrade was successful, unpause the socket
108
+ chrome.sockets.tcp.setPaused(node.pair, false, () =>
109
+ delete node.purr
110
+ );
111
+ } else {
112
+ // The upgrade was not successful, close the connection
113
+ console.error('Error happened during HTTP upgrade...')
114
+ cleanupClient(recv.socketId);
115
+ }
116
+
117
+ } else { // Transmit normally
118
+ chrome.sockets.tcp.send(node.pair, recv.data, () => null);
119
+ }
120
+ });
121
+
122
+ // Error handling for client connections
123
+ chrome.sockets.tcp.onReceiveError.addListener((err) =>
124
+ cleanupClient(err.socketId)
125
+ );
126
+
127
+ // Keeping up the promise-chain
128
+ return promise;
129
+ };
130
+
131
+ const createClient = (peer) => new Promise((resolve, reject) =>
132
+ chrome.sockets.tcp.create({}, (client) =>
133
+ chrome.sockets.tcp.connect(client.socketId, window.request.hostname, window.request.port, (result) => {
134
+ if (result < 0) {
135
+ reject(`tcp.connect returned with ${result}`);
136
+ } else {
137
+ // Set up socket pairing information
138
+ window.pairing[peer] = { pair: client.socketId };
139
+ window.pairing[client.socketId] = { pair: peer, purr: true };
140
+ updateClients();
141
+ resolve(client.socketId);
142
+ }
143
+ })
144
+ )
145
+ );
146
+
147
+ const cleanupClient = (sock) => {
148
+ let node = window.pairing[sock];
149
+ if (node) { // Do not close them twice
150
+ delete window.pairing[sock];
151
+ delete window.pairing[node.pair];
152
+
153
+ updateClients();
154
+
155
+ chrome.sockets.tcp.close(sock);
156
+ chrome.sockets.tcp.close(node.pair);
157
+ }
158
+ };
159
+
160
+ const acceptServer = (server) => {
161
+ chrome.sockets.tcpServer.onAccept.addListener((client) => {
162
+ createClient(client.clientSocketId).then(sendHttpRequest, showError);
163
+ return server;
164
+ })
165
+ };
166
+
167
+ const updateClients = () => {
168
+ document.querySelector('.clients').textContent = parseInt(Object.keys(pairing).length / 2);
169
+ };
170
+
171
+ window.pairing = {};
172
+
173
+ windowLoaded()
174
+ .then(parseBackgroundURL)
175
+ .then(createServer)
176
+ .then(setListeners)
177
+ .then(acceptServer)
178
+ .catch(showError);
@@ -0,0 +1,20 @@
1
+ strong.address:hover {
2
+ cursor: pointer;
3
+ text-decoration: underline;
4
+ }
5
+
6
+ .error {
7
+ color: red;
8
+ }
9
+
10
+ .hidden {
11
+ display: none;
12
+ }
13
+
14
+ .container {
15
+ text-align: center;
16
+ }
17
+
18
+ .info-messages {
19
+ font-size: larger;
20
+ }
data/lib/purr.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'purr/version'
2
+ require 'purr/server'
3
+
4
+ # A Rack-based server capable of smuggling TCP traffic through a persisent HTTP connection
5
+ #
6
+ # It uses the Rack socket hijacking API for accessing the TCP level of an incoming HTTP session.
7
+ # The remote endpoint selection should be implemented as a block passed to the server returning
8
+ # an two element array containing a host string and a port integer.
9
+ #
10
+ # @example Simple rackup file for a local SSH connection
11
+ # require 'purr'
12
+ #
13
+ # # This middleware is to support optional logging
14
+ # use Rack::Logger
15
+ #
16
+ # app = Purr.server do |env|
17
+ # ['localhost', 22]
18
+ # end
19
+ #
20
+ # run app
21
+ #
22
+ # @see http://www.rubydoc.info/github/rack/rack/master/file/SPEC#Hijacking
23
+
24
+ module Purr
25
+ class << self
26
+ # Creates or returns a singleton instance of the Rack-server
27
+ #
28
+ # @see Purr::Server.initialize
29
+ def server(&block)
30
+ @server ||= Server.new(&block)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,92 @@
1
+ require 'surro-gate'
2
+
3
+ module Purr
4
+ # This class implements a Rack-based server with socket hijacking and proxying to a remote TCP endpoint.
5
+ #
6
+ # The remote TCP endpoint selection is implemented by passing a block to the class instantiation.
7
+ # If any kind of error happens during the hijacking process a 404 error is returned to the requester.
8
+ class Server
9
+ # @yield [env] The block responsible for the remote TCP endpoint selection
10
+ # @yieldparam env [Hash] The environment hash returned by the Rack middleware
11
+ # @yieldreturn [Array[String, Integer]] The host:port pair as a two element array
12
+ # @raise [ArgumentError] If the passed block takes other number of arguments than one
13
+ def initialize(&block)
14
+ raise ArgumentError, 'The method requires a block with a single argument' unless block && block.arity == 1
15
+
16
+ @remote = block
17
+ @proxy = SurroGate.new
18
+ end
19
+
20
+ # Method required by the Rack API
21
+ #
22
+ # @see https://rack.github.io/
23
+ def call(env)
24
+ upgrade = parse_headers(env)
25
+ # Return with a 404 error if the upgrade header is not present
26
+ return not_found unless %i(websocket purr).include?(upgrade)
27
+
28
+ host, port = @remote.call(env)
29
+ # Return with a 404 error if no host:port pair was determined
30
+ if host.nil? || port.nil?
31
+ logger(env, :error, "No matching endpoint found for request incoming from #{env['REMOTE_ADDR']}")
32
+ return not_found
33
+ end
34
+
35
+ # Hijack the HTTP socket from the Rack middleware
36
+ http = env['rack.hijack'].call
37
+ # Write a proper HTTP response
38
+ http.write(http_response(upgrade))
39
+ # Open the remote TCP socket
40
+ sock = TCPSocket.new(host, port)
41
+
42
+ # Start proxying
43
+ @proxy.push(http, sock)
44
+ logger(env, :info, "Redirecting incoming request from #{env['REMOTE_ADDR']} to [#{host}]:#{port}")
45
+
46
+ # Rack requires this line below
47
+ return [200, {}, []]
48
+ rescue => ex
49
+ logger(env, :error, "#{ex.class} happened for #{env['REMOTE_ADDR']} trying to access #{host}:#{port}")
50
+ # Clean up the opened sockets if available
51
+ http.close unless http.nil? || http.closed?
52
+ sock.close unless sock.nil? || sock.closed?
53
+ # Return with a 404 error
54
+ return not_found
55
+ end
56
+
57
+ private
58
+
59
+ def parse_headers(env)
60
+ case true
61
+ when env['HTTP_PURR_REQUEST'] != 'MEOW'
62
+ logger(env, :error, "Invalid request from #{env['REMOTE_ADDR']}")
63
+ when !SUPPORT.include?(env['HTTP_PURR_VERSION'])
64
+ logger(env, :error, "Unsupported client from #{env['REMOTE_ADDR']}")
65
+ when %w(websocket purr).include?(env['HTTP_UPGRADE'])
66
+ logger(env, :info, "Upgrading to #{env['HTTP_UPGRADE']}")
67
+ return env['HTTP_UPGRADE'].to_sym
68
+ else
69
+ logger(env, :error, "Invalid upgrade request from #{env['REMOTE_ADDR']}")
70
+ end
71
+ end
72
+
73
+ def http_response(upgrade)
74
+ <<~HEREDOC.sub(/\n$/, "\n\n").gsub(/ {2,}/, '').gsub("\n", "\r\n")
75
+ HTTP/1.1 101 Switching Protocols
76
+ Upgrade: #{upgrade}
77
+ PURR_VERSION: #{Purr::VERSION}
78
+ PURR_REQUEST: MEOW
79
+ Connection: Upgrade
80
+ HEREDOC
81
+ end
82
+
83
+ def not_found
84
+ [404, { 'Content-Type' => 'text/plain' }, ['Not found!']]
85
+ end
86
+
87
+ def logger(env, level, message)
88
+ # Do logging only if Rack::Logger is loaded as a middleware
89
+ env['rack.logger'].send(level, message) if env['rack.logger']
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,4 @@
1
+ module Purr
2
+ VERSION = '0.1.0'.freeze
3
+ SUPPORT = %w(0.1.0).freeze
4
+ end
data/purr.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'purr/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'purr'
8
+ spec.version = Purr::VERSION
9
+ spec.authors = ['Dávid Halász']
10
+ spec.email = ['skateman@skateman.eu']
11
+
12
+ spec.summary = 'Smuggle TCP connections through HTTP'
13
+ spec.description = 'Smuggle TCP connections through HTTP'
14
+ spec.homepage = 'https://github.com/skateman/purr'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency 'rack', '~> 2.0.0'
25
+ spec.add_dependency 'surro-gate', '~> 0.1.1'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.13'
28
+ spec.add_development_dependency 'codecov', '~> 0.1.0'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'rspec', '~> 3.0'
31
+ spec.add_development_dependency 'simplecov', '~> 0.12'
32
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: purr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dávid Halász
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-11-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: surro-gate
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.13'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.13'
55
+ - !ruby/object:Gem::Dependency
56
+ name: codecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.1.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.1.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.12'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.12'
111
+ description: Smuggle TCP connections through HTTP
112
+ email:
113
+ - skateman@skateman.eu
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".rubocop.yml"
121
+ - ".travis.yml"
122
+ - Gemfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - bin/console
127
+ - bin/setup
128
+ - contrib/chrome/_locales/en/messages.json
129
+ - contrib/chrome/images/icon-128.png
130
+ - contrib/chrome/images/icon-16.png
131
+ - contrib/chrome/index.html
132
+ - contrib/chrome/manifest.json
133
+ - contrib/chrome/scripts/background.js
134
+ - contrib/chrome/scripts/main.js
135
+ - contrib/chrome/styles/main.css
136
+ - lib/purr.rb
137
+ - lib/purr/server.rb
138
+ - lib/purr/version.rb
139
+ - purr.gemspec
140
+ homepage: https://github.com/skateman/purr
141
+ licenses:
142
+ - MIT
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.5.1
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Smuggle TCP connections through HTTP
164
+ test_files: []