purr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []