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

// The oauth package provides support for making
// OAuth2-authenticated HTTP requests.
//
// Example usage:
//
//	// Specify your configuration. (typically as a global variable)
//	var config = &oauth.Config{
//		ClientId:     YOUR_CLIENT_ID,
//		ClientSecret: YOUR_CLIENT_SECRET,
//		Scope:        "https://www.googleapis.com/auth/buzz",
//		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
//		TokenURL:     "https://accounts.google.com/o/oauth2/token",
//		RedirectURL:  "http://you.example.org/handler",
//	}
//
//	// A landing page redirects to the OAuth provider to get the auth code.
//	func landing(w http.ResponseWriter, r *http.Request) {
//		http.Redirect(w, r, config.AuthCodeURL("foo"), http.StatusFound)
//	}
//
//	// The user will be redirected back to this handler, that takes the
//	// "code" query parameter and Exchanges it for an access token.
//	func handler(w http.ResponseWriter, r *http.Request) {
//		t := &oauth.Transport{Config: config}
//		t.Exchange(r.FormValue("code"))
//		// The Transport now has a valid Token. Create an *http.Client
//		// with which we can make authenticated API requests.
//		c := t.Client()
//		c.Post(...)
//		// ...
//		// btw, r.FormValue("state") == "foo"
//	}
//
package oauth

import (
	"encoding/json"
	"io/ioutil"
	"mime"
	"net/http"
	"net/url"
	"os"
	"time"
)

const (
	WEIBO_URL = "api.weibo.com"
)

type OAuthError struct {
	prefix string
	msg    string
}

func (oe OAuthError) Error() string {
	return "OAuthError: " + oe.prefix + ": " + oe.msg
}

// Cache specifies the methods that implement a Token cache.
type Cache interface {
	Token() (*Token, error)
	PutToken(*Token) error
}

// CacheFile implements Cache. Its value is the name of the file in which
// the Token is stored in JSON format.
type CacheFile string

func (f CacheFile) Token() (*Token, error) {
	file, err := os.Open(string(f))
	if err != nil {
		return nil, OAuthError{"CacheFile.Token", err.Error()}
	}
	tok := &Token{}
	dec := json.NewDecoder(file)
	if err = dec.Decode(tok); err != nil {
		return nil, OAuthError{"CacheFile.Token", err.Error()}
	}
	return tok, nil
}

func (f CacheFile) PutToken(tok *Token) error {
	file, err := os.OpenFile(string(f), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return OAuthError{"CacheFile.PutToken", err.Error()}
	}
	enc := json.NewEncoder(file)
	return enc.Encode(tok)
}

// Config is the configuration of an OAuth consumer.
type Config struct {
	// ClientId is the OAuth client identifier used when communicating with
	// the configured OAuth provider.
	ClientId string

	// ClientSecret is the OAuth client secret used when communicating with
	// the configured OAuth provider.
	ClientSecret string

	// Scope identifies the level of access being requested. Multiple scope
	// values should be provided as a space-delimited string.
	Scope string

	// AuthURL is the URL the user will be directed to in order to grant
	// access.
	AuthURL string

	// TokenURL is the URL used to retrieve OAuth tokens.
	TokenURL string

	// RedirectURL is the URL to which the user will be returned after
	// granting (or denying) access.
	RedirectURL string

	// TokenCache allows tokens to be cached for subsequent requests.
	TokenCache Cache

	AccessType string // Optional, "online" (default) or "offline", no refresh token if "online"

	// ApprovalPrompt indicates whether the user should be
	// re-prompted for consent. If set to "auto" (default) the
	// user will be prompted only if they haven't previously
	// granted consent and the code can only be exchanged for an
	// access token.
	// If set to "force" the user will always be prompted, and the
	// code can be exchanged for a refresh token.
	ApprovalPrompt string
}

// Token contains an end-user's tokens.
// This is the data you must store to persist authentication.
type Token struct {
	AccessToken  string
	RefreshToken string
	Expiry       time.Time // If zero the token has no (known) expiry time.
}

func (t *Token) Expired() bool {
	if t.Expiry.IsZero() {
		return false
	}
	return t.Expiry.Before(time.Now())
}

// Transport implements http.RoundTripper. When configured with a valid
// Config and Token it can be used to make authenticated HTTP requests.
//
//	t := &oauth.Transport{config}
//      t.Exchange(code)
//      // t now contains a valid Token
//	r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
//
// It will automatically refresh the Token if it can,
// updating the supplied Token in place.
type Transport struct {
	*Config
	*Token

	// Transport is the HTTP transport to use when making requests.
	// It will default to http.DefaultTransport if nil.
	// (It should never be an oauth.Transport.)
	Transport http.RoundTripper
}

// Client returns an *http.Client that makes OAuth-authenticated requests.
func (t *Transport) Client() *http.Client {
	return &http.Client{Transport: t}
}

func (t *Transport) transport() http.RoundTripper {
	if t.Transport != nil {
		return t.Transport
	}
	return http.DefaultTransport
}

// AuthCodeURL returns a URL that the end-user should be redirected to,
// so that they may obtain an authorization code.
func (c *Config) AuthCodeURL(state string) string {
	url_, err := url.Parse(c.AuthURL)
	if err != nil {
		panic("AuthURL malformed: " + err.Error())
	}
	q := url.Values{
		"response_type":   {"code"},
		"client_id":       {c.ClientId},
		"redirect_uri":    {c.RedirectURL},
		"scope":           {c.Scope},
		"state":           {state},
		"access_type":     {c.AccessType},
		"approval_prompt": {c.ApprovalPrompt},
	}.Encode()
	if url_.RawQuery == "" {
		url_.RawQuery = q
	} else {
		url_.RawQuery += "&" + q
	}
	return url_.String()
}

// Exchange takes a code and gets access Token from the remote server.
func (t *Transport) Exchange(code string) (*Token, error) {
	if t.Config == nil {
		return nil, OAuthError{"Exchange", "no Config supplied"}
	}

	// If the transport or the cache already has a token, it is
	// passed to `updateToken` to preserve existing refresh token.
	tok := t.Token
	if tok == nil && t.TokenCache != nil {
		tok, _ = t.TokenCache.Token()
	}
	if tok == nil {
		tok = new(Token)
	}
	err := t.updateToken(tok, url.Values{
		"grant_type":   {"authorization_code"},
		"redirect_uri": {t.RedirectURL},
		"scope":        {t.Scope},
		"code":         {code},
	})
	if err != nil {
		return nil, err
	}
	t.Token = tok
	if t.TokenCache != nil {
		return tok, t.TokenCache.PutToken(tok)
	}
	return tok, nil
}

// RoundTrip executes a single HTTP transaction using the Transport's
// Token as authorization headers.
//
// This method will attempt to renew the Token if it has expired and may return
// an error related to that Token renewal before attempting the client request.
// If the Token cannot be renewed a non-nil os.Error value will be returned.
// If the Token is invalid callers should expect HTTP-level errors,
// as indicated by the Response's StatusCode.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	if t.Token == nil {
		if t.Config == nil {
			return nil, OAuthError{"RoundTrip", "no Config supplied"}
		}
		if t.TokenCache == nil {
			return nil, OAuthError{"RoundTrip", "no Token supplied"}
		}
		var err error
		t.Token, err = t.TokenCache.Token()
		if err != nil {
			return nil, err
		}
	}

	// Refresh the Token if it has expired.
	if t.Expired() {
		if err := t.Refresh(); err != nil {
			return nil, err
		}
	}

	// To set the Authorization header, we must make a copy of the Request
	// so that we don't modify the Request we were given.
	// This is required by the specification of http.RoundTripper.
	req = cloneRequest(req)

	if req.Host == WEIBO_URL {
		req.Header.Set("Authorization", "OAuth2 "+t.AccessToken) // for weibo.com
	} else {
		req.Header.Set("Authorization", "Bearer "+t.AccessToken)
	}

	// Make the HTTP request.
	return t.transport().RoundTrip(req)
}

// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
	// shallow copy of the struct
	r2 := new(http.Request)
	*r2 = *r
	// deep copy of the Header
	r2.Header = make(http.Header)
	for k, s := range r.Header {
		r2.Header[k] = s
	}
	return r2
}

// Refresh renews the Transport's AccessToken using its RefreshToken.
func (t *Transport) Refresh() error {
	if t.Token == nil {
		return OAuthError{"Refresh", "no existing Token"}
	}
	if t.RefreshToken == "" {
		return OAuthError{"Refresh", "Token expired; no Refresh Token"}
	}
	if t.Config == nil {
		return OAuthError{"Refresh", "no Config supplied"}
	}

	err := t.updateToken(t.Token, url.Values{
		"grant_type":    {"refresh_token"},
		"refresh_token": {t.RefreshToken},
	})
	if err != nil {
		return err
	}
	if t.TokenCache != nil {
		return t.TokenCache.PutToken(t.Token)
	}
	return nil
}

func (t *Transport) updateToken(tok *Token, v url.Values) error {
	v.Set("client_id", t.ClientId)
	v.Set("client_secret", t.ClientSecret)
	r, err := (&http.Client{Transport: t.transport()}).PostForm(t.TokenURL, v)
	if err != nil {
		return err
	}
	defer r.Body.Close()
	if r.StatusCode != 200 {
		return OAuthError{"updateToken", r.Status}
	}
	var b struct {
		Access    string        `json:"access_token"`
		Refresh   string        `json:"refresh_token"`
		ExpiresIn time.Duration `json:"expires_in"`
	}

	if r.Request.Host == WEIBO_URL {
		if err = json.NewDecoder(r.Body).Decode(&b); err != nil {
			return err
		}
		// weibo return the expires in ms
		b.ExpiresIn *= time.Millisecond
	} else {
		content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
		switch content {
		case "application/x-www-form-urlencoded", "text/plain":
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				return err
			}
			vals, err := url.ParseQuery(string(body))
			if err != nil {
				return err
			}

			b.Access = vals.Get("access_token")
			b.Refresh = vals.Get("refresh_token")
			b.ExpiresIn, _ = time.ParseDuration(vals.Get("expires_in") + "s")
		default:
			if err = json.NewDecoder(r.Body).Decode(&b); err != nil {
				return err
			}
			// The JSON parser treats the unitless ExpiresIn like 'ns' instead of 's' as above,
			// so compensate here.
			b.ExpiresIn *= time.Second
		}
	}

	tok.AccessToken = b.Access
	// Don't overwrite `RefreshToken` with an empty value
	if len(b.Refresh) > 0 {
		tok.RefreshToken = b.Refresh
	}
	if b.ExpiresIn == 0 {
		tok.Expiry = time.Time{}
	} else {
		tok.Expiry = time.Now().Add(b.ExpiresIn)
	}
	return nil
}
<commit_msg>as weibo returns uid when getting token, it is convinient to just reuse it<commit_after>// Copyright 2011 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The oauth package provides support for making
// OAuth2-authenticated HTTP requests.
//
// Example usage:
//
//	// Specify your configuration. (typically as a global variable)
//	var config = &oauth.Config{
//		ClientId:     YOUR_CLIENT_ID,
//		ClientSecret: YOUR_CLIENT_SECRET,
//		Scope:        "https://www.googleapis.com/auth/buzz",
//		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
//		TokenURL:     "https://accounts.google.com/o/oauth2/token",
//		RedirectURL:  "http://you.example.org/handler",
//	}
//
//	// A landing page redirects to the OAuth provider to get the auth code.
//	func landing(w http.ResponseWriter, r *http.Request) {
//		http.Redirect(w, r, config.AuthCodeURL("foo"), http.StatusFound)
//	}
//
//	// The user will be redirected back to this handler, that takes the
//	// "code" query parameter and Exchanges it for an access token.
//	func handler(w http.ResponseWriter, r *http.Request) {
//		t := &oauth.Transport{Config: config}
//		t.Exchange(r.FormValue("code"))
//		// The Transport now has a valid Token. Create an *http.Client
//		// with which we can make authenticated API requests.
//		c := t.Client()
//		c.Post(...)
//		// ...
//		// btw, r.FormValue("state") == "foo"
//	}
//
package oauth

