// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Parse URLs (actually URIs, but that seems overly pedantic).
// RFC 3986

package http

import (
	"os"
	"strconv"
	"strings"
)

// URLError reports an error and the operation and URL that caused it.
type URLError struct {
	Op    string
	URL   string
	Error os.Error
}

func (e *URLError) String() string { return e.Op + " " + e.URL + ": " + e.Error.String() }

func ishex(c byte) bool {
	switch {
	case '0' <= c && c <= '9':
		return true
	case 'a' <= c && c <= 'f':
		return true
	case 'A' <= c && c <= 'F':
		return true
	}
	return false
}

func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}
	return 0
}

type URLEscapeError string

func (e URLEscapeError) String() string {
	return "invalid URL escape " + strconv.Quote(string(e))
}

// Return true if the specified character should be escaped when
// appearing in a URL string, according to RFC 2396.
func shouldEscape(c byte) bool {
	if c <= ' ' || c >= 0x7F {
		return true
	}
	switch c {
	case '<', '>', '#', '%', '"', // RFC 2396 delims
		'{', '}', '|', '\\', '^', '[', ']', '`', // RFC2396 unwise
		'?', '&', '=', '+': // RFC 2396 reserved in path
		return true
	}
	return false
}

// CanonicalPath applies the algorithm specified in RFC 2396 to
// simplify the path, removing unnecessary  . and .. elements.
func CanonicalPath(path string) string {
	buf := []byte(path)
	a := buf[0:0]
	// state helps to find /.. ^.. ^. and /. patterns.
	// state == 1 - prev char is '/' or beginning of the string.
	// state > 1  - prev state > 0 and prev char was '.'
	// state == 0 - otherwise
	state := 1
	cnt := 0
	for _, v := range buf {
		switch v {
		case '/':
			s := state
			state = 1
			switch s {
			case 2:
				a = a[0 : len(a)-1]
				continue
			case 3:
				if cnt > 0 {
					i := len(a) - 4
					for ; i >= 0 && a[i] != '/'; i-- {
					}
					a = a[0 : i+1]
					cnt--
					continue
				}
			default:
				if len(a) > 0 {
					cnt++
				}
			}
		case '.':
			if state > 0 {
				state++
			}
		default:
			state = 0
		}
		l := len(a)
		a = a[0 : l+1]
		a[l] = v
	}
	switch {
	case state == 2:
		a = a[0 : len(a)-1]
	case state == 3 && cnt > 0:
		i := len(a) - 4
		for ; i >= 0 && a[i] != '/'; i-- {
		}
		a = a[0 : i+1]
	}
	return string(a)
}

// URLUnescape unescapes a URL-encoded string,
// converting %AB into the byte 0xAB and '+' into ' ' (space).
// It returns an error if any % is not followed
// by two hexadecimal digits.
func URLUnescape(s string) (string, os.Error) { return urlUnescape(s, true) }

// urlUnescape is like URLUnescape but can be told not to
// convert + into space.  URLUnescape implements what is
// called "URL encoding" but that only applies to query strings.
// Elsewhere in the URL, + does not mean space.
func urlUnescape(s string, doPlus bool) (string, os.Error) {
	// Count %, check that they're well-formed.
	n := 0
	hasPlus := false
	for i := 0; i < len(s); {
		switch s[i] {
		case '%':
			n++
			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
				s = s[i:]
				if len(s) > 3 {
					s = s[0:3]
				}
				return "", URLEscapeError(s)
			}
			i += 3
		case '+':
			hasPlus = doPlus
			i++
		default:
			i++
		}
	}

	if n == 0 && !hasPlus {
		return s, nil
	}

	t := make([]byte, len(s)-2*n)
	j := 0
	for i := 0; i < len(s); {
		switch s[i] {
		case '%':
			t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
			j++
			i += 3
		case '+':
			if doPlus {
				t[j] = ' '
			} else {
				t[j] = '+'
			}
			j++
			i++
		default:
			t[j] = s[i]
			j++
			i++
		}
	}
	return string(t), nil
}

// URLEscape converts a string into URL-encoded form.
func URLEscape(s string) string { return urlEscape(s, true) }

func urlEscape(s string, doPlus bool) string {
	spaceCount, hexCount := 0, 0
	for i := 0; i < len(s); i++ {
		c := s[i]
		if shouldEscape(c) {
			if c == ' ' && doPlus {
				spaceCount++
			} else {
				hexCount++
			}
		}
	}

	if spaceCount == 0 && hexCount == 0 {
		return s
	}

	t := make([]byte, len(s)+2*hexCount)
	j := 0
	for i := 0; i < len(s); i++ {
		switch c := s[i]; {
		case c == ' ' && doPlus:
			t[j] = '+'
			j++
		case shouldEscape(c):
			t[j] = '%'
			t[j+1] = "0123456789abcdef"[c>>4]
			t[j+2] = "0123456789abcdef"[c&15]
			j += 3
		default:
			t[j] = s[i]
			j++
		}
	}
	return string(t)
}

