if [ ! "$_MEDIA_HTTPPROXY_SUBR" ]; then _MEDIA_HTTPPROXY_SUBR=1
#
# Copyright (c) 2012-2013 Devin Teske
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $FreeBSD$
#
############################################################ INCLUDES

BSDCFG_SHARE="/usr/share/bsdconfig"
. $BSDCFG_SHARE/common.subr || exit 1
f_dprintf "%s: loading includes..." media/httpproxy.subr
f_include $BSDCFG_SHARE/dialog.subr
f_include $BSDCFG_SHARE/media/ftp.subr
f_include $BSDCFG_SHARE/media/tcpip.subr
f_include $BSDCFG_SHARE/variable.subr

BSDCFG_LIBE="/usr/libexec/bsdconfig"
f_include_lang $BSDCFG_LIBE/include/messages.subr

############################################################ FUNCTIONS

# f_media_set_http_proxy
#
# Return success if we both found and set the media type to be an ftp server,
# accessed via http proxy.
#
# Variables from variable.subr that can be used to script user input:
#
# 	VAR_HTTP_PROXY
# 		HTTP Proxy server to use. Valid examples include:
# 			myhost
# 			somename:3128
# 			192.168.2.3
# 			[::1]:8080
# 		The default port if not specified is 3128.
#
# Variables from variable.subr that are set after successful execution include
# the following:
#
# 	VAR_HTTP_PROXY_HOST	The host portion of VAR_HTTP_PROXY.
# 	VAR_HTTP_PROXY_PORT	The TCP port parsed from VAR_HTTP_PROXY.
#
# See also f_media_set_ftp() for additional variables.
#
f_media_set_http_proxy()
{
	FTP_SKIP_RESOLV=1 f_media_set_ftp || return $FAILURE

	f_variable_get_value $VAR_HTTP_PROXY \
		"$msg_please_enter_the_address_of_the_http_proxy"

	local proxy
	f_getvar $VAR_HTTP_PROXY proxy
	[ "$proxy" ] || return $FAILURE

	local hostname="$proxy" port=3128
	case "$hostname" in
	#
	# The order in-which the below individual cases appear is important!
	#
	"["*"]":*) # IPv6 address with port
		f_dprintf "Looks like an IPv6 addr with port: %s" "$hostname"
		hostname="${hostname#\[}"
		port="${hostname#*\]:}"
		port="${port%%[!0-9]*}"
		hostname="${hostname%%\]:*}"
		;;
	"["*"]") # IPv6 address
		f_dprintf "Looks like an IPv6 addr: %s" "$hostname"
		hostname="${hostname#\[}"
		hostname="${hostname%\]}"
		;;
	#
	# ^^^ IPv6 above / DNS Name or IPv4 below vvv
	#
	*:*) # DNS name or IPv4 address with port
		f_dprintf "Looks like a DNS name or IPv4 addr with port: %s" \
		          "$hostname"
		port="${hostname#*:}"
		hostname="${hostname%%:*}"
		;;
	*) # DNS name or IPv4 address
		f_dprintf "Looks like a DNS name or IPv4 addr: %s" "$hostname"
		: leave hostname as-is
	esac

	setvar $VAR_HTTP_PROXY_HOST "$hostname"
	setvar $VAR_HTTP_PROXY_PORT "$port"

	if f_debugging; then
		f_dprintf "VAR_FTP_PATH : %s" "$( f_getvar $VAR_FTP_PATH )"
		f_dprintf "VAR_HTTP_PROXY_HOST, _PORT: %s:%s" \
		          "$( f_getvar $VAR_HTTP_PROXY_HOST )" \
		          "$( f_getvar $VAR_HTTP_PROXY_PORT )"
	fi

	# media device has been set by f_media_set_ftp(), overwrite partly:
	device_media set   type     $DEVICE_TYPE_HTTP_PROXY
	device_media set   init     f_media_init_http_proxy
	device_media set   get      f_media_get_http_proxy
	device_media unset shutdown

	return $SUCCESS
}