import (
	"encoding/json"
	"io/ioutil"
	"mime"
	"net/http"
	"net/url"
	"os"
	"time"
)

const (
	WEIBO_URL = "api.weibo.com"
)

type OAuthError struct {
	prefix string
	msg    string
}

func (oe OAuthError) Error() string {
	return "OAuthError: " + oe.prefix + ": " + oe.msg
}

// Cache specifies the methods that implement a Token cache.
type Cache interface {
	Token() (*Token, error)
	PutToken(*Token) error
}

// CacheFile implements Cache. Its value is the name of the file in which
// the Token is stored in JSON format.
type CacheFile string

func (f CacheFile) Token() (*Token, error) {
	file, err := os.Open(string(f))
	if err != nil {
		return nil, OAuthError{"CacheFile.Token", err.Error()}
	}
	tok := &Token{}
	dec := json.NewDecoder(file)
	if err = dec.Decode(tok); err != nil {
		return nil, OAuthError{"CacheFile.Token", err.Error()}
	}
	return tok, nil
}

func (f CacheFile) PutToken(tok *Token) error {
	file, err := os.OpenFile(string(f), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return OAuthError{"CacheFile.PutToken", err.Error()}
	}
	enc := json.NewEncoder(file)
	return enc.Encode(tok)
}

// Config is the configuration of an OAuth consumer.
type Config struct {
	// ClientId is the OAuth client identifier used when communicating with
	// the configured OAuth provider.
	ClientId string

	// ClientSecret is the OAuth client secret used when communicating with
	// the configured OAuth provider.
	ClientSecret string

	// Scope identifies the level of access being requested. Multiple scope
	// values should be provided as a space-delimited string.
	Scope string

	// AuthURL is the URL the user will be directed to in order to grant
	// access.
	AuthURL string

	// TokenURL is the URL used to retrieve OAuth tokens.
	TokenURL string

	// RedirectURL is the URL to which the user will be returned after
	// granting (or denying) access.
	RedirectURL string

	// TokenCache allows tokens to be cached for subsequent requests.
	TokenCache Cache

	AccessType string // Optional, "online" (default) or "offline", no refresh token if "online"

	// ApprovalPrompt indicates whether the user should be
	// re-prompted for consent. If set to "auto" (default) the
	// user will be prompted only if they haven't previously
	// granted consent and the code can only be exchanged for an
	// access token.
	// If set to "force" the user will always be prompted, and the
	// code can be exchanged for a refresh token.
	ApprovalPrompt string
}

// Token contains an end-user's tokens.
// This is the data you must store to persist authentication.
type Token struct {
	AccessToken  string
	RefreshToken string
	Expiry       time.Time // If zero the token has no (known) expiry time.
	Uid          string
}

func (t *Token) Expired() bool {
	if t.Expiry.IsZero() {
		return false
	}
	return t.Expiry.Before(time.Now())
}

// Transport implements http.RoundTripper. When configured with a valid
// Config and Token it can be used to make authenticated HTTP requests.
//
//	t := &oauth.Transport{config}
//      t.Exchange(code)
//      // t now contains a valid Token
//	r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
//
// It will automatically refresh the Token if it can,
// updating the supplied Token in place.
type Transport struct {
	*Config
	*Token

	// Transport is the HTTP transport to use when making requests.
	// It will default to http.DefaultTransport if nil.
	// (It should never be an oauth.Transport.)
	Transport http.RoundTripper
}

// Client returns an *http.Client that makes OAuth-authenticated requests.
func (t *Transport) Client() *http.Client {
	return &http.Client{Transport: t}
}

func (t *Transport) transport() http.RoundTripper {
	if t.Transport != nil {
		return t.Transport
	}
	return http.DefaultTransport
}

// AuthCodeURL returns a URL that the end-user should be redirected to,
// so that they may obtain an authorization code.
func (c *Config) AuthCodeURL(state string) string {
	url_, err := url.Parse(c.AuthURL)
	if err != nil {
		panic("AuthURL malformed: " + err.Error())
	}
	q := url.Values{
		"response_type":   {"code"},
		"client_id":       {c.ClientId},
		"redirect_uri":    {c.RedirectURL},
		"scope":           {c.Scope},
		"state":           {state},
		"access_type":     {c.AccessType},
		"approval_prompt": {c.ApprovalPrompt},
	}.Encode()
	if url_.RawQuery == "" {
		url_.RawQuery = q
	} else {
		url_.RawQuery += "&" + q
	}
	return url_.String()
}

// Exchange takes a code and gets access Token from the remote server.
func (t *Transport) Exchange(code string) (*Token, error) {
	if t.Config == nil {
		return nil, OAuthError{"Exchange", "no Config supplied"}
	}

	// If the transport or the cache already has a token, it is
	// passed to `updateToken` to preserve existing refresh token.
	tok := t.Token
	if tok == nil && t.TokenCache != nil {
		tok, _ = t.TokenCache.Token()
	}
	if tok == nil {
		tok = new(Token)
	}
	err := t.updateToken(tok, url.Values{
		"grant_type":   {"authorization_code"},
		"redirect_uri": {t.RedirectURL},
		"scope":        {t.Scope},
		"code":         {code},
	})
	if err != nil {
		return nil, err
	}
	t.Token = tok
	if t.TokenCache != nil {
		return tok, t.TokenCache.PutToken(tok)
	}
	return tok, nil
}

// RoundTrip executes a single HTTP transaction using the Transport's
// Token as authorization headers.
//
// This method will attempt to renew the Token if it has expired and may return
// an error related to that Token renewal before attempting the client request.
// If the Token cannot be renewed a non-nil os.Error value will be returned.
// If the Token is invalid callers should expect HTTP-level errors,
// as indicated by the Response's StatusCode.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	if t.Token == nil {
		if t.Config == nil {
			return nil, OAuthError{"RoundTrip", "no Config supplied"}
		}
		if t.TokenCache == nil {
			return nil, OAuthError{"RoundTrip", "no Token supplied"}
		}
		var err error
		t.Token, err = t.TokenCache.Token()
		if err != nil {
			return nil, err
		}
	}

	// Refresh the Token if it has expired.
	if t.Expired() {
		if err := t.Refresh(); err != nil {
			return nil, err
		}
	}

	// To set the Authorization header, we must make a copy of the Request
	// so that we don't modify the Request we were given.
	// This is required by the specification of http.RoundTripper.
	req = cloneRequest(req)

	if req.Host == WEIBO_URL {
		req.Header.Set("Authorization", "OAuth2 "+t.AccessToken) // for weibo.com
	} else {
		req.Header.Set("Authorization", "Bearer "+t.AccessToken)
	}

	// Make the HTTP request.
	return t.transport().RoundTrip(req)
}

// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
	// shallow copy of the struct
	r2 := new(http.Request)
	*r2 = *r
	// deep copy of the Header
	r2.Header = make(http.Header)
	for k, s := range r.Header {
		r2.Header[k] = s
	}
	return r2
}

// Refresh renews the Transport's AccessToken using its RefreshToken.
func (t *Transport) Refresh() error {
	if t.Token == nil {
		return OAuthError{"Refresh", "no existing Token"}
	}
	if t.RefreshToken == "" {
		return OAuthError{"Refresh", "Token expired; no Refresh Token"}
	}
	if t.Config == nil {
		return OAuthError{"Refresh", "no Config supplied"}
	}

	err := t.updateToken(t.Token, url.Values{
		"grant_type":    {"refresh_token"},
		"refresh_token": {t.RefreshToken},
	})
	if err != nil {
		return err
	}
	if t.TokenCache != nil {
		return t.TokenCache.PutToken(t.Token)
	}
	return nil
}

func (t *Transport) updateToken(tok *Token, v url.Values) error {
	v.Set("client_id", t.ClientId)
	v.Set("client_secret", t.ClientSecret)
	r, err := (&http.Client{Transport: t.transport()}).PostForm(t.TokenURL, v)
	if err != nil {
		return err
	}
	defer r.Body.Close()
	if r.StatusCode != 200 {
		return OAuthError{"updateToken", r.Status}
	}
	var b struct {
		Access    string        `json:"access_token"`
		Refresh   string        `json:"refresh_token"`
		ExpiresIn time.Duration `json:"expires_in"`
		Uid       string
	}

	if r.Request.Host == WEIBO_URL {
		if err = json.NewDecoder(r.Body).Decode(&b); err != nil {
			return err
		}
		// weibo return the expires in ms
		b.ExpiresIn *= time.Millisecond
		tok.Uid = b.Uid //weibo returns uid when getting the token, kind of convinient
	} else {
		content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
		switch content {
		case "application/x-www-form-urlencoded", "text/plain":
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				return err
			}
			vals, err := url.ParseQuery(string(body))
			if err != nil {
				return err
			}

			b.Access = vals.Get("access_token")
			b.Refresh = vals.Get("refresh_token")
			b.ExpiresIn, _ = time.ParseDuration(vals.Get("expires_in") + "s")
		default:
			if err = json.NewDecoder(r.Body).Decode(&b); err != nil {
				return err
			}
			// The JSON parser treats the unitless ExpiresIn like 'ns' instead of 's' as above,
			// so compensate here.
			b.ExpiresIn *= time.Second
		}
	}

	tok.AccessToken = b.Access
	// Don't overwrite `RefreshToken` with an empty value
	if len(b.Refresh) > 0 {
		tok.RefreshToken = b.Refresh
	}
	if b.ExpiresIn == 0 {
		tok.Expiry = time.Time{}
	} else {
		tok.Expiry = time.Now().Add(b.ExpiresIn)
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
    "strings"
    "encoding/json"
    "net/http"
    "io/ioutil"
    "log"
    "fmt"
    "os"
    "os/signal"
    "github.com/zeebe-io/zbc-go/zbc"
    "github.com/zeebe-io/zbc-go/zbc/common"
    "github.com/zeebe-io/zbc-go/zbc/models/zbsubscriptions"
    "github.com/zeebe-io/zbc-go/zbc/services/zbsubscribe"
)

const ZeebeBrokerAddr = "0.0.0.0:51015"
const port = "9001"
var zbClient *zbc.Client;

func init() {
    initRestApi()
    initZeebe()
}

func main() {
    // Start HTTP server
    log.Fatal(
        http.ListenAndServe(":" + port, nil))
}

func initRestApi() {
    // setup REST endpoint (yay - this is not really REST - I know - but sufficient for this example)
    http.HandleFunc("/payment", handleHttpRequest)
}

