--[[----------------------------------------------------------------------------

WFURI.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2012 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

----------------------------------------------------------------------------]]--

	-- WF.core.logging
local WFLogger = require 'WFLogger'

	-- WF.core.utils
local WFArguments = require 'WFArguments'


--------------------------------------------------------------------------------

local logger = WFLogger 'WFURI'
--	:setLevel 'debug'

local check = WFArguments.check


--============================================================================--
--[[!
    \brief This namespace provides basic URI manipulation and parsing routines.
]]--

-- @mark WFURI namespace

local WFURI = {}

--------------------------------------------------------------------------------
-- This implementation is based on a close reading of the following RFC:

--[[
		
		Network Working Group                                     T. Berners-Lee
		Request for Comments: 2396                                       MIT/LCS
		Updates: 1808, 1738                                          R. Fielding
		Category: Standards Track                                    U.C. Irvine
		                                                             L. Masinter
		                                                       Xerox Corporation
		                                                             August 1998
		
		
		           Uniform Resource Identifiers (URI): Generic Syntax

{snip}
		
		5.2. Resolving Relative References to Absolute Form
		
		   This section describes an example algorithm for resolving URI
		   references that might be relative to a given base URI.
		
		   The base URI is established according to the rules of Section 5.1 and
		   parsed into the four main components as described in Section 3.  Note
		   that only the scheme component is required to be present in the base
		   URI; the other components may be empty or undefined.  A component is
		   undefined if its preceding separator does not appear in the URI
		   reference; the path component is never undefined, though it may be
		   empty.  The base URI's query component is not used by the resolution
		   algorithm and may be discarded.
		
		   For each URI reference, the following steps are performed in order:
		
		   1) The URI reference is parsed into the potential four components and
		      fragment identifier, as described in Section 4.3.
		]]

local function parse( base )

	local _, _, a, b =  string.find( base, "^(([^:/?#]+):)" )
	if a then base = string.sub( base, string.len( a )+1 ) end

	local _, _, c, d =  string.find( base, "^(//([^/?#]*))" )
	if c then base = string.sub( base, string.len( c )+1 ) end

	local _, _, e=  string.find( base, "^([^?#]*)" )
	if e then base = string.sub( base, string.len( e )+1 ) end

	local _, _, f, g =  string.find( base, "^(\?([^#]*))" )
	if f then base = string.sub( base, string.len( f )+1 ) end

	local _, _, h, i =  string.find( base, "^(#(.*))" )
	if h then base = string.sub( base, string.len( h )+1 ) end

	return a, b, c, d, e, f, g, h, i

end

--------------------------------------------------------------------------------
--[[!
    \brief Resolves a relative URI against a base URI.
    
    \param base (string) The base URI.
    \param s (string) The relative URI.
    
    \return (string) A new absolute URI.
]]--