# f_http_proxy_check_access [$connect_only]
#
# Return success if able list a remote FTP directory via HTTP proxy. If
# $connect_only is present and non-null, then returns success if a connection
# can be made. Variables from variable.subr that can be used to script user
# input:
#
# 	VAR_HTTP_PROXY_HOST
# 		The HTTP proxy server host name, IPv4 address or IPv6 address.
# 		Valid examples include:
# 			myhost
# 			192.168.2.3
# 			::1
# 	VAR_HTTP_PROXY_PORT
# 		The TCP port to connect to when communicating with the HTTP
# 		proxy server.
# 	VAR_HTTP_PROXY_PATH
# 		The FTP URL sent to the HTTP proxy server. Unused if
# 		$connect_only is present and non-NULL.
#
f_http_proxy_check_access()
{
	local connect_only="$1" hosts=

	local proxy_host proxy_port
	f_getvar $VAR_HTTP_PROXY_HOST proxy_host
	f_getvar $VAR_HTTP_PROXY_PORT proxy_port

	if ! {
		f_validate_ipaddr "$proxy_host" ||
		f_validate_ipaddr6 "$proxy_host" ||
		{
		  f_dprintf "%s: Looking up hostname, %s, using host(1)" \
		            "f_http_proxy_check_access" "$proxy_host"
		  f_host_lookup "$proxy_host" hosts
		}
	}; then
		# All the above validations failed
		[ "$hosts" ] && f_dialog_msgbox "$hosts"
		unset $VAR_HTTP_PROXY_HOST
		return $FAILURE
	elif [ ! "$hosts" ]; then
		# One of the first two validations passed
		hosts="$proxy_host"
	fi

	local host connected=
	for host in $hosts; do
		f_quietly nc -nz "$host" "$proxy_port" || continue
		connected=1; break
	done
	if [ ! "$connected" ]; then
		f_show_msg "$msg_couldnt_connect_to_proxy %s:%s" \
		           "$proxy_host" "$proxy_port"
		unset $VAR_HTTP_PROXY_HOST
		return $FAILURE
	fi
	[ "$connect_only" ] && return $SUCCESS

	#
	# Some proxies fetch files with certain extensions in "ascii mode"
	# instead of "binary mode" for FTP. The FTP server then translates all
	# LF to CRLF.
	#
	# You can force Squid to use binary mode by appending ";type=i" to the
	# URL, which is what sysinstall(8) has traditionally done.
	#

	local proxy_path
	f_getvar $VAR_HTTP_PROXY_PATH proxy_path
	f_show_info "$msg_checking_access_to" "$proxy_path"

	local rx
	if ! rx=$(
		printf "GET %s/ HTTP/1.0\r\n\r\n" "${proxy_path%/}" |
			nc -n "$host" "$proxy_port"
	); then
		f_show_msg "$msg_couldnt_connect_to_proxy %s:%s" \
		           "$proxy_host" "$proxy_port"
		unset $VAR_HTTP_PROXY_HOST
		return $FAILURE
	fi

	local hdr
	hdr=$( echo "$rx" | awk '/^\r$/{exit}{print}' )

	local http_found=$FAILURE
	if echo "$hdr" | awk '
		BEGIN { found = 0 }
		/^HTTP.... 200 / {
			found = 1
			exit
		}
		END { exit ! found }
	'; then
		http_found=$SUCCESS
	fi

	#
	# Scan the headers of the response
	# this is extremely quick'n dity
	#

	unset $VAR_HTTP_FTP_MODE
	if echo "$hdr" | awk '
		BEGIN { found = 0 }
		{
			if (!match($0, /^Server: /)) next
			found = ( substr($0, 9, 5) ~ /[Ss]quid/ )
		}
		END { exit ! found }
	'; then
		setvar $VAR_HTTP_FTP_MODE ";type=i"
	else
		setvar $VAR_HTTP_FTP_MODE ""
	fi

	return $http_found
}