func initZeebe() {
	// connect to Zeebe Broker
    newClient, err := zbc.NewClient(ZeebeBrokerAddr)
    if err != nil { log.Fatal( err ) }
    zbClient = newClient

    // register job handler for 'charge-credit-card' and subscribe
    subscription1, err := zbClient.JobSubscription("default-topic", "SomeWorker", "charge-credit-card",  1000, 32, hadleChargeCreditCardJob)
    if err != nil { log.Fatal(err) }
    subscription1.StartAsync()

    // register job handler for 'deduct-customer-credit' and subscribe
    subscription2, err := zbClient.JobSubscription("default-topic", "SomeWorker", "deduct-customer-credit",  1000, 32, handleDeductCustomerCredit)
    if err != nil { log.Fatal(err) }
    subscription2.StartAsync()

    // deploy workflow model if requested
    if (contains(os.Args, "deploy")) {
        deployment, err := zbClient.CreateWorkflowFromFile("default-topic", zbcommon.BpmnXml, "payment.bpmn")
        if err != nil { log.Fatal(err) }
        fmt.Println("deployed workflow model: ", deployment)
    }

    // disconnect nicely
    osCh := make(chan os.Signal, 1)
    signal.Notify(osCh, os.Interrupt)
    go func() {
        <-osCh
        err := subscription1.Close()
        if err != nil { log.Fatal(err) }

        err2 := subscription2.Close()
        if err2 != nil { log.Fatal(err2) }

        fmt.Println("Subscriptions closed.")
        os.Exit(0)
    }()
}

func handleHttpRequest(w http.ResponseWriter, r *http.Request) {
    bodyBytes, _ := ioutil.ReadAll(r.Body)
    jsonStr := string(bodyBytes)
    fmt.Println("Retrieving payment request" + jsonStr)

    chargeCreditCard(jsonStr, w)

    w.WriteHeader(http.StatusOK)
}

func chargeCreditCard(someDataAsJson string, w http.ResponseWriter) error {
    payload := make(map[string]interface{})
	json.Unmarshal([]byte(someDataAsJson), &payload)

    instance := zbc.NewWorkflowInstance("paymentV5", -1, payload)
    workflowInstance, err := zbClient.CreateWorkflowInstance("default-topic", instance)

    if (err != nil) {
        fmt.Fprintf(w, "Bam, error: " + err.Error())
        return err;
    } else {
        fmt.Fprintf(w, "Yeah, started: " + workflowInstance.String())
        return nil;
    }
}

func hadleChargeCreditCardJob(client zbsubscribe.ZeebeAPI, event *zbsubscriptions.SubscriptionEvent) {
    job, err := event.GetJob()
    if err != nil { log.Fatal(err) }
    payload, err := job.GetPayload();
    if err != nil { log.Fatal(err) }
    jsonPayload, err := json.Marshal( payload );
    if err != nil { log.Fatal(err) }

    _, err = doHttpCall(string(jsonPayload))
    if err != nil {
        // couldn't do http call, fail job to trigger retry
        client.FailJob(event)
    } else {
        // complete job after processing
        client.CompleteJob(event)
    }
}

func doHttpCall(someDataAsJson string) (resp *http.Response, err error) {
    fmt.Println("Doing http call", someDataAsJson)
    return http.Post("http://localhost:8099/charge", "application/json", strings.NewReader(someDataAsJson))
}

func handleDeductCustomerCredit(client zbsubscribe.ZeebeAPI, event *zbsubscriptions.SubscriptionEvent) {
    job, err := event.GetJob()
    if err != nil { log.Fatal(err) }
    payload, err := job.GetPayload()
    if err != nil { log.Fatal(err) }

    log.Println(" Substracting  from customer account") // " + strconv.Itoa( (*payload)["amount"].(int) ) + "

    // Hardcoded remaining amount, TODO: replace with randomized value
    (*payload)["remainingAmount"] = 5
    event.UpdatePayload(payload)

    client.CompleteJob(event)
}

/* Helper to check if "deploy" was given as argument */
func contains(arr []string, e string) bool {
    for _, a := range arr {
        if a == e {
            return true
        }
    }
    return false
}<commit_msg>added randomizer<commit_after>package main

import (
    "strings"
    "encoding/json"
    "net/http"
    "io/ioutil"
    "log"
    "fmt"
    "os"
    "os/signal"
    "github.com/zeebe-io/zbc-go/zbc"
    "github.com/zeebe-io/zbc-go/zbc/common"
    "github.com/zeebe-io/zbc-go/zbc/models/zbsubscriptions"
    "github.com/zeebe-io/zbc-go/zbc/services/zbsubscribe"
    "math/rand"
)

const ZeebeBrokerAddr = "0.0.0.0:51015"
const port = "9001"
var zbClient *zbc.Client;

func init() {
    initRestApi()
    initZeebe()
}

func main() {
    // Start HTTP server
    log.Fatal(
        http.ListenAndServe(":" + port, nil))
}

func initRestApi() {
    // setup REST endpoint (yay - this is not really REST - I know - but sufficient for this example)
    http.HandleFunc("/payment", handleHttpRequest)
}

func initZeebe() {
	// connect to Zeebe Broker
    newClient, err := zbc.NewClient(ZeebeBrokerAddr)
    if err != nil { log.Fatal( err ) }
    zbClient = newClient

    // register job handler for 'charge-credit-card' and subscribe
    subscription1, err := zbClient.JobSubscription("default-topic", "SomeWorker", "charge-credit-card",  1000, 32, hadleChargeCreditCardJob)
    if err != nil { log.Fatal(err) }
    subscription1.StartAsync()

    // register job handler for 'deduct-customer-credit' and subscribe
    subscription2, err := zbClient.JobSubscription("default-topic", "SomeWorker", "deduct-customer-credit",  1000, 32, handleDeductCustomerCredit)
    if err != nil { log.Fatal(err) }
    subscription2.StartAsync()

    // deploy workflow model if requested
    if (contains(os.Args, "deploy")) {
        deployment, err := zbClient.CreateWorkflowFromFile("default-topic", zbcommon.BpmnXml, "payment.bpmn")
        if err != nil { log.Fatal(err) }
        fmt.Println("deployed workflow model: ", deployment)
    }

    // disconnect nicely
    osCh := make(chan os.Signal, 1)
    signal.Notify(osCh, os.Interrupt)
    go func() {
        <-osCh
        err := subscription1.Close()
        if err != nil { log.Fatal(err) }

        err2 := subscription2.Close()
        if err2 != nil { log.Fatal(err2) }

        fmt.Println("Subscriptions closed.")
        os.Exit(0)
    }()
}

func handleHttpRequest(w http.ResponseWriter, r *http.Request) {
    bodyBytes, _ := ioutil.ReadAll(r.Body)
    jsonStr := string(bodyBytes)
    fmt.Println("Retrieving payment request" + jsonStr)

    chargeCreditCard(jsonStr, w)

    w.WriteHeader(http.StatusOK)
}

func chargeCreditCard(someDataAsJson string, w http.ResponseWriter) error {
    payload := make(map[string]interface{})
	json.Unmarshal([]byte(someDataAsJson), &payload)

    instance := zbc.NewWorkflowInstance("paymentV5", -1, payload)
    workflowInstance, err := zbClient.CreateWorkflowInstance("default-topic", instance)

    if (err != nil) {
        fmt.Fprintf(w, "Bam, error: " + err.Error())
        return err;
    } else {
        fmt.Fprintf(w, "Yeah, started: " + workflowInstance.String())
        return nil;
    }
}

func hadleChargeCreditCardJob(client zbsubscribe.ZeebeAPI, event *zbsubscriptions.SubscriptionEvent) {
    job, err := event.GetJob()
    if err != nil { log.Fatal(err) }
    payload, err := job.GetPayload();
    if err != nil { log.Fatal(err) }
    jsonPayload, err := json.Marshal( payload );
    if err != nil { log.Fatal(err) }

    _, err = doHttpCall(string(jsonPayload))
    if err != nil {
        // couldn't do http call, fail job to trigger retry
        client.FailJob(event)
    } else {
        // complete job after processing
        client.CompleteJob(event)
    }
}

func doHttpCall(someDataAsJson string) (resp *http.Response, err error) {
    fmt.Println("Doing http call", someDataAsJson)
    return http.Post("http://localhost:8099/charge", "application/json", strings.NewReader(someDataAsJson))
}

func handleDeductCustomerCredit(client zbsubscribe.ZeebeAPI, event *zbsubscriptions.SubscriptionEvent) {
    job, err := event.GetJob()
    if err != nil { log.Fatal(err) }
    payload, err := job.GetPayload()
    if err != nil { log.Fatal(err) }

    log.Println(" Substracting  from customer account") // " + strconv.Itoa( (*payload)["amount"].(int) ) + "

    // Hardcoded remaining amount, TODO: replace with randomized value
    if (rand.Intn(10) > 3) {
        (*payload)["remainingAmount"] = 5
    } else {
        (*payload)["remainingAmount"] = 0
    }
    event.UpdatePayload(payload)

    client.CompleteJob(event)
}

/* Helper to check if "deploy" was given as argument */
func contains(arr []string, e string) bool {
    for _, a := range arr {
        if a == e {
            return true
        }
    }
    return false
}<|endoftext|>


<commit_before>package opengraph