function WFURI.resolve( --[[string]] base,
						--[[string]] s )

	local _, bscheme, _, bauthority, bpath, _, _, _, bi = parse( base )
	local _, sscheme, _, sauthority, spath, _, squery, _, si = parse( s )

		--[[
		   2) If the path component is empty and the scheme, authority, and
		      query components are undefined, then it is a reference to the
		      current document and we are done.  Otherwise, the reference URI's
		      query and fragment components are defined as found (or not found)
		      within the URI reference and not inherited from the base URI.
		]]--

	if ( spath == nil or string.len( spath ) == 0 ) and sscheme == nil and sauthority == nil and squery == nil then
		local ans =  ""
		if si ~= nil then
			if bi ~= nil then
				ans = string.gsub( base, "#(.*)$", "#" .. si )
			else
				ans = string.format( "%s#%s", base, si )
			end
		end
		return ans
	end
		
		--[[
		   3) If the scheme component is defined, indicating that the reference
		      starts with a scheme name, then the reference is interpreted as an
		      absolute URI and we are done.  Otherwise, the reference URI's
		      scheme is inherited from the base URI's scheme component.
		]]--

	if sscheme ~= nil then
		return s
	end
		
		--[[
		
		      Due to a loophole in prior specifications [RFC1630], some parsers
		      allow the scheme name to be present in a relative URI if it is the
		      same as the base URI scheme.  Unfortunately, this can conflict
		      with the correct parsing of non-hierarchical URI.  For backwards
		      compatibility, an implementation may work around such references
		      by removing the scheme if it matches that of the base URI and the
		      scheme is known to always use the <hier_part> syntax.  The parser
		
		
		
		
		Berners-Lee, et. al.        Standards Track                    [Page 20]
		
		RFC 2396                   URI Generic Syntax                August 1998
		
		
		      can then continue with the steps below for the remainder of the
		      reference components.  Validating parsers should mark such a
		      misformed relative reference as an error.
		
		   4) If the authority component is defined, then the reference is a
		      network-path and we skip to step 7.  Otherwise, the reference
		      URI's authority is inherited from the base URI's authority
		      component, which will also be undefined if the URI scheme does not
		      use an authority component.
		]]--

	if sauthority == nil

		--[[
		
		   5) If the path component begins with a slash character ("/"), then
		      the reference is an absolute-path and we skip to step 7.
		]]--

				and not string.find( spath, "^/" ) then
		--[[
		
		   6) If this step is reached, then we are resolving a relative-path
		      reference.  The relative path needs to be merged with the base
		      URI's path.  Although there are many ways to do this, we will
		      describe a simple method using a separate string buffer.
		
		      a) All but the last segment of the base URI's path component is
		         copied to the buffer.  In other words, any characters after the
		         last (right-most) slash character, if any, are excluded.
		]]--

		local _, _, buffer = string.find( bpath, "^(.+/)")
		if buffer == nil then
			buffer = ''
		end
	
		--[[
		      b) The reference's path component is appended to the buffer
		         string.
		]]--
	
		buffer = string.format( "%s%s", buffer, spath or bpath )
		
		-- SPAM [ arahn 09/14/2006 ]
		-- a path of '.' should just be ''
		if buffer == '.' then
			buffer = ''
		end
		
		if string.len( buffer ) > 0 and buffer:byte( 1 ) ~= 95 then
			buffer = '/' .. buffer
		end
	
			--[[
		      c) All occurrences of "./", where "." is a complete path segment,
		         are removed from the buffer string.
		]]
	
		while string.find( buffer, "/[.]/" ) do
			buffer = string.replace( buffer, "/./", "/" )
		end
		
		--[[
		      d) If the buffer string ends with "." as a complete path segment,
		         that "." is removed.
		]]--
	
		buffer = string.gsub( buffer, "/[.]$", "/" )
	
		--[[
		      e) All occurrences of "<segment>/../", where <segment> is a
		         complete path segment not equal to "..", are removed from the
		         buffer string.  Removal of these path segments is performed
		         iteratively, removing the leftmost matching pattern on each
		         iteration, until no matching pattern remains.
		]]--
		
		-- SPAM, we shouldn't just blindly remove /../../ but since that
		-- is technically invalid, removing it is a reasonable way of
		-- dealing with it.

		while string.find( buffer, "/[^/]+/[.][.]/" ) do
			buffer = string.gsub( buffer, "/[^/]+/[.][.]/", "/" )
		end
	
		--[[
		      f) If the buffer string ends with "<segment>/..", where <segment>
		         is a complete path segment not equal to "..", that
		         "<segment>/.." is removed.
		]]--
	
		buffer = string.gsub( buffer, "/[^/]+/[.][.]$", "/" )
	
		--[[
		      g) If the resulting buffer string still begins with one or more
		         complete path segments of "..", then the reference is
		         considered to be in error.  Implementations may handle this
		         error by retaining these components in the resolved path (i.e.,
		         treating them as part of the final URI), by removing them from
		         the resolved path (i.e., discarding relative levels above the
		         root), or by avoiding traversal of the reference.
		]]--
	
		while string.find( buffer, "/[.][.]/" ) or
			string.find( buffer, "^[.][.]/" ) or
			string.find( buffer, "/[.][.]$" ) or
			string.find( buffer, "^[.][.]$" ) do
			
