From f0e727867aadc01cef32fe42ac3021834569f86e Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@allmydata.com>
Date: Thu, 11 Oct 2007 22:29:23 -0700
Subject: [PATCH] cli: simplify code by using stdlib's httplib module

---
 src/allmydata/scripts/common.py      |  1 +
 src/allmydata/scripts/common_http.py | 60 ++++++++++++++++++++++++
 src/allmydata/scripts/tahoe_mv.py    | 42 ++---------------
 src/allmydata/scripts/tahoe_put.py   | 68 +++++-----------------------
 src/allmydata/scripts/tahoe_rm.py    | 54 +++++-----------------
 5 files changed, 88 insertions(+), 137 deletions(-)
 create mode 100644 src/allmydata/scripts/common_http.py

diff --git a/src/allmydata/scripts/common.py b/src/allmydata/scripts/common.py
index 64cceb88..794d9868 100644
--- a/src/allmydata/scripts/common.py
+++ b/src/allmydata/scripts/common.py
@@ -62,3 +62,4 @@ class NoDefaultBasedirMixin(BasedirMixin):
         if not self.basedirs:
             raise usage.UsageError("--basedir must be provided")
 
+
diff --git a/src/allmydata/scripts/common_http.py b/src/allmydata/scripts/common_http.py
new file mode 100644
index 00000000..fbeaa597
--- /dev/null
+++ b/src/allmydata/scripts/common_http.py
@@ -0,0 +1,60 @@
+
+from cStringIO import StringIO
+import urlparse, httplib
+import allmydata # for __version__
+
+# copied from twisted/web/client.py
+def parse_url(url, defaultPort=None):
+    url = url.strip()
+    parsed = urlparse.urlparse(url)
+    scheme = parsed[0]
+    path = urlparse.urlunparse(('','')+parsed[2:])
+    if defaultPort is None:
+        if scheme == 'https':
+            defaultPort = 443
+        else:
+            defaultPort = 80
+    host, port = parsed[1], defaultPort
+    if ':' in host:
+        host, port = host.split(':')
+        port = int(port)
+    if path == "":
+        path = "/"
+    return scheme, host, port, path
+
+
+def do_http(method, url, body=""):
+    if isinstance(body, str):
+        body = StringIO(body)
+    elif isinstance(body, unicode):
+        raise RuntimeError("do_http body must be a bytestring, not unicode")
+    else:
+        assert body.tell
+        assert body.seek
+        assert body.read
+    scheme, host, port, path = parse_url(url)
+    if scheme == "http":
+        c = httplib.HTTPConnection(host, port)
+    elif scheme == "https":
+        c = httplib.HTTPSConnection(host, port)
+    else:
+        raise ValueError("unknown scheme '%s', need http or https" % scheme)
+    c.putrequest(method, path)
+    c.putheader("Hostname", host)
+    c.putheader("User-Agent", "tahoe_cli/%s" % allmydata.__version__)
+    c.putheader("Connection", "close")
+
+    old = body.tell()
+    body.seek(0, 2)
+    length = body.tell()
+    body.seek(old)
+    c.putheader("Content-Length", str(length))
+    c.endheaders()
+
+    while True:
+        data = body.read(8192)
+        if not data:
+            break
+        c.send(data)
+
+    return c.getresponse()
diff --git a/src/allmydata/scripts/tahoe_mv.py b/src/allmydata/scripts/tahoe_mv.py
index 4020c6e8..2bb49c4e 100644
--- a/src/allmydata/scripts/tahoe_mv.py
+++ b/src/allmydata/scripts/tahoe_mv.py
@@ -1,44 +1,9 @@
 #! /usr/bin/python
 
 import re
-import urllib, httplib
-import urlparse
+import urllib
 import simplejson