import (
	"encoding/json"
	"io"
	"strconv"
	"time"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

// Image defines Open Graph Image type
type Image struct {
	URL       string `json:"url"`
	SecureURL string `json:"secure_url"`
	Type      string `json:"type"`
	Width     uint64 `json:"width"`
	Height    uint64 `json:"height"`
}

// Video defines Open Graph Video type
type Video struct {
	URL       string `json:"url"`
	SecureURL string `json:"secure_url"`
	Type      string `json:"type"`
	Width     uint64 `json:"width"`
	Height    uint64 `json:"height"`
}

// Audio defines Open Graph Audio Type
type Audio struct {
	URL       string `json:"url"`
	SecureURL string `json:"secure_url"`
	Type      string `json:"type"`
}

// Article contain Open Graph Article structure
type Article struct {
	PublishedTime  *time.Time `json:"published_time"`
	ModifiedTime   *time.Time `json:"modified_time"`
	ExpirationTime *time.Time `json:"expiration_time"`
	Section        string     `json:"section"`
	Tags           []string   `json:"tags"`
	Authors        []*Profile `json:"authors"`
}

// Profile contains Open Graph Profile structure
type Profile struct {
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	Username  string `json:"username"`
	Gender    string `json:"gender"`
}

// Book contains Open Graph Book structure
type Book struct {
	ISBN        string     `json:"isbn"`
	ReleaseDate *time.Time `json:"release_date"`
	Tags        []string   `json:"tags"`
	Authors     []*Profile `json:"authors"`
}

// OpenGraph contains facebook og data
type OpenGraph struct {
	isArticle        bool
	isBook           bool
	isProfile        bool
	Type             string   `json:"type"`
	URL              string   `json:"url"`
	Title            string   `json:"title"`
	Description      string   `json:"description"`
	Determiner       string   `json:"determiner"`
	SiteName         string   `json:"site_name"`
	Locale           string   `json:"locale"`
	LocalesAlternate []string `json:"locales_alternate"`
	Images           []*Image `json:"images"`
	Audios           []*Audio `json:"audios"`
	Videos           []*Video `json:"videos"`
	Article          *Article `json:"article,omitempty"`
	Book             *Book    `json:"book,omitempty"`
	Profile          *Profile `json:"profile,omitempty"`
}

// NewOpenGraph returns new instance of Open Graph structure
func NewOpenGraph() *OpenGraph {
	return &OpenGraph{}
}

// ToJSON a simple wrapper around json.Marshal
func (og *OpenGraph) ToJSON() ([]byte, error) {
	return json.Marshal(og)
}

// String return json representation of structure, or error string
func (og *OpenGraph) String() string {
	data, err := og.ToJSON()

	if err != nil {
		return err.Error()
	}

	return string(data[:])
}

// ProcessHTML parses given html from Reader interface and fills up OpenGraph structure
func (og *OpenGraph) ProcessHTML(buffer io.Reader) error {
	z := html.NewTokenizer(buffer)
	for {
		tt := z.Next()
		switch tt {
		case html.ErrorToken:
			if z.Err() == io.EOF {
				return nil
			}
			return z.Err()
		case html.StartTagToken, html.SelfClosingTagToken, html.EndTagToken:
			name, hasAttr := z.TagName()
			if atom.Lookup(name) == atom.Body {
				return nil // OpenGraph is only in head, so we don't need body
			}
			if atom.Lookup(name) != atom.Meta || !hasAttr {
				continue
			}
			m := make(map[string]string)
			var key, val []byte
			for hasAttr {
				key, val, hasAttr = z.TagAttr()
				m[atom.String(key)] = string(val)
			}
			og.ProcessMeta(m)
		}
	}
	return nil
}

// ProcessMeta processes meta attributes and adds them to Open Graph structure if they are suitable for that
func (og *OpenGraph) ProcessMeta(metaAttrs map[string]string) {
	switch metaAttrs["property"] {
	case "og:description":
		og.Description = metaAttrs["content"]
	case "og:type":
		og.Type = metaAttrs["content"]
		switch og.Type {
		case "article":
			og.isArticle = true
		case "book":
			og.isBook = true
		case "profile":
			og.isProfile = true
		}
	case "og:title":
		og.Title = metaAttrs["content"]
	case "og:url":
		og.URL = metaAttrs["content"]
	case "og:determiner":
		og.Determiner = metaAttrs["content"]
	case "og:site_name":
		og.SiteName = metaAttrs["content"]
	case "og:locale":
		og.Locale = metaAttrs["content"]
	case "og:locale:alternate":
		og.LocalesAlternate = append(og.LocalesAlternate, metaAttrs["content"])
	case "og:image":
		og.Images = append(og.Images, &Image{URL: metaAttrs["content"]})
	case "og:image:url":
		if len(og.Images) > 0 {
			og.Images[len(og.Images)-1].URL = metaAttrs["content"]
		}
	case "og:image:secure_url":
		if len(og.Images) > 0 {
			og.Images[len(og.Images)-1].SecureURL = metaAttrs["content"]
		}
	case "og:image:type":
		if len(og.Images) > 0 {
			og.Images[len(og.Images)-1].Type = metaAttrs["content"]
		}
	case "og:image:width":
		if len(og.Images) > 0 {
			w, err := strconv.ParseUint(metaAttrs["content"], 10, 64)
			if err == nil {
				og.Images[len(og.Images)-1].Width = w
			}
		}
	case "og:image:height":
		if len(og.Images) > 0 {
			h, err := strconv.ParseUint(metaAttrs["content"], 10, 64)
			if err == nil {
				og.Images[len(og.Images)-1].Height = h
			}
		}
	case "og:video":
		og.Videos = append(og.Videos, &Video{URL: metaAttrs["content"]})
	case "og:video:url":
		if len(og.Videos) > 0 {
			og.Videos[len(og.Videos)-1].URL = metaAttrs["content"]
		}
	case "og:video:secure_url":
		if len(og.Videos) > 0 {
			og.Videos[len(og.Videos)-1].SecureURL = metaAttrs["content"]
		}
	case "og:video:type":
		if len(og.Videos) > 0 {
			og.Videos[len(og.Videos)-1].Type = metaAttrs["content"]
		}
	case "og:video:width":
		if len(og.Videos) > 0 {
			w, err := strconv.ParseUint(metaAttrs["content"], 10, 64)
			if err == nil {
				og.Videos[len(og.Videos)-1].Width = w
			}
		}
	case "og:video:height":
		if len(og.Videos) > 0 {
			h, err := strconv.ParseUint(metaAttrs["content"], 10, 64)
			if err == nil {
				og.Videos[len(og.Videos)-1].Height = h
			}
		}
	default:
		if og.isArticle {
			og.processArticleMeta(metaAttrs)
		} else if og.isBook {
			og.processBookMeta(metaAttrs)
		} else if og.isProfile {
			og.processProfileMeta(metaAttrs)
		}
	}
}

func (og *OpenGraph) processArticleMeta(metaAttrs map[string]string) {
	if og.Article == nil {
		og.Article = &Article{}
	}
	switch metaAttrs["property"] {
	case "article:published_time":
		t, err := time.Parse(time.RFC3339, metaAttrs["content"])
		if err == nil {
			og.Article.PublishedTime = &t
		}
	case "article:modified_time":
		t, err := time.Parse(time.RFC3339, metaAttrs["content"])
		if err == nil {
			og.Article.ModifiedTime = &t
		}
	case "article:expiration_time":
		t, err := time.Parse(time.RFC3339, metaAttrs["content"])
		if err == nil {
			og.Article.ExpirationTime = &t
		}
	case "article:secttion":
		og.Article.Section = metaAttrs["content"]
	case "article:tag":
		og.Article.Tags = append(og.Article.Tags, metaAttrs["content"])
	case "article:author:first_name":
		if len(og.Article.Authors) == 0 {
			og.Article.Authors = append(og.Article.Authors, &Profile{})
		}
		og.Article.Authors[len(og.Article.Authors)-1].FirstName = metaAttrs["content"]
	case "article:author:last_name":
		if len(og.Article.Authors) == 0 {
			og.Article.Authors = append(og.Article.Authors, &Profile{})
		}
		og.Article.Authors[len(og.Article.Authors)-1].LastName = metaAttrs["content"]
	case "article:author:username":
		if len(og.Article.Authors) == 0 {
			og.Article.Authors = append(og.Article.Authors, &Profile{})
		}
		og.Article.Authors[len(og.Article.Authors)-1].Username = metaAttrs["content"]
	case "article:author:gender":
		if len(og.Article.Authors) == 0 {
			og.Article.Authors = append(og.Article.Authors, &Profile{})
		}
		og.Article.Authors[len(og.Article.Authors)-1].Gender = metaAttrs["content"]
	}
}

func (og *OpenGraph) processBookMeta(metaAttrs map[string]string) {
	if og.Book == nil {
		og.Book = &Book{}
	}
	switch metaAttrs["property"] {
	case "book:release_date":
		t, err := time.Parse(time.RFC3339, metaAttrs["content"])
		if err == nil {
			og.Book.ReleaseDate = &t
		}
	case "book:isbn":
		og.Book.ISBN = metaAttrs["content"]
	case "book:tag":
		og.Book.Tags = append(og.Book.Tags, metaAttrs["content"])
	case "book:author:first_name":
		if len(og.Book.Authors) == 0 {
			og.Book.Authors = append(og.Book.Authors, &Profile{})
		}
		og.Book.Authors[len(og.Book.Authors)-1].FirstName = metaAttrs["content"]
	case "book:author:last_name":
		if len(og.Book.Authors) == 0 {
			og.Book.Authors = append(og.Book.Authors, &Profile{})
		}
		og.Book.Authors[len(og.Book.Authors)-1].LastName = metaAttrs["content"]
	case "book:author:username":
		if len(og.Book.Authors) == 0 {
			og.Book.Authors = append(og.Book.Authors, &Profile{})
		}
		og.Book.Authors[len(og.Book.Authors)-1].Username = metaAttrs["content"]
	case "book:author:gender":
		if len(og.Book.Authors) == 0 {
			og.Book.Authors = append(og.Book.Authors, &Profile{})
		}
		og.Book.Authors[len(og.Book.Authors)-1].Gender = metaAttrs["content"]
	}
}

func (og *OpenGraph) processProfileMeta(metaAttrs map[string]string) {
	if og.Profile == nil {
		og.Profile = &Profile{}
	}
	switch metaAttrs["property"] {
	case "profile:first_name":
		og.Profile.FirstName = metaAttrs["content"]
	case "profile:last_name":
		og.Profile.LastName = metaAttrs["content"]
	case "profile:username":
		og.Profile.Username = metaAttrs["content"]
	case "profile:gender":
		og.Profile.Gender = metaAttrs["content"]
	}
}
<commit_msg>Fix a bug via typo ;)<commit_after>package opengraph

