Commits

npe committed fb5fa96

initial commit, contains some bug fixes to work with the latest go apis and a debug flag to allow session debugging

Comments (0)

Files changed (11)

+# This is the official list of GOAuth authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+
+# Names should be added to this file as
+#	Name or Organization <email address>
+# The email address is not required for organizations.
+
+# Please keep the list sorted.
+Andrew Scott <hoka@hokapoka.com> 
+

goauth/CONTRIBUTORS

+# This is the official list of people who can contribute
+# (and typically have contributed) code to the GOAuth repository.
+# The AUTHORS file lists the copyright holders; this file
+# lists people.  For example, Hokapoka employees are listed here
+# but not in AUTHORS, because Hokapoka holds the copyright.
+#
+# The submission process automatically checks to make sure
+# that people submitting code are listed in this file (by email address).
+#
+# Names should be added to this file only after verifying that
+# the individual or the individual's organization has agreed to
+# the appropriate Contributor License Agreement. Please contact
+# for details.
+#
+# When adding J Random Contributor's name to this file,
+# either J's name or J's organization's name should be
+# added to the AUTHORS file, depending on whether the
+# individual or corporate CLA was used.
+
+# Names should be added to this file like so:
+#     Name <email address>
+
+# Please keep the list sorted.
+
+Hokapoka
+Andrew Scott <hoka@hokapoka.com>
+Noah Evans <noah.evans@gmail.com>
+// Copyright (c) 2010 The GOAuth Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * 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.
+//    * Neither the name of Hokapoka. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+// OWNER 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.
+//
+// Subject to the terms and conditions of this License, Hokapoka hereby
+// grants to You a perpetual, worldwide, non-exclusive, no-charge,
+// royalty-free, irrevocable (except as stated in this section) patent
+// license to make, have made, use, offer to sell, sell, import, and
+// otherwise transfer this implementation of GOAuth, where such license
+// applies only to those patent claims licensable by Hokapoka that are
+// necessarily infringed by use of this implementation of GOAuth. If You
+// institute patent litigation against any entity (including a
+// cross-claim or counterclaim in a lawsuit) alleging that this
+// implementation of GOAuth or a Contribution incorporated within this
+// implementation of GOAuth constitutes direct or contributory patent
+// infringement, then any patent licenses granted to You under this
+// License for this implementation of GOAuth shall terminate as of the 
+// date such litigation is filed.
+include $(GOROOT)/src/Make.inc
+
+TARG=github.com/hokapoka/goauth
+GOFILES=\
+	urlencode.go\
+	token.go\
+	pairs.go\
+	http.go\
+	oauthconsumer.go\
+	oauth.go\
+
+include $(GOROOT)/src/Make.pkg
+
+GOAuth
+======
+
+This is the source code repository for the GOAuth an OAuth consumer
+written on the Go programming language. This is a clone of the original from github.com/hokapoka/goauth, to keep some of my bugfixes and changes to the api.
+
+Copyright 2010 The GOAuth Authors. All rights reserved.
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file.
+
+V 0.0.5
+
+Example Usage :
+
+...
+
+var AT *goauth.AccessToken
+var goauthcon *goauth.OAuthConsumer
+
+...
+
+GET OAuth
+---------
+
+	r, err := goauthcon.Get(
+		"http://api.twitter.com/1/account/verify_credentials.json",
+		nil,
+		AT )
+
+
+POST OAuth 
+----------
+	
+	r, err := goauthcon.Post(
+		"http://api.twitter.com/1/statuses/update.json",
+		goauth.Params{
+			&goauth.Pair{Key:"status", Value:"Testing Status Update via GOAuth - OAuth consumer for #Golang"},
+		},
+		AT )
+
+More Complete Example 
+---------------------
+
+See example.go for a simple http example that connects to twitter 
+allows you to get the Home Time Lime for the OAuthed twitter account, 
+send a tweet & Check Credentials. 
+
+Note that the Check Credentials & Home Time Line use GET methods and the 
+Update status uses a POST method.
+
+Another Example (example_buzz) shows a simple Google Buzz Consumer
+that searching for activites.  Note: This example is dependant on 
+my gobuzz package here : http://github.com/hokapoka/gobuzz
+
+Revision History 
+----------------
+
+    == Version 0.0.5 ==
+	
+	Minor changes to make GetRequestToken more robust.
+
+	Until now, if the ConsumerKey / ConsumerSecrets were incorrect
+	there was no feedback to the consumer that it had failed to 
+	obtain the Request token correctally.
+
+	Now the consumer returns the an error that reflects the status
+	that has been returned from the OAuth service.  Additionally 
+	it also returns an error if to token has been recivied even if
+	the service hasn't responed with an error.
+
+	An additional Exported member has been added to the AccessToken
+	that allows it to be stored in a persistant data store with the 
+	service that it relates to.
+
+    == Version 0.0.4 ==
+
+	Added a Google Buzz example that demos the use of the package 
+	with a google OAuth supported service. 
+
+	Also added minor fix to the original twitter example so that 
+	it works with the latest Exported interfaces provided by the 
+	OAuthConsumer.
+
+	Tested with : 
+		Google Buzz, Calender, Adwords
+		Twitter 
+		Digg 
+	
+	All work.  
+
+    == Version 0.0.3 ==
+
+	Previous versions only worked with twitter.  Issues, especially
+	with Googles API, have been fixed.
+   
+	Replaced URL Encoder with Calvin McAnarney version that conforms
+	to RFC 3986.
+	(https://github.com/csmcanarney/gooauth/blob/master/encode.go)
+ 
+	Additional issues with Googles OAuth API fixed. - Inparticular 
+	Certain chars contiained in their tokens end up getting double 
+	encoded if you follow the RFC specification.  Current fix is a
+ 	bit of a hack, parts are allowed to be Encoded twice, and then
+	replaced with the actual values that should be sent.
+
+	Tested with Googles Buzz, Adwords, Calender & Reader OAuth 
+	servies all functions as expected. 
+
+	Additional testing is required with other services, such as
+	twitter, digg to see if these fixes have effected these.
+
+		oauth.Get now recives a Params object too. 
+
+   == Version  0.0.2 ==
+   
+	Minor fixes to Makefile to work with latest revision of Go. 
+	Additional fixes to other changes in Go.
+
+
+   == Version 0.0.1 ==
+
+	Initial release, general mechs are functional GET/POST, creation
+	of an OAuth consumer.
+
+Contact 
+-------
+
+If you have any issues please feel free to contact : 
+  email - noah.evans@gmail.com 
+   buzz - noah.evans@gmail.com 
+twitter - @npe9
+ bitbucket - bitbucket.org/npe/goauth
+
+The original author lives here:
+    email - hoka@hokapoka.com
+      web - http://go.hokapoka.com
+     buzz - hokapoka.com@gmail.com 
+  twitter - @hokapokadotcom
+   github - github.com/hokapoka/goauth
+      irc - hokapoka (freenode #go-nuts)
+
+
+============================================================
+
+TODO : add link to example on go.hokapoka.com (once written)
+
+
+package oauth
+
+import (
+
+
+	//	"encoding/base64"
+
+
+
+	"strings"
+	"bufio"
+	"crypto/tls"
+	"os"
+	"net"
+	"fmt"
+	"io"
+	"http"
+)
+
+var HttpDebug = false
+
+
+// get Taken from the golang source modifed to allow headers to be passed and no redirection allowed
+func get(url string, headers map[string]string) (r *http.Response, err os.Error) {
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return
+	}
+
+	for k, v := range headers {
+		req.Header.Add(k, v)
+	}
+	req.URL, err = http.ParseURL(url)
+
+	r, err = send(req)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// post taken from Golang modified to allow Headers to be pased
+func post(url string, headers map[string]string, body io.Reader) (r *http.Response, err os.Error) {
+	req, err := http.NewRequest("POST", url, nil)
+	if err != nil {
+		return
+	}
+
+	for k, v := range headers {
+		req.Header.Add(k, v)
+	}
+	req.TransferEncoding = []string{"chunked"}
+
+	req.URL, err = http.ParseURL(url)
+	if err != nil {
+		return nil, err
+	}
+
+	return send(req)
+}
+
+// Copyright (c) 2009 The Go Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * 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.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+
+// From the http package - modified to allow Headers to be sent to the Post method
+type nopCloser struct {
+	io.Reader
+}
+
+func (nopCloser) Close() os.Error	{ return nil }
+
+type readClose struct {
+	io.Reader
+	io.Closer
+}
+
+func send(req *http.Request) (resp *http.Response, err os.Error) {
+	if HttpDebug {
+		fmt.Printf("req url %#v %#v\n", req.URL, req)
+	}
+	if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
+		return nil, nil
+	}
+
+	addr := req.URL.Host
+	if !hasPort(addr) {
+		addr += ":" + req.URL.Scheme
+	}
+	/*
+		info := req.URL.Userinfo
+		  if len(info) > 0 {
+		      enc := base64.URLEncoding
+		      encoded := make([]byte, enc.EncodedLen(len(info)))
+		      enc.Encode(encoded, []byte(info))
+		      if req.Header == nil {
+		          req.Header = make(map[string]string)
+		      }
+		      req.Header["Authorization"] = "Basic " + string(encoded)
+		  }
+	*/
+	var conn io.ReadWriteCloser
+	if req.URL.Scheme == "http" {
+		conn, err = net.Dial("tcp", addr)
+	} else {	// https
+		conn, err = tls.Dial("tcp", addr, nil)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	err = req.Write(conn)
+	if err != nil {
+		conn.Close()
+		return nil, err
+	}
+
+	reader := bufio.NewReader(conn)
+	resp, err = http.ReadResponse(reader, req)
+	if err != nil {
+		conn.Close()
+		return nil, err
+	}
+
+	resp.Body = readClose{resp.Body, conn}
+	if HttpDebug {
+		fmt.Printf("resp %#v\n", resp)
+	}
+	return
+}
+
+
+func hasPort(s string) bool	{ return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
+package oauth
+
+import (
+)
+
+func init(){
+}
+
+
+
+
+

goauth/oauthconsumer.go

+package oauth
+
+import(
+	"fmt"
+	"http"
+	"rand"
+	"strconv"
+	"time"
+	"sort"
+	"strings"
+	"bytes"
+	"crypto/hmac"
+	"io/ioutil"
+	"os"
+)
+
+
+type OAuthConsumer struct{
+	Service string
+	RequestTokenURL string
+	AccessTokenURL string
+	AuthorizationURL string
+	ConsumerKey string
+	ConsumerSecret string
+	CallBackURL string
+	requestTokens []*RequestToken
+	AdditionalParams Params
+}
+
+
+// GetRequestAuthorizationURL Returns the URL for the visitor to Authorize the Access
+func (oc *OAuthConsumer) GetRequestAuthorizationURL() (string, *RequestToken, os.Error){
+	// Gather the params
+	p := Params{}
+
+	// Add required OAuth params
+	p.Add( &Pair{ Key:"oauth_version", Value:"1.0" } )
+	p.Add( &Pair{ Key:"oauth_timestamp", Value:strconv.Itoa64(time.Seconds()) } )
+	p.Add( &Pair{ Key:"oauth_consumer_key", Value:oc.ConsumerKey } )
+	p.Add( &Pair{ Key:"oauth_callback", Value:oc.CallBackURL } )
+	p.Add( &Pair{ Key:"oauth_nonce", Value:strconv.Itoa64(rand.Int63()) } )
+	p.Add( &Pair{ Key:"oauth_signature_method", Value:"HMAC-SHA1" } )
+
+	// Sort the collection
+	sort.Sort(p)
+
+	// Generate string of sorted params
+	sigBaseCol := make([]string, len(p) + len(oc.AdditionalParams))
+	for i := range p {
+		sigBaseCol[i] = Encode(p[i].Key) + "=" + Encode( p[i].Value )
+	}
+
+    buf := &bytes.Buffer{}
+
+	i := len(p)
+	for _, kv := range oc.AdditionalParams{
+        buf.Write([]byte(kv.Key + "=" + Encode( kv.Value ) + ""))
+        sigBaseCol[i] = kv.Key + "=" + Encode( kv.Value )
+		i++
+    }
+
+	sigBaseStr :=	"GET&" +
+					Encode(oc.RequestTokenURL) + "&" +
+					Encode(strings.Join(sigBaseCol, "&"))
+
+	// Generate Composite Signing key
+	key := Encode(oc.ConsumerSecret) + "&" + "" // token secrect is blank on the Request Token
+
+	// Generate Signature
+	d := oc.digest(key, sigBaseStr)
+
+	// Build Auth Header
+	authHeader := "OAuth "
+	for i := range p {
+		authHeader +=  p[i].Key + "=\"" + Encode(p[i].Value ) + "\", "
+	}
+
+	// Add the signature
+	authHeader += "oauth_signature=\"" + Encode(d) + "\""
+
+	headers := map[string]string{
+		"Content-Type":"text/plain",
+		"Authorization":authHeader,
+	}
+
+    lAddParams := len(oc.AdditionalParams)
+	if lAddParams > 0 {
+		oc.RequestTokenURL += "?" + string(buf.Bytes())
+	}
+
+	r, err := get(oc.RequestTokenURL, headers)
+
+	if err != nil {
+		return "", nil, err
+	}
+
+	if r.StatusCode != 200 {
+		// OAuth service returned an error
+		return "", nil, os.NewError("OAuth Service returned an error : " + r.Status )
+	}
+
+
+	b, _ := ioutil.ReadAll( r.Body ) 
+	s := string(b)
+
+
+	rt := &RequestToken{}
+
+	if strings.Index(s, "&") == -1 {
+		// Body is empty 
+		return "", nil, os.NewError("Empty response from server")
+	}
+
+	vals := strings.Split(s, "&", 10)
+
+	for i := range vals {
+		if strings.Index(vals[i], "=") > -1 {
+			kv := strings.Split(vals[i], "=", 2)
+			if len(kv) > 0 { // Adds the key even if there's no value. 
+				switch kv[0]{
+					case "oauth_token":					if len(kv) > 1 { rt.Token = kv[1] }; break
+					case "oauth_token_secret":			if len(kv) > 1 { rt.Secret = kv[1] }; break
+				}
+			}
+		}
+	}
+
+	oc.appendRequestToken(rt)
+
+	return oc.AuthorizationURL + "?oauth_token=" + rt.Token, rt, nil
+
+}
+
+// GetAccessToken gets the access token for the response from the Authorization URL
+func (oc *OAuthConsumer) GetAccessToken(token string, verifier string, ) *AccessToken{
+
+	var rt *RequestToken
+
+	// Match the RequestToken by Token
+	for i := range oc.requestTokens {
+		if oc.requestTokens[i].Token == token || 
+		   oc.requestTokens[i].Token == Encode(token) {
+			rt = oc.requestTokens[i]
+		}
+	}
+
+	rt.Verifier = verifier
+
+	// Gather the params
+	p := Params{}
+
+	// Add required OAuth params
+	p.Add( &Pair{ Key:"oauth_consumer_key", Value:oc.ConsumerKey } )
+	p.Add( &Pair{ Key:"oauth_token", Value:rt.Token })
+	p.Add( &Pair{ Key:"oauth_verifier", Value:rt.Verifier })
+	p.Add( &Pair{ Key:"oauth_signature_method", Value:"HMAC-SHA1" } )
+	p.Add( &Pair{ Key:"oauth_timestamp", Value:strconv.Itoa64(time.Seconds()) } )
+	p.Add( &Pair{ Key:"oauth_nonce", Value:strconv.Itoa64(rand.Int63()) } )
+	p.Add( &Pair{ Key:"oauth_version", Value:"1.0" } )
+
+	// Sort the collection
+	sort.Sort(p)
+
+	// Generate string of sorted params
+	sigBaseCol := make([]string, len(p))
+	for i := range p {
+		sigBaseCol[i] = Encode(p[i].Key) + "=" + Encode( p[i].Value )
+	}
+
+	sigBaseStr :=	"POST&" +
+					Encode(oc.AccessTokenURL) + "&" +
+					Encode(strings.Join(sigBaseCol, "&"))
+
+	sigBaseStr= strings.Replace(sigBaseStr, Encode(Encode(rt.Token)), Encode(rt.Token), 1)
+
+	// Generate Composite Signing key
+	key := Encode(oc.ConsumerSecret) + "&" + rt.Secret
+
+	// Generate Signature
+	d := oc.digest(key, sigBaseStr)
+
+	// Build Auth Header
+	authHeader := "OAuth "
+	for i := range p {
+		authHeader +=  p[i].Key + "=\"" + Encode(p[i].Value ) + "\", "
+	}
+
+
+	// Add the signature
+	authHeader += "oauth_signature=\"" + Encode(d) + "\""
+
+	authHeader = strings.Replace(authHeader, Encode(rt.Token), rt.Token, 1)
+
+	// Add Header & Buffer for params
+	buf := &bytes.Buffer{}
+	headers := map[string]string{
+		"Content-Type":"application/x-www-form-urlencoded",
+		"Authorization":authHeader,
+	}
+
+	// Action the POST to get the AccessToken
+	r, err :=  post(oc.AccessTokenURL, headers, buf)
+
+	if err != nil {
+		fmt.Println(err.String())
+		return nil
+	}
+
+	// Read response Body & Create AccessToken
+	b, _ := ioutil.ReadAll( r.Body ) 
+	s := string(b)
+	at := &AccessToken{ Service:oc.Service }
+
+	if strings.Index(s, "&") > -1 {
+		vals := strings.Split(s, "&", 10)
+
+		for i := range vals {
+			if strings.Index(vals[i], "=") > -1 {
+				kv := strings.Split(vals[i], "=", 2)
+				if len(kv) > 0 { // Adds the key even if there's no value. 
+					switch kv[0]{
+						case "oauth_token":					if len(kv) > 1 { at.Token = kv[1] };  break
+						case "oauth_token_secret":			if len(kv) > 1 { at.Secret = kv[1] }; break
+					}
+				}
+			}
+		}
+	}
+
+	// Return the AccessToken
+	return at
+
+}
+
+// OAuthRequestGet return the response via a GET for the url with the AccessToken passed
+func (oc *OAuthConsumer) Get( url string, fparams Params, at *AccessToken) (r *http.Response, err os.Error) {
+	return oc.oAuthRequest(url, fparams, at, "GET")
+}
+
+// OAuthRequest returns the response via a POST for the url with the AccessToken passed & the Form params passsed in fparams
+func (oc *OAuthConsumer) Post( url string, fparams Params, at *AccessToken) (r *http.Response, err os.Error) {
+	return oc.oAuthRequest( url, fparams, at, "POST")
+}
+
+func (oc *OAuthConsumer) oAuthRequest( url string, fparams Params, at *AccessToken, method string) (r *http.Response, err os.Error) {
+
+	// Gather the params
+	p := Params{}
+
+	hp := Params{}
+
+	// Add required OAuth params
+	p.Add( &Pair{ Key:"oauth_token", Value:at.Token })
+	p.Add( &Pair{ Key:"oauth_signature_method", Value:"HMAC-SHA1" } )
+	p.Add( &Pair{ Key:"oauth_consumer_key", Value:oc.ConsumerKey } )
+	p.Add( &Pair{ Key:"oauth_timestamp", Value:strconv.Itoa64(time.Seconds()) } )
+	p.Add( &Pair{ Key:"oauth_nonce", Value:strconv.Itoa64(rand.Int63()) } )
+	p.Add( &Pair{ Key:"oauth_version", Value:"1.0" } )
+
+	// Add the params to the Header collection
+	for i := range p {
+		hp.Add( &Pair{ Key:p[i].Key, Value:p[i].Value } )
+	}
+
+	fparamsStr := ""
+	// Add any additional params passed
+	for i := range fparams{
+		k, v := fparams[i].Key, fparams[i].Value
+		p.Add( &Pair{ Key:k, Value:v } )
+		fparamsStr += k + "=" + Encode(v) + "&"
+	}
+
+	// Sort the collection
+	sort.Sort(p)
+
+	// Generate string of sorted params
+	sigBaseCol := make([]string, len(p))
+	for i := range p {
+		sigBaseCol[i] = Encode(p[i].Key) + "=" + Encode( p[i].Value )
+	}
+
+	sigBaseStr :=	method + "&" +
+					Encode(url) + "&" +
+					Encode(strings.Join(sigBaseCol, "&"))
+
+	sigBaseStr= strings.Replace(sigBaseStr, Encode(Encode(at.Token)), Encode(at.Token), 1)
+
+	// Generate Composite Signing key
+	key := Encode( oc.ConsumerSecret ) + "&" + at.Secret 
+
+	// Generate Signature
+	d := oc.digest(key, sigBaseStr)
+
+	// Build Auth Header
+	authHeader := "OAuth "
+	for i := range hp {
+		if strings.Index(hp[i].Key, "oauth") == 0 {
+			//Add it to the authHeader
+			authHeader += hp[i].Key + "=\"" + Encode(hp[i].Value ) + "\", "
+		}
+	}
+
+	// Add the signature
+	authHeader += "oauth_signature=\"" + Encode(d) + "\""
+
+	authHeader = strings.Replace(authHeader, Encode(at.Token), at.Token, 1)
+
+
+	// Add Header & Buffer for params
+	buf := bytes.NewBufferString(fparamsStr)
+	headers := map[string]string{
+		"Authorization":authHeader,
+	}
+
+	if method == "GET" {
+		// return Get response
+		return get(url + "?" + fparamsStr, headers)
+	}
+
+	// return POSTs response
+	return post(url, headers, buf)
+
+}
+
+
+// digest Generates a HMAC-1234 for the signature
+func (oc *OAuthConsumer) digest(key string, m string) string {
+	h := hmac.NewSHA1([]byte(key))
+	h.Write([]byte(m))
+	return base64encode(h.Sum())
+
+/*	s := bytes.TrimSpace(h.Sum())
+	d := make([]byte, base64.StdEncoding.EncodedLen(len(s)))
+	base64.StdEncoding.Encode(d, s)
+	ds := strings.TrimSpace(bytes.NewBuffer(d).String())
+*/
+//	return ds
+
+}
+
+// appendRequestToken adds the Request Tokens to a localy temp collection
+func (oc *OAuthConsumer) appendRequestToken(token *RequestToken){
+
+	if oc.requestTokens == nil { oc.requestTokens = make([]*RequestToken, 0, 4) }
+
+	n := len(oc.requestTokens)
+
+	if n+1 > cap(oc.requestTokens) {
+		s := make([]*RequestToken, n, 2*n+1)
+		copy(s, oc.requestTokens)
+		oc.requestTokens = s
+	}
+	oc.requestTokens = oc.requestTokens[0 : n+1]
+	oc.requestTokens[n] = token
+
+}
+
+
+package oauth
+
+import(
+)
+
+type Pair struct {
+	Key string
+	Value string
+}
+
+type Params []*Pair
+
+func (p Params) Len() int {  return len(p)}
+func (p Params) Less(i, j int) bool {
+	if p[i].Key == p[j].Key {
+		return p[i].Value < p[j].Value
+	}
+	return p[i].Key < p[j].Key
+}
+func (p Params) Swap(i, j int) { p[i], p[j] = p[j], p[i]}
+
+
+func (p *Params) Add(pair *Pair){
+	a := *p
+	n := len(a)
+
+	if n+1 > cap(a) {
+		s := make([]*Pair, n, 2*n+1)
+		copy(s, a)
+		a = s
+	}
+	a = a[0 : n+1]
+	a[n] = pair
+	*p = a
+
+}
+
+
+package oauth
+
+import (
+)
+
+type RequestToken struct{
+	Token string
+	Secret string
+	Verifier string
+}
+
+type AccessToken struct{
+	Id string
+	Token string
+	Secret string
+	UserRef string
+	Verifier string
+	Service string
+}
+
+
+
+

goauth/urlencode.go

+// Copyright 2010 Calvin McAnarney <calvin@mcanarney.org>
+//
+// Use of this file is governed by the ISC license. See the LICENSE file for
+// details.
+
+package oauth
+
+import (
+	"bytes"
+	"encoding/base64"
+)
+
+// base64encode returns a string representation of Base64-encoded src.
+func base64encode(src []byte) string {
+	var buf bytes.Buffer
+	encoder := base64.NewEncoder(base64.StdEncoding, &buf)
+	encoder.Write(src)
+	encoder.Close()
+	return buf.String()
+}
+
+// Encode percent-encodes a string as defined in RFC 3986.
+func Encode(s string) string {
+	var enc string
+	for _, c := range []byte(s) {
+		if isEncodable(c) {
+			enc += "%"
+			enc += string("0123456789ABCDEF"[c>>4])
+			enc += string("0123456789ABCDEF"[c&15])
+		} else {
+			enc += string(c)
+		}
+	}
+	return enc
+}
+
+// isEncodable returns true if a given character should be percent-encoded
+// according to RFC 3986.
+func isEncodable(c byte) bool {
+	// return false if c is an unreserved character (see RFC 3986 section 2.3)
+	switch {
+	case (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'):
+		return false
+	case c >= '0' && c <= '9':
+		return false
+	case c == '-' || c == '.' || c == '_' || c == '~':
+		return false
+	}
+	return true
+}
+