# f_media_init_http_proxy $device
#
# Initializes the HTTP Proxy media device. Returns success if able to confirm
# the existence of at least one known FTP server release path via HTTP proxy
# using f_http_proxy_check_access(), above.
#
# Variables from variable.subr that can be used to script user input:
#
# 	VAR_HTTP_PROXY_HOST
#		The HTTP proxy server to connect to. Usually set by having
# 		f_media_set_http_proxy() parse VAR_HTTP_PROXY. Must be set.
# 		Also see f_http_proxy_check_access() for additional variables.
# 	VAR_RELNAME
# 		Usually set to `uname -r' but can be overridden.
# 	VAR_FTP_PATH
# 		The FTP URL to send to the HTTP proxy server. Usually set by
# 		calling f_media_set_ftp().
#
# Meanwhile, after successful execution, the following variables (also from
# variable.subr) are set:
#
# 	VAR_HTTP_PROXY_PATH
# 		The [possibly] adjusted VAR_FTP_PATH that was found to contain
# 		a valid FreeBSD repository.
#
f_media_init_http_proxy()
{
	local dev="$1"
	f_dprintf "Init routine called for HTTP Proxy device. dev=[%s]" "$dev"

	#
	# First verify access
	#
	local connect_only=1
	f_http_proxy_check_access $connect_only

	local proxy_host
	f_getvar $VAR_HTTP_PROXY_HOST proxy_host
	while [ ! "$proxy_host" ]; do
		f_media_set_http_proxy || return $FAILURE
		f_http_proxy_check_access $connect_only
		f_getvar $VAR_HTTP_PROXY_HOST proxy_host
	done

	local rel proxy_path http_found=$FAILURE
	while :; do
		#
		# If the release is specified as "__RELEASE" or "any", then
		# just assume that the path the user gave is ok.
		#
		f_getvar $VAR_RELNAME rel
		f_dprintf "f_media_init_http_proxy: rel=[%s]" "$rel"

		case "$rel" in
		__RELEASE|any)
			f_getvar $VAR_FTP_PATH $VAR_HTTP_PROXY_PATH
			f_http_proxy_check_access
			http_found=$?
			;;
		*)
			local fdir fp
			f_getvar $VAR_FTP_PATH%/ fp
			for fdir in $FTP_DIRS; do
				setvar $VAR_HTTP_PROXY_PATH "$fp/$fdir/$rel"
				if f_http_proxy_check_access; then
					http_found=$SUCCESS
					break
				fi
			done
		esac

		[ $http_found -eq $SUCCESS ] && break

		f_getvar $VAR_HTTP_PROXY_PATH proxy_path
		f_show_msg "$msg_please_check_the_url_and_try_again" \
		           "$proxy_path"

		unset $VAR_HTTP_PROXY_PATH
		f_media_set_http_proxy || break
	done

	return $http_found
}