import (
	"encoding/json"
	"io"
	"strconv"
	"time"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

// Image defines Open Graph Image type
type Image struct {
	URL       string `json:"url"`
	SecureURL string `json:"secure_url"`
	Type      string `json:"type"`
	Width     uint64 `json:"width"`
	Height    uint64 `json:"height"`
}

// Video defines Open Graph Video type
type Video struct {
	URL       string `json:"url"`
	SecureURL string `json:"secure_url"`
	Type      string `json:"type"`
	Width     uint64 `json:"width"`
	Height    uint64 `json:"height"`
}

// Audio defines Open Graph Audio Type
type Audio struct {
	URL       string `json:"url"`
	SecureURL string `json:"secure_url"`
	Type      string `json:"type"`
}

// Article contain Open Graph Article structure
type Article struct {
	PublishedTime  *time.Time `json:"published_time"`
	ModifiedTime   *time.Time `json:"modified_time"`
	ExpirationTime *time.Time `json:"expiration_time"`
	Section        string     `json:"section"`
	Tags           []string   `json:"tags"`
	Authors        []*Profile `json:"authors"`
}

// Profile contains Open Graph Profile structure
type Profile struct {
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	Username  string `json:"username"`
	Gender    string `json:"gender"`
}

// Book contains Open Graph Book structure
type Book struct {
	ISBN        string     `json:"isbn"`
	ReleaseDate *time.Time `json:"release_date"`
	Tags        []string   `json:"tags"`
	Authors     []*Profile `json:"authors"`
}

// OpenGraph contains facebook og data
type OpenGraph struct {
	isArticle        bool
	isBook           bool
	isProfile        bool
	Type             string   `json:"type"`
	URL              string   `json:"url"`
	Title            string   `json:"title"`
	Description      string   `json:"description"`
	Determiner       string   `json:"determiner"`
	SiteName         string   `json:"site_name"`
	Locale           string   `json:"locale"`
	LocalesAlternate []string `json:"locales_alternate"`
	Images           []*Image `json:"images"`
	Audios           []*Audio `json:"audios"`
	Videos           []*Video `json:"videos"`
	Article          *Article `json:"article,omitempty"`
	Book             *Book    `json:"book,omitempty"`
	Profile          *Profile `json:"profile,omitempty"`
}

// NewOpenGraph returns new instance of Open Graph structure
func NewOpenGraph() *OpenGraph {
	return &OpenGraph{}
}

// ToJSON a simple wrapper around json.Marshal
func (og *OpenGraph) ToJSON() ([]byte, error) {
	return json.Marshal(og)
}

// String return json representation of structure, or error string
func (og *OpenGraph) String() string {
	data, err := og.ToJSON()

	if err != nil {
		return err.Error()
	}

	return string(data[:])
}

// ProcessHTML parses given html from Reader interface and fills up OpenGraph structure
func (og *OpenGraph) ProcessHTML(buffer io.Reader) error {
	z := html.NewTokenizer(buffer)
	for {
		tt := z.Next()
		switch tt {
		case html.ErrorToken:
			if z.Err() == io.EOF {
				return nil
			}
			return z.Err()
		case html.StartTagToken, html.SelfClosingTagToken, html.EndTagToken:
			name, hasAttr := z.TagName()
			if atom.Lookup(name) == atom.Body {
				return nil // OpenGraph is only in head, so we don't need body
			}
			if atom.Lookup(name) != atom.Meta || !hasAttr {
				continue
			}
			m := make(map[string]string)
			var key, val []byte
			for hasAttr {
				key, val, hasAttr = z.TagAttr()
				m[atom.String(key)] = string(val)
			}
			og.ProcessMeta(m)
		}
	}
	return nil
}

// ProcessMeta processes meta attributes and adds them to Open Graph structure if they are suitable for that
func (og *OpenGraph) ProcessMeta(metaAttrs map[string]string) {
	switch metaAttrs["property"] {
	case "og:description":
		og.Description = metaAttrs["content"]
	case "og:type":
		og.Type = metaAttrs["content"]
		switch og.Type {
		case "article":
			og.isArticle = true
		case "book":
			og.isBook = true
		case "profile":
			og.isProfile = true
		}
	case "og:title":
		og.Title = metaAttrs["content"]
	case "og:url":
		og.URL = metaAttrs["content"]
	case "og:determiner":
		og.Determiner = metaAttrs["content"]
	case "og:site_name":
		og.SiteName = metaAttrs["content"]
	case "og:locale":
		og.Locale = metaAttrs["content"]
	case "og:locale:alternate":
		og.LocalesAlternate = append(og.LocalesAlternate, metaAttrs["content"])
	case "og:image":
		og.Images = append(og.Images, &Image{URL: metaAttrs["content"]})
	case "og:image:url":
		if len(og.Images) > 0 {
			og.Images[len(og.Images)-1].URL = metaAttrs["content"]
		}
	case "og:image:secure_url":
		if len(og.Images) > 0 {
			og.Images[len(og.Images)-1].SecureURL = metaAttrs["content"]
		}
	case "og:image:type":
		if len(og.Images) > 0 {
			og.Images[len(og.Images)-1].Type = metaAttrs["content"]
		}
	case "og:image:width":
		if len(og.Images) > 0 {
			w, err := strconv.ParseUint(metaAttrs["content"], 10, 64)
			if err == nil {
				og.Images[len(og.Images)-1].Width = w
			}
		}
	case "og:image:height":
		if len(og.Images) > 0 {
			h, err := strconv.ParseUint(metaAttrs["content"], 10, 64)
			if err == nil {
				og.Images[len(og.Images)-1].Height = h
			}
		}
	case "og:video":
		og.Videos = append(og.Videos, &Video{URL: metaAttrs["content"]})
	case "og:video:url":
		if len(og.Videos) > 0 {
			og.Videos[len(og.Videos)-1].URL = metaAttrs["content"]
		}
	case "og:video:secure_url":
		if len(og.Videos) > 0 {
			og.Videos[len(og.Videos)-1].SecureURL = metaAttrs["content"]
		}
	case "og:video:type":
		if len(og.Videos) > 0 {
			og.Videos[len(og.Videos)-1].Type = metaAttrs["content"]
		}
	case "og:video:width":
		if len(og.Videos) > 0 {
			w, err := strconv.ParseUint(metaAttrs["content"], 10, 64)
			if err == nil {
				og.Videos[len(og.Videos)-1].Width = w
			}
		}
	case "og:video:height":
		if len(og.Videos) > 0 {
			h, err := strconv.ParseUint(metaAttrs["content"], 10, 64)
			if err == nil {
				og.Videos[len(og.Videos)-1].Height = h
			}
		}
	default:
		if og.isArticle {
			og.processArticleMeta(metaAttrs)
		} else if og.isBook {
			og.processBookMeta(metaAttrs)
		} else if og.isProfile {
			og.processProfileMeta(metaAttrs)
		}
	}
}

func (og *OpenGraph) processArticleMeta(metaAttrs map[string]string) {
	if og.Article == nil {
		og.Article = &Article{}
	}
	switch metaAttrs["property"] {
	case "article:published_time":
		t, err := time.Parse(time.RFC3339, metaAttrs["content"])
		if err == nil {
			og.Article.PublishedTime = &t
		}
	case "article:modified_time":
		t, err := time.Parse(time.RFC3339, metaAttrs["content"])
		if err == nil {
			og.Article.ModifiedTime = &t
		}
	case "article:expiration_time":
		t, err := time.Parse(time.RFC3339, metaAttrs["content"])
		if err == nil {
			og.Article.ExpirationTime = &t
		}
	case "article:section":
		og.Article.Section = metaAttrs["content"]
	case "article:tag":
		og.Article.Tags = append(og.Article.Tags, metaAttrs["content"])
	case "article:author:first_name":
		if len(og.Article.Authors) == 0 {
			og.Article.Authors = append(og.Article.Authors, &Profile{})
		}
		og.Article.Authors[len(og.Article.Authors)-1].FirstName = metaAttrs["content"]
	case "article:author:last_name":
		if len(og.Article.Authors) == 0 {
			og.Article.Authors = append(og.Article.Authors, &Profile{})
		}
		og.Article.Authors[len(og.Article.Authors)-1].LastName = metaAttrs["content"]
	case "article:author:username":
		if len(og.Article.Authors) == 0 {
			og.Article.Authors = append(og.Article.Authors, &Profile{})
		}
		og.Article.Authors[len(og.Article.Authors)-1].Username = metaAttrs["content"]
	case "article:author:gender":
		if len(og.Article.Authors) == 0 {
			og.Article.Authors = append(og.Article.Authors, &Profile{})
		}
		og.Article.Authors[len(og.Article.Authors)-1].Gender = metaAttrs["content"]
	}
}

func (og *OpenGraph) processBookMeta(metaAttrs map[string]string) {
	if og.Book == nil {
		og.Book = &Book{}
	}
	switch metaAttrs["property"] {
	case "book:release_date":
		t, err := time.Parse(time.RFC3339, metaAttrs["content"])
		if err == nil {
			og.Book.ReleaseDate = &t
		}
	case "book:isbn":
		og.Book.ISBN = metaAttrs["content"]
	case "book:tag":
		og.Book.Tags = append(og.Book.Tags, metaAttrs["content"])
	case "book:author:first_name":
		if len(og.Book.Authors) == 0 {
			og.Book.Authors = append(og.Book.Authors, &Profile{})
		}
		og.Book.Authors[len(og.Book.Authors)-1].FirstName = metaAttrs["content"]
	case "book:author:last_name":
		if len(og.Book.Authors) == 0 {
			og.Book.Authors = append(og.Book.Authors, &Profile{})
		}
		og.Book.Authors[len(og.Book.Authors)-1].LastName = metaAttrs["content"]
	case "book:author:username":
		if len(og.Book.Authors) == 0 {
			og.Book.Authors = append(og.Book.Authors, &Profile{})
		}
		og.Book.Authors[len(og.Book.Authors)-1].Username = metaAttrs["content"]
	case "book:author:gender":
		if len(og.Book.Authors) == 0 {
			og.Book.Authors = append(og.Book.Authors, &Profile{})
		}
		og.Book.Authors[len(og.Book.Authors)-1].Gender = metaAttrs["content"]
	}
}

func (og *OpenGraph) processProfileMeta(metaAttrs map[string]string) {
	if og.Profile == nil {
		og.Profile = &Profile{}
	}
	switch metaAttrs["property"] {
	case "profile:first_name":
		og.Profile.FirstName = metaAttrs["content"]
	case "profile:last_name":
		og.Profile.LastName = metaAttrs["content"]
	case "profile:username":
		og.Profile.Username = metaAttrs["content"]
	case "profile:gender":
		og.Profile.Gender = metaAttrs["content"]
	}
}
<|endoftext|>


<commit_before>package handle

import (
	"github.com/smotti/ircx"
	"github.com/smotti/tad/config"
	"github.com/smotti/tad/report"
	"github.com/sorcix/irc"
)

// CmdHostOs handles the CMD_HOST_OS bot command, by sending back data about
// the hosts operating system gathered by CFEngine.
func CmdHostOs(s ircx.Sender, m *irc.Message) {
	report := report.HostInfo{
		Filename: config.HostInfoReport,
	}
	var msg string
	if err := report.Read(); err != nil {
		msg = "Failed to read report file"
	} else {
		msg = report.Os.ToString()
	}

	s.Send(&irc.Message{
		Command:  irc.PRIVMSG,
		Trailing: msg,
	})
}

// CmdHostId handle the CMD_HOST_ID bot command.
func CmdHostId(s ircx.Sender, m *irc.Message) {
	report := report.HostInfo{
		Filename: config.HostInfoReport,
	}
	var msg string
	if err := report.Read(); err != nil {
		msg = "Failed to read report file"
	} else {
		msg = report.Identity.ToString()
	}

	s.Send(&irc.Message{
		Command:  irc.PRIVMSG,
		Trailing: msg,
	})
}
<commit_msg>Fix missing call to Params<commit_after>package handle

import (
	"github.com/smotti/ircx"
	"github.com/smotti/tad/config"
	"github.com/smotti/tad/report"
	"github.com/sorcix/irc"
)

// CmdHostOs handles the CMD_HOST_OS bot command, by sending back data about
// the hosts operating system gathered by CFEngine.
func CmdHostOs(s ircx.Sender, m *irc.Message) {
	report := report.HostInfo{
		Filename: config.HostInfoReport,
	}
	var msg string
	if err := report.Read(); err != nil {
		msg = "Failed to read report file"
	} else {
		msg = report.Os.ToString()
	}

	s.Send(&irc.Message{
		Command:  irc.PRIVMSG,
		Params:   Params(m),
		Trailing: msg,
	})
}

// CmdHostId handle the CMD_HOST_ID bot command.
func CmdHostId(s ircx.Sender, m *irc.Message) {
	report := report.HostInfo{
		Filename: config.HostInfoReport,
	}
	var msg string
	if err := report.Read(); err != nil {
		msg = "Failed to read report file"
	} else {
		msg = report.Identity.ToString()
	}

	s.Send(&irc.Message{
		Command:  irc.PRIVMSG,
		Params:   Params(m),
		Trailing: msg,
	})
}
<|endoftext|>


<commit_before>package handler

// Contains common methods used for writing appengine apps.

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"

	"strings"

	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
)

type handlerError struct {
	AppVersion string   `json:"appVersion"`
	URL        *url.URL `json:"url"`
	Method     string   `json:"method"`
	StatusCode int      `json:"statusCode"`
	InstanceID string   `json:"instanceId"`
	VersionID  string   `json:"versionId"`
	RequestID  string   `json:"requestId"`
	ModuleName string   `json:"moduleName"`
	Err        string   `json:"message"`
}

func (e *handlerError) Error() string {
	b, err := json.MarshalIndent(e, "", "  ")
	if err != nil {
		return err.Error()
	}
	return string(b)
}

// Base struct designed to be extended by more specific url handlers
type Base struct {
	Ctx context.Context
	Req *http.Request
	Res http.ResponseWriter
}

