
local CL_LIMIT, CL_LIMIT_OVERRIDE, CL_NO_CLENGTH

if CLIENT then
	CL_LIMIT = CreateConVar("pac_webcontent_limit", "-1", {FCVAR_ARCHIVE}, "webcontent limit in kb, -1 = unlimited")
	CL_NO_CLENGTH = CreateConVar("pac_webcontent_allow_no_content_length", "0", {FCVAR_ARCHIVE}, "allow downloads with no content length")
	CL_LIMIT_OVERRIDE = CreateConVar("pac_webcontent_limit_force", "0", {FCVAR_ARCHIVE}, "Override serverside setting")
end

local SV_LIMIT = CreateConVar("sv_pac_webcontent_limit", "-1", CLIENT and {FCVAR_REPLICATED} or {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "webcontent limit in kb, -1 = unlimited")
local SV_NO_CLENGTH = CreateConVar("sv_pac_webcontent_allow_no_content_length", "-1", CLIENT and {FCVAR_REPLICATED} or {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "allow downloads with no content length")

function pac.FixGMODUrl(url)
	-- to avoid "invalid url" errors
	-- gmod does not allow urls containing "10.", "172.16.", "192.168.", "127." or "://localhost"
	-- we escape 10. and 127. can occur (mydomain.com/model10.zip) and assume the server supports
	-- the escaped request
	return url:Replace("10.", "%31%30%2e"):Replace("127.", "%31%32%37%2e")
end

local function http(method, url, headers, cb, failcb)

	url = pac.FixGMODUrl(url)

	return HTTP({
		method = method,
		url = url,
		headers = headers,
		success = function(code, data, headers)
			if code < 400 then
				cb(data, #data, headers)
			else
				local header = {}
				for k,v in pairs(headers) do
					table.insert(header, tostring(k) .. ": " .. tostring(v))
				end

				local err = "server returned code " .. code .. ":\n\n"
				err = err .. "url: "..url.."\n"
				err = err .. "================\n"

				err = err .. "HEADER:\n"
				err = err .. table.concat(header, "\n") .. "\n"

				err = err .. "================\n"

				err = err .. "BODY:\n"
				err = err .. data .. "\n"

				err = err .. "================\n"
				failcb(err, code >= 400, code)
			end
		end,
		failed = function(err)
			if failcb then
				failcb("_G.HTTP error: " .. err)
			else
				pac.Message("_G.HTTP error: " .. err)
			end
		end
	})
end

function pac.FixUrl(url)
	url = url:Trim()
	url = url:gsub("[\"'<>\n\\]+", "")

	if url:find("dropbox", 1, true) then
		url = url:gsub([[^http%://dl%.dropboxusercontent%.com/]], [[https://dl.dropboxusercontent.com/]])
		url = url:gsub([[^https?://dl.dropbox.com/]], [[https://www.dropbox.com/]])
		url = url:gsub([[^https?://www.dropbox.com/s/(.+)%?dl%=[01]$]], [[https://dl.dropboxusercontent.com/s/%1]])
		url = url:gsub([[^https?://www.dropbox.com/s/(.+)$]], [[https://dl.dropboxusercontent.com/s/%1]])
		url = url:gsub([[^https?://www.dropbox.com/scl/(.+)$]], [[https://dl.dropboxusercontent.com/scl/%1]]) --Fix for new dropbox format.
		return url
	end

	if url:find("drive.google.com", 1, true) and not url:find("export=download", 1, true) then
		local id =
			url:match("https://drive.google.com/file/d/(.-)/") or
			url:match("https://drive.google.com/file/d/(.-)$") or
			url:match("https://drive.google.com/open%?id=(.-)$")

		if id then
			return "https://drive.google.com/uc?export=download&id=" .. id
		end
		return url
	end

	if url:find("gitlab.com", 1, true) then
		return url:gsub("^(https?://.-/.-/.-/)blob", "%1raw")
	end

	url = url:gsub([[^http%://onedrive%.live%.com/redir?]],[[https://onedrive.live.com/download?]])
	url = url:gsub("pastebin.com/([a-zA-Z0-9]*)$", "pastebin.com/raw.php?i=%1")
	url = url:gsub("github.com/([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/blob/", "github.com/%1/%2/raw/")

	return url
end

function pac.getContentLength(url, cb, failcb)
	return http("HEAD", url, {["Accept-Encoding"] = "none"}, function(_, _, headers)
		local length

		-- server have rights to send headers in any case
		for key, value in pairs(headers) do
			if string.lower(key) == "content-length" then
				length = tonumber(value)

				--we started to get stuff like "7182199,0" from Google Drive, triggering the failstate because tonumber doesn't like varargs
				if not length then
					if string.find(value, ",") then
						local args = string.Split(value, ",")
						if args[1] then
							length = tonumber(args[1])
						end
					end
				end

				if not length or math.floor(length) ~= length then
					return failcb(string.format("malformed server reply with header content-length (got %q, expected valid integer number)", value), true)
				end

				break
			end
		end

		if length then return cb(length) end

		return pac.contentLengthFallback(url, cb, failcb)
	end, function(err, over400, code)
		if code == 405 then
			return pac.contentLengthFallback(url, cb, failcb)
		end

		return failcb(err, over400)
	end )
end

-- Performs a GET but requests 0 bytes
-- We can then read the response headers to determine the content size.
-- This allows Google Drive and other hosts to work with PAC even with content-length limits set
-- (They typically block HEAD requests)
function pac.contentLengthFallback(url, cb, failcb)
	local function fail()
		return failcb("unable to determine content length", true)
	end

	return http("GET", url, {["Range"] = "bytes=0-0"}, function(data, data_length, headers)
		-- e.g. "bytes 0-0/11784402"
		local contentRange = headers["Content-Range"]
		if not contentRange then return fail() end

		local spl = string.Split(contentRange, "/")
		local contentLength = spl[2]

		if contentLength then return cb(tonumber(contentLength)) end

		return fail()
	end )
end

function pac.HTTPGet(url, cb, failcb)
	if not url or url:len() < 4 then
		failcb("url length is less than 4 (" .. tostring(url) .. ")", true)
		return
	end

	url = pac.FixUrl(url)

	local limit = SV_LIMIT:GetInt()

	if CLIENT and (CL_LIMIT_OVERRIDE:GetBool() or limit == -1) then
		limit = CL_LIMIT:GetInt()
	end

	if limit == -1 then
		return http("GET", url, nil, cb, failcb)
	end

	return pac.getContentLength(url, function(length)
		if length then
			if length <= (limit * 1024) then
				http("GET", url, nil, cb, failcb)
			else
				failcb("download is too big (" .. string.NiceSize(length) .. ")", true)
			end
		else
			local allow_no_contentlength = SV_NO_CLENGTH:GetInt()

			if CLIENT and (CL_LIMIT_OVERRIDE:GetBool() or allow_no_contentlength < 0) then
				allow_no_contentlength = CL_NO_CLENGTH:GetInt()
			end

			if allow_no_contentlength > 0 then
				http("GET", url, nil, cb, failcb)
			else
				failcb("unknown file size when allow_no_contentlength is " .. allow_no_contentlength, true)
			end
		end
	end, failcb)
end