--			error( string.format("Too many .. for base (%s) + %s which resulted in %s", base, s, buffer ) )
			-- just deal with these by removing them

			buffer = string.gsub( buffer, "/[.][.]/", "/" )
			buffer = string.gsub( buffer, "^[.][.]/", "/" )
			buffer = string.gsub( buffer, "/[.][.]$", "/" )
			buffer = string.gsub( buffer, "^[.][.]$", "/" )
			
		end
		
		-- SPAM [ arahn 06/01/2006 ]
		-- The path shouldn't have '//' in it.  If it does, that's an error which
		-- we'll clean up by replacing all instances of // with /

		while string.find( buffer, "//" ) do
			buffer = string.replace( buffer, "//", "/" )
		end

		--[[
		
		Berners-Lee, et. al.        Standards Track                    [Page 21]
		
		RFC 2396                   URI Generic Syntax                August 1998
		
		
		      h) The remaining buffer string is the reference URI's new path
		         component.
		]]--
		
		spath = buffer
		
	end -- if network path
	
		--[[
		
		   7) The resulting URI components, including any inherited from the
		      base URI, are recombined to give the absolute form of the URI
		      reference.  Using pseudocode, this would be
		]]--
		
		     --    result = ""
	local result = ""
		
	--	         if scheme is defined then
	if bscheme then
		--             append scheme to result
		--             append ":" to result
		result = string.format("%s:", bscheme)
	end
		
	--	         if authority is defined then
	if sauthority or bauthority then
		--             append "//" to result
		--             append authority to result
		result = string.format("%s//%s", result, sauthority or bauthority )
	end
		
	--	         append path to result
	if spath then
		result = string.format("%s%s", result, spath)
	end
		
	--	         if query is defined then
	if squery then
		--             append "?" to result
		--             append query to result
		result = string.format("%s?%s", result, squery )
	end
		
	--	         if fragment is defined then
	if si then
		--             append "#" to result
		--            append fragment to result
		result = string.format("%s#%s", result, si )
	end
		
		         return result
end
		--[[
		      Note that we must be careful to preserve the distinction between a
		      component that is undefined, meaning that its separator was not
		      present in the reference, and a component that is empty, meaning
		      that the separator was present and was immediately followed by the
		      next component separator or the end of the reference.
		
		   The above algorithm is intended to provide an example by which the
		   output of implementations can be tested -- implementation of the
		   algorithm itself is not required.  For example, some systems may find
		   it more efficient to implement step 6 as a pair of segment stacks
		   being merged, rather than as a series of string pattern replacements.
		
		      Note: Some WWW client applications will fail to separate the
		      reference's query component from its path component before merging
		      the base and reference paths in step 6 above.  This may result in
		      a loss of information if the query component contains the strings
		      "/../" or "/./".
		
		   Resolution examples are provided in Appendix C.
		
		
		
		Berners-Lee, et. al.        Standards Track                    [Page 22]
		
{snip}
		
		B. Parsing a URI Reference with a Regular Expression
		
		   As described in Section 4.3, the generic URI syntax is not sufficient
		   to disambiguate the components of some forms of URI.  Since the
		   "greedy algorithm" described in that section is identical to the
		   disambiguation method used by POSIX regular expressions, it is
		   natural and commonplace to use a regular expression for parsing the
		   potential four components and fragment identifier of a URI reference.
		
		   The following line is the regular expression for breaking-down a URI
		   reference into its components.
		
		      ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
		       12            3  4          5       6  7        8 9
		
		   The numbers in the second line above are only to assist readability;
		   they indicate the reference points for each subexpression (i.e., each
		   paired parenthesis).  We refer to the value matched for subexpression
		   <n> as $<n>.  For example, matching the above expression to
		
		      http://www.ics.uci.edu/pub/ietf/uri/#Related
		
		   results in the following subexpression matches:
		
		      $1 = http:
		      $2 = http
		      $3 = //www.ics.uci.edu
		      $4 = www.ics.uci.edu
		      $5 = /pub/ietf/uri/
		      $6 = <undefined>
		      $7 = <undefined>
		      $8 = #Related
		      $9 = Related
		
		   where <undefined> indicates that the component is not present, as is
		   the case for the query component in the above example.  Therefore, we
		   can determine the value of the four components and fragment as
		
		      scheme    = $2
		      authority = $4
		      path      = $5
		      query     = $7
		      fragment  = $9
		
		   and, going in the opposite direction, we can recreate a URI reference
		   from its components using the algorithm in step 7 of Section 5.2.
		
		
		
		
		
		Berners-Lee, et. al.        Standards Track                    [Page 29]
		
		RFC 2396                   URI Generic Syntax                August 1998
		
]]