// OriginMiddleware returns a middleware function that validates the origin
// header within the request matches the allowed values
func OriginMiddleware(allowed []string) func(context.Context, http.ResponseWriter, *http.Request) context.Context {
	return func(c context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		origin := r.Header.Get("Origin")
		if len(origin) == 0 {
			return c
		}
		ok := validateOrigin(origin, allowed)
		if !ok {
			c2, cancel := context.WithCancel(c)
			cancel()
			return c2
		}

		w.Header().Add("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
		w.Header().Add("Access-Control-Allow-Origin", origin)

		return c
	}
}

// ValidateOrigin is a helper method called within the ServeHTTP method on
// OPTION requests to validate the allowed origins
func (b *Base) ValidateOrigin(allowed []string) {
	origin := b.Req.Header.Get("Origin")
	ok := validateOrigin(origin, allowed)
	if !ok {
		_, cancel := context.WithCancel(b.Ctx)
		cancel()
	}
}

func validateOrigin(origin string, allowed []string) bool {
	if allowed == nil || len(allowed) == 0 {
		return true
	}
	if len(origin) == 0 {
		return false
	}
	for _, allowedOrigin := range allowed {
		if origin == allowedOrigin {
			return true
		}
	}
	return false
}

// ToJSON encodes an interface into the response writer with a default http
// status code of 200
func (b *Base) ToJSON(data interface{}) {
	err := json.NewEncoder(b.Res).Encode(data)
	if err != nil {
		b.Abort(http.StatusInternalServerError, fmt.Errorf("Decoding JSON: %v", err))
	}
}

// ToJSONWithStatus json encodes an interface into the response writer with a
// custom http status code
func (b *Base) ToJSONWithStatus(data interface{}, status int) {
	b.Res.WriteHeader(status)
	b.ToJSON(data)
}

// SendStatus writes the passed in status to the response without any data
func (b *Base) SendStatus(status int) {
	b.Res.WriteHeader(status)
}

// Bind must be called at the beginning of every request to set the required references
func (b *Base) Bind(c context.Context, w http.ResponseWriter, r *http.Request) {
	b.Ctx, b.Res, b.Req = c, w, r
}

// Header gets the request header value
func (b *Base) Header(name string) string {
	return b.Req.Header.Get(name)
}

// SetHeader sets a response header value
func (b *Base) SetHeader(name, value string) {
	b.Res.Header().Set(name, value)
}

// Abort is called when pre-maturally exiting from a handler function due to an
// error. A detailed error is delivered to the client and logged to provide the
// details required to identify the issue.
func (b *Base) Abort(statusCode int, err error) {
	c, cancel := context.WithCancel(b.Ctx)
	defer cancel()

	// testapp is the name given to all apps when being tested
	var isTest = appengine.AppID(c) == "testapp"

	hErr := &handlerError{
		URL:        b.Req.URL,
		Method:     b.Req.Method,
		StatusCode: statusCode,
		AppVersion: appengine.AppID(c),
		RequestID:  appengine.RequestID(c),
	}
	if err != nil {
		hErr.Err = err.Error()
	}

	if !isTest {
		hErr.InstanceID = appengine.InstanceID()
		hErr.VersionID = appengine.VersionID(c)
		hErr.ModuleName = appengine.ModuleName(c)
	}

	// log method to appengine log
	log.Errorf(c, hErr.Error())

	if strings.Index(b.Req.Header.Get("Accept"), "application/json") > 0 {
		b.Res.WriteHeader(statusCode)
		json.NewEncoder(b.Res).Encode(hErr)
	}
}

// QueryParam obtains the value matching the passed in name within the request
// url's querystring
func (b *Base) QueryParam(name string) string {
	return b.Req.URL.Query().Get(name)
}

// QueryKey returns the decoded *datastore.Key value from the query params
func (b *Base) QueryKey(name string) *datastore.Key {
	raw := b.QueryParam(name)
	if len(raw) == 0 {
		return nil
	}

	key, _ := datastore.DecodeKey(raw)
	return key
}

// PathParam returns the decoded *datastore.Key value from the url
func (b *Base) PathParam(tpl string) string {
	path := b.Req.URL.Path
	startIndex := strings.Index(tpl, ":")
	endIndex := strings.Index(path[startIndex:], "/")
	if startIndex == -1 {
		return ""
	}
	if endIndex == -1 {
		endIndex = len(path)
	}
	return path[startIndex : startIndex+endIndex]
}

// PathParamByIndex extracts the path param by index. Negative indexes are allowed.
func (b *Base) PathParamByIndex(index int) string {
	parts := strings.Split(b.Req.URL.Path, "/")
	count := len(parts)

	if index < 0 && (index+1)*-1 > count {
		return ""
	}
	if index > count {
		return ""
	}

	if index < 0 {
		return parts[count+index]
	}

	return parts[index]
}

// PathKey returns the decodded *datastore.Key value from the url
func (b *Base) PathKey(tpl string) *datastore.Key {
	rawKey := b.PathParam(tpl)
	if len(rawKey) == 0 {
		return nil
	}
	key, err := datastore.DecodeKey(rawKey)
	if err != nil {
		return nil
	}
	return key
}

// Redirect is a simple wrapper around the core http method
func (b *Base) Redirect(url string, perm bool) {
	status := http.StatusTemporaryRedirect
	if perm {
		status = http.StatusMovedPermanently
	}
	http.Redirect(b.Res, b.Req, url, status)
}
<commit_msg>fix incorrect extraction of path params<commit_after>package handler

// Contains common methods used for writing appengine apps.

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"

	"strings"

	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
)

type handlerError struct {
	AppVersion string   `json:"appVersion"`
	URL        *url.URL `json:"url"`
	Method     string   `json:"method"`
	StatusCode int      `json:"statusCode"`
	InstanceID string   `json:"instanceId"`
	VersionID  string   `json:"versionId"`
	RequestID  string   `json:"requestId"`
	ModuleName string   `json:"moduleName"`
	Err        string   `json:"message"`
}

func (e *handlerError) Error() string {
	b, err := json.MarshalIndent(e, "", "  ")
	if err != nil {
		return err.Error()
	}
	return string(b)
}

// Base struct designed to be extended by more specific url handlers
type Base struct {
	Ctx context.Context
	Req *http.Request
	Res http.ResponseWriter
}

// OriginMiddleware returns a middleware function that validates the origin
// header within the request matches the allowed values
func OriginMiddleware(allowed []string) func(context.Context, http.ResponseWriter, *http.Request) context.Context {
	return func(c context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		origin := r.Header.Get("Origin")
		if len(origin) == 0 {
			return c
		}
		ok := validateOrigin(origin, allowed)
		if !ok {
			c2, cancel := context.WithCancel(c)
			cancel()
			return c2
		}

		w.Header().Add("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
		w.Header().Add("Access-Control-Allow-Origin", origin)

		return c
	}
}

// ValidateOrigin is a helper method called within the ServeHTTP method on
// OPTION requests to validate the allowed origins
func (b *Base) ValidateOrigin(allowed []string) {
	origin := b.Req.Header.Get("Origin")
	ok := validateOrigin(origin, allowed)
	if !ok {
		_, cancel := context.WithCancel(b.Ctx)
		cancel()
	}
}

func validateOrigin(origin string, allowed []string) bool {
	if allowed == nil || len(allowed) == 0 {
		return true
	}
	if len(origin) == 0 {
		return false
	}
	for _, allowedOrigin := range allowed {
		if origin == allowedOrigin {
			return true
		}
	}
	return false
}

// ToJSON encodes an interface into the response writer with a default http
// status code of 200
func (b *Base) ToJSON(data interface{}) {
	err := json.NewEncoder(b.Res).Encode(data)
	if err != nil {
		b.Abort(http.StatusInternalServerError, fmt.Errorf("Decoding JSON: %v", err))
	}
}

// ToJSONWithStatus json encodes an interface into the response writer with a
// custom http status code
func (b *Base) ToJSONWithStatus(data interface{}, status int) {
	b.Res.WriteHeader(status)
	b.ToJSON(data)
}

// SendStatus writes the passed in status to the response without any data
func (b *Base) SendStatus(status int) {
	b.Res.WriteHeader(status)
}

// Bind must be called at the beginning of every request to set the required references
func (b *Base) Bind(c context.Context, w http.ResponseWriter, r *http.Request) {
	b.Ctx, b.Res, b.Req = c, w, r
}

// Header gets the request header value
func (b *Base) Header(name string) string {
	return b.Req.Header.Get(name)
}

// SetHeader sets a response header value
func (b *Base) SetHeader(name, value string) {
	b.Res.Header().Set(name, value)
}

// Abort is called when pre-maturally exiting from a handler function due to an
// error. A detailed error is delivered to the client and logged to provide the
// details required to identify the issue.
func (b *Base) Abort(statusCode int, err error) {
	c, cancel := context.WithCancel(b.Ctx)
	defer cancel()

	// testapp is the name given to all apps when being tested
	var isTest = appengine.AppID(c) == "testapp"

	hErr := &handlerError{
		URL:        b.Req.URL,
		Method:     b.Req.Method,
		StatusCode: statusCode,
		AppVersion: appengine.AppID(c),
		RequestID:  appengine.RequestID(c),
	}
	if err != nil {
		hErr.Err = err.Error()
	}

	if !isTest {
		hErr.InstanceID = appengine.InstanceID()
		hErr.VersionID = appengine.VersionID(c)
		hErr.ModuleName = appengine.ModuleName(c)
	}

	// log method to appengine log
	log.Errorf(c, hErr.Error())

	if strings.Index(b.Req.Header.Get("Accept"), "application/json") > 0 {
		b.Res.WriteHeader(statusCode)
		json.NewEncoder(b.Res).Encode(hErr)
	}
}

// QueryParam obtains the value matching the passed in name within the request
// url's querystring
func (b *Base) QueryParam(name string) string {
	return b.Req.URL.Query().Get(name)
}

// QueryKey returns the decoded *datastore.Key value from the query params
func (b *Base) QueryKey(name string) *datastore.Key {
	raw := b.QueryParam(name)
	if len(raw) == 0 {
		return nil
	}

	key, _ := datastore.DecodeKey(raw)
	return key
}

// PathParam returns the decoded *datastore.Key value from the url
func (b *Base) PathParam(tpl string) string {
	path := b.Req.URL.Path
	startIndex := strings.Index(tpl, ":")
	if startIndex == -1 {
		return ""
	}
	endIndex := strings.Index(path[startIndex:], "/")
	if endIndex == -1 {
		return path[startIndex:]
	}
	return path[startIndex : startIndex+endIndex]
}

// PathParamByIndex extracts the path param by index. Negative indexes are allowed.
func (b *Base) PathParamByIndex(index int) string {
	parts := strings.Split(b.Req.URL.Path, "/")
	count := len(parts)

	if index < 0 && (index+1)*-1 > count {
		return ""
	}
	if index > count {
		return ""
	}

	if index < 0 {
		return parts[count+index]
	}

	return parts[index]
}

// PathKey returns the decodded *datastore.Key value from the url
func (b *Base) PathKey(tpl string) *datastore.Key {
	rawKey := b.PathParam(tpl)
	if len(rawKey) == 0 {
		return nil
	}
	key, err := datastore.DecodeKey(rawKey)
	if err != nil {
		return nil
	}
	return key
}

// Redirect is a simple wrapper around the core http method
func (b *Base) Redirect(url string, perm bool) {
	status := http.StatusTemporaryRedirect
	if perm {
		status = http.StatusMovedPermanently
	}
	http.Redirect(b.Res, b.Req, url, status)
}
<|endoftext|>


<commit_before>package ytmp3

import (
	"io"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"sync"
)
import "github.com/jpillora/ytmp3/ytdl"
import "github.com/jpillora/ytmp3/static"
import "github.com/jpillora/go-realtime"

type Config struct {
	Foo       int    `help:"foo"`
	YoutubeDL string `help:"path to youtube-dl"`
	FFMPEG    string `help:"path to ffmpeg"`
}

func New(c Config) http.Handler {

	if c.YoutubeDL == "" {
		c.YoutubeDL = "youtube-dl"
	}
	// if c.FFMPEG == "" {
	// 	c.FFMPEG = "ffmpeg"
	// }

	m := http.NewServeMux()

	y := &ytHandler{
		Config:   c,
		ServeMux: m,
		fs:       static.FileSystemHandler(),
	}

	y.rt, _ = realtime.Sync(y.shared)

	if err := ytdl.Check(c.YoutubeDL); err != nil {
		log.Fatalf("youtube-dl check failed: %s", err)
	}

	// if _, err := exec.LookPath(c.FFMPEG); err != nil {
	// 	log.Fatal("cannot locate %s", c.FFMPEG)
	// }

	return http.HandlerFunc(y.handle)
}

type ytHandler struct {
	Config
	*http.ServeMux
	fs http.Handler
	//state
	rt      *realtime.Realtime
	joblock sync.Mutex
	shared  struct {
		Running bool
		Total   int
		Job     struct {
			State string
			ID    string
		}
	}
}

func (y *ytHandler) handle(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/realtime" {
		y.rt.ServeHTTP(w, r)
		return
	} else if r.URL.Path == "/realtime.js" {
		realtime.JS.ServeHTTP(w, r)
		return
	} else if r.URL.Path == "/ytdl" {
		y.ytdl(w, r)
		return
	} else if strings.HasPrefix(r.URL.Path, "/mp3/") {
		r.URL.Path = r.URL.Path[5:]
		y.mp3(w, r)
		return
	}
	y.fs.ServeHTTP(w, r)
}