// A URL represents a parsed URL (technically, a URI reference).
// The general form represented is:
//	scheme://[userinfo@]host/path[?query][#fragment]
// The Raw, RawPath, and RawQuery fields are in "wire format" (special
// characters must be hex-escaped if not meant to have special meaning).
// All other fields are logical values; '+' or '%' represent themselves.
//
// Note, the reason for using wire format for the query is that it needs
// to be split into key/value pairs before decoding.
type URL struct {
	Raw       string // the original string
	Scheme    string // scheme
	Authority string // [userinfo@]host
	Userinfo  string // userinfo
	Host      string // host
	RawPath   string // /path[?query][#fragment]
	Path      string // /path
	RawQuery  string // query
	Fragment  string // fragment
}

// Maybe rawurl is of the form scheme:path.
// (Scheme must be [a-zA-Z][a-zA-Z0-9+-.]*)
// If so, return scheme, path; else return "", rawurl.
func getscheme(rawurl string) (scheme, path string, err os.Error) {
	for i := 0; i < len(rawurl); i++ {
		c := rawurl[i]
		switch {
		case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
		// do nothing
		case '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.':
			if i == 0 {
				return "", rawurl, nil
			}
		case c == ':':
			if i == 0 {
				return "", "", os.ErrorString("missing protocol scheme")
			}
			return rawurl[0:i], rawurl[i+1:], nil
		default:
			// we have encountered an invalid character,
			// so there is no valid scheme
			return "", rawurl, nil
		}
	}
	return "", rawurl, nil
}

// Maybe s is of the form t c u.
// If so, return t, c u (or t, u if cutc == true).
// If not, return s, "".
func split(s string, c byte, cutc bool) (string, string) {
	for i := 0; i < len(s); i++ {
		if s[i] == c {
			if cutc {
				return s[0:i], s[i+1:]
			}
			return s[0:i], s[i:]
		}
	}
	return s, ""
}

// ParseURL parses rawurl into a URL structure.
// The string rawurl is assumed not to have a #fragment suffix.
// (Web browsers strip #fragment before sending the URL to a web server.)
func ParseURL(rawurl string) (url *URL, err os.Error) {
	if rawurl == "" {
		err = os.ErrorString("empty url")
		goto Error
	}
	url = new(URL)
	url.Raw = rawurl

	// split off possible leading "http:", "mailto:", etc.
	var path string
	if url.Scheme, path, err = getscheme(rawurl); err != nil {
		goto Error
	}

	// RFC 2396: a relative URI (no scheme) has a ?query,
	// but absolute URIs only have query if path begins with /
	var query string
	if url.Scheme == "" || len(path) > 0 && path[0] == '/' {
		path, query = split(path, '?', false)
		if len(query) > 1 {
			url.RawQuery = query[1:]
		}
	}

	// Maybe path is //authority/path
	if url.Scheme != "" && len(path) > 2 && path[0:2] == "//" {
		url.Authority, path = split(path[2:], '/', false)
	}
	url.RawPath = path + query

	// If there's no @, split's default is wrong.  Check explicitly.
	if strings.Index(url.Authority, "@") < 0 {
		url.Host = url.Authority
	} else {
		url.Userinfo, url.Host = split(url.Authority, '@', true)
	}

	if url.Path, err = urlUnescape(path, false); err != nil {
		goto Error
	}

	// Remove escapes from the Authority and Userinfo fields, and verify
	// that Scheme and Host contain no escapes (that would be illegal).
	if url.Authority, err = urlUnescape(url.Authority, false); err != nil {
		goto Error
	}
	if url.Userinfo, err = urlUnescape(url.Userinfo, false); err != nil {
		goto Error
	}
	if strings.Index(url.Scheme, "%") >= 0 {
		err = os.ErrorString("hexadecimal escape in scheme")
		goto Error
	}
	if strings.Index(url.Host, "%") >= 0 {
		err = os.ErrorString("hexadecimal escape in host")
		goto Error
	}

	return url, nil

Error:
	return nil, &URLError{"parse", rawurl, err}

}

// ParseURLReference is like ParseURL but allows a trailing #fragment.
func ParseURLReference(rawurlref string) (url *URL, err os.Error) {
	// Cut off #frag.
	rawurl, frag := split(rawurlref, '#', false)
	if url, err = ParseURL(rawurl); err != nil {
		return nil, err
	}
	url.Raw += frag
	url.RawPath += frag
	if len(frag) > 1 {
		frag = frag[1:]
		if url.Fragment, err = urlUnescape(frag, false); err != nil {
			return nil, &URLError{"parse", rawurl, err}
		}
	}
	return url, nil
}

// String reassembles url into a valid URL string.
//
// There are redundant fields stored in the URL structure:
// the String method consults Scheme, Path, Host, Userinfo,
// RawQuery, and Fragment, but not Raw, RawPath or Authority.
func (url *URL) String() string {
	result := ""
	if url.Scheme != "" {
		result += url.Scheme + ":"
	}
	if url.Host != "" || url.Userinfo != "" {
		result += "//"
		if url.Userinfo != "" {
			result += urlEscape(url.Userinfo, false) + "@"
		}
		result += url.Host
	}
	result += urlEscape(url.Path, false)
	if url.RawQuery != "" {
		result += "?" + url.RawQuery
	}
	if url.Fragment != "" {
		result += "#" + urlEscape(url.Fragment, false)
	}
	return result
}
