Changes to allow curl to follow redirects when retreiving an Apple sucatalog; this is needed to support Lion Server's Software Update service.

This commit is contained in:
Greg Neagle
2012-02-14 16:51:34 -08:00
parent 5489fb5dc3
commit 34ab750460
5 changed files with 67 additions and 45 deletions
+3 -1
View File
@@ -653,7 +653,9 @@ def main():
appleupdatesavailable = \
appleupdates.appleSoftwareUpdatesAvailable(
forcecheck=(options.manualcheck or
runtype == 'checkandinstallatstartup'))
runtype == 'checkandinstallatstartup' or
(runtype == 'custom' and
applesoftwareupdatesonly)))
except:
munkicommon.display_error('Unexpected error in appleupdates:')
munkicommon.display_error(traceback.format_exc())
+2 -1
View File
@@ -260,7 +260,8 @@ class AppleUpdates(object):
url,
destinationpath,
custom_headers=[user_agent_header],
resume=resume)
resume=resume,
follow_redirects=True)
def CacheUpdateMetadata(self):
"""Copies ServerMetadata (.smd), Metadata (.pkm), and
+59 -36
View File
@@ -61,6 +61,10 @@ class CurlDownloadError(MunkiDownloadError):
class FileCopyError(MunkiDownloadError):
"""Download failed because of file copy errors."""
pass
class PackageVerificationError(MunkiDownloadError):
"""Package failed verification"""
pass
def getxattr(pathname, attr):
@@ -86,7 +90,7 @@ def writeCachedChecksum(file_path, fhash=None):
WARNINGSLOGGED = {}
def curl(url, destinationpath,
cert_info=None, custom_headers=None, donotrecurse=False, etag=None,
message=None, onlyifnewer=False, resume=False):
message=None, onlyifnewer=False, resume=False, follow_redirects=False):
"""Gets an HTTP or HTTPS URL and stores it in
destination path. Returns a dictionary of headers, which includes
http_result_code and http_result_description.
@@ -123,6 +127,10 @@ def curl(url, destinationpath,
print >> fileobj, 'ciphers = HIGH,!ADH' #use only secure >=128 bit SSL
print >> fileobj, 'url = "%s"' % url
munkicommon.display_debug2('follow_redirects is %s', follow_redirects)
if follow_redirects:
print >> fileobj, 'location' # follow redirects
if cert_info:
cacert = cert_info.get('cacert')
capath = cert_info.get('capath')
@@ -203,6 +211,7 @@ def curl(url, destinationpath,
if not donewithheaders:
info = proc.stdout.readline().strip('\r\n')
if info:
munkicommon.display_debug2(info)
if info.startswith('HTTP/'):
header['http_result_code'] = info.split(None, 2)[1]
header['http_result_description'] = info.split(None, 2)[2]
@@ -212,37 +221,46 @@ def curl(url, destinationpath,
header[fieldname] = part[1]
else:
# we got an empty line; end of headers (or curl exited)
donewithheaders = True
try:
# Prefer Content-Length header to determine download size,
# otherwise fall back to a custom X-Download-Size header.
# This is primary for servers that use chunked transfer
# encoding, when Content-Length is forbidden by
# RFC2616 4.4. An example of such a server is
# Google App Engine Blobstore.
targetsize = (
header.get('content-length') or
header.get('x-download-size'))
targetsize = int(targetsize)
except (ValueError, TypeError):
targetsize = 0
if header.get('http_result_code') == '206':
# partial content because we're resuming
munkicommon.display_detail(
'Resuming partial download for %s' %
os.path.basename(destinationpath))
contentrange = header.get('content-range')
if contentrange.startswith('bytes'):
try:
targetsize = int(contentrange.split('/')[1])
except (ValueError, TypeError):
targetsize = 0
if follow_redirects:
if header.get('http_result_code') in ['301', '302', '303']:
# redirect, so more headers are coming.
# Throw away the headers we've received so far
header = {}
header['http_result_code'] = '000'
header['http_result_description'] = ''
else:
donewithheaders = True
try:
# Prefer Content-Length header to determine download
# size, otherwise fall back to a custom X-Download-Size
# header.
# This is primary for servers that use chunked transfer
# encoding, when Content-Length is forbidden by
# RFC2616 4.4. An example of such a server is
# Google App Engine Blobstore.
targetsize = (
header.get('content-length') or
header.get('x-download-size'))
targetsize = int(targetsize)
except (ValueError, TypeError):
targetsize = 0
if header.get('http_result_code') == '206':
# partial content because we're resuming
munkicommon.display_detail(
'Resuming partial download for %s' %
os.path.basename(destinationpath))
contentrange = header.get('content-range')
if contentrange.startswith('bytes'):
try:
targetsize = int(contentrange.split('/')[1])
except (ValueError, TypeError):
targetsize = 0
if message and header.get('http_result_code') != '304':
if message:
# log always, display if verbose is 1 or more
# also display in MunkiStatus detail field
munkicommon.display_status_minor(message)
if message and header.get('http_result_code') != '304':
if message:
# log always, display if verbose is 1 or more
# also display in MunkiStatus detail field
munkicommon.display_status_minor(message)
elif targetsize and header.get('http_result_code').startswith('2'):
# display progress if we get a 2xx result code
@@ -258,6 +276,7 @@ def curl(url, destinationpath,
else:
# Headers have finished, but not targetsize or HTTP2xx.
# It's possible that Content-Length was not in the headers.
# so just sleep and loop again. We can't show progress.
time.sleep(0.1)
if (proc.poll() != None):
@@ -305,7 +324,8 @@ def curl(url, destinationpath,
etag=etag,
message=message,
onlyifnewer=onlyifnewer,
resume=resume)
resume=resume,
follow_redirects=follow_redirects)
elif retcode == 22:
# TODO: Made http(s) connection but 400 series error.
# What should we do?
@@ -387,7 +407,8 @@ def getResourceIfChangedAtomically(url,
expected_hash=None,
message=None,
resume=False,
verify=False):
verify=False,
follow_redirects=False):
"""Gets file from a URL.
Checks first if there is already a file with the necessary checksum.
Then checks if the file has changed on the server, resuming or
@@ -428,7 +449,7 @@ def getResourceIfChangedAtomically(url,
changed = getHTTPfileIfChangedAtomically(
url, destinationpath,
cert_info=cert_info, custom_headers=custom_headers,
message=message, resume=resume)
message=message, resume=resume, follow_redirects=follow_redirects)
elif url_parse.scheme == 'file':
changed = getFileIfChangedAtomically(url_parse.path, destinationpath)
else:
@@ -507,7 +528,8 @@ def getFileIfChangedAtomically(path, destinationpath):
def getHTTPfileIfChangedAtomically(url, destinationpath,
cert_info=None, custom_headers=None,
message=None, resume=False):
message=None, resume=False,
follow_redirects=False):
"""Gets file from HTTP URL, checking first to see if it has changed on the
server.
@@ -533,7 +555,8 @@ def getHTTPfileIfChangedAtomically(url, destinationpath,
etag=etag,
message=message,
onlyifnewer=getonlyifnewer,
resume=resume)
resume=resume,
follow_redirects=follow_redirects)
except CurlError, err:
err = 'Error %s: %s' % tuple(err)
+2 -6
View File
@@ -659,10 +659,6 @@ def getInstalledVersion(item_plist):
# if we fall through to here we have no idea what version we have
return 'UNKNOWN'
class PackageVerificationError(fetch.MunkiDownloadError):
"""Download failed because it could not be verified"""
pass
def download_installeritem(item_pl, installinfo, uninstalling=False):
"""Downloads an (un)installer item.
@@ -1524,7 +1520,7 @@ def processInstall(manifestitem, cataloglist, installinfo):
cataloglist,
installinfo)
return True
except PackageVerificationError:
except fetch.PackageVerificationError:
munkicommon.display_warning(
'Can\'t install %s because the integrity check failed.'
% manifestitem)
@@ -1940,7 +1936,7 @@ def processRemoval(manifestitem, cataloglist, installinfo):
iteminfo['uninstaller_item'] = filename
iteminfo['adobe_package_name'] = \
uninstall_item.get('adobe_package_name','')
except PackageVerificationError:
except fetch.PackageVerificationError:
munkicommon.display_warning(
'Can\'t uninstall %s because the integrity check '
'failed.' % iteminfo['name'])