var ytid = regexp.MustCompile(`^[A-Za-z0-9\-\_]{11}$`)

func (y *ytHandler) ytdl(w http.ResponseWriter, r *http.Request) {
	cmd := r.URL.Query().Get("cmd")
	if cmd == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("cmd missing"))
		return
	}
	args := strings.Split(cmd, " ")
	out, err := ytdl.Run(args...)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(err.Error() + "\n"))
	}
	w.Write(out)
}

func (y *ytHandler) mp3(w http.ResponseWriter, r *http.Request) {

	id := ""
	if ytid.MatchString(r.URL.Path) {
		id = r.URL.Path
	} else if v := r.URL.Query().Get("v"); ytid.MatchString(v) {
		id = v
	} else {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("missing ID"))
		return
	}

	out, err := ytdl.Run("-g", "https://www.youtube.com/watch?v="+id)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Failed to retrieve content URLs\n" + string(out)))
		return
	}

	videoURL := ""
	audioURL := ""
	for _, s := range strings.Split(string(out), "\n") {
		if s == "" {
			continue
		}
		u, err := url.Parse(s)
		if err != nil {
			log.Printf("Not a valid URL: %s", s)
			continue
		}
		mime := u.Query().Get("mime")
		switch mime {
		case "video/mp4":
			videoURL = s
		case "audio/mp4":
			audioURL = s
		default:
			log.Printf("Unhandled mime type: %s", mime)
		}
	}

	//if we have audio, proxy straight through
	if audioURL != "" {
		resp, err := http.Get(audioURL)
		if err != nil {
			w.WriteHeader(http.StatusBadGateway)
			w.Write([]byte("Invalid audio URL"))
			return
		}
		w.Header().Set("Content-Type", "audio/mp4")
		w.Header().Set("Content-Length", resp.Header.Get("Content-Length"))
		io.Copy(w, resp.Body)
		return
	}

	//no audio and video, exit
	if videoURL == "" {
		w.WriteHeader(http.StatusBadGateway)
		w.Write([]byte("Failed to extract content URLs"))
		return
	}

	//others wait here
	// y.joblock.Lock()
	//start!
	// y.shared.Running = true
	// y.shared.Total++
	// y.shared.Job.State = "VIDEO"
	// y.shared.Job.ID = id
	// y.rt.Update()

	// _release := func() {
	// 	y.shared.Running = false
	// 	y.rt.Update()
	// 	y.joblock.Unlock()
	// }

	// var s sync.Once{}
	// release := func() {
	// 	s.Do(release)
	// }

	//video! must extract audio out
	w.WriteHeader(http.StatusNotImplemented)
	w.Write([]byte("Video not implemented"))
	return
}
<commit_msg>add -f option<commit_after>package ytmp3

import (
	"io"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"sync"
)
import "github.com/jpillora/ytmp3/ytdl"
import "github.com/jpillora/ytmp3/static"
import "github.com/jpillora/go-realtime"

type Config struct {
	Foo       int    `help:"foo"`
	YoutubeDL string `help:"path to youtube-dl"`
	FFMPEG    string `help:"path to ffmpeg"`
}

func New(c Config) http.Handler {

	if c.YoutubeDL == "" {
		c.YoutubeDL = "youtube-dl"
	}
	// if c.FFMPEG == "" {
	// 	c.FFMPEG = "ffmpeg"
	// }

	m := http.NewServeMux()

	y := &ytHandler{
		Config:   c,
		ServeMux: m,
		fs:       static.FileSystemHandler(),
	}

	y.rt, _ = realtime.Sync(y.shared)

	if err := ytdl.Check(c.YoutubeDL); err != nil {
		log.Fatalf("youtube-dl check failed: %s", err)
	}

	// if _, err := exec.LookPath(c.FFMPEG); err != nil {
	// 	log.Fatal("cannot locate %s", c.FFMPEG)
	// }

	return http.HandlerFunc(y.handle)
}

type ytHandler struct {
	Config
	*http.ServeMux
	fs http.Handler
	//state
	rt      *realtime.Realtime
	joblock sync.Mutex
	shared  struct {
		Running bool
		Total   int
		Job     struct {
			State string
			ID    string
		}
	}
}

func (y *ytHandler) handle(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/realtime" {
		y.rt.ServeHTTP(w, r)
		return
	} else if r.URL.Path == "/realtime.js" {
		realtime.JS.ServeHTTP(w, r)
		return
	} else if r.URL.Path == "/ytdl" {
		y.ytdl(w, r)
		return
	} else if strings.HasPrefix(r.URL.Path, "/mp3/") {
		r.URL.Path = r.URL.Path[5:]
		y.mp3(w, r)
		return
	}
	y.fs.ServeHTTP(w, r)
}

var ytid = regexp.MustCompile(`^[A-Za-z0-9\-\_]{11}$`)

func (y *ytHandler) ytdl(w http.ResponseWriter, r *http.Request) {
	cmd := r.URL.Query().Get("cmd")
	if cmd == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("cmd missing"))
		return
	}
	args := strings.Split(cmd, " ")
	out, err := ytdl.Run(args...)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(err.Error() + "\n"))
	}
	w.Write(out)
}

func (y *ytHandler) mp3(w http.ResponseWriter, r *http.Request) {

	id := ""
	if ytid.MatchString(r.URL.Path) {
		id = r.URL.Path
	} else if v := r.URL.Query().Get("v"); ytid.MatchString(v) {
		id = v
	} else {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("missing ID"))
		return
	}

	out, err := ytdl.Run("-f", "140", "-g", "https://www.youtube.com/watch?v="+id)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Failed to retrieve content URLs\n" + string(out)))
		return
	}

	videoURL := ""
	audioURL := ""
	for _, s := range strings.Split(string(out), "\n") {
		if s == "" {
			continue
		}
		u, err := url.Parse(s)
		if err != nil {
			log.Printf("Not a valid URL: %s", s)
			continue
		}
		mime := u.Query().Get("mime")
		switch mime {
		case "video/mp4":
			videoURL = s
		case "audio/mp4":
			audioURL = s
		default:
			log.Printf("Unhandled mime type: %s", mime)
		}
	}

	//if we have audio, proxy straight through
	if audioURL != "" {
		resp, err := http.Get(audioURL)
		if err != nil {
			w.WriteHeader(http.StatusBadGateway)
			w.Write([]byte("Invalid audio URL: " + err.Error()))
			return
		}
		w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
		w.Header().Set("Content-Length", resp.Header.Get("Content-Length"))
		io.Copy(w, resp.Body)
		return
	}

	//no audio and video, exit
	if videoURL == "" {
		w.WriteHeader(http.StatusBadGateway)
		w.Write([]byte("Failed to extract content URLs"))
		return
	}

	//others wait here
	// y.joblock.Lock()
	//start!
	// y.shared.Running = true
	// y.shared.Total++
	// y.shared.Job.State = "VIDEO"
	// y.shared.Job.ID = id
	// y.rt.Update()

	// _release := func() {
	// 	y.shared.Running = false
	// 	y.rt.Update()
	// 	y.joblock.Unlock()
	// }

	// var s sync.Once{}
	// release := func() {
	// 	s.Do(release)
	// }

	//video! must extract audio out
	w.WriteHeader(http.StatusNotImplemented)
	w.Write([]byte("Video to audio transcoding not implemented yet"))
	return
}
<|endoftext|>


<commit_before>package handlers

import (
	"crypto/rand"
	"encoding/base64"
	"errors"
	"io"
	"net/http"
	"time"

	"github.com/google/go-github/github"
	"github.com/influxdata/mrfusion"
	"golang.org/x/oauth2"
	ogh "golang.org/x/oauth2/github"
)

const (
	DefaultCookieName     = "session"
	DefaultCookieDuration = time.Hour * 24 * 30
)

// Cookie represents the location and expiration time of new cookies.
type Cookie struct {
	Name     string
	Duration time.Duration
}

// NewCookie creates a Cookie with DefaultCookieName and DefaultCookieDuration
func NewCookie() Cookie {
	return Cookie{
		Name:     DefaultCookieName,
		Duration: DefaultCookieDuration,
	}
}

// Github provides OAuth Login and Callback handlers. Callback will set
// an authentication cookie.  This cookie's value is a JWT containing
// the user's primary Github email address.
type Github struct {
	Cookie        Cookie
	Authenticator mrfusion.Authenticator
	ClientID      string
	ClientSecret  string
	Scopes        []string
	SuccessURL    string // SuccessURL is redirect location after successful authorization
	FailureURL    string // FailureURL is redirect location after authorization failure
	Now           func() time.Time
	Logger        mrfusion.Logger
}

// NewGithub constructs a Github with default cookie behavior and scopes.
func NewGithub(clientID, clientSecret, successURL, failureURL string, auth mrfusion.Authenticator, log mrfusion.Logger) Github {
	return Github{
		ClientID:      clientID,
		ClientSecret:  clientSecret,
		Cookie:        NewCookie(),
		Scopes:        []string{"user:email"},
		SuccessURL:    successURL,
		FailureURL:    failureURL,
		Authenticator: auth,
		Now:           time.Now,
		Logger:        log,
	}
}

func (g *Github) config() *oauth2.Config {
	return &oauth2.Config{
		ClientID:     g.ClientID,
		ClientSecret: g.ClientSecret,
		Scopes:       g.Scopes,
		Endpoint:     ogh.Endpoint,
	}
}