--------------------------------------------------------------------------------
--[[!
    \brief Separates the resource name from the path part of a URI.

    \param path (string) The URI-style path (NOT a platform path).
    \warning This is not suitable for manipulating platform file paths, especially in Windows.
    
    \return (string) The path, without the resource name.
    \return (string) The resource name (the last path component).
]]--

function WFURI.stringByDeletingLastPathComponent( --[[string]] path )

	-- Why not using AgPathUtils?  Because unlike paths, which may have
	-- platform specific separator characters, URIs always have the same
	-- separators
	
	-- This is a minimal implementation, just enough to provide a bottleneck
	-- for where I need it currently.
	
	if path == "/" then
		return "/", ""
	end
	
	if path == nil then
		return nil
	end
	
	local _, _, resultName = string.find( path, "^/([^/]+)/?$" )
	
	if resultName then
		return "/", resultName
	end
	
	local _, resultDir
	_, _, resultDir, resultName = string.find( path, "^(.-)/([^/]+)/?$")
	
	return resultDir or "", resultName or path

end

--------------------------------------------------------------------------------
--[[!
    \brief Appends a path component to a URI.
    
    \param a (string) The URI-style path (NOT a platform path).
    \warning This is not suitable for manipulating platform file paths, especially in Windows.
    \param b (string) The additional path component.
    
    \return a path containing the base + additional path.
]]--

function WFURI.stringByAppendingPathComponents( --[[string]] a,
						--[[string]] b )
	
	-- Fix for defect 1306001 : If server path is unspecified, files go
	--    into root of file system not "home" directory

	if a == nil or a == "" then
		return b
	end
	
	local _, _, aEndsInSlash = string.find( a, "^(.*)/$" )
	local bStartsInSlash = string.find( b, "^/" )

	if aEndsInSlash and bStartsInSlash then
		-- aEndsInSlash is actually the capture without that slash
		return aEndsInSlash .. b
	elseif aEndsInSlash or bStartsInSlash then
		return a .. b
	end

	return string.format( "%s/%s", a, b )

end

--------------------------------------------------------------------------------
--[[
	Replaces the URI path separator character ('/') with the correct path separator
	for the local file system.
	
	Input : A URI path (only the path part) for example foo/bar/baz or /bin/bash
	Output : A file path, using local separators : foo\bar\baz on windows, or /bin/bash on mac
]]--
-- SPAM [arahn 2012-05-23]:  This goes to some extraordinary contortions to work on both Lr and Revel

local getFileSeparator
getFileSeparator = function()
	if rawget( _G, "MAC_ENV" ) then
		-- Lightroom on Mac
		getFileSeparator = function() return "/" end
	elseif rawget( _G, "WIN_ENV" ) then
		-- Lightroom on Win
		getFileSeparator = function() return "\\" end
	elseif require "WFPlatformInfo".isWindows then
		-- Revel on Win
		getFileSeparator = function() return "\\" end
	else
		-- Revel on anything else (iOS or Mac)
		getFileSeparator = function() return "/" end
	end
	return getFileSeparator()

end

--------------------------------------------------------------------------------
--[[!
    \brief Converts a URI path to a file path for the current machine
    
    \param uriPath (string) The URI path.
    
    \return (string) A platform file system path (for example, one using backslash separators in Windows).
]]--

function WFURI.uriPathToFilePath( --[[string]] uriPath )
	
	local pathSeparator = getFileSeparator()

	if pathSeparator == "/" then
		return uriPath
	end

	return string.replace( uriPath, "/", pathSeparator )

end

--------------------------------------------------------------------------------
--[[!
    \brief Converts a URI into a table describing its component parts.
    
    \param uristr (string) The URI.
    
    \return (table) A table containing these entries:
    <ul><li>scheme</li>
    	<li>authority</li>
    	<li>path</li>
    	<li>rawPath (value has not had % escaped sequences replaced)</li>
	<li>query</li>
	<li>uri (the input URI)</li>
    </ul>
]]--