-
-# copied from twisted/web/client.py
-def _parse(url, defaultPort=None):
-    url = url.strip()
-    parsed = urlparse.urlparse(url)
-    scheme = parsed[0]
-    path = urlparse.urlunparse(('','')+parsed[2:])
-    if defaultPort is None:
-        if scheme == 'https':
-            defaultPort = 443
-        else:
-            defaultPort = 80
-    host, port = parsed[1], defaultPort
-    if ':' in host:
-        host, port = host.split(':')
-        port = int(port)
-    if path == "":
-        path = "/"
-    return scheme, host, port, path
-
-def do_http(method, url, body=""):
-    scheme, host, port, path = _parse(url)
-    if scheme == "http":
-        c = httplib.HTTPConnection(host, port)
-    elif scheme == "https":
-        c = httplib.HTTPSConnection(host, port)
-    else:
-        raise ValueError("unknown scheme '%s', need http or https" % scheme)
-    c.putrequest(method, path)
-    import allmydata
-    c.putheader("User-Agent", "tahoe_mv/%s" % allmydata.__version__)
-    c.putheader("Content-Length", str(len(body)))
-    c.endheaders()
-    c.send(body)
-    return c.getresponse()
+from allmydata.scripts.common_http import do_http
 
 def mv(nodeurl, root_uri, frompath, topath, stdout, stderr):
     if nodeurl[-1] != "/":
@@ -48,6 +13,9 @@ def mv(nodeurl, root_uri, frompath, topath, stdout, stderr):
 
     nodetype, attrs = simplejson.loads(data)
     uri = attrs.get("rw_uri") or attrs["ro_uri"]
+    # simplejson always returns unicode, but we know that it's really just a
+    # bytestring.
+    uri = str(uri)
 
     put_url = url + topath + "?t=uri"
     resp = do_http("PUT", put_url, uri)
diff --git a/src/allmydata/scripts/tahoe_put.py b/src/allmydata/scripts/tahoe_put.py
index e2b0feee..244e5e2a 100644
--- a/src/allmydata/scripts/tahoe_put.py
+++ b/src/allmydata/scripts/tahoe_put.py
@@ -1,8 +1,7 @@
 #!/usr/bin/env python
 
-import re, socket, urllib
-
-NODEURL_RE=re.compile("http://([^:]*)(:([1-9][0-9]*))?")
+import urllib
+from allmydata.scripts.common_http import do_http
 
 def put(nodeurl, root_uri, local_fname, vdrive_fname, verbosity,
         stdout, stderr):
