Sure, http://github.com/gonzojive/drakma/commit/869007460ae2eb804bcd1600faacf9bda27e3709 contains a diff. I also attached one.
- Red On Sat, Aug 7, 2010 at 1:26 PM, Edi Weitz <e...@agharta.de> wrote: > Hi Red, > > First of all, thanks for this, sounds good. Could you show us the > diff somehow so that it's easier to see what exactly you've added and > how? > > Edi. > > > On Sat, Aug 7, 2010 at 1:29 AM, Red Daly <redd...@gmail.com> wrote: >> I have modified Drakma to use a SOCKS5 proxy if desired. Before I >> submit a patch I'd like to know what is the preferred interface for >> this: >> >> 1. Modify http-request with a SOCKS-PROXY option analogous to the >> PROXY option. Introduce SOCKS5 protocol code (about 200 lines) into >> hunchentoot and rely on no external libraries >> >> 2. Modify http-request with a SOCKS-PROXY option analogous to the >> PROXY option. Introduce a dependency on an external SOCKS5 library. >> >> 3. Modify Drakma in some way that allows a user to establish a >> socks-proxy connection without explicitly supporting this in Drakma >> itself. >> >> The implementation requires that instead of establishing a socket >> connection to HOST/PORT, the socket connects to SOCKS-HOST/SOCKS-PORT >> instead and then sets up the socket to forward to HOST/PORT. >> >> The implementation is available here: >> http://github.com/gonzojive/drakma/tree/socks >> >> socks5 spec: http://www.faqs.org/rfcs/rfc1928.html >> >> Red >> >> _______________________________________________ >> drakma-devel mailing list >> drakma-devel@common-lisp.net >> http://common-lisp.net/cgi-bin/mailman/listinfo/drakma-devel >> > > _______________________________________________ > drakma-devel mailing list > drakma-devel@common-lisp.net > http://common-lisp.net/cgi-bin/mailman/listinfo/drakma-devel >
diff --git a/cl-socks.lisp b/cl-socks.lisp deleted file mode 100644 index ab29dc4..0000000 --- a/cl-socks.lisp +++ /dev/null @@ -1,186 +0,0 @@ -(defpackage :cl-socks - (:nicknames :socks) - (:use :cl) - (:export #:socks-connect)) - -(in-package :cl-socks) - - -(defconstant +socks5-no-authorization+ 0 - "Authentication method that requires no credentials.") - -(defconstant +socks5-version-byte+ 5 - "Authentication method that requires no credentials.") - -(defconstant +socks5-address-type-ipv4+ 1 - "IPV4") - -(defconstant +socks5-address-type-domainname+ 3 - "Domain name address type byte") - -(defconstant +socks5-address-type-ipv6+ 4 - "IPV6") - - -(defconstant +socks5-version-byte+ 5 - "Authentication method that requires no credentials.") - -(defconstant +socks5-version-byte+ 5 - "Authentication method that requires no credentials.") - -(defclass socks-client () - ((underlying-stream :initarg :underlying-stream :reader client-underlying-stream - :documentation "The STREAM object this object wraps") - )) - -(defun wrap-stream (wrapper-class underlying-stream &rest initargs) - (apply 'make-instance wrapper-class :underlying-stream underlying-stream initargs)) - -(defun socks-connect (stream destination-address destination-port) - "Establishes a SOCKS5 connection to DESTINATION-ADDRESS on - DESTINATION-PORT through (socket) binary stream STREAM. After - this (blocking) function completes, the stream should behave mostly - as if a direct connection had been made to the desination. - -DESTINATION-ADDRESS is either a Fully Qualified Domain Name (including -the dot at the end and less than 255 characters) string, a vector of 4 -bytes (ipv4 address), or a vector of 6 bytes (ipv6 address). - -Returns (BOUND-ADDRESS-TYPE BOUND-ADDRESS BOUND-PORT) of the -connection on the proxy server. - -See http://www.faqs.org/rfcs/rfc1928.html" - (socks-client-connect (wrap-stream 'socks-client stream) - destination-address destination-port)) - -(defun destructure-user-address (address) - "Takes as input a string or byte array and determines the type of -desination address specified. Returns 2 values: its type (one -of :ipv4 :ipv6 or :domainname) and the processed address that is valid -to pass into SOCKS-REQUEST." - (typecase address - (string (values :domainname address)) - (sequence (case (length address) - (4 (values :ipv4 address)) - (6 (values :ipv6 address)) - (t (error "Invalid Address ~A" address)))) - (t (error "Invalid Address ~A" address)))) - -(defun socks-client-connect (client destination-address destination-port) - (socks-authenticate client) - (multiple-value-bind (bound-address-type bound-address port) - (socks-request client :connect :domainname destination-address destination-port) - #+nil - (format t "Successfully connected to socks server bound to ~A ~A ~A!~%" - bound-address-type bound-address port) - (values bound-address-type bound-address port))) - - -(defmacro with-syntax-sugar ((socks-stream-var) &body body) - `(labels ((expect-byte (expected &optional (error-format "Read unexpected SOCKS stream byte value ~A")) - (let ((value (read-byte ,socks-stream-var))) - (unless (eql expected value) - (error error-format value)) - expected)) - - (read-n-bytes (n) - (let ((array (make-array n :element-type '(unsigned-byte 8)))) - (unless (eql n (read-sequence array ,socks-stream-var)) - (error "Failed to read ~A bytes from SOCKS stream" n)) - array)) - - (expect-version-byte () - (expect-byte +socks5-version-byte+ "Invalid SOCKS connection/version ~A"))) - ,@body)) - -(defun socks-authenticate (client) - "Performs handshake using the underlying stream." - (let ((socks-stream (client-underlying-stream client))) - (with-syntax-sugar (socks-stream) - ;; http://www.faqs.org/rfcs/rfc1928.html - - ;; Send acceptable authentication methods. only support no - ;; authorization for now - (write-sequence (vector +socks5-version-byte+ 1 +socks5-no-authorization+) socks-stream) - (finish-output socks-stream) - - (expect-version-byte) - (expect-byte +socks5-no-authorization+) - - client))) - -(defun socks-request (client command address-type address port) - (declare (type (member :connect) command) - (type (member :ipv4 :ipv6 :domainname) address-type) - (type integer port) - (type socks-client client)) - (let ((socks-stream (client-underlying-stream client))) - (with-syntax-sugar (socks-stream) - ;; write version, cmd, rsv - (write-sequence (vector +socks5-version-byte+ - (case command - (:connect 1) - (:bind 2) - (:udp-associate 3)) - 0) - socks-stream) - - ;; write the address type - (case address-type - (:ipv4 (write-sequence (concatenate 'vector - (list +socks5-address-type-ipv4+) - address) - socks-stream)) - (:ipv6 (write-sequence (concatenate 'vector - (list +socks5-address-type-ipv6+) - address) - socks-stream)) - (:domainname (let ((octets (flexi-streams:string-to-octets address))) - (unless (<= (length octets) 255) - (error "Fully qualified domain name too long")) - (when (not (eql #\. (elt address (- (length octets) 1)))) - (error "Fully qualified domain name must end in '.'")) - (write-sequence (concatenate 'vector - (list +socks5-address-type-domainname+ - (length octets)) - octets) - socks-stream)))) - - ;; write the port number - (unless (> (expt 2 16) port -1) - (error "Invalid port number ~A" port)) - - (write-sequence (vector (ldb (byte 8 8) port) - (ldb (byte 8 0) port)) - socks-stream) - - (finish-output socks-stream) - - ;;; Receive the response - (expect-version-byte) - (expect-byte 0 "SOCKS5 error during connect. Code: ~A") - (read-byte socks-stream) - - (multiple-value-bind (server-bound-address-type server-bound-address) - (let ((type-byte (read-byte socks-stream))) - (cond - ((eql type-byte +socks5-address-type-ipv4+) - (values :ipv4 - (read-n-bytes 4))) - ((eql type-byte +socks5-address-type-ipv6+) - (values :ipv6 - (read-n-bytes 6))) - - ((eql type-byte +socks5-address-type-domainname+) - (values :domainname - (let ((addr-len (read-byte socks-stream))) - (flexi-streams:octets-to-string (read-n-bytes addr-len))))) - - (t (error "Invalid address type byte received ~A" type-byte)))) - - (let* ((port-bytes (read-n-bytes 2)) - (port (+ (ash (elt port-bytes 0) 8) - (elt port-bytes 1)))) - #+nil - (format t "Connected ~A ~A ~A~%" server-bound-address-type server-bound-address port) - (values server-bound-address-type server-bound-address port)))))) diff --git a/request.lisp b/request.lisp index a720da9..777922a 100644 --- a/request.lisp +++ b/request.lisp @@ -188,40 +188,6 @@ second value." result))) (chunked-input-stream-trailers (flexi-stream-stream stream))))) -(defun drakma-open-tcp-stream (host port &key socks-host socks-port - #+:lispworks connection-timeout - #+:lispworks read-timeout - #+(and :lispworks (not :lw-does-not-have-write-timeout)) write-timeout) - "Creates a TCP stream and returns it." - (declare (optimize (debug 3))) - (flet ((create-stream (host port) - "This is a raw socket stream creation function that we wrap - with various other functionality like SOCKS negotiation." -#+:lispworks - (comm:open-tcp-stream host port - :element-type 'octet - :timeout connection-timeout - :read-timeout read-timeout - #-:lw-does-not-have-write-timeout - :write-timeout - #-:lw-does-not-have-write-timeout - write-timeout - :errorp t) - #-:lispworks - (usocket:socket-stream - (usocket:socket-connect host port - :element-type 'octet - #+:openmcl :deadline - #+:openmcl deadline - :nodelay t)))) - (if socks-host - (let* ((socks-stream (create-stream socks-host socks-port))) - (socks:socks-connect socks-stream - (concatenate 'string host ".") - port) - socks-stream) - (create-stream host port)))) - (defun http-request (uri &rest args &key (protocol :http/1.1) (method :get) @@ -239,7 +205,6 @@ second value." (decompress t) proxy proxy-basic-authorization - socks-proxy additional-headers (redirect 5) (redirect-methods '(:get :head)) @@ -373,19 +338,13 @@ decompressed body sequence or string; if WANT-STREAM is T, then BODY-OR-STREAM is a flexi-stream backed by a gzip-input-stream, which in turn wraps the socket stream. -If PROXY is not NIL, it should be a string denoting an HTTP proxy -server through which the request should be sent. Or it can be a list -of two values -- a string denoting the proxy server and an integer -denoting the port to use \(which will default to 80 otherwise). -PROXY-BASIC-AUTHORIZATION is used like BASIC-AUTHORIZATION, but for -the proxy, and only if PROXY is true. - -If SOCKS-PROXY is not NIL, it should be a string denoting a SOCKSu5 -proxy server through which the request should be sent. Or it can be a -list of two values -- a string denoting the proxy server and an -integer denoting the port to use \(which will default to 1080 -otherwise). If both PROXY and SOCKS-PROXY are specified, then the -request goes through the SOCKS5 proxy and then through the HTTP proxy. +If PROXY is not NIL, it should be a string denoting a proxy +server through which the request should be sent. Or it can be a +list of two values - a string denoting the proxy server and an +integer denoting the port to use \(which will default to 80 +otherwise). PROXY-BASIC-AUTHORIZATION is used like +BASIC-AUTHORIZATION, but for the proxy, and only if PROXY is +true. ADDITIONAL-HEADERS is a name/value alist of additional HTTP headers which should be sent with the request. Unlike in PARAMETERS, the cdrs @@ -471,10 +430,6 @@ only available on CCL 1.2 and later." (when proxy (when (atom proxy) (setq proxy (list proxy 80)))) - ;; convert SOCKS-PROXY argument to canonical form - (when socks-proxy - (when (atom socks-proxy) - (setq socks-proxy (list socks-proxy 1080)))) ;; make sure we don't get :CRLF on Windows (let ((*default-eol-style* :lf) (file-parameters-p (find-if-not #'stringp parameters :key #'cdr)) @@ -515,19 +470,22 @@ only available on CCL 1.2 and later." (setq write-timeout nil)) (setq http-stream (or stream #+:lispworks - (drakma-open-tcp-stream host port - :socks-host (first socks-proxy) - :socks-port (second socks-proxy) - :timeout connection-timeout - :read-timeout read-timeout - #-:lw-does-not-have-write-timeout - :write-timeout - #-:lw-does-not-have-write-timeout - write-timeout) + (comm:open-tcp-stream host port + :element-type 'octet + :timeout connection-timeout + :read-timeout read-timeout + #-:lw-does-not-have-write-timeout + :write-timeout + #-:lw-does-not-have-write-timeout + write-timeout + :errorp t) #-:lispworks - (drakma-open-tcp-stream host port - :socks-host (first socks-proxy) - :socks-port (second socks-proxy)))) + (usocket:socket-stream + (usocket:socket-connect host port + :element-type 'octet + #+:openmcl :deadline + #+:openmcl deadline + :nodelay t)))) #+:openmcl (when deadline ;; it is correct to set the deadline here even though
_______________________________________________ drakma-devel mailing list drakma-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/drakma-devel