Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/rollback' into rollback-merge
Browse files Browse the repository at this point in the history
Conflicts:
	server.coffee
	server.js
  • Loading branch information
josh committed Jan 16, 2014
2 parents b5cefb4 + 4123daa commit f15b4fe
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 175 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Features
* Follow redirects to a configurable depth
* Proxy remote images with a content-type of `image/*`
* 404s for anything other than a 200, 301, 302, 303, 304 or 307 HTTP response
* Disallows proxying to private IP ranges

At GitHub we render markdown and replace all of the `src` attributes on the `img` tags with the appropriate URL to hit the proxies. There's example code for creating URLs in [the tests](https://github.com/atmos/camo/blob/master/test/proxy_test.rb).

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "camo",
"version": "1.3.0",
"version": "1.2.1",
"dependencies": {
},
"engines": {
Expand Down
80 changes: 13 additions & 67 deletions server.coffee
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
Fs = require 'fs'
Dns = require 'dns'
Url = require 'url'
Http = require 'http'
Crypto = require 'crypto'
QueryString = require 'querystring'

port = parseInt process.env.PORT || 8081
version = "1.3.0"
version = "1.2.1"
shared_key = process.env.CAMO_KEY || '0x24FEEDFACEDEADBEEFCAFE'
max_redirects = process.env.CAMO_MAX_REDIRECTS || 4
camo_hostname = process.env.CAMO_HOSTNAME || "unknown"
socket_timeout = process.env.CAMO_SOCKET_TIMEOUT || 10
logging_enabled = process.env.CAMO_LOGGING_ENABLED || "disabled"
content_length_limit = parseInt(process.env.CAMO_LENGTH_LIMIT || 5242880, 10)

# Enable test mode if no custom keys is configured.
# This maybe a weak assumption, but no one should be using camo with the
# default key configuration. Test mode only has the affect of allowing
# proxying to 127.0.0.1 test servers.
testMode = !process.env.CAMO_KEY?

debug_log = (msg) ->
if logging_enabled == "debug"
console.log("--------------------------------------------")
Expand All @@ -30,8 +23,6 @@ error_log = (msg) ->
unless logging_enabled == "disabled"
console.error("[#{new Date().toISOString()}] #{msg}")

RESTRICTED_IPS = /^((10\.)|(127\.)|(169\.254)|(192\.168)|(172\.((1[6-9])|(2[0-9])|(3[0-1]))))/

total_connections = 0
current_connections = 0
started_at = new Date
Expand All @@ -46,56 +37,17 @@ finish = (resp, str) ->
current_connections = 0 if current_connections < 1
resp.connection && resp.end str

# A Transform Stream that limits the piped data to the specified length
Stream = require('stream')
class LimitStream extends Stream.Transform
constructor: (length) ->
super()
@remaining = length

_transform: (chunk, encoding, cb) ->
if @remaining > 0
if @remaining < chunk.length
chunk = chunk.slice(0, @remaining)
@push(chunk)
@remaining -= chunk.length
if @remaining <= 0
@emit('length_limited')
@end()
cb()

write: (chunk, encoding, cb) ->
if @remaining > 0
super
else
false

process_url = (url, transferredHeaders, resp, remaining_redirects) ->
if !url.host?
return four_oh_four(resp, "Invalid host", url)

if url.protocol == 'https:'
error_log("Redirecting https URL to origin: #{url.format()}")
resp.writeHead 301, {'Location': url.format()}
finish resp
return
else if url.protocol != 'http:'
four_oh_four(resp, "Unknown protocol", url)
return

Dns.lookup url.hostname, (err, address, family) ->
if err
return four_oh_four(resp, "No host found: #{err}", url)

if address.match(RESTRICTED_IPS)
if testMode and address is '127.0.0.1'
# don't block localhost server for testing
else
return four_oh_four(resp, "Hitting excluded IP", url)

fetch_url address, url, transferredHeaders, resp, remaining_redirects
if url.host?
if url.protocol == 'https:'
error_log("Redirecting https URL to origin: #{url.format()}")
resp.writeHead 301, {'Location': url.format()}
finish resp
return
else if url.protocol != 'http:'
four_oh_four(resp, "Unknown protocol", url)
return

fetch_url = (ip_address, url, transferredHeaders, resp, remaining_redirects) ->
queryPath = url.pathname
if url.query?
queryPath += "?#{url.query}"
Expand Down Expand Up @@ -151,15 +103,7 @@ process_url = (url, transferredHeaders, resp, remaining_redirects) ->
debug_log newHeaders

resp.writeHead srcResp.statusCode, newHeaders

limit = new LimitStream(content_length_limit)
srcResp.pipe(limit)
limit.pipe(resp)

limit.on 'length_limited', ->
srcResp.destroy()
error_log("Killed connection at content_length_limit: #{url.format()}")

srcResp.pipe resp
when 301, 302, 303, 307
srcResp.destroy()
if remaining_redirects <= 0
Expand Down Expand Up @@ -196,6 +140,8 @@ process_url = (url, transferredHeaders, resp, remaining_redirects) ->
resp.on 'error', (e) ->
error_log("Request error: #{e}")
srcReq.abort()
else
four_oh_four(resp, "No host found " + url.host, url)

# decode a string of two char hex digits
hexdec = (str) ->
Expand Down
104 changes: 19 additions & 85 deletions server.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 1 addition & 21 deletions test/proxy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,32 +142,12 @@ def test_404s_on_non_image_content_type
end
end

def test_404s_on_10_0_ip_range
def test_404s_on_connect_timeout
assert_raise RestClient::ResourceNotFound do
request('http://10.0.0.1/foo.cgi')
end
end

16.upto(31) do |i|
define_method :"test_404s_on_172_#{i}_ip_range" do
assert_raise RestClient::ResourceNotFound do
request("http://172.#{i}.0.1/foo.cgi")
end
end
end

def test_404s_on_169_254_ip_range
assert_raise RestClient::ResourceNotFound do
request('http://169.254.0.1/foo.cgi')
end
end

def test_404s_on_192_168_ip_range
assert_raise RestClient::ResourceNotFound do
request('http://192.168.0.1/foo.cgi')
end
end

def test_404s_on_environmental_excludes
assert_raise RestClient::ResourceNotFound do
request('http://iphone.internal.example.org/foo.cgi')
Expand Down

0 comments on commit f15b4fe

Please sign in to comment.