162 lines
4.3 KiB
Go
162 lines
4.3 KiB
Go
package flickr
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/md5"
|
|
"crypto/sha1"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Generate a random string of 8 chars, needed for OAuth signature
|
|
func generateNonce() string {
|
|
rand.Seed(time.Now().UTC().UnixNano())
|
|
// For convenience, use a set of chars we don't need to url-escape
|
|
var letters = []rune("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ")
|
|
b := make([]rune, 8)
|
|
for i := range b {
|
|
b[i] = letters[rand.Intn(len(letters))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// An utility type to wrap all resources and data needed to complete requests
|
|
// to the Flickr API
|
|
type FlickrClient struct {
|
|
// Flickr application api key
|
|
ApiKey string
|
|
// Flickr application api secret
|
|
ApiSecret string
|
|
// A generic HTTP client to perform GET and POST requests
|
|
HTTPClient *http.Client
|
|
// The base url for API endpoints
|
|
EndpointUrl string
|
|
// A string containing POST or GET, needed for OAuth signing
|
|
HTTPVerb string
|
|
// A set of url params to query the API
|
|
Args url.Values
|
|
// User access token
|
|
OAuthToken string
|
|
// User secret token
|
|
OAuthTokenSecret string
|
|
// User flickr ID
|
|
Id string
|
|
}
|
|
|
|
// Create a Flickr client, apiKey and apiSecret are mandatory
|
|
func NewFlickrClient(apiKey string, apiSecret string) *FlickrClient {
|
|
return &FlickrClient{
|
|
ApiKey: apiKey,
|
|
ApiSecret: apiSecret,
|
|
HTTPClient: &http.Client{},
|
|
HTTPVerb: "GET",
|
|
Args: url.Values{},
|
|
}
|
|
}
|
|
|
|
// Sign the next request performed by the FlickrClient
|
|
func (c *FlickrClient) Sign(tokenSecret string) {
|
|
// the "oauth_signature" param must not be included in the signing process
|
|
c.Args.Del("oauth_signature")
|
|
c.Args.Set("oauth_signature", c.getSignature(tokenSecret))
|
|
}
|
|
|
|
// Set the mandatory params for an OAuth request
|
|
func (c *FlickrClient) SetOAuthDefaults() {
|
|
c.Args.Add("oauth_version", "1.0")
|
|
c.Args.Add("oauth_signature_method", "HMAC-SHA1")
|
|
c.Args.Add("oauth_nonce", generateNonce())
|
|
c.Args.Add("oauth_timestamp", fmt.Sprintf("%d", time.Now().Unix()))
|
|
}
|
|
|
|
// Sign the request with a default set of OAuth parameters, needed to authorize
|
|
// users for certain writing/destructive operations.
|
|
func (c *FlickrClient) OAuthSign() {
|
|
c.SetOAuthDefaults()
|
|
c.Args.Set("oauth_token", c.OAuthToken)
|
|
c.Args.Set("oauth_consumer_key", c.ApiKey)
|
|
c.Args.Set("api_key", c.ApiKey)
|
|
|
|
c.Sign(c.OAuthTokenSecret)
|
|
}
|
|
|
|
// Specific signing process for API calls: not the same as OAuth sign, used
|
|
// for requests that don't need user authorizations.
|
|
func (c *FlickrClient) ApiSign() {
|
|
c.Args.Set("api_key", c.ApiKey)
|
|
// the "api_sig" param must not be included in the signing process
|
|
c.Args.Del("api_sig")
|
|
c.Args.Set("api_sig", c.getApiSignature(c.ApiSecret))
|
|
}
|
|
|
|
// Evaluate the complete URL to make requests (base url + params)
|
|
func (c *FlickrClient) GetUrl() string {
|
|
return fmt.Sprintf("%s?%s", c.EndpointUrl, c.Args.Encode())
|
|
}
|
|
|
|
// Remove all query params
|
|
func (c *FlickrClient) ClearArgs() {
|
|
c.Args = url.Values{}
|
|
}
|
|
|
|
// Reset Args and set the default endpoint
|
|
func (c *FlickrClient) Init() {
|
|
c.ClearArgs()
|
|
c.EndpointUrl = API_ENDPOINT
|
|
}
|
|
|
|
// Get the base string to compose the signature
|
|
func (c *FlickrClient) getSigningBaseString() string {
|
|
request_url := url.QueryEscape(c.EndpointUrl)
|
|
flickr_encoded := strings.Replace(c.Args.Encode(), "+", "%20", -1)
|
|
query := url.QueryEscape(flickr_encoded)
|
|
|
|
ret := fmt.Sprintf("%s&%s&%s", c.HTTPVerb, request_url, query)
|
|
return ret
|
|
}
|
|
|
|
// Compute the signature of a signed request
|
|
func (c *FlickrClient) getSignature(token_secret string) string {
|
|
key := fmt.Sprintf("%s&%s", url.QueryEscape(c.ApiSecret), url.QueryEscape(token_secret))
|
|
base_string := c.getSigningBaseString()
|
|
|
|
mac := hmac.New(sha1.New, []byte(key))
|
|
mac.Write([]byte(base_string))
|
|
|
|
ret := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
|
|
|
return ret
|
|
}
|
|
|
|
// Sign API requests. This method differs from the signing process needed for
|
|
// OAuth authenticated requests.
|
|
func (c *FlickrClient) getApiSignature(token_secret string) string {
|
|
var buf bytes.Buffer
|
|
buf.WriteString(token_secret)
|
|
|
|
keys := make([]string, 0, len(c.Args))
|
|
for k := range c.Args {
|
|
keys = append(keys, k)
|
|
}
|
|
// args needs to be in alphabetical order
|
|
sort.Strings(keys)
|
|
|
|
for _, k := range keys {
|
|
arg := c.Args[k][0]
|
|
buf.WriteString(k)
|
|
buf.WriteString(arg)
|
|
}
|
|
|
|
base := buf.String()
|
|
|
|
data := []byte(base)
|
|
return fmt.Sprintf("%x", md5.Sum(data))
|
|
}
|