// Login returns a handler that redirects to Github's OAuth login.
// Uses JWT with a random string as the state validation method.
// JWTs are used because they can be validated without storing
// state.
func (g *Github) Login() http.Handler {
	conf := g.config()
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// We are creating a token with an encoded random string to prevent CSRF attacks
		// This token will be validated during the OAuth callback.
		// We'll give our users 10 minutes from this point to type in their github password.
		// If the callback is not received within 10 minutes, then authorization will fail.
		csrf := randomString(32) // 32 is not important... just long
		state, err := g.Authenticator.Token(r.Context(), mrfusion.Principal(csrf), 10*time.Minute)
		// This is likely an internal server error
		if err != nil {
			g.Logger.
				WithField("component", "auth").
				WithField("remote_addr", r.RemoteAddr).
				WithField("method", r.Method).
				WithField("url", r.URL).
				Error("Internal authentication error: ", err.Error())
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		url := conf.AuthCodeURL(state, oauth2.AccessTypeOnline)
		http.Redirect(w, r, url, http.StatusTemporaryRedirect)
	})
}

// Logout will expire our authentication cookie and redirect to the SuccessURL
func (g *Github) Logout() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		deleteCookie := http.Cookie{
			Name:     g.Cookie.Name,
			Value:    "none",
			Expires:  g.Now().Add(-1 * time.Hour),
			HttpOnly: true,
			Path:     "/",
		}
		http.SetCookie(w, &deleteCookie)
		http.Redirect(w, r, g.SuccessURL, http.StatusTemporaryRedirect)
	})
}

// Callback used by github callback after authorization is granted.  If granted, Callback will set a cookie with a month-long expiration.  The value of the cookie is a JWT because the JWT can be validated without the need for saving state. The JWT contains the Github user's primary email address.
func (g *Github) Callback() http.Handler {
	conf := g.config()
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log := g.Logger.
			WithField("component", "auth").
			WithField("remote_addr", r.RemoteAddr).
			WithField("method", r.Method).
			WithField("url", r.URL)

		state := r.FormValue("state")
		// Check if the OAuth state token is valid to prevent CSRF
		_, err := g.Authenticator.Authenticate(r.Context(), state)
		if err != nil {
			log.Error("Invalid OAuth state received: ", err.Error())
			http.Redirect(w, r, g.FailureURL, http.StatusTemporaryRedirect)
			return
		}

		code := r.FormValue("code")
		token, err := conf.Exchange(r.Context(), code)
		if err != nil {
			log.Error("Unable to exchange code for token ", err.Error())
			http.Redirect(w, r, g.FailureURL, http.StatusTemporaryRedirect)
			return
		}

		oauthClient := conf.Client(r.Context(), token)
		client := github.NewClient(oauthClient)

		emails, resp, err := client.Users.ListEmails(nil)
		if err != nil {
			switch resp.StatusCode {
			case http.StatusUnauthorized, http.StatusForbidden:
				log.Error("OAuth access to email address forbidden ", err.Error())
			default:
				log.Error("Unable to retrieve Github email ", err.Error())
			}

			http.Redirect(w, r, g.FailureURL, http.StatusTemporaryRedirect)
			return
		}

		email, err := primaryEmail(emails)
		if err != nil {
			log.Error("Unable to retrieve primary Github email ", err.Error())
			http.Redirect(w, r, g.FailureURL, http.StatusTemporaryRedirect)
		}

		// We create an auth token that will be used by all other endpoints to validate the principal has a claim
		authToken, err := g.Authenticator.Token(r.Context(), mrfusion.Principal(email), g.Cookie.Duration)
		if err != nil {
			log.Error("Unable to create cookie auth token ", err.Error())
			http.Redirect(w, r, g.FailureURL, http.StatusTemporaryRedirect)
		}

		expireCookie := time.Now().Add(g.Cookie.Duration)
		cookie := http.Cookie{
			Name:     g.Cookie.Name,
			Value:    authToken,
			Expires:  expireCookie,
			HttpOnly: true,
			Path:     "/",
		}
		log.Info("User ", email, " is authenticated")
		http.SetCookie(w, &cookie)
		http.Redirect(w, r, g.SuccessURL, http.StatusTemporaryRedirect)
	})
}

func randomString(length int) string {
	k := make([]byte, length)
	if _, err := io.ReadFull(rand.Reader, k); err != nil {
		return ""
	}
	return base64.StdEncoding.EncodeToString(k)
}

func primaryEmail(emails []*github.UserEmail) (string, error) {
	for _, m := range emails {
		if m != nil && m.Primary != nil && m.Verified != nil && m.Email != nil {
			return *m.Email, nil
		}
	}
	return "", errors.New("No primary email address")
}
<commit_msg>Fix poorly formatted comment<commit_after>package handlers

import (
	"crypto/rand"
	"encoding/base64"
	"errors"
	"io"
	"net/http"
	"time"

	"github.com/google/go-github/github"
	"github.com/influxdata/mrfusion"
	"golang.org/x/oauth2"
	ogh "golang.org/x/oauth2/github"
)

const (
	DefaultCookieName     = "session"
	DefaultCookieDuration = time.Hour * 24 * 30
)

// Cookie represents the location and expiration time of new cookies.
type Cookie struct {
	Name     string
	Duration time.Duration
}

// NewCookie creates a Cookie with DefaultCookieName and DefaultCookieDuration
func NewCookie() Cookie {
	return Cookie{
		Name:     DefaultCookieName,
		Duration: DefaultCookieDuration,
	}
}

// Github provides OAuth Login and Callback handlers. Callback will set
// an authentication cookie.  This cookie's value is a JWT containing
// the user's primary Github email address.
type Github struct {
	Cookie        Cookie
	Authenticator mrfusion.Authenticator
	ClientID      string
	ClientSecret  string
	Scopes        []string
	SuccessURL    string // SuccessURL is redirect location after successful authorization
	FailureURL    string // FailureURL is redirect location after authorization failure
	Now           func() time.Time
	Logger        mrfusion.Logger
}

// NewGithub constructs a Github with default cookie behavior and scopes.
func NewGithub(clientID, clientSecret, successURL, failureURL string, auth mrfusion.Authenticator, log mrfusion.Logger) Github {
	return Github{
		ClientID:      clientID,
		ClientSecret:  clientSecret,
		Cookie:        NewCookie(),
		Scopes:        []string{"user:email"},
		SuccessURL:    successURL,
		FailureURL:    failureURL,
		Authenticator: auth,
		Now:           time.Now,
		Logger:        log,
	}
}

func (g *Github) config() *oauth2.Config {
	return &oauth2.Config{
		ClientID:     g.ClientID,
		ClientSecret: g.ClientSecret,
		Scopes:       g.Scopes,
		Endpoint:     ogh.Endpoint,
	}
}

// Login returns a handler that redirects to Github's OAuth login.
// Uses JWT with a random string as the state validation method.
// JWTs are used because they can be validated without storing
// state.
func (g *Github) Login() http.Handler {
	conf := g.config()
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// We are creating a token with an encoded random string to prevent CSRF attacks
		// This token will be validated during the OAuth callback.
		// We'll give our users 10 minutes from this point to type in their github password.
		// If the callback is not received within 10 minutes, then authorization will fail.
		csrf := randomString(32) // 32 is not important... just long
		state, err := g.Authenticator.Token(r.Context(), mrfusion.Principal(csrf), 10*time.Minute)
		// This is likely an internal server error
		if err != nil {
			g.Logger.
				WithField("component", "auth").
				WithField("remote_addr", r.RemoteAddr).
				WithField("method", r.Method).
				WithField("url", r.URL).
				Error("Internal authentication error: ", err.Error())
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		url := conf.AuthCodeURL(state, oauth2.AccessTypeOnline)
		http.Redirect(w, r, url, http.StatusTemporaryRedirect)
	})
}

// Logout will expire our authentication cookie and redirect to the SuccessURL
func (g *Github) Logout() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		deleteCookie := http.Cookie{
			Name:     g.Cookie.Name,
			Value:    "none",
			Expires:  g.Now().Add(-1 * time.Hour),
			HttpOnly: true,
			Path:     "/",
		}
		http.SetCookie(w, &deleteCookie)
		http.Redirect(w, r, g.SuccessURL, http.StatusTemporaryRedirect)
	})
}

// Callback used by github callback after authorization is granted.  If
// granted, Callback will set a cookie with a month-long expiration.  The
// value of the cookie is a JWT because the JWT can be validated without
// the need for saving state. The JWT contains the Github user's primary
// email address.
func (g *Github) Callback() http.Handler {
	conf := g.config()
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log := g.Logger.
			WithField("component", "auth").
			WithField("remote_addr", r.RemoteAddr).
			WithField("method", r.Method).
			WithField("url", r.URL)

		state := r.FormValue("state")
		// Check if the OAuth state token is valid to prevent CSRF
		_, err := g.Authenticator.Authenticate(r.Context(), state)
		if err != nil {
			log.Error("Invalid OAuth state received: ", err.Error())
			http.Redirect(w, r, g.FailureURL, http.StatusTemporaryRedirect)
			return
		}

		code := r.FormValue("code")
		token, err := conf.Exchange(r.Context(), code)
		if err != nil {
			log.Error("Unable to exchange code for token ", err.Error())
			http.Redirect(w, r, g.FailureURL, http.StatusTemporaryRedirect)
			return
		}

		oauthClient := conf.Client(r.Context(), token)
		client := github.NewClient(oauthClient)

		emails, resp, err := client.Users.ListEmails(nil)
		if err != nil {
			switch resp.StatusCode {
			case http.StatusUnauthorized, http.StatusForbidden:
				log.Error("OAuth access to email address forbidden ", err.Error())
			default:
				log.Error("Unable to retrieve Github email ", err.Error())
			}

			http.Redirect(w, r, g.FailureURL, http.StatusTemporaryRedirect)
			return
		}

		email, err := primaryEmail(emails)
		if err != nil {
			log.Error("Unable to retrieve primary Github email ", err.Error())
			http.Redirect(w, r, g.FailureURL, http.StatusTemporaryRedirect)
		}

		// We create an auth token that will be used by all other endpoints to validate the principal has a claim
		authToken, err := g.Authenticator.Token(r.Context(), mrfusion.Principal(email), g.Cookie.Duration)
		if err != nil {
			log.Error("Unable to create cookie auth token ", err.Error())
			http.Redirect(w, r, g.FailureURL, http.StatusTemporaryRedirect)
		}

		expireCookie := time.Now().Add(g.Cookie.Duration)
		cookie := http.Cookie{
			Name:     g.Cookie.Name,
			Value:    authToken,
			Expires:  expireCookie,
			HttpOnly: true,
			Path:     "/",
		}
		log.Info("User ", email, " is authenticated")
		http.SetCookie(w, &cookie)
		http.Redirect(w, r, g.SuccessURL, http.StatusTemporaryRedirect)
	})
}

func randomString(length int) string {
	k := make([]byte, length)
	if _, err := io.ReadFull(rand.Reader, k); err != nil {
		return ""
	}
	return base64.StdEncoding.EncodeToString(k)
}

func primaryEmail(emails []*github.UserEmail) (string, error) {
	for _, m := range emails {
		if m != nil && m.Primary != nil && m.Verified != nil && m.Email != nil {
			return *m.Email, nil
		}
	}
	return "", errors.New("No primary email address")
}
<|endoftext|>