@@ -11,66 +10,21 @@ def put(nodeurl, root_uri, local_fname, vdrive_fname, verbosity,
 
     @return: a Deferred which eventually fires with the exit code
     """
-    mo = NODEURL_RE.match(nodeurl)
-    host = mo.group(1)
-    port = int(mo.group(3))
-
-    url = "/uri/%s/" % urllib.quote(root_uri.replace("/","!"))
+    if nodeurl[-1] != "/":
+        nodeurl += "/"
+    url = nodeurl + "uri/%s/" % urllib.quote(root_uri.replace("/","!"))
     if vdrive_fname:
         url += vdrive_fname
 
     infileobj = open(local_fname, "rb")
-    infileobj.seek(0, 2)
-    infilelen = infileobj.tell()
-    infileobj.seek(0, 0)
-
-    so = socket.socket()
-    so.connect((host, port,))
-
-    CHUNKSIZE=2**16
-    data = "PUT %s HTTP/1.1\r\nConnection: close\r\nContent-Length: %s\r\nHostname: %s\r\n\r\n" % (url, infilelen, host,)
-    while data:
-        try:
-            sent = so.send(data)
-        except Exception, le:
-            print >>stderr, "got socket error: %s" % (le,)
-            return -1
-
-        if sent == len(data):
-            data = infileobj.read(CHUNKSIZE)
-        else:
-            data = data[sent:]
+    resp = do_http("PUT", url, infileobj)
 
-    respbuf = []
-    data = so.recv(CHUNKSIZE)
-    while data:
-        respbuf.append(data)
-        data = so.recv(CHUNKSIZE)
+    if resp.status in (200, 201,):
+        print >>stdout, "%s %s" % (resp.status, resp.reason)
+        return 0
 
-    so.shutdown(socket.SHUT_WR)
-
-    data = so.recv(CHUNKSIZE)
-    while data:
-        respbuf.append(data)
-        data = so.recv(CHUNKSIZE)
-
-    respstr = ''.join(respbuf)
-
-    headerend = respstr.find('\r\n\r\n')
-    if headerend == -1:
-        headerend = len(respstr)
-    header = respstr[:headerend]
-    RESP_RE=re.compile("^HTTP/[0-9]\.[0-9] ([0-9]*) *([A-Za-z_ ]*)")  # This regex is soooo ad hoc...  --Zooko 2007-08-16
-    mo = RESP_RE.match(header)
-    if mo:
-        code = int(mo.group(1))
-        word = mo.group(2)
-
-        if code in (200, 201,):
-            print >>stdout, "%s %s" % (code, word,)
-            return 0
-    
-    print >>stderr, respstr[headerend:]
+    print >>stderr, "error, got %s %s" % (resp.status, resp.reason)
+    print >>stderr, resp.read()
     return 1
 
 def main():
diff --git a/src/allmydata/scripts/tahoe_rm.py b/src/allmydata/scripts/tahoe_rm.py
index ee2acfe6..abc58542 100644
--- a/src/allmydata/scripts/tahoe_rm.py
+++ b/src/allmydata/scripts/tahoe_rm.py
@@ -1,8 +1,7 @@
 #!/usr/bin/env python
 
-import re, socket, urllib
-
-NODEURL_RE=re.compile("http://([^:]*)(:([1-9][0-9]*))?")
+import urllib
+from allmydata.scripts.common_http import do_http
 
 def rm(nodeurl, root_uri, vdrive_pathname, verbosity, stdout, stderr):
     """
@@ -10,51 +9,20 @@ def rm(nodeurl, root_uri, vdrive_pathname, verbosity, stdout, stderr):
 
     @return: a Deferred which eventually fires with the exit code
     """
-    mo = NODEURL_RE.match(nodeurl)
-    host = mo.group(1)
-    port = int(mo.group(3))
-
-    url = "/uri/%s/" % urllib.quote(root_uri.replace("/","!"))
+    if nodeurl[-1] != "/":
+        nodeurl += "/"
+    url = nodeurl + "uri/%s/" % urllib.quote(root_uri.replace("/","!"))
     if vdrive_pathname:
         url += vdrive_pathname
 
-    so = socket.socket()
-    so.connect((host, port,))
-
-    CHUNKSIZE=2**16
-    data = "DELETE %s HTTP/1.1\r\nConnection: close\r\nHostname: %s\r\n\r\n" % (url, host,)
-    sent = so.send(data)
-
-    respbuf = []
-    data = so.recv(CHUNKSIZE)
-    while data:
-        respbuf.append(data)
-        data = so.recv(CHUNKSIZE)
+    resp = do_http("DELETE", url)
 
-    so.shutdown(socket.SHUT_WR)
+    if resp.status in (200,):
+        print >>stdout, "%s %s" % (resp.status, resp.reason)
+        return 0
 
-    data = so.recv(CHUNKSIZE)
-    while data:
-        respbuf.append(data)
-        data = so.recv(CHUNKSIZE)
-
-    respstr = ''.join(respbuf)
-
-    headerend = respstr.find('\r\n\r\n')
-    if headerend == -1:
-        headerend = len(respstr)
-    header = respstr[:headerend]
-    RESP_RE=re.compile("^HTTP/[0-9]\.[0-9] ([0-9]*) *([A-Za-z_ ]*)")  # This regex is soooo ad hoc...  --Zooko 2007-08-16
-    mo = RESP_RE.match(header)
-    if mo:
-        code = int(mo.group(1))
-        word = mo.group(2)
-
-        if code == 200:
-            print >>stdout, "%s %s" % (code, word,)
-            return 0
-    
-    print >>stderr, respstr[headerend:]
+    print >>stderr, "error, got %s %s" % (resp.status, resp.reason)
+    print >>stderr, resp.read()
     return 1
 
 def main():
-- 
2.45.2