# f_media_get_http_proxy $device $file [$probe_type]
#
# Returns data from $file on an FTP server via HTTP proxy using nc(1). Please
# note that $device is unused but must be present (even if null). Information
# is instead gathered from the environment. If $probe_type is both present and
# non-NULL, this function exits after receiving the HTTP header response from
# the proxy server (if the HTTP response code is 200, success is returned;
# otherwise failure). If $probe_type is equal to $PROBE_SIZE, prints the
# content-length in bytes from the response (or -1 if not found) to standard-
# out.
#
# The variables used to configure the connection are as follows (all of which
# are configured by f_media_set_http_proxy above):
#
# 	VAR_HTTP_PROXY_HOST
# 		HTTP proxy host to connect. Can be an IPv4 address, IPv6
# 		address, or DNS hostname of your choice.
# 	VAR_HTTP_PROXY_PORT
# 		TCP port to connect on; see f_media_set_http_proxy above.
# 	VAR_HTTP_PROXY_PATH
# 		URL (including "ftp://" protocol-prefix) of FTP directory to
# 		use as a prefix when requesting $file via HTTP proxy.
#
# See variable.subr for additional information.
#
# Example usage:
# 	f_media_set_http_proxy
# 	f_media_get_http_proxy media $file
#
f_media_get_http_proxy()
{
	local dev="$1" file="$2" probe_type="$3" hosts=

	f_dprintf "f_media_get_http_proxy: dev=[%s] file=[%s] probe_type=%s" \
	          "$dev" "$file" "$probe_type"

	local proxy_host proxy_port
	f_getvar $VAR_HTTP_PROXY_HOST proxy_host
	f_getvar $VAR_HTTP_PROXY_PORT proxy_port

	if ! {
		f_validate_ipaddr "$proxy_host" ||
		f_validate_ipaddr6 "$proxy_host" ||
		{
		  f_dprintf "%s: Looking up hostname, %s, using host(1)" \
		            "f_media_get_http_proxy" "$proxy_host"
		  f_host_lookup "$proxy_host" hosts
		}
	}; then
		# All the above validations failed
		[ "$hosts" ] && f_dialog_msgbox "$hosts"
		return $FAILURE
	elif [ ! "$hosts" ]; then
		# One of the first two validations passed
		hosts="$proxy_host"
	fi

	local host connected=
	for host in $hosts; do
		f_quietly nc -nz "$host" "$proxy_port" || continue
		connected=1; break
	done
	if [ ! "$connected" ]; then
		f_show_msg "$msg_couldnt_connect_to_proxy %s:%s" \
		           "$proxy_host" "$proxy_port"
		return $FAILURE
	fi

	local proxy_path mode
	f_getvar $VAR_HTTP_PROXY_PATH%/ proxy_path
	f_getvar $VAR_HTTP_FTP_MODE mode
	local url="$proxy_path/$file$mode" rx

	f_dprintf "sending http request for: %s" "$url"
	printf "GET %s HTTP/1.0\r\n\r\n" "$url" | nc -n "$host" "$proxy_port" |
	(
		#
		# scan the headers of the response
		# this is extremely quick'n dirty
		#

		rv=0 length=-1
		while read LINE; do
			case "$LINE" in
			HTTP*)
				f_dprintf "received response: %s" "$LINE"
				set -- $LINE; rv=$2
				f_isinteger "$rv" || rv=0
				;;
			"Content-Length: "*)
				length="${LINE%
}"
				length="${length#Content-Length: }"
				f_dprintf "received content-length: %s" \
				          "$length"
				;;
			*)
				[ "${LINE%
}" ] || break # End of headers
			esac
		done

		[ $rv -ge 500 ] && exit 5
		[ $rv -eq 404 ] && exit 44
		[ $rv -ge 400 ] && exit 4
		[ $rv -ge 300 ] && exit 3
		[ $rv -eq 200 ] || exit $FAILURE

		if [ ! "$probe_type" ]; then
			cat # output the rest ``as-is''
		elif [ "$probe_type" = "$PROBE_SIZE" ]; then
			f_isinteger "$length" || length=-1
			echo "$length"
		fi
		exit 200
	)
	local retval=$?
	[ $retval -eq 200 ] && return $SUCCESS
	[ "$probe_type" ] && return $FAILURE

	case "$retval" in
	  5) f_show_msg "$msg_server_error_when_requesting_url" "$url" ;;
	 44) f_show_msg "$msg_url_was_not_found" "$url" ;;
	  4) f_show_msg "$msg_client_error" ;;
	  *) f_show_msg "$msg_error_when_requesting_url" "$url" ;;
	esac 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
	return $FAILURE
}

############################################################ MAIN

f_dprintf "%s: Successfully loaded." media/httpproxy.subr

fi # ! $_MEDIA_HTTPPROXY_SUBR