function WFURI.parse( --[[string]] uristr )

	local _, bscheme, _, bauthority, bpath, _, bquery, _, bfragment = parse( uristr )
	
	-- fix %20 type of URI encoding
	-- note that paths never encode spaces as + so do not decode that here.
	local decodedpath = string.gsub( bpath, '%%(%x%x)', function( hex ) return string.char( tonumber( hex, 16 ) ) end )

	return {
		scheme = bscheme,
		authority = bauthority,
		path = decodedpath,
		rawPath = bpath,
		query = bquery,
		uri = uristr,
		fragment = bfragment
	}

end

--------------------------------------------------------------------------------
--[[!
    \brief Converts a table describing URI component parts into a URI string
    
    \param parsedUri (table) A table containing these entries:
    <ul><li>scheme</li>
    	<li>authority</li>
    	<li>path</li>
    	<li>rawPath (value has not had % escaped sequences replaced)</li>
		<li>query</li>
		<li>uri (the input URI)</li>
    </ul>
	Note: if rawPath is provided, then path is ignored.
	
	\return (string) The URI
]]--

function WFURI.buildUri( parsedUri )

	check( 'table' )
	
	local pathSep = '/'
	
	if ( parsedUri.rawPath or parsedUri.path ):byte( 1 ) == 47 then
		-- 47 is /
		-- We expect paths to start with a forward slash.  However
		-- if they don't we will add one.
		pathSep = ''
	end
	
	local result = {
		parsedUri.scheme,
		"://",
		parsedUri.authority,
		pathSep,
		parsedUri.rawPath or WFURI.escape( parsedUri.path, 'path' ),
	}
	
	local query = parsedUri.query
	
	if query then
		result[ #result + 1 ] = "?"
		result[ #result + 1 ] = query
	end
	
	local fragment = parsedUri.fragment
	
	if fragment then
		result[ #result + 1 ] = "#"
		result[ #result + 1 ] = fragment
	end
	
	return table.concat( result )

end

--------------------------------------------------------------------------------
--[[!
    \brief Converts a URI query string into a table of key/value pairs.
    
    \param queryStr (string) The URI query string.
    
    \return (table) A table containing key/value pairs for each query parameter.
]]--

function WFURI.parseQueryString( --[[string]] queryStr )

	-- FIXME [bzibble 2013-10-23 via arahn]: What about encoded values in the
	-- query string (e.g. %3F)?

	local t = {}
	
	for kvpair in string.gmatch( queryStr, "[^&]+" ) do
		local _, _, k, v = string.find( kvpair, "^([^=]+)=(.+)$" )
		if k and v then
			if t[ k ] then
				logger:warnf( "Duplicate key in url Query string: %s", k )
			end
			t[ k ] = v
		end
	end

	return t
end

--------------------------------------------------------------------------------
--[[!
    \brief Converts a map of a key/value pairs into a URI query string.
    
    \param paramsTable (table) A map.
    \param shouldEscape (optional, boolean) Indicates whether or not the key/value pairs
	\should be escaped. By default, the key/value pairs are escaped.

    \return (table) A string containing all key/value pairs in the format; "k1=v1&k2=v2&k3=v3"
]]--

function WFURI.buildQueryString( --[[table]] paramsTable, --[[bool]] shouldEscape )

	local params = {}
	local escape = WFURI.escape
	shouldEscape = shouldEscape == nil or shouldEscape

	local keys = {}
	for k,v in pairs( paramsTable ) do
		keys[ #keys + 1 ] = k
	end
	
	table.sort( keys )
	
	for _, k in ipairs( keys ) do
		local v = paramsTable[ k ]
		if shouldEscape then
			table.insert( params, string.format( "%s=%s", escape( k, 'http_query_key' ), escape( v, 'http_query_key' ) ) )
		else
			table.insert( params, string.format( "%s=%s",  k, v ) )
		end
	end

	return table.concat( params, "&" )

end

--------------------------------------------------------------------------------
--[[!
    \brief Converts a platform file-system path into a URI path.
    
    \param filePath (string) The platform file-system path
    (for example, one using backslash separators in Windows).
    
    \return (string) A path suitable for use in a URI (with forward-slash separators).
]]--

function WFURI.filePathToURIPath( --[[string]] filePath )
	
	if #filePath == 0 then
		return ""
	end
	
	local components = {}
	local escape = WFURI.escape
	local startWithSeparator = string.sub( filePath, 1, 1 ) == getFileSeparator()
	local endsWithSeparator = #filePath > 1 and string.sub( filePath, -1, -1 ) == getFileSeparator()
	
	for component in string.gmatch( filePath, "[^" .. getFileSeparator() .. "]+" ) do
		components[ #components + 1 ] = escape( component, 'path' )
	end
	local result = table.concat( components, "/" )
	if startWithSeparator then
		result = "/" .. result
	end
	if endsWithSeparator then
		result = result .. "/"
	end
	return result

end

local filePathToURIPath = WFURI.filePathToURIPath

--------------------------------------------------------------------------------
--[[!
    \brief Creates a file URI from a platform file-system path.
    
    \param filePath (string) The platform file-system path
    	(for example, one using backslash separators in Windows).
    
    \return (string) A file URI path.
]]--

function WFURI.uriFromFilePath( --[[string]] filePath )

	local subs = filePathToURIPath( filePath )
	return "file:///" .. subs

end

--------------------------------------------------------------------------------
--[[!
    \brief Encodes a string into its escaped hexadecimal representation.
    
    \param s (string) The binary string to encode.
	\param context (optional string) If omitted, then defaults to 'path'.  This
		defines what context to escape characters, according to RFC 3986.
		<ul>
			<li>unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
			<li>path = pchar_no_colon / "/"
			<li>query = pchar_no_plus / "/" / "?"
			<li>query_key = pchar_no_plus_ampersand_equal / "/" / "?"
			<li>http_query_key = unreserved / "!" / "(" / ")"
                  / "*" / " "
		</ul>
		For your reference, these definitions are included from the spec, but
		are not valid values for context:
		<ul>
			<li>sub-delims  = "!" / "$" / "&amp;" / "'" / "(" / ")"
                  / "*" / "+" / "," / ";" / "="
			<li>pchar = unreserved / sub-delims / ":" / "@"
			<li>pchar_no_colon = unreserved / sub-delims / "@"
			<li>pchar_no_plus = unreserved / "!" / "$" / "'" / "(" / ")"
                  / "*" / "," / ";" / "@"
			<li>pchar_no_plus_ampersand_equal = unreserved / "!" / "$" / "'" / "(" / ")"
                  / "*" / "," / ";" / "@"
    	</ul>
		Note: a colon is sometimes a valid character in a path, so long as a
		scheme is specified.  However, for relative URIs or a URI that does
		not have a scheme, a colon in the path might be mistaken as the scheme
		separator.  Thus we always escape colons in paths.
		<p>
		Note 2: Some URL encoders interpret plus characters as spaces; thus plusses
		will be escaped to distinguish them from spaces.
		<p>
		The 'query_key' context is useful for escaping individual query keys and values, whereas
		'query' will be used to escape the entire query string.
		The 'http_query_key' context needs to be used with HTTP query keys and values.
    \return (string) The encoded string.
]]--

function WFURI.escape( s, context )
	
	check( 'string', '?+string' )
	
	local pattern
	
	if context == nil or context == 'path' then
		pattern = "([^-A-Za-z0-9._~!$&'()*+,;=@/])"
	elseif context == 'unreserved' then
		pattern = "([^-A-Za-z0-9._~])"
	elseif context == 'query' then
		pattern = "([^-A-Za-z0-9._~!$&'()*,;=:@/?])"
	elseif context == 'query_key' then
		pattern = "([^-A-Za-z0-9._~!$'()*,;:@/?])"
	elseif context == 'http_query_key' then
		pattern = "([^-A-Za-z0-9._~!()* ])"
	else
		error( "Invalid escape context: " .. context .. "\n\texpected one of 'unreserved', 'path', or 'query'" )
	end
	
    local result = string.gsub( s, pattern, function( c )
        return string.format("%%%02X", string.byte( c ) )
			-- For consistency, URI producers and normalizers should use
			-- uppercase hexadecimal digits for all percent-encodings
    end )

	if context == 'http_query_key' then
		result = string.replace( result, ' ', '+' )
	end
	
	return result

end

--------------------------------------------------------------------------------

--[[!
    \brief Does the same thing as escape. Additionally removes a +, - characters in a string from its escaped hexadecimal representation.

    \return (string) The decoded string.
]]--

function WFURI.escapeWithPlusAndMinus( s, context )

    check( 'string', '?+string' )

    s = WFURI.escape(s)

    local pattern

    pattern = "([+-])"

    local result = string.gsub( s, pattern, function( c )
        return string.format("%%%02X", string.byte( c ) )
        -- For consistency, URI producers and normalizers should use
        -- uppercase hexadecimal digits for all percent-encodings
    end )

    return result

end

--------------------------------------------------------------------------------
--[[!
    \brief Decodes a string from its escaped hexadecimal representation.
    
    \param s (string) A string containing escaped characters in the form
		percent + hex + hex (as returned by the \c escape method).
    
    \return (string) The decoded string.
]]--

function WFURI.unescape( s )

	check( "?string" )

	if s == nil then
		return s
	end

    return string.gsub(s, "%%(%x%x)", function( hex )
        return string.char( tonumber( hex, 16 ) )
    end )

end

--------------------------------------------------------------------------------

--[[!
	\brief Compares two URI strings and returns true if they are
	equal.  This comparison does Syntax-Based Normalization (see
	RFC 3986 section 6.2.2): Case Normalization (6.2.2.1),
	Percent-Encoding Normalization (6.2.2.2), and Path Segment
	Normalization (6.2.2.3).  Note: Case Normalization only applies
	to percent-encoding triplets -- it will not normalize the scheme.
	Thus "HTTP://a/b" is considered different than "http://a/b".
	
	\param a (url string) a valid URL
	\param b (url string) a valid URL
	
	\return (boolean) whether the two URLs are the same.
]]--

function WFURI.isEqual( a, b )
	
	local parse = WFURI.parse
	local resolve = WFURI.resolve
	local unescape = WFURI.unescape
	
	a = parse( a )
	b = parse( b )
	
	-- Use our URI resolution logic to remove .. and /./ elements
	local _, an, aprefix = string.find( a.path, "^(/+)" )
	local _, bn, bprefix = string.find( b.path, "^(/+)" )
	
	local resolvedAPath = resolve( "", a.path:sub( ( an or 0 ) + 1 ) )
	local resolvedBPath = resolve( "", b.path:sub( ( bn or 0 ) + 1 ) )
	
	resolvedAPath = aprefix .. resolvedAPath:gsub( "^[/]+", "" )
	resolvedBPath = bprefix .. resolvedBPath:gsub( "^[/]+", "" )
	
	return a.scheme == b.scheme
		and unescape( a.authority ) == unescape( b.authority )
		and resolvedAPath == resolvedBPath
		and unescape( a.query ) == unescape( b.query )
			-- by unescaping, we normalize percent encoding (and elminate
			-- any case differences in the percent encoding).
		and unescape( a.fragment ) == unescape( b.fragment )
	
end

--------------------------------------------------------------------------------
--[[!
    \brief Creates a string version of the URL suitable for logging or
	descriptive purposes.  We replace any embedded username or password
	with astersisk to avoid printing out such info to logs.
    
    \param s (url string) a valid URL
    
    \return (string) A string that looks like a URL but has the password
		replaced with asterisks
]]--

function WFURI.sanitizedString( s )
	
	check( 'string' )

    local parsed = WFURI.parse( s )
		
	local authority = parsed.authority

	if not authority then
		return s
	end
	
	local _, _, uname, host = string.find( authority, "([^:]+):.+@([^@]+)$" )
	
	if uname and host then
		
		parsed.authority = string.format( "%s:*****@%s", uname, host )
		return WFURI.buildUri( parsed )
		
	end
	
	return s

end

--------------------------------------------------------------------------------

return WFURI
