Permalink
Browse files

use more aggressive hex encoding instead of url encoding for path format

Rolling with this for now due to some insane URL decoding in older
versions of node (<= v0.2.4). Ostensibly, old node versions decode
the piss out of anything in the path part of the URL, include %2F
and also squash // characters together. The result is that you cannot
cleanly separate a URL encoded value in the path.

With this new scheme, every byte of the image URL is encoded into a
2 char hex value. I actually kind of like it for its similarity to
the digest hex encoding.
  • Loading branch information...
1 parent 3ccfb0e commit 4cbaa3cc391b09eb168d1198831ecb610439822a @rtomayko rtomayko committed Mar 14, 2011
Showing with 36 additions and 13 deletions.
  1. +5 −3 README.md
  2. +11 −3 server.coffee
  3. +15 −6 server.js
  4. +5 −1 test/proxy_test.rb
View
@@ -27,9 +27,11 @@ Camo supports two distinct URL formats:
http://example.org/<digest>/<image-url>
The `<digest>` is a 40 character hex encoded HMAC digest generated with a shared
-secret key and the unescaped `<image-url>` value. The `<image-url>` is the absolute
-URL locating an image. In either format, the `<image-url>` should be URL escaped
-aggressively to ensure the original value isn't mangled in transit.
+secret key and the unescaped `<image-url>` value. The `<image-url>` is the
+absolute URL locating an image. In the first format, the `<image-url>` should be
+URL escaped aggressively to ensure the original value isn't mangled in transit.
+In the second format, each byte of the `<image-url>` should be hex encoded such
+that the resulting value includes only characters `[0-9a-f]`.
## Testing Functionality
View
@@ -33,6 +33,14 @@ finish = (resp, str) ->
current_connections = 0 if current_connections < 1
resp.end str
+# decode a string of two char hex digits
+hexdec = (str) ->
+ if str and str.length > 0 and str.length % 2 == 0 and not str.match(/[^0-9a-f]/)
+ buf = new Buffer(str.length / 2)
+ for i in [0...str.length] by 2
+ buf[i/2] = parseInt(str[i..i+1], 16)
+ buf.toString()
+
server = Http.createServer (req, resp) ->
if req.method != 'GET' || req.url == '/'
resp.writeHead 200
@@ -57,10 +65,10 @@ server = Http.createServer (req, resp) ->
delete(req.headers.cookie)
- [query_digest, dest_url...] = url.pathname.replace(/^\//, '').split("/")
- if dest_url.length > 0
+ [query_digest, encoded_url] = url.pathname.replace(/^\//, '').split("/", 2)
+ if encoded_url = hexdec(encoded_url)
url_type = 'path'
- dest_url = unescape(dest_url.join("/"))
+ dest_url = encoded_url
else
url_type = 'query'
dest_url = QueryString.parse(url.query).url
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View
@@ -103,10 +103,14 @@ def request(image_url)
class CamoProxyPathTest < Test::Unit::TestCase
include CamoProxyTests
+ def hexenc(image_url)
+ image_url.to_enum(:each_byte).map { |byte| "%02x" % byte }.join
+ end
+
def request(image_url)
hexdigest = OpenSSL::HMAC.hexdigest(
OpenSSL::Digest::Digest.new('sha1'), config['key'], image_url)
- encoded_image_url = URI.escape(image_url, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
+ encoded_image_url = hexenc(image_url)
uri = "#{config['host']}/#{hexdigest}/#{encoded_image_url}"
RestClient.get(uri)
end

0 comments on commit 4cbaa3c

Please sign in to comment.