Hi Christoph,

Christoph Haas wrote:
> The files cream_0.39.orig.tar.gz and cream_0.39-1.diff.gz are uploaded
> without any delay in between. In other cases the server does not see any
> communication for ~15 seconds.

> It appears that dput always tries to upload a file without sending the
> credentials in the HTTP "Authorization" request header even though the
> user has entered the password already. dput bumps into the 401
> (unauthorized) error time and again for every file and tries again with
> the credentials. Perhaps this bug will fade away if the "Authorization"
> header is sent automatically if the password is known already. Seems
> like dput doesn't cope well with 401 responses and times out somewhere
> in the depths of the urllib2. :)
Indeed the problem seems to be uploading the file twice which leads to disaster
with large files.

I guess fixing this means thoroughly rewriting the http.py module. OTOH, this
bug and the general problem of having reading all of the file into memory before
sending is grave enough to warrant a timely fix. I have attached a preliminary
version of an improved (by ripping out most urllib2 stuff) http.py. Does that
work better for you?
I think it should work with current dput, even though I have modified my own
copy to get progress output for http, otherwise I'll put up an experimental dput
package later this year.

Kind regards

T.
-- 
Thomas Viehmann, http://thomas.viehmann.net/
import os, sys, httplib, urllib2, urlparse, getpass, dputhelper
# note: requires >= python 2.4 httplib

# Custom password manager that prompts for a password using getpass() if
# required, and mangles the saved URL so that only one password is prompted for.
class PromptingPasswordMgr(urllib2.HTTPPasswordMgr):
    def __init__(self, username):
        urllib2.HTTPPasswordMgr.__init__(self)
        self.username = username

    def find_user_password(self, realm, authuri):
        # Hack so that we only prompt for a password once
        authuri = self.reduce_uri(authuri)[0]
        authinfo = urllib2.HTTPPasswordMgr.find_user_password(self, realm, authuri)
        if authinfo != (None, None):
            return authinfo

        password = getpass.getpass("    Password for %s:" % realm)
        self.add_password(realm, authuri, self.username, password)
        return (self.username, password)


class AuthHandlerHackAround:
  # fake request and parent object...
  def __init__(self, url, resp_headers, pwman):
    # fake request header dict
    self.headers = {}
    # data
    self.url = url
    self.resp_headers = resp_headers
    self.authhandlers = []
    # digest untested
    for authhandler_class in [urllib2.HTTPBasicAuthHandler, urllib2.HTTPDigestAuthHandler]:
      ah = authhandler_class(pwman)
      ah.add_parent(self)
      self.authhandlers.append(ah)

  # fake request methods
  def add_header(self, k, v):
    self.headers[k] = v
  def get_full_url(self):
    return self.url
  # fake parent method
  def open(self,*args):
    pass

  # and what we really want
  def get_auth_headers(self):
    for ah in self.authhandlers:
      try:
	ah.http_error_401(self, None, 401, None, self.resp_headers)
      except ValueError, e:
	pass
      if self.headers:
	return self.headers
    return self.headers
  
# Upload the files via WebDAV
def upload(fqdn, login, incoming, files_to_upload, debug, dummy, progress=0, protocol="http"):
    # EXCEPTION HANDLING!
    if protocol == 'https':
      connclass = httplib.HTTPSConnection
    elif protocol == 'http':
      connclass = httplib.HTTPConnection
    else:
      print >> sys.stderr, "Wrong protocol for upload http[s].py method"
      sys.exit(1)
    if not incoming.startswith('/'):
      incoming = '/'+incoming
    if not incoming.endswith('/'):
      incoming += '/'
    unprocessed_files_to_upload = files_to_upload[:]
    auth_headers = {}
    pwman = PromptingPasswordMgr(login)
    while unprocessed_files_to_upload:
        afile = unprocessed_files_to_upload[0]
        path_to_package, package_name = os.path.split(afile)
	# print without linefeed
	sys.stdout.write("  %s: "% package_name)
	sys.stdout.flush()
	try:
	  size = os.stat(afile).st_size
	except:
	  print >> sys.stderr, "Determining size of file '%s' failed"%afile
	  sys.exit(1)
	f = open(afile,'r')
	if progress:
	  f = dputhelper.FileWithProgress(f, ptype=progress,
					  progressf=sys.stderr,
					  size=size)
	url_path = incoming+package_name
	url = "%s://%s%s" % (protocol,fqdn,url_path)
        if debug:
          print "D: HTTP-PUT to URL: %s"%url
	conn = connclass(fqdn)
	conn.putrequest("PUT", url_path, skip_accept_encoding=True)
	# Host: should be automatic
	conn.putheader('User-Agent','dput')
	for k, v in auth_headers.items():
	  conn.putheader(k, v)
	conn.putheader('Connection','close')
	conn.putheader('Content-Length',str(size))
	conn.endheaders()
	pos = 0
	while pos < size:
	  s = f.read(65536) # sending in 64k steps (screws progress a bit)
	  conn.send(s)
	  pos += len(s)
	s = ""
	res = conn.getresponse()
	if res.status >= 200 and res.status < 300:
	  print "done."
	  del unprocessed_files_to_upload[0]
	elif res.status == 401 and not auth_headers:
	  print "need authentication."
	  auth_headers = AuthHandlerHackAround(url, res.msg, pwman).get_auth_headers()
	else:
	  if res.status == 401:
	    print "Upload failed as unauthorized: %s"%res.msg
	    print "  Maybe wrong username or password?"
	  else:
	    print "Upload failed: %d %d"%res.status, res.msg
	  sys.exit(1)
	res.read() # must be done, but we're not interested

Reply via email to