<commit_before>// # wallaby - Write Ahead Log
//
// This file contains all of the errors, constants and entry points for wallaby.
//
package wallaby

import (
    "bytes"
    "errors"
    "os"

    xxhash "github.com/OneOfOne/xxhash/native"
    "github.com/swiftkick-io/xbinary"
)

// ## **Possible Log Errors**

var (
    // - `ErrReadIndexHeader` occurs when the index header cannot be read
    ErrReadIndexHeader = errors.New("failed to read index header")

    // - `ErrWriteIndexHeader` occurs when the index header cannot be written
    ErrWriteIndexHeader = errors.New("failed to write index header")

    // - `ErrReadLogHeader` occurs when the log header cannot be read
    ErrReadLogHeader = errors.New("failed to read log header")

    // - `ErrWriteLogHeader` occurs when the log header cannot be written
    ErrWriteLogHeader = errors.New("failed to write log header")

    // - `ErrSliceOutOfBounds` occurs when index.Slice is called and the offset
    // is larger than the size of the index.
    ErrSliceOutOfBounds = errors.New("read offset out of bounds")

    // - `ErrReadIndex` occurs when index.Slice fails to read the records
    ErrReadIndex = errors.New("failed to read index records")

    // - `ErrConfigRequired` occurs when no log config is given when creating
    // or opening a log file.
    ErrConfigRequired = errors.New("log config required")

    // - `ErrInvalidFileVersion` occurs when the version in the file header
    // is unrecognized.
    ErrInvalidFileVersion = errors.New("invalid file version")

    // - `ErrInvalidFileSignature` occurs when the signature in the file header
    // is unrecognized.
    ErrInvalidFileSignature = errors.New("invalid file signature")

    // - `ErrWriteLogRecord` occurs when a record fails to be written to the log
    ErrWriteLogRecord = errors.New("failed to write record")

    // - `ErrReadLogRecord` occurs when a record fails to be read from the log
    ErrReadLogRecord = errors.New("failed to read record")

    // - `ErrLogAlreadyOpen` occurs when an open log tries to be opened again
    ErrLogAlreadyOpen = errors.New("log already open")

    // - `ErrLogClosed` occurs when `Append` is called after the log has been
    // closed.
    ErrLogClosed = errors.New("log has been closed")

    // `ErrRecordTooLarge` occurs when writing a record which exceed the max
    // record size for the log.
    ErrRecordTooLarge = errors.New("record is too large")

    // ErrExceedsBufferSize occurs when the BufferedWriter is not large enough
    // to contain all the data being written to it.
    ErrExceedsBufferSize = errors.New("buffer too large")

    // ErrInvalidRecordSize
    ErrInvalidRecordSize = errors.New("invalid record size")

    // ErrInvalidSnapshot occurs when a Snapshot cannot be decoded.
    ErrInvalidSnapshot = errors.New("invalid snapshot")

    // ErrInvalidTTL occurs when the `config.TimeToLive` is less than 0
    ErrInvalidTTL = errors.New("invalid ttl; must be >= 0")

    // ErrInvalidLogStrategy occurs when the `config.Strategy` is nil
    ErrInvalidLogStrategy = errors.New("invalid write strategy")

    // ## **Log Variables**

    // Log signature bytes - `LOG`
    LogFileSignature = []byte("LOG")

    // Index signature bytes - `IDX`
    IndexFileSignature = []byte("IDX")
)

// ## **Log Constants**

const (
    // - `DefaultIndexFlags` is the default boolean flags for an index file.
    DefaultIndexFlags = 0

    // - `DefaultRecordFlags` represents the default boolean flags for each log record.
    DefaultRecordFlags uint32 = 0

    // - `DefaultMaxRecordSize` is the default maximum size of a log record.
    DefaultMaxRecordSize = 0xffff

    // - `LogHeaderSize` is the size of the file header.
    LogHeaderSize = 8

    // - `VersionOne` is an integer denoting the first version
    VersionOne = 1

    // - `VersionOneIndexHeaderSize` is the size of the index file header.
    VersionOneIndexHeaderSize = 8

    // - `VersionOneIndexRecordSize` is the size of the index records.
    VersionOneIndexRecordSize = 32

    // - `VersionOneLogHeaderSize` is the header size of version 1 log files
    VersionOneLogHeaderSize = 8

    // - `VersionOneLogRecordHeaderSize` is the size of the log record headers.
    VersionOneLogRecordHeaderSize = 24

    // - `MaximumIndexSlice` is the maximum number of index records to be read at
    // one time
    MaximumIndexSlice = 32000
)

// ## **Metadata**
// Metadata simply contains descriptive information about the log
type Metadata struct {
    Size             int64
    LastModifiedTime int64
    FileName         string
    IndexFileName    string
}

// ## **Config**

// Config stores several log settings. This is used to describe how the log
// should be opened.
type Config struct {
    FileMode      os.FileMode
    MaxRecordSize int
    Flags         uint32
    Version       uint8
    Truncate      bool
    TimeToLive    int64
    Strategy      AtomicStrategy
}

// > `DefaultConfig` can be used for sensible default log configuration.
var DefaultConfig Config = Config{
    FileMode:      0600,
    MaxRecordSize: DefaultMaxRecordSize,
    Flags:         DefaultRecordFlags,
    Version:       VersionOne,
    Truncate:      false,
    TimeToLive:    0,
    Strategy:      SyncOnWrite,
}

// ## **Create a log file**
// Create returns a `WriteAheadLog` implementation if no errors occur. If the
// given filename already exists, the log file will try to be opened. If the
// file format can be verified, the existing log will be returned. If the
// file does not exist, a new log will be created with the given config.
//
// If the file already exists and the file version is different than the given
// `config.Version`, the file will remain the version in which it was created.
// In other words the file will not be updated to the newer version.

// ###### Implementation
func Create(filename string, config Config) (WriteAheadLog, error) {

    // Determine if the given config is valid. If the given config is `nil`,
    // a `ErrConfigRequired` error will be returned.
    if &config == nil {
        return nil, ErrConfigRequired
    }

    if config.TimeToLive < 0 {
        return nil, ErrInvalidTTL
    }

    if config.Strategy == nil {
        return nil, ErrInvalidLogStrategy
    }

    // Open the file name, creating the file if it does not already exist. The
    // file is opened with the `APPEND` flag, which means all writes are
    // appended to the file. Additional file modes can be given with the config.
    file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, config.FileMode)

    // If there was an error opening the file, the open error is returned.
    if err != nil {
        return nil, err
    }

    // Truncate the log file if requested in the given config
    if config.Truncate {
        err = file.Truncate(0)
        if err != nil {
            file.Close()
            return nil, err
        }
    }

    // Get the file stat. The file size is gotten from this call. This helps
    // to determine if the file already has a header or not.
    stat, err := file.Stat()

    // Return an error if the `os.FileStat` could not be retrieved. The file
    // is closed before returning.
    if err != nil {
        file.Close()
        return nil, err
    }

    // If the file size suggests the header exists, open an existing file.
    // Otherwise create a new file based on the given config.
    if stat.Size() >= LogHeaderSize {
        return openExisting(file, filename, config)
    }
    return createNew(file, filename, config)
}

// ## **Utility functions**
// These functions assist in opening both new and existing log files.

// ### **Creates a new log file**
// A new log file is created with a file header consisting of a `LOG` signature
// followed by an 8-bit version. The file header is then synced to disk and
// a new log is created.

// ###### Implentation
func createNew(file *os.File, filename string, config Config) (WriteAheadLog, error) {

    // Create a buffer for the 4-byte file header.
    // The first 3 bytes contain the signature, `LOG`, followed by an 8-bit
    // version.
    buf := make([]byte, 8)

    // Write the `LOG` file signature to the first 3 bytes of the file.
    xbinary.LittleEndian.PutString(buf, 0, string(LogFileSignature))

    // Set file version to the given `config.Version`.
    buf[3] = byte(config.Version)

    // Write the config flags into the buffer
    xbinary.LittleEndian.PutUint32(buf, 4, config.Flags)

    // Write the file header buffer to the file.
    _, err := file.Write(buf)

    // If the header could not be written, close the file and return a
    // `ErrWriteLogHeader` error along with a `nil` log.
    if err != nil {
        file.Close()
        return nil, ErrWriteLogHeader
    }

    // If writing the file header succeeded, sync the file header to disk.
    err = file.Sync()

    // If the sync command failed, return a `ErrWriteLogHeader` error and a
    // `nil` log.
    if err != nil {
        return nil, ErrWriteLogHeader
    }

    // Returns the proper log parser based on the given `config.Version`.
    return selectVersion(file, filename, config)
}

// ### **Opens an existing log file**
// Opens an existing file and returns a log based on the file header. If the
// file contains a version which is not understood, the error
// `ErrInvalidFileVersion` is returned along with a `nil` log.
//
// If the file header cannot be read, an error is also returned.

// ###### Implementation
func openExisting(file *os.File, filename string, config Config) (WriteAheadLog, error) {
    // Create a buffer for the 8-byte file header.
    // The first 3 bytes are the signature `LOG` followed by an 8-bit version
    // and the boolean flags. Then read the file header into the buffer.
    buf := make([]byte, 8)
    _, err := file.ReadAt(buf, 0)

    // If there was an error reading the file header, close the file and return
    // a nil log and the read error.
    if err != nil {
        file.Close()
        return nil, err
    }

    // If the header was read sucessfully, verify the file signature matches
    // the expected "LOG" signature. If the first 3 bytes do not match `LOG`,
    // return a `nil` log and a `ErrInvalidFileSignature`.
    if !bytes.Equal(buf[0:3], LogFileSignature) {
        return nil, ErrInvalidFileSignature
    }

    // Read the boolean flags from the file header and overwrite the config
    // flags with the ones from the file.
    flags, err := xbinary.LittleEndian.Uint32(buf, 4)
    if err != nil {
        return nil, err
    }
    config.Flags = flags

    // The config version is updated to reflect the actual version of the file.
    // Then return the proper log parser based on the file version.
    config.Version = uint8(buf[3])
    return selectVersion(file, filename, config)
}

// ### **Select log version**
// `selectVersion` is only here to make the code a bit `DRY`er. It simple
// returns the proper log file based on the given version.

// ###### Implementation
// Open the log file based on the current version of the file.
// If the version is unrecognized, a `nil` log is returned as well as an
// `ErrInvalidFileVersion` error.
func selectVersion(file *os.File, filename string, config Config) (WriteAheadLog, error) {
    switch config.Version {
    case VersionOne:
        return createVersionOne(file, filename, config)
    default:
        return nil, ErrInvalidFileVersion
    }
}

// ### **Creates a v1 log file**
// This function is used when creating a v1 log file.

// ###### *Implementation*
func createVersionOne(file *os.File, filename string, config Config) (WriteAheadLog, error) {

    // Create the file header structure from the log flags and version.
    header := BasicFileHeader{flags: config.Flags, version: config.Version}

    index, err := VersionOneIndexFactory(filename+".idx", config.Version, config.Flags)
    if err != nil {
        file.Close()
        return nil, err
    }

    // Stat the file to get the size. If unsuccessful, close the file and return
    // the error.
    stat, err := file.Stat()
    if err != nil {
        file.Close()
        return nil, err
    }

    // Finally, create the log file and return.
    return &versionOneLogFile{file, config.Strategy(file), config, header, index,
        stat.Size(), UNOPENED, xxhash.New64(), make([]byte, VersionOneIndexRecordSize), config.TimeToLive, int64(stat.ModTime().Nanosecond())}, nil
}
<commit_msg>Move errors and constants into common and move version specific code into separate directories<commit_after>// # wallaby - Write Ahead Log
//
// This file contains all of the errors, constants and entry points for wallaby.
//
package wallaby

import (
    "bytes"
    "os"

    "github.com/swiftkick-io/wallaby/v1"
    "github.com/swiftkick-io/xbinary"
)

// ## **Create a log file**
// Create returns a `WriteAheadLog` implementation if no errors occur. If the
// given filename already exists, the log file will try to be opened. If the
// file format can be verified, the existing log will be returned. If the
// file does not exist, a new log will be created with the given config.
//
// If the file already exists and the file version is different than the given
// `config.Version`, the file will remain the version in which it was created.
// In other words the file will not be updated to the newer version.

// ###### Implementation
func Create(filename string, config Config) (WriteAheadLog, error) {

    // Determine if the given config is valid. If the given config is `nil`,
    // a `ErrConfigRequired` error will be returned.
    if &config == nil {
        return nil, ErrConfigRequired
    }

    if config.TimeToLive < 0 {
        return nil, ErrInvalidTTL
    }

    if config.Strategy == nil {
        return nil, ErrInvalidLogStrategy
    }

    // Open the file name, creating the file if it does not already exist. The
    // file is opened with the `APPEND` flag, which means all writes are
    // appended to the file. Additional file modes can be given with the config.
    file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, config.FileMode)

    // If there was an error opening the file, the open error is returned.
    if err != nil {
        return nil, err
    }

    // Truncate the log file if requested in the given config
    if config.Truncate {
        err = file.Truncate(0)
        if err != nil {
            file.Close()
            return nil, err
        }
    }

    // Get the file stat. The file size is gotten from this call. This helps
    // to determine if the file already has a header or not.
    stat, err := file.Stat()

    // Return an error if the `os.FileStat` could not be retrieved. The file
    // is closed before returning.
    if err != nil {
        file.Close()
        return nil, err
    }

    // If the file size suggests the header exists, open an existing file.
    // Otherwise create a new file based on the given config.
    if stat.Size() >= LogHeaderSize {
        return openExisting(file, filename, config)
    }
    return createNew(file, filename, config)
}

// ## **Utility functions**
// These functions assist in opening both new and existing log files.

// ### **Creates a new log file**
// A new log file is created with a file header consisting of a `LOG` signature
// followed by an 8-bit version. The file header is then synced to disk and
// a new log is created.

// ###### Implentation
func createNew(file *os.File, filename string, config Config) (WriteAheadLog, error) {

    // Create a buffer for the 4-byte file header.
    // The first 3 bytes contain the signature, `LOG`, followed by an 8-bit
    // version.
    buf := make([]byte, 8)

    // Write the `LOG` file signature to the first 3 bytes of the file.
    xbinary.LittleEndian.PutString(buf, 0, string(LogFileSignature))

    // Set file version to the given `config.Version`.
    buf[3] = byte(config.Version)

    // Write the config flags into the buffer
    xbinary.LittleEndian.PutUint32(buf, 4, config.Flags)

    // Write the file header buffer to the file.
    _, err := file.Write(buf)

    // If the header could not be written, close the file and return a
    // `ErrWriteLogHeader` error along with a `nil` log.
    if err != nil {
        file.Close()
        return nil, ErrWriteLogHeader
    }

    // If writing the file header succeeded, sync the file header to disk.
    err = file.Sync()

    // If the sync command failed, return a `ErrWriteLogHeader` error and a
    // `nil` log.
    if err != nil {
        return nil, ErrWriteLogHeader
    }

    // Returns the proper log parser based on the given `config.Version`.
    return selectVersion(file, filename, config)
}

// ### **Opens an existing log file**
// Opens an existing file and returns a log based on the file header. If the
// file contains a version which is not understood, the error
// `ErrInvalidFileVersion` is returned along with a `nil` log.
//
// If the file header cannot be read, an error is also returned.

// ###### Implementation
func openExisting(file *os.File, filename string, config Config) (WriteAheadLog, error) {
    // Create a buffer for the 8-byte file header.
    // The first 3 bytes are the signature `LOG` followed by an 8-bit version
    // and the boolean flags. Then read the file header into the buffer.
    buf := make([]byte, 8)
    _, err := file.ReadAt(buf, 0)

    // If there was an error reading the file header, close the file and return
    // a nil log and the read error.
    if err != nil {
        file.Close()
        return nil, err
    }

    // If the header was read sucessfully, verify the file signature matches
    // the expected "LOG" signature. If the first 3 bytes do not match `LOG`,
    // return a `nil` log and a `ErrInvalidFileSignature`.
    if !bytes.Equal(buf[0:3], LogFileSignature) {
        return nil, ErrInvalidFileSignature
    }

    // Read the boolean flags from the file header and overwrite the config
    // flags with the ones from the file.
    flags, err := xbinary.LittleEndian.Uint32(buf, 4)
    if err != nil {
        return nil, err
    }
    config.Flags = flags

    // The config version is updated to reflect the actual version of the file.
    // Then return the proper log parser based on the file version.
    config.Version = uint8(buf[3])
    return selectVersion(file, filename, config)
}

// ### **Select log version**
// `selectVersion` is only here to make the code a bit `DRY`er. It simple
// returns the proper log file based on the given version.

// ###### Implementation
// Open the log file based on the current version of the file.
// If the version is unrecognized, a `nil` log is returned as well as an
// `ErrInvalidFileVersion` error.
func selectVersion(file *os.File, filename string, config Config) (WriteAheadLog, error) {
    switch config.Version {
    case VersionOne:
        return v1.Create(file, filename, config)
    default:
        return nil, ErrInvalidFileVersion
    }
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/PuerkitoBio/goquery"
	"github.com/gorilla/feeds"
	"github.com/hawx/tw-linkfeed/store"
	"github.com/hawx/tw-linkfeed/stream"
	"github.com/hawx/tw-linkfeed/views"

	"flag"
	"fmt"
	"log"
	"net"
	"net/http"
	"time"
)

var (
	consumerKey    = flag.String("consumer-key", "", "")
	consumerSecret = flag.String("consumer-secret", "", "")
	accessToken    = flag.String("access-token", "", "")
	accessSecret   = flag.String("access-secret", "", "")

	title = flag.String("title", "tw-linkfeed", "")
	url   = flag.String("url", "http://localhost:8080/", "")

	port   = flag.String("port", "8080", "")
	socket = flag.String("socket", "", "")
	help   = flag.Bool("help", false, "")
)

const HELP = `Usage: tw-linkfeed [options]

  Serves a feed (in html at '/', and rss at '/feed') of all links in
  your twitter timeline.

    --consumer-key <value>
    --consumer-secret <value>
    --access-token <value>
    --access-secret <value>

    --title <title>     # Title of page/feed (default: 'tw-linkfeed')
    --url <url>         # URL running at (default: 'http://localhost:8080/')

    --port <port>       # Port to run on (default: '8080')
    --socket <path>     # Serve using a unix socket instead
    --help              # Display this help message
`

func run(store store.Store) {
	auth := stream.Auth(*consumerKey, *consumerSecret, *accessToken, *accessSecret)

	for tweet := range stream.Timeline(auth) {
		if len(tweet.Entities.Urls) > 0 {
			go func(tweet stream.Tweet) {
				doc, err := goquery.NewDocument(*tweet.Entities.Urls[0].ExpandedUrl)
				if err != nil {
					store.Add(tweet)
					return
				}

				title := doc.Find("title").Text()
				if title != "" {
					tweet.Entities.Urls[0].DisplayUrl = title
				}
				store.Add(tweet)
			}(tweet)
		}
	}
}

func main() {
	flag.Parse()

	if *help {
		fmt.Println(HELP)
		return
	}

	store := store.New(24, time.Hour)
	go run(store)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		views.List.Execute(w, struct {
			Tweets []stream.Tweet
			Url    string
			Title  string
		}{store.Latest(), *url, *title})
	})

	http.HandleFunc("/feed", func(w http.ResponseWriter, r *http.Request) {
		now := time.Now()
		feed := &feeds.Feed{
			Title:   *title,
			Link:    &feeds.Link{Href: *url + "feed"},
			Created: now,
		}

		for _, tweet := range store.Latest() {
			url := tweet.Entities.Urls[0]

			feed.Items = append(feed.Items, &feeds.Item{
				Title:       url.DisplayUrl,
				Link:        &feeds.Link{Href: *url.ExpandedUrl},
				Description: tweet.Text,
				Created:     tweet.CreatedAt.Time,
			})
		}

		rss, err := feed.ToRss()
		if err != nil {
			w.WriteHeader(500)
			return
		}

		w.Header().Add("Content-Type", "application/rss+xml")
		fmt.Fprintf(w, rss)
	})

	if *socket == "" {
		log.Println("listening on :" + *port)
		log.Fatal(http.ListenAndServe(":"+*port, nil))
	} else {
		l, err := net.Listen("unix", *socket)
		if err != nil {
			log.Fatal(err)
		}

		log.Println("listening on", *socket)
		log.Fatal(http.Serve(l, nil))
	}
}
<commit_msg>Gracefully shutdown, closing socket<commit_after>package main

import (
	"github.com/PuerkitoBio/goquery"
	"github.com/gorilla/feeds"
	"github.com/hawx/tw-linkfeed/store"
	"github.com/hawx/tw-linkfeed/stream"
	"github.com/hawx/tw-linkfeed/views"

	"flag"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"time"
)

var (
	consumerKey    = flag.String("consumer-key", "", "")
	consumerSecret = flag.String("consumer-secret", "", "")
	accessToken    = flag.String("access-token", "", "")
	accessSecret   = flag.String("access-secret", "", "")

	title = flag.String("title", "tw-linkfeed", "")
	url   = flag.String("url", "http://localhost:8080/", "")

	port   = flag.String("port", "8080", "")
	socket = flag.String("socket", "", "")
	help   = flag.Bool("help", false, "")
)

const HELP = `Usage: tw-linkfeed [options]

  Serves a feed (in html at '/', and rss at '/feed') of all links in
  your twitter timeline.

    --consumer-key <value>
    --consumer-secret <value>
    --access-token <value>
    --access-secret <value>

    --title <title>     # Title of page/feed (default: 'tw-linkfeed')
    --url <url>         # URL running at (default: 'http://localhost:8080/')

    --port <port>       # Port to run on (default: '8080')
    --socket <path>     # Serve using a unix socket instead
    --help              # Display this help message
`

func run(store store.Store) {
	auth := stream.Auth(*consumerKey, *consumerSecret, *accessToken, *accessSecret)

	for tweet := range stream.Timeline(auth) {
		if len(tweet.Entities.Urls) > 0 {
			go func(tweet stream.Tweet) {
				doc, err := goquery.NewDocument(*tweet.Entities.Urls[0].ExpandedUrl)
				if err != nil {
					store.Add(tweet)
					return
				}

				title := doc.Find("title").Text()
				if title != "" {
					tweet.Entities.Urls[0].DisplayUrl = title
				}
				store.Add(tweet)
			}(tweet)
		}
	}
}

func main() {
	flag.Parse()

	if *help {
		fmt.Println(HELP)
		return
	}

	store := store.New(24, time.Hour)
	go run(store)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		views.List.Execute(w, struct {
			Tweets []stream.Tweet
			Url    string
			Title  string
		}{store.Latest(), *url, *title})
	})

	http.HandleFunc("/feed", func(w http.ResponseWriter, r *http.Request) {
		now := time.Now()
		feed := &feeds.Feed{
			Title:   *title,
			Link:    &feeds.Link{Href: *url + "feed"},
			Created: now,
		}

		for _, tweet := range store.Latest() {
			url := tweet.Entities.Urls[0]

			feed.Items = append(feed.Items, &feeds.Item{
				Title:       url.DisplayUrl,
				Link:        &feeds.Link{Href: *url.ExpandedUrl},
				Description: tweet.Text,
				Created:     tweet.CreatedAt.Time,
			})
		}

		rss, err := feed.ToRss()
		if err != nil {
			w.WriteHeader(500)
			return
		}

		w.Header().Add("Content-Type", "application/rss+xml")
		fmt.Fprintf(w, rss)
	})

	if *socket == "" {
		go func() {
			log.Println("listening on :" + *port)
			log.Fatal(http.ListenAndServe(":"+*port, nil))
		}()
	} else {
		l, err := net.Listen("unix", *socket)
		if err != nil {
			log.Fatal(err)
		}

		defer l.Close()

		go func() {
			log.Println("listening on", *socket)
			log.Fatal(http.Serve(l, nil))
		}()
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	s := <-c
	log.Printf("caught %s: shutting down", s)
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"log"
	"net/http"
	"net/http/httputil"
	"time"

	"github.com/GeertJohan/yubigo"
)

type authProxyHandler struct {
	acl              *ACLConfig
	cache            Cache
	authCookieName   string
	proxy            *httputil.ReverseProxy
	yubiAuth         *yubigo.YubiAuth
	cookieExpiration time.Duration
}

// Returns a boolean only (no error) to make validation of this return value easier.
func (a *authProxyHandler) validateCredentialsForEntry(entry UserEntry, username, password, yubiKey string) bool {

	// Validate username.

	if entry.Username != username {
		return false
	}

	// Validate password.

	if ok, err := entry.PasswordHash.Test(password); !ok {
		log.Println("Could not validate password:", err)
		return false
	}

	// Validate Yubikey.

	_, ok, err := a.yubiAuth.Verify(yubiKey)
	if err != nil {
		log.Println("Could not validate against Yubico:", err)
	}
	return ok
}

func (a *authProxyHandler) validateCredentials(username string, basicAuthPassword string) (bool, error) {
	if len(username) == 0 {
		return false, errors.New("Username must not be empty.")
	}

	if len(basicAuthPassword) < 44 {
		return false, errors.New("Yubikey missing.")
	}
	passwordString := basicAuthPassword[0 : len(basicAuthPassword)-44]
	yubikeyString := basicAuthPassword[len(basicAuthPassword)-44 : len(basicAuthPassword)]

	for _, entry := range a.acl.Entries {
		if a.validateCredentialsForEntry(entry, username, passwordString, yubikeyString) {
			return true, nil
		}
	}

	return false, nil
}

func (a authProxyHandler) isAuthenticated(req *http.Request) bool {
	if cookie, err := req.Cookie(a.authCookieName); err != nil {
		return false
	} else {
		return a.cache.IsStillThere(cookie.Value)
	}
}

func generateRandomString(bytesSource int) (string, error) {
	slots := make([]byte, 32)
	if _, err := rand.Reader.Read(slots); err != nil {
		return "", err
	} else {
		return hex.EncodeToString(slots), nil
	}

}

func (a authProxyHandler) authenticate(resp http.ResponseWriter, req *http.Request) {
	valid := false

	if a.isAuthenticated(req) {
		valid = true
	}

	if !valid {
		if username, password, ok := req.BasicAuth(); ok {
			valid, _ = a.validateCredentials(username, password)
		}
	}

	if valid {
		var randValue string
		if _randValue, err := generateRandomString(32); err != nil {
			// TODO: Log
			resp.WriteHeader(http.StatusInternalServerError)
			return
		} else {
			randValue = _randValue
		}

		cookie := http.Cookie{
			Name:   a.authCookieName,
			Value:  randValue,
			MaxAge: int(a.cookieExpiration.Seconds()),
		}
		http.SetCookie(resp, &cookie)
		a.cache.Add(randValue)

		// Important we don't proxy our username and password upstream!
		delete(req.Header, "Authorization")

		a.proxy.ServeHTTP(resp, req)
	} else {
		// Ask for authentication
		resp.Header()["WWW-Authenticate"] = []string{"Basic realm=\"Please enter your username, followed by password+yubikey\""}
		resp.WriteHeader(http.StatusUnauthorized)
	}
}

func (a authProxyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	if a.isAuthenticated(req) {
		// Proxy upstream
		a.proxy.ServeHTTP(resp, req)
	} else {
		a.authenticate(resp, req)
	}
}
<commit_msg>fix/security: don't proxy auth cookie<commit_after>package main

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"log"
	"net/http"
	"net/http/httputil"
	"strings"
	"time"

	"github.com/GeertJohan/yubigo"
)

type authProxyHandler struct {
	acl              *ACLConfig
	cache            Cache
	authCookieName   string
	proxy            *httputil.ReverseProxy
	yubiAuth         *yubigo.YubiAuth
	cookieExpiration time.Duration
}

// Returns a boolean only (no error) to make validation of this return value easier.
func (a *authProxyHandler) validateCredentialsForEntry(entry UserEntry, username, password, yubiKey string) bool {

	// Validate username.

	if entry.Username != username {
		return false
	}

	// Validate password.

	if ok, err := entry.PasswordHash.Test(password); !ok {
		log.Println("Could not validate password:", err)
		return false
	}

	// Validate Yubikey.

	_, ok, err := a.yubiAuth.Verify(yubiKey)
	if err != nil {
		log.Println("Could not validate against Yubico:", err)
	}
	return ok
}

func (a *authProxyHandler) validateCredentials(username string, basicAuthPassword string) (bool, error) {
	if len(username) == 0 {
		return false, errors.New("Username must not be empty.")
	}

	if len(basicAuthPassword) < 44 {
		return false, errors.New("Yubikey missing.")
	}
	passwordString := basicAuthPassword[0 : len(basicAuthPassword)-44]
	yubikeyString := basicAuthPassword[len(basicAuthPassword)-44 : len(basicAuthPassword)]

	for _, entry := range a.acl.Entries {
		if a.validateCredentialsForEntry(entry, username, passwordString, yubikeyString) {
			return true, nil
		}
	}

	return false, nil
}

func (a authProxyHandler) isAuthenticated(req *http.Request) bool {
	if cookie, err := req.Cookie(a.authCookieName); err != nil {
		return false
	} else {
		return a.cache.IsStillThere(cookie.Value)
	}
}

func generateRandomString(bytesSource int) (string, error) {
	slots := make([]byte, 32)
	if _, err := rand.Reader.Read(slots); err != nil {
		return "", err
	} else {
		return hex.EncodeToString(slots), nil
	}

}

func (a authProxyHandler) stripAuthCookie(req *http.Request) {
	if cookieHeaders, ok := req.Header["Cookie"]; ok {
		for i, cookieHeader := range cookieHeaders {
			cookies := strings.Split(cookieHeader, "; ")
			newCookies := make([]string, 0)
			for _, cookie := range cookies {
				if !strings.HasPrefix(cookie, a.authCookieName+"=") {
					newCookies = append(newCookies, cookie)
				}
			}
			cookieHeaders[i] = strings.Join(newCookies, "; ")
		}
	}
}

func (a authProxyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	valid := false

	if a.isAuthenticated(req) {
		valid = true
	}

	if !valid {
		if username, password, ok := req.BasicAuth(); ok {
			valid, _ = a.validateCredentials(username, password)
		}
	}

	if valid {
		var randValue string
		if _randValue, err := generateRandomString(32); err != nil {
			// TODO: Log
			resp.WriteHeader(http.StatusInternalServerError)
			return
		} else {
			randValue = _randValue
		}

		cookie := http.Cookie{
			Name:   a.authCookieName,
			Value:  randValue,
			MaxAge: int(a.cookieExpiration.Seconds()),
		}
		http.SetCookie(resp, &cookie)
		a.cache.Add(randValue)

		// Important we don't proxy our username and password upstream!
		delete(req.Header, "Authorization")

		// Don't proxy the auth cookie.
		a.stripAuthCookie(req)

		a.proxy.ServeHTTP(resp, req)
	} else {
		// Ask for authentication
		resp.Header()["WWW-Authenticate"] = []string{"Basic realm=\"Please enter your username, followed by password+yubikey\""}
		resp.WriteHeader(http.StatusUnauthorized)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"archive/zip"
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"strings"

	cb "github.com/couchbaselabs/go-couchbase"
)

var missingFile = errors.New("missing file")

type namedFile struct {
	name string
	meta fileMeta
	err  error
}

func pathGenerator(from string, ch chan *namedFile,
	errs chan cb.ViewError, quit chan bool) {
	defer close(ch)
	defer close(errs)

	parts := strings.Split(from, "/")

	viewRes := struct {
		Rows []struct {
			Key []string
			Id  string
		}
		Errors []cb.ViewError
	}{}

	limit := 1000
	startKey := parts
	done := false
	for !done {
		err := couchbase.ViewCustom("cbfs", "file_browse",
			map[string]interface{}{
				"stale":    false,
				"reduce":   false,
				"limit":    limit,
				"startkey": startKey,
			}, &viewRes)
		if err != nil {
			log.Printf("View error: %v", err)
			return
		}
		for _, e := range viewRes.Errors {
			errs <- e
		}

		done = len(viewRes.Rows) < limit

		paths := map[string]*namedFile{}
		keys := []string{}

		for _, r := range viewRes.Rows {
			k := r.Id
			if !strings.HasPrefix(k, from) {
				done = true
				break
			}
			startKey = r.Key

			nf := namedFile{name: k, err: missingFile}

			paths[k] = &nf
			keys = append(keys, k)
		}

		for k, res := range couchbase.GetBulk(keys) {
			ob := paths[k]
			ob.err = json.Unmarshal(res.Body, &ob.meta)
			if ob.err != nil {
				log.Printf("Error unmarshaling %v: %v", k, ob.err)
			}
			select {
			case <-quit:
				return
			case ch <- ob:
				// We sent one.
			}
		}
	}
}

func doZipDocs(w http.ResponseWriter, req *http.Request,
	path string) {

	quit := make(chan bool)
	defer close(quit)
	ch := make(chan *namedFile)
	cherr := make(chan cb.ViewError)

	go pathGenerator(path, ch, cherr, quit)

	go func() {
		for e := range cherr {
			log.Printf("View error: %v", e)
		}
	}()

	w.Header().Set("Content-Type", "application/zip")
	w.WriteHeader(200)

	zw := zip.NewWriter(w)
	for nf := range ch {
		if nf.err != nil {
			log.Printf("Error on %v: %v", nf.name, nf.err)
			continue
		}

		fh := zip.FileHeader{
			Name:               nf.name,
			Method:             zip.Deflate,
			UncompressedSize64: uint64(nf.meta.Length),
			Comment:            nf.meta.OID,
		}
		fh.SetModTime(nf.meta.Modified)

		zf, err := zw.CreateHeader(&fh)
		if err != nil {
			log.Printf("Error making zip file: %v", err)
			// Client will get a broken zip file
			return
		}

		err = copyBlob(zf, nf.meta.OID)
		if err != nil {
			log.Printf("Error copying blob for %v: %v",
				nf.name, err)
			// Client will get a broken zip file
			return
		}
	}
	zw.Close()
}
<commit_msg>Apparently UncompressedSize64 isn't out yet.<commit_after>package main

import (
	"archive/zip"
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"strings"

	cb "github.com/couchbaselabs/go-couchbase"
)

var missingFile = errors.New("missing file")

type namedFile struct {
	name string
	meta fileMeta
	err  error
}

func pathGenerator(from string, ch chan *namedFile,
	errs chan cb.ViewError, quit chan bool) {
	defer close(ch)
	defer close(errs)

	parts := strings.Split(from, "/")

	viewRes := struct {
		Rows []struct {
			Key []string
			Id  string
		}
		Errors []cb.ViewError
	}{}

	limit := 1000
	startKey := parts
	done := false
	for !done {
		err := couchbase.ViewCustom("cbfs", "file_browse",
			map[string]interface{}{
				"stale":    false,
				"reduce":   false,
				"limit":    limit,
				"startkey": startKey,
			}, &viewRes)
		if err != nil {
			log.Printf("View error: %v", err)
			return
		}
		for _, e := range viewRes.Errors {
			errs <- e
		}

		done = len(viewRes.Rows) < limit

		paths := map[string]*namedFile{}
		keys := []string{}

		for _, r := range viewRes.Rows {
			k := r.Id
			if !strings.HasPrefix(k, from) {
				done = true
				break
			}
			startKey = r.Key

			nf := namedFile{name: k, err: missingFile}

			paths[k] = &nf
			keys = append(keys, k)
		}

		for k, res := range couchbase.GetBulk(keys) {
			ob := paths[k]
			ob.err = json.Unmarshal(res.Body, &ob.meta)
			if ob.err != nil {
				log.Printf("Error unmarshaling %v: %v", k, ob.err)
			}
			select {
			case <-quit:
				return
			case ch <- ob:
				// We sent one.
			}
		}
	}
}

func doZipDocs(w http.ResponseWriter, req *http.Request,
	path string) {

	quit := make(chan bool)
	defer close(quit)
	ch := make(chan *namedFile)
	cherr := make(chan cb.ViewError)

	go pathGenerator(path, ch, cherr, quit)

	go func() {
		for e := range cherr {
			log.Printf("View error: %v", e)
		}
	}()

	w.Header().Set("Content-Type", "application/zip")
	w.WriteHeader(200)

	zw := zip.NewWriter(w)
	for nf := range ch {
		if nf.err != nil {
			log.Printf("Error on %v: %v", nf.name, nf.err)
			continue
		}

		fh := zip.FileHeader{
			Name:             nf.name,
			Method:           zip.Deflate,
			UncompressedSize: uint32(nf.meta.Length),
			Comment:          nf.meta.OID,
		}
		fh.SetModTime(nf.meta.Modified)

		zf, err := zw.CreateHeader(&fh)
		if err != nil {
			log.Printf("Error making zip file: %v", err)
			// Client will get a broken zip file
			return
		}

		err = copyBlob(zf, nf.meta.OID)
		if err != nil {
			log.Printf("Error copying blob for %v: %v",
				nf.name, err)
			// Client will get a broken zip file
			return
		}
	}
	zw.Close()
}
<|endoftext|>


<commit_before>package emath
<commit_msg>trying to commit<commit_after>package emath

import (
	"fmt"
	"net/http"
)
<|endoftext|>


<commit_before>/*
Execute shell commands via http server

Install:
	go get github.com/msoap/shell2http
	ln -s $GOPATH/bin/shell2http ~/bin/shell2http

Usage:
	shell2http [options] /path "shell command" /path2 "shell command2" ...
	options:
		-host="host" : host for http server
		-host=       : for bind to all hosts
		-port=NNNN   : port for http server
		-form        : parse query into enviroment vars
		-cgi         : set some CGI variables in enviroment
		-log=filename: log filename, default - STDOUT
		-help

Examples:
	shell2http /top "top -l 1 | head -10"
	shell2http /date date /ps "ps aux"
	shell2http /env 'printenv | sort' /env/path 'echo $PATH' /env/gopath 'echo $GOPATH'
	shell2http /shell_vars_json 'perl -MJSON -E "say to_json(\%ENV)"'
	shell2http /cal_html 'echo "
Calendar
Date: $(date)

$(cal $(date +%Y))
"'
	shell2http -form /form 'echo $v_from, $v_to'
	shell2http -cgi /query 'echo $QUERY_STRING'

Update:
	go get -u github.com/msoap/shell2http

*/
package main

import (
	"flag"
	"fmt"
	"html"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
)

// default port for http-server
const PORT = 8080

// default host for bind
const HOST = "localhost"

// ------------------------------------------------------------------
const INDEX_HTML = `
<!DOCTYPE html>

	<title>shell2http</title>

shell2http

		%s

/exit

	Get from: github.com/msoap/shell2http

`

// ------------------------------------------------------------------
// one command type
type t_command struct {
	path string
	cmd  string
}

// ------------------------------------------------------------------
// parse arguments
func get_config() (cmd_handlers []t_command, host string, port int, set_cgi bool, set_form bool, err error) {
	var log_filename string
	flag.StringVar(&log_filename, "log", "", "log filename, default - STDOUT")
	flag.IntVar(&port, "port", PORT, "port for http server")
	flag.StringVar(&host, "host", HOST, "host for http server")
	flag.BoolVar(&set_cgi, "cgi", false, "set some CGI variables in enviroment")
	flag.BoolVar(&set_form, "form", false, "parse query into enviroment vars")
	flag.Usage = func() {
		fmt.Printf("usage: %s [options] /path \"shell command\" /path2 \"shell command2\"\n", os.Args[0])
		flag.PrintDefaults()
		os.Exit(0)
	}
	flag.Parse()

	// setup log file
	if len(log_filename) > 0 {
		fh_log, err := os.OpenFile(log_filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatalf("error opening log file: %v", err)
		}
		log.SetOutput(fh_log)
	}

	// need >= 2 arguments and count of it must be even
	args := flag.Args()
	if len(args) < 2 || len(args)%2 == 1 {
		return nil, host, port, set_cgi, set_form, fmt.Errorf("error: need pairs of path and shell command")
	}

	args_i := 0
	for args_i < len(args) {
		path, cmd := args[args_i], args[args_i+1]
		if path[0] != '/' {
			return nil, host, port, set_cgi, set_form, fmt.Errorf("error: path %s dont starts with /", path)
		}
		cmd_handlers = append(cmd_handlers, t_command{path: path, cmd: cmd})
		args_i += 2
	}

	return cmd_handlers, host, port, set_cgi, set_form, nil
}

// ------------------------------------------------------------------
// setup http handlers
func setup_handlers(cmd_handlers []t_command, host string, port int, set_cgi bool, set_form bool) {
	index_li_html := ""
	for _, row := range cmd_handlers {
		path, cmd := row.path, row.cmd
		shell_handler := func(rw http.ResponseWriter, req *http.Request) {
			log.Println("GET", path)

			if set_form {
				get_form(req)
			}
			if set_cgi {
				set_cgi_env(req, path, host, port)
			}

			os_exec_command := exec.Command("sh", "-c", cmd)
			os_exec_command.Stderr = os.Stderr
			shell_out, err := os_exec_command.Output()

			if err != nil {
				log.Println("exec error: ", err)
				fmt.Fprint(rw, "exec error: ", err)
			} else {
				fmt.Fprint(rw, string(shell_out))
			}

			return
		}

		log.Printf("register: %s (%s)\n", path, cmd)
		http.HandleFunc(path, shell_handler)
		index_li_html += fmt.Sprintf(`<li><a href="%s">%s</a> <span style="color: #888">- %s<span></li>`, path, path, html.EscapeString(cmd))
	}

	// --------------
	index_html := fmt.Sprintf(INDEX_HTML, index_li_html)
	http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
		log.Println("GET /")
		fmt.Fprint(rw, index_html)

		return
	})

	// --------------
	http.HandleFunc("/exit", func(rw http.ResponseWriter, req *http.Request) {
		log.Println("GET /exit")
		fmt.Fprint(rw, "Bye...")
		go os.Exit(0)

		return
	})
}

// ------------------------------------------------------------------
// set some CGI variables
func set_cgi_env(req *http.Request, path string, host string, port int) {
	req_headers := [...]struct {
		req_name, cgi_name string
	}{
		{"Accept", "HTTP_ACCEPT"},
		{"Accept-Encoding", "HTTP_ACCEPT_ENCODING"},
		{"Accept-Language", "HTTP_ACCEPT_LANGUAGE"},
		{"User-Agent", "HTTP_USER_AGENT"},
	}

	for _, row := range req_headers {
		if header, exists := req.Header[row.req_name]; exists && len(header) > 0 {
			os.Setenv(row.cgi_name, header[0])
		}
	}

	remote_addr := strings.Split(req.RemoteAddr, ":")
	CGI_vars := [...]struct {
		name, value string
	}{
		{"PATH_INFO", req.URL.Path},
		{"QUERY_STRING", req.URL.RawQuery},
		{"REMOTE_ADDR", remote_addr[0]},
		{"REMOTE_PORT", remote_addr[1]},
		{"REQUEST_METHOD", req.Method},
		{"REQUEST_URI", req.RequestURI},
		{"SCRIPT_NAME", path},
		{"SERVER_NAME", host},
		{"SERVER_PORT", fmt.Sprintf("%d", port)},
		{"SERVER_PROTOCOL", req.Proto},
		{"SERVER_SOFTWARE", "shell2http"},
	}

	for _, row := range CGI_vars {
		os.Setenv(row.name, row.value)
	}
}

// ------------------------------------------------------------------
// parse form into enviroment vars
func get_form(req *http.Request) {
	// clear old variables
	for _, env_raw := range os.Environ() {
		env := strings.SplitN(env_raw, "=", 2)
		if strings.HasPrefix(env[0], "v_") && len(env[0]) > 2 {
			err := os.Unsetenv(env[0])
			if err != nil {
				log.Println(err)
				return
			}
		}
	}

	// set new
	err := req.ParseForm()
	if err != nil {
		log.Println(err)
		return
	}

	for key, value := range req.Form {
		os.Setenv("v_"+key, strings.Join(value, ","))
	}
}

// ------------------------------------------------------------------
func main() {
	cmd_handlers, host, port, set_cgi, set_form, err := get_config()
	if err != nil {
		log.Println(err)
		return
	}
	setup_handlers(cmd_handlers, host, port, set_cgi, set_form)

	adress := fmt.Sprintf("%s:%d", host, port)
	log.Printf("listen http://%s/\n", adress)
	err = http.ListenAndServe(adress, nil)
	if err != nil {
		log.Println(err)
		return
	}
}
<commit_msg>Refactoring: use config struct instead single vars<commit_after>/*
Execute shell commands via http server

Install:
	go get github.com/msoap/shell2http
	ln -s $GOPATH/bin/shell2http ~/bin/shell2http

Usage:
	shell2http [options] /path "shell command" /path2 "shell command2" ...
	options:
		-host="host" : host for http server
		-host=       : for bind to all hosts
		-port=NNNN   : port for http server
		-form        : parse query into enviroment vars
		-cgi         : set some CGI variables in enviroment
		-log=filename: log filename, default - STDOUT
		-help

Examples:
	shell2http /top "top -l 1 | head -10"
	shell2http /date date /ps "ps aux"
	shell2http /env 'printenv | sort' /env/path 'echo $PATH' /env/gopath 'echo $GOPATH'
	shell2http /shell_vars_json 'perl -MJSON -E "say to_json(\%ENV)"'
	shell2http /cal_html 'echo "
Calendar
Date: $(date)

$(cal $(date +%Y))
"'
	shell2http -form /form 'echo $v_from, $v_to'
	shell2http -cgi /query 'echo $QUERY_STRING'

Update:
	go get -u github.com/msoap/shell2http

*/
package main

import (
	"flag"
	"fmt"
	"html"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
)

// default port for http-server
const PORT = 8080

// default host for bind
const HOST = "localhost"

// ------------------------------------------------------------------
const INDEX_HTML = `
<!DOCTYPE html>

	<title>shell2http</title>

shell2http

		%s

/exit

	Get from: github.com/msoap/shell2http

`

// ------------------------------------------------------------------
// one command type
type t_command struct {
	path string
	cmd  string
}

// ------------------------------------------------------------------
// config struct
type t_config struct {
	host     string // server host
	port     int    // server port
	set_cgi  bool   // set CGI variables
	set_form bool   // parse form from URL
}

// ------------------------------------------------------------------
// parse arguments
func get_config() (cmd_handlers []t_command, config t_config, err error) {
	var log_filename string
	flag.StringVar(&log_filename, "log", "", "log filename, default - STDOUT")
	flag.IntVar(&config.port, "port", PORT, "port for http server")
	flag.StringVar(&config.host, "host", HOST, "host for http server")
	flag.BoolVar(&config.set_cgi, "cgi", false, "set some CGI variables in enviroment")
	flag.BoolVar(&config.set_form, "form", false, "parse query into enviroment vars")
	flag.Usage = func() {
		fmt.Printf("usage: %s [options] /path \"shell command\" /path2 \"shell command2\"\n", os.Args[0])
		flag.PrintDefaults()
		os.Exit(0)
	}
	flag.Parse()

	// setup log file
	if len(log_filename) > 0 {
		fh_log, err := os.OpenFile(log_filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatalf("error opening log file: %v", err)
		}
		log.SetOutput(fh_log)
	}

	// need >= 2 arguments and count of it must be even
	args := flag.Args()
	if len(args) < 2 || len(args)%2 == 1 {
		return nil, t_config{}, fmt.Errorf("error: need pairs of path and shell command")
	}

	args_i := 0
	for args_i < len(args) {
		path, cmd := args[args_i], args[args_i+1]
		if path[0] != '/' {
			return nil, t_config{}, fmt.Errorf("error: path %s dont starts with /", path)
		}
		cmd_handlers = append(cmd_handlers, t_command{path: path, cmd: cmd})
		args_i += 2
	}

	return cmd_handlers, config, nil
}

// ------------------------------------------------------------------
// setup http handlers
func setup_handlers(cmd_handlers []t_command, config t_config) {
	index_li_html := ""
	for _, row := range cmd_handlers {
		path, cmd := row.path, row.cmd
		shell_handler := func(rw http.ResponseWriter, req *http.Request) {
			log.Println("GET", path)

			if config.set_form {
				get_form(req)
			}
			if config.set_cgi {
				set_cgi_env(req, config)
			}

			os_exec_command := exec.Command("sh", "-c", cmd)
			os_exec_command.Stderr = os.Stderr
			shell_out, err := os_exec_command.Output()

			if err != nil {
				log.Println("exec error: ", err)
				fmt.Fprint(rw, "exec error: ", err)
			} else {
				fmt.Fprint(rw, string(shell_out))
			}

			return
		}

		log.Printf("register: %s (%s)\n", path, cmd)
		http.HandleFunc(path, shell_handler)
		index_li_html += fmt.Sprintf(`<li><a href="%s">%s</a> <span style="color: #888">- %s<span></li>`, path, path, html.EscapeString(cmd))
	}

	// --------------
	index_html := fmt.Sprintf(INDEX_HTML, index_li_html)
	http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
		log.Println("GET /")
		fmt.Fprint(rw, index_html)

		return
	})

	// --------------
	http.HandleFunc("/exit", func(rw http.ResponseWriter, req *http.Request) {
		log.Println("GET /exit")
		fmt.Fprint(rw, "Bye...")
		go os.Exit(0)

		return
	})
}

// ------------------------------------------------------------------
// set some CGI variables
func set_cgi_env(req *http.Request, config t_config) {
	req_headers := [...]struct {
		req_name, cgi_name string
	}{
		{"Accept", "HTTP_ACCEPT"},
		{"Accept-Encoding", "HTTP_ACCEPT_ENCODING"},
		{"Accept-Language", "HTTP_ACCEPT_LANGUAGE"},
		{"User-Agent", "HTTP_USER_AGENT"},
	}

	for _, row := range req_headers {
		if header, exists := req.Header[row.req_name]; exists && len(header) > 0 {
			os.Setenv(row.cgi_name, header[0])
		}
	}

	remote_addr := strings.Split(req.RemoteAddr, ":")
	CGI_vars := [...]struct {
		name, value string
	}{
		{"PATH_INFO", req.URL.Path},
		{"QUERY_STRING", req.URL.RawQuery},
		{"REMOTE_ADDR", remote_addr[0]},
		{"REMOTE_PORT", remote_addr[1]},
		{"REQUEST_METHOD", req.Method},
		{"REQUEST_URI", req.RequestURI},
		{"SCRIPT_NAME", req.URL.Path},
		{"SERVER_NAME", config.host},
		{"SERVER_PORT", fmt.Sprintf("%d", config.port)},
		{"SERVER_PROTOCOL", req.Proto},
		{"SERVER_SOFTWARE", "shell2http"},
	}

	for _, row := range CGI_vars {
		os.Setenv(row.name, row.value)
	}
}

// ------------------------------------------------------------------
// parse form into enviroment vars
func get_form(req *http.Request) {
	// clear old variables
	for _, env_raw := range os.Environ() {
		env := strings.SplitN(env_raw, "=", 2)
		if strings.HasPrefix(env[0], "v_") && len(env[0]) > 2 {
			err := os.Unsetenv(env[0])
			if err != nil {
				log.Println(err)
				return
			}
		}
	}

	// set new
	err := req.ParseForm()
	if err != nil {
		log.Println(err)
		return
	}

	for key, value := range req.Form {
		os.Setenv("v_"+key, strings.Join(value, ","))
	}
}

// ------------------------------------------------------------------
func main() {
	cmd_handlers, config, err := get_config()
	if err != nil {
		log.Println(err)
		return
	}
	setup_handlers(cmd_handlers, config)

	adress := fmt.Sprintf("%s:%d", config.host, config.port)
	log.Printf("listen http://%s/\n", adress)
	err = http.ListenAndServe(adress, nil)
	if err != nil {
		log.Println(err)
		return
	}
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
)

func TestFilter(t *testing.T) {
	allowed := []string{"meow", "dow"}
	allowedPrefixes := []string{"fu", "do"}
	regexes := []string{
		"^\\d+ BUILD hello",
	}
	regexreplacements := map[string]string{
		"sensitive data \\d+ " : "ok data 000 ",
	}
	replacements := map[string]string{
		"123":     "456",
		"how now": "brown cow",
	}
	replacementPrefixes := map[string]string{
		"the quick brown": "but not the burning of books",
	}
	sieve := NewSieve(allowed, allowedPrefixes, regexes, regexreplacements, replacements, replacementPrefixes)

	tests := []struct {
		in   string
		want string
	}{
		{
			"dow",
			"dow",
		},
		{
			"cow",
			"",
		},
		{
			"meow",
			"meow",
		},
		{
			"fufuyyyyyyyyyy",
			"fufuyyyyyyyyyy",
		},
		{
			"do not want",
			"do not want",
		},
		{
			"1234",
			"",
		},
		{
			"123",
			"456",
		},
		{
			"how now brown cow",
			"",
		},
		{
			"how now",
			"brown cow",
		},
		{
			"the quick brown fox jumped over the capitalist",
			"but not the burning of books",
		},
		{
			"the quick car broke down",
			"",
		},
		{
			"12345 BUILD this is a test",
			"12345 BUILD this s a test",
		},
		{
			"this is sensitive data 12345 now i am done",
			"this is ok data 000 now i am done",
		},
	}

	for _, test := range tests {
		output := sieve.Filter(test.in)
		if output != test.want {
			t.Errorf("input '%s'\nproduced output '%s'\nbut wanted '%s'\n", test.in, output, test.want)
			t.Fail()
		}
	}
}

func TestLoadFilters(t *testing.T) {
	p := NewPolicyList()

	err := p.LoadFilters("not_exist")
	if err == nil {
		t.Errorf("LoadFilters should have err`ed")
		t.Fail()
	}

	// we have no policies loaded so this should fail
	policy := p.getFilterForPath("not_an_existent_exec_path")
	if policy != nil {
		t.Errorf("getFilterForPath should have err`ed")
		t.Fail()
	}

	invalidContent := []byte("temporary file's content")
	validContent := []byte(`
{
    "AuthNetAddr" : "tcp",
    "AuthAddr" : "127.0.0.1:6651",
    "user-id" : 123,
    "exec-path": "/srv/oz/rootfs/home/user/.local/share/torbrowser/tbb/x86_64/tor-browser_en-US/Browser/firefox",
    "client-allowed" : ["SIGNAL NEWNYM","GETINFO net/listeners/socks", "GETCONF UseBridges", "GETCONF Bridge",
			"GETCONF Socks4Proxy", "GETCONF Socks5Proxy", "GETCONF HTTPSProxy",
		       "GETCONF ReachableAddresses"],
    "client-allowed-prefixes" : ["AUTHENTICATE"],
    "client-replacements" : {},
    "client-replacement-prefixes" : {},
    "server-allowed" : ["250 OK", "250-net/listeners/socks=\"127.0.0.1:9050\"", "250 UseBridges=0",
		       "250 ReachableAddresses"],
    "server-allowed-prefixes" : ["650 STREAM"],
    "server-regexes": ["^\\d+ BUILT.*", "^\\d+ EXTENDED.*"],
    "server-replacement-prefixes" : {}
}`)

	dir, err := ioutil.TempDir("", "filter_load_test")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(dir)

	file1 := filepath.Join(dir, "invalid_app_filter.json")
	if err := ioutil.WriteFile(file1, invalidContent, 0666); err != nil {
		panic(err)
	}

	file2 := filepath.Join(dir, "valid_app_filter.json")
	if err := ioutil.WriteFile(file2, validContent, 0666); err != nil {
		panic(err)
	}

	err = p.LoadFilters(dir)
	if err != nil {
		t.Errorf("LoadFilters should have succeeded")
		t.Fail()
	}

	// we have no policies with this exec path; should fail
	policy = p.getFilterForPath("not_an_existent_exec_path")
	if policy != nil {
		t.Errorf("getFilterForPath should have err`ed")
		t.Fail()
	}

	policy = p.getFilterForPathAndUID("/srv/oz/rootfs/home/user/.local/share/torbrowser/tbb/x86_64/tor-browser_en-US/Browser/firefox", 123)
	if policy == nil {
		t.Errorf("getFilterForPathAndUID should have succeeded")
		t.Fail()
	}

	if len(p.loadedFilters) != 1 {
		t.Errorf("LoadFilters should have loaded one filter.")
		t.Fail()
	}
}

func TestLoadFilterFile(t *testing.T) {
	p := NewPolicyList()
	policy, err := p.LoadFilterFile("not_exist")
	if err == nil {
		t.Errorf("LoadFilterFile should have err`ed")
		t.Fail()
	}
	dir, err := ioutil.TempDir("", "filter_load_filter_file")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(dir)
	file1 := filepath.Join(dir, "vlad")
	invalidContent := "{}"
	if err := ioutil.WriteFile(file1, []byte(invalidContent), 0666); err != nil {
		panic(err)
	}
	policy, err = p.LoadFilterFile(file1)
	if policy != nil && err != nil {
		t.Errorf("LoadFilterFile should have err`ed")
		t.Fail()
	}
}
<commit_msg>Fixed a regex test case for TestFilter<commit_after>package main

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
)

func TestFilter(t *testing.T) {
	allowed := []string{"meow", "dow"}
	allowedPrefixes := []string{"fu", "do"}
	regexes := []string{
		"^\\d+ BUILD hello",
	}
	regexreplacements := map[string]string{
		"sensitive data \\d+ ": "ok data 000 ",
	}
	replacements := map[string]string{
		"123":     "456",
		"how now": "brown cow",
	}
	replacementPrefixes := map[string]string{
		"the quick brown": "but not the burning of books",
	}
	sieve := NewSieve(allowed, allowedPrefixes, regexes, regexreplacements, replacements, replacementPrefixes)

	tests := []struct {
		in   string
		want string
	}{
		{
			"dow",
			"dow",
		},
		{
			"cow",
			"",
		},
		{
			"meow",
			"meow",
		},
		{
			"fufuyyyyyyyyyy",
			"fufuyyyyyyyyyy",
		},
		{
			"do not want",
			"do not want",
		},
		{
			"1234",
			"",
		},
		{
			"123",
			"456",
		},
		{
			"how now brown cow",
			"",
		},
		{
			"how now",
			"brown cow",
		},
		{
			"the quick brown fox jumped over the capitalist",
			"but not the burning of books",
		},
		{
			"the quick car broke down",
			"",
		},
		{
			"12345 BUILD this is a test",
			"",
		},
		{
			"this is sensitive data 12345 now i am done",
			"this is ok data 000 now i am done",
		},
	}

	for _, test := range tests {
		output := sieve.Filter(test.in)
		if output != test.want {
			t.Errorf("input '%s'\nproduced output '%s'\nbut wanted '%s'\n", test.in, output, test.want)
			t.Fail()
		}
	}
}

func TestLoadFilters(t *testing.T) {
	p := NewPolicyList()

	err := p.LoadFilters("not_exist")
	if err == nil {
		t.Errorf("LoadFilters should have err`ed")
		t.Fail()
	}

	// we have no policies loaded so this should fail
	policy := p.getFilterForPath("not_an_existent_exec_path")
	if policy != nil {
		t.Errorf("getFilterForPath should have err`ed")
		t.Fail()
	}

	invalidContent := []byte("temporary file's content")
	validContent := []byte(`
{
    "AuthNetAddr" : "tcp",
    "AuthAddr" : "127.0.0.1:6651",
    "user-id" : 123,
    "exec-path": "/srv/oz/rootfs/home/user/.local/share/torbrowser/tbb/x86_64/tor-browser_en-US/Browser/firefox",
    "client-allowed" : ["SIGNAL NEWNYM","GETINFO net/listeners/socks", "GETCONF UseBridges", "GETCONF Bridge",
			"GETCONF Socks4Proxy", "GETCONF Socks5Proxy", "GETCONF HTTPSProxy",
		       "GETCONF ReachableAddresses"],
    "client-allowed-prefixes" : ["AUTHENTICATE"],
    "client-replacements" : {},
    "client-replacement-prefixes" : {},
    "server-allowed" : ["250 OK", "250-net/listeners/socks=\"127.0.0.1:9050\"", "250 UseBridges=0",
		       "250 ReachableAddresses"],
    "server-allowed-prefixes" : ["650 STREAM"],
    "server-regexes": ["^\\d+ BUILT.*", "^\\d+ EXTENDED.*"],
    "server-replacement-prefixes" : {}
}`)

	dir, err := ioutil.TempDir("", "filter_load_test")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(dir)

	file1 := filepath.Join(dir, "invalid_app_filter.json")
	if err := ioutil.WriteFile(file1, invalidContent, 0666); err != nil {
		panic(err)
	}

	file2 := filepath.Join(dir, "valid_app_filter.json")
	if err := ioutil.WriteFile(file2, validContent, 0666); err != nil {
		panic(err)
	}

	err = p.LoadFilters(dir)
	if err != nil {
		t.Errorf("LoadFilters should have succeeded")
		t.Fail()
	}

	// we have no policies with this exec path; should fail
	policy = p.getFilterForPath("not_an_existent_exec_path")
	if policy != nil {
		t.Errorf("getFilterForPath should have err`ed")
		t.Fail()
	}

	policy = p.getFilterForPathAndUID("/srv/oz/rootfs/home/user/.local/share/torbrowser/tbb/x86_64/tor-browser_en-US/Browser/firefox", 123)
	if policy == nil {
		t.Errorf("getFilterForPathAndUID should have succeeded")
		t.Fail()
	}

	if len(p.loadedFilters) != 1 {
		t.Errorf("LoadFilters should have loaded one filter.")
		t.Fail()
	}
}

func TestLoadFilterFile(t *testing.T) {
	p := NewPolicyList()
	policy, err := p.LoadFilterFile("not_exist")
	if err == nil {
		t.Errorf("LoadFilterFile should have err`ed")
		t.Fail()
	}
	dir, err := ioutil.TempDir("", "filter_load_filter_file")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(dir)
	file1 := filepath.Join(dir, "vlad")
	invalidContent := "{}"
	if err := ioutil.WriteFile(file1, []byte(invalidContent), 0666); err != nil {
		panic(err)
	}
	policy, err = p.LoadFilterFile(file1)
	if policy != nil && err != nil {
		t.Errorf("LoadFilterFile should have err`ed")
		t.Fail()
	}
}
<|endoftext|>


<commit_before>package silverback

import (
	"net/http"
	"path"

	"github.com/gorilla/mux"
)

// A Response is a container for typical response fields.
type Response struct {
	Status  int
	Headers http.Header
	Body    interface{}
}

// A Router is an extension of "github.com/gorilla/mux".Router.
type Router struct {
	mux.Router
}

// Route routes the methods on Handler to paths, based on the
// Handler's Path().
func (r *Router) Route(handler Handler) {
	routePath := handler.Path()
	idRoutePath := path.Join(routePath, "{id}")

	_, hasGetter := handler.(Getter)
	_, hasPutter := handler.(Putter)
	_, hasPatcher := handler.(Patcher)
	_, hasDeleter := handler.(Deleter)
	var idAllowed []string
	if hasGetter {
		idAllowed = append(idAllowed, "GET", "HEAD")
	}
	if hasPutter {
		idAllowed = append(idAllowed, "PUT")
	}
	if hasPatcher {
		idAllowed = append(idAllowed, "PATCH")
	}
	if hasDeleter {
		idAllowed = append(idAllowed, "DELETE")
	}
	if len(idAllowed) > 0 {
		idAllowed = append(idAllowed, "OPTIONS")
		route := r.Path(idRoutePath)
		subRouter := route.Subrouter()
		subRouter.Methods("OPTIONS").HandlerFunc(optionsHandler(idAllowed))
		if hasGetter {
			subRouter.Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Getter)
				resp := idHandle(h, h.Get, mux.Vars(r)["id"])
				writeResponse(resp, w)
			})
			subRouter.Methods("HEAD").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Getter)
				resp := idHandle(h, h.Get, mux.Vars(r)["id"])
				writeHead(resp, w)
			})
		}
		if hasPutter {
			subRouter.Methods("PUT").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Putter)
				resp := idHandle(h, h.Put, mux.Vars(r)["id"])
				writeResponse(resp, w)
			})
		}
		if hasPatcher {
			subRouter.Methods("PATCH").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Patcher)
				resp := idHandle(h, h.Patch, mux.Vars(r)["id"])
				writeResponse(resp, w)
			})
		}
		if hasDeleter {
			subRouter.Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Deleter)
				resp := idHandle(h, h.Delete, mux.Vars(r)["id"])
				writeResponse(resp, w)
			})
		}
		route.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			writeAllowHeader(idAllowed, w)
			w.WriteHeader(http.StatusMethodNotAllowed)
		})
	}

	_, hasQuerier := handler.(Querier)
	_, hasPoster := handler.(Poster)
	var allowed []string
	if hasQuerier {
		allowed = append(allowed, "GET", "HEAD")
	}
	if hasPoster {
		allowed = append(allowed, "POST")
	}
	if len(allowed) > 0 {
		allowed = append(allowed, "OPTIONS")
		route := r.Path(routePath)
		subRouter := route.Subrouter()
		subRouter.Methods("OPTIONS").HandlerFunc(optionsHandler(allowed))
		if hasQuerier {
			subRouter.Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Querier)
				resp := handle(h, h.Query)
				writeResponse(resp, w)
			})
			subRouter.Methods("HEAD").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Querier)
				resp := handle(h, h.Query)
				writeHead(resp, w)
			})
		}
		if hasPoster {
			subRouter.Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Poster)
				resp := handle(h, h.Post)
				writeResponse(resp, w)
			})
		}
		route.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			for _, method := range allowed {
				w.Header().Add("Allow", method)
			}
			w.WriteHeader(http.StatusMethodNotAllowed)
		})
	}
}

func optionsHandler(methods []string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		writeAllowHeader(methods, w)
		w.WriteHeader(http.StatusNoContent)
	}
}

func writeAllowHeader(methods []string, w http.ResponseWriter) {
	for _, method := range methods {
		w.Header().Add("Allow", method)
	}
}

func handle(h Handler, f func() Response) Response {
	if before, ok := h.(BeforeHandler); ok {
		before.BeforeHandle()
	}
	resp := f()
	if after, ok := h.(AfterHandler); ok {
		after.AfterHandle(resp)
	}
	return resp
}

func idHandle(h Handler, f func(string) Response, id string) Response {
	if before, ok := h.(BeforeHandler); ok {
		before.BeforeHandle()
	}
	resp := f(id)
	if after, ok := h.(AfterHandler); ok {
		after.AfterHandle(resp)
	}
	return resp
}

func writeHeaders(resp Response, w http.ResponseWriter) {
	for name, values := range resp.Headers {
		for _, v := range values {
			w.Header().Add(name, v)
		}
	}
}

func writeHead(resp Response, w http.ResponseWriter) {
	writeHeaders(resp, w)
	if resp.Status < http.StatusBadRequest {
		resp.Status = http.StatusNoContent
	}
	// TODO: write Content-Length
	w.WriteHeader(resp.Status)
}

func writeResponse(resp Response, w http.ResponseWriter) {
	writeHeaders(resp, w)
	w.WriteHeader(resp.Status)
	// TODO: Write body.
}
<commit_msg>Use writeAllowHeader more<commit_after>package silverback

import (
	"net/http"
	"path"

	"github.com/gorilla/mux"
)

// A Response is a container for typical response fields.
type Response struct {
	Status  int
	Headers http.Header
	Body    interface{}
}

// A Router is an extension of "github.com/gorilla/mux".Router.
type Router struct {
	mux.Router
}

// Route routes the methods on Handler to paths, based on the
// Handler's Path().
func (r *Router) Route(handler Handler) {
	routePath := handler.Path()
	idRoutePath := path.Join(routePath, "{id}")

	_, hasGetter := handler.(Getter)
	_, hasPutter := handler.(Putter)
	_, hasPatcher := handler.(Patcher)
	_, hasDeleter := handler.(Deleter)
	var idAllowed []string
	if hasGetter {
		idAllowed = append(idAllowed, "GET", "HEAD")
	}
	if hasPutter {
		idAllowed = append(idAllowed, "PUT")
	}
	if hasPatcher {
		idAllowed = append(idAllowed, "PATCH")
	}
	if hasDeleter {
		idAllowed = append(idAllowed, "DELETE")
	}
	if len(idAllowed) > 0 {
		idAllowed = append(idAllowed, "OPTIONS")
		route := r.Path(idRoutePath)
		subRouter := route.Subrouter()
		subRouter.Methods("OPTIONS").HandlerFunc(optionsHandler(idAllowed))
		if hasGetter {
			subRouter.Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Getter)
				resp := idHandle(h, h.Get, mux.Vars(r)["id"])
				writeResponse(resp, w)
			})
			subRouter.Methods("HEAD").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Getter)
				resp := idHandle(h, h.Get, mux.Vars(r)["id"])
				writeHead(resp, w)
			})
		}
		if hasPutter {
			subRouter.Methods("PUT").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Putter)
				resp := idHandle(h, h.Put, mux.Vars(r)["id"])
				writeResponse(resp, w)
			})
		}
		if hasPatcher {
			subRouter.Methods("PATCH").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Patcher)
				resp := idHandle(h, h.Patch, mux.Vars(r)["id"])
				writeResponse(resp, w)
			})
		}
		if hasDeleter {
			subRouter.Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Deleter)
				resp := idHandle(h, h.Delete, mux.Vars(r)["id"])
				writeResponse(resp, w)
			})
		}
		route.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			writeAllowHeader(idAllowed, w)
			w.WriteHeader(http.StatusMethodNotAllowed)
		})
	}

	_, hasQuerier := handler.(Querier)
	_, hasPoster := handler.(Poster)
	var allowed []string
	if hasQuerier {
		allowed = append(allowed, "GET", "HEAD")
	}
	if hasPoster {
		allowed = append(allowed, "POST")
	}
	if len(allowed) > 0 {
		allowed = append(allowed, "OPTIONS")
		route := r.Path(routePath)
		subRouter := route.Subrouter()
		subRouter.Methods("OPTIONS").HandlerFunc(optionsHandler(allowed))
		if hasQuerier {
			subRouter.Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Querier)
				resp := handle(h, h.Query)
				writeResponse(resp, w)
			})
			subRouter.Methods("HEAD").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Querier)
				resp := handle(h, h.Query)
				writeHead(resp, w)
			})
		}
		if hasPoster {
			subRouter.Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				h := handler.New(r).(Poster)
				resp := handle(h, h.Post)
				writeResponse(resp, w)
			})
		}
		route.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			writeAllowHeader(allowed, w)
			w.WriteHeader(http.StatusMethodNotAllowed)
		})
	}
}

func optionsHandler(methods []string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		writeAllowHeader(methods, w)
		w.WriteHeader(http.StatusNoContent)
	}
}

func writeAllowHeader(methods []string, w http.ResponseWriter) {
	for _, method := range methods {
		w.Header().Add("Allow", method)
	}
}

func handle(h Handler, f func() Response) Response {
	if before, ok := h.(BeforeHandler); ok {
		before.BeforeHandle()
	}
	resp := f()
	if after, ok := h.(AfterHandler); ok {
		after.AfterHandle(resp)
	}
	return resp
}

func idHandle(h Handler, f func(string) Response, id string) Response {
	if before, ok := h.(BeforeHandler); ok {
		before.BeforeHandle()
	}
	resp := f(id)
	if after, ok := h.(AfterHandler); ok {
		after.AfterHandle(resp)
	}
	return resp
}

func writeHeaders(resp Response, w http.ResponseWriter) {
	for name, values := range resp.Headers {
		for _, v := range values {
			w.Header().Add(name, v)
		}
	}
}

func writeHead(resp Response, w http.ResponseWriter) {
	writeHeaders(resp, w)
	if resp.Status < http.StatusBadRequest {
		resp.Status = http.StatusNoContent
	}
	// TODO: write Content-Length
	w.WriteHeader(resp.Status)
}

func writeResponse(resp Response, w http.ResponseWriter) {
	writeHeaders(resp, w)
	w.WriteHeader(resp.Status)
	// TODO: Write body.
}
<|endoftext|>


<commit_before>package eval

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"jacob/dusk/object"
	"jacob/dusk/token"
	"os"
)

var builtins = map[string]*object.Builtin{
	"len":     &object.Builtin{Fn: length},
	"first":   &object.Builtin{Fn: first},
	"last":    &object.Builtin{Fn: last},
	"rest":    &object.Builtin{Fn: rest},
	"push":    &object.Builtin{Fn: push},
	"alloc":   &object.Builtin{Fn: alloc},
	"set":     &object.Builtin{Fn: set},
	"println": &object.Builtin{Fn: println},
	"print":   &object.Builtin{Fn: print},
	"readln":  &object.Builtin{Fn: readln},
	"read":    &object.Builtin{Fn: read},
	"readc":   &object.Builtin{Fn: readc},
	"readall": &object.Builtin{Fn: readall},
}

func length(args ...object.Object) object.Object {
	if len(args) != 1 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '1'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		return &object.Integer{Value: int64(len(arg.Value))}
	case *object.Array:
		return &object.Integer{Value: int64(len(arg.Elements))}
	default:
		return newError(token.Position{}, "argument to 'len' not supported, got '%s'", args[0].Type())
	}
}

func first(args ...object.Object) object.Object {
	if len(args) != 1 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '1'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		if len(arg.Value) > 0 {
			return &object.String{Value: string(arg.Value[0])}
		}
		return ConstNil
	case *object.Array:
		if len(arg.Elements) > 0 {
			return arg.Elements[0]
		}
		return ConstNil
	default:
		return newError(token.Position{}, "argument to 'first' not supported, got '%s'", args[0].Type())
	}
}

func last(args ...object.Object) object.Object {
	if len(args) != 1 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '1'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		if len(arg.Value) > 0 {
			return &object.String{Value: string(arg.Value[len(arg.Value)-1])}
		}
		return ConstNil
	case *object.Array:
		if len(arg.Elements) > 0 {
			return arg.Elements[len(arg.Elements)-1]
		}
		return ConstNil
	default:
		return newError(token.Position{}, "argument to 'last' not supported, got '%s'", args[0].Type())
	}
}

func rest(args ...object.Object) object.Object {
	if len(args) != 1 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '1'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		l := len(arg.Value)
		if l > 0 {
			newStr := make([]byte, l-1, l-1)
			copy(newStr, arg.Value[1:l])
			return &object.String{Value: string(newStr)}
		}
		return ConstNil
	case *object.Array:
		l := len(arg.Elements)
		if l > 0 {
			newElems := make([]object.Object, l-1, l-1)
			copy(newElems, arg.Elements[1:l])
			return &object.Array{Elements: newElems}
		}
		return ConstNil
	default:
		return newError(token.Position{}, "argument to 'rest' not supported, got '%s'", args[0].Type())
	}
}

func push(args ...object.Object) object.Object {
	if len(args) != 2 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '2'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		if p, ok := args[1].(*object.String); ok {
			str := arg.Value + p.Value
			return &object.String{Value: str}
		}
		return newError(token.Position{}, "cannot push '%s' to string", args[1].Type())
	case *object.Array:
		l := len(arg.Elements)
		newElems := make([]object.Object, l+1, l+1)
		copy(newElems, arg.Elements)
		newElems[l] = args[1]
		return &object.Array{Elements: newElems}
	default:
		return newError(token.Position{}, "argument to 'push' not supported, got '%s'", args[0].Type())
	}
}

func alloc(args ...object.Object) object.Object {
	if len(args) != 2 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '2'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.Integer:
		if arg.Value >= 0 {
			newArr := make([]object.Object, arg.Value, arg.Value)
			for i := range newArr {
				newArr[i] = args[1]
			}
			return &object.Array{Elements: newArr}
		}
		return ConstNil
	default:
		return newError(token.Position{}, "argument to 'alloc' not supported, got '%s'", arg.Type())
	}
}

func set(args ...object.Object) object.Object {
	if len(args) != 3 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '3'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.Array:
		if i, ok := args[1].(*object.Integer); ok {
			arg.Elements[i.Value] = args[2]
			return ConstNil
		}
		return newError(token.Position{}, "second argument to 'set' not supported, got '%s'", args[1].Type())
	default:
		return newError(token.Position{}, "argument to 'set' not supported, got '%s'", arg.Type())
	}
}

func println(args ...object.Object) object.Object {
	for _, arg := range args {
		fmt.Println(arg)
	}
	return ConstNil
}

func print(args ...object.Object) object.Object {
	for _, arg := range args {
		fmt.Print(arg)
	}
	return ConstNil
}

func readln(args ...object.Object) object.Object {
	if len(args) != 0 {
		return newError(token.Position{}, "readln does not take any arguments. given '%d'", len(args))
	}

	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()

	return &object.String{Value: scanner.Text()}
}

func read(args ...object.Object) object.Object {
	if len(args) != 0 {
		return newError(token.Position{}, "readln does not take any arguments. given '%d'", len(args))
	}

	s := ""
	fmt.Scanln(&s)

	return &object.String{Value: s}
}

func readc(args ...object.Object) object.Object {
	if len(args) != 0 {
		return newError(token.Position{}, "readln does not take any arguments. given '%d'", len(args))
	}

	var c byte
	fmt.Scanf("%c", &c)

	return &object.String{Value: string(c)}
}

func readall(args ...object.Object) object.Object {
	if len(args) != 0 {
		return newError(token.Position{}, "readln does not take any arguments. given '%d'", len(args))
	}

	s, _ := ioutil.ReadAll(os.Stdin)

	return &object.String{Value: string(s)}
}
<commit_msg>split and join<commit_after>package eval

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"jacob/dusk/object"
	"jacob/dusk/token"
	"os"
	"strings"
)

var builtins = map[string]*object.Builtin{
	"len":     &object.Builtin{Fn: length},
	"first":   &object.Builtin{Fn: first},
	"last":    &object.Builtin{Fn: last},
	"rest":    &object.Builtin{Fn: rest},
	"push":    &object.Builtin{Fn: push},
	"alloc":   &object.Builtin{Fn: alloc},
	"set":     &object.Builtin{Fn: set},
	"join":    &object.Builtin{Fn: join},
	"split":   &object.Builtin{Fn: split},
	"println": &object.Builtin{Fn: println},
	"print":   &object.Builtin{Fn: print},
	"readln":  &object.Builtin{Fn: readln},
	"read":    &object.Builtin{Fn: read},
	"readc":   &object.Builtin{Fn: readc},
	"readall": &object.Builtin{Fn: readall},
}

func length(args ...object.Object) object.Object {
	if len(args) != 1 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '1'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		return &object.Integer{Value: int64(len(arg.Value))}
	case *object.Array:
		return &object.Integer{Value: int64(len(arg.Elements))}
	default:
		return newError(token.Position{}, "argument to 'len' not supported, got '%s'", args[0].Type())
	}
}

func first(args ...object.Object) object.Object {
	if len(args) != 1 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '1'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		if len(arg.Value) > 0 {
			return &object.String{Value: string(arg.Value[0])}
		}
		return ConstNil
	case *object.Array:
		if len(arg.Elements) > 0 {
			return arg.Elements[0]
		}
		return ConstNil
	default:
		return newError(token.Position{}, "argument to 'first' not supported, got '%s'", args[0].Type())
	}
}

func last(args ...object.Object) object.Object {
	if len(args) != 1 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '1'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		if len(arg.Value) > 0 {
			return &object.String{Value: string(arg.Value[len(arg.Value)-1])}
		}
		return ConstNil
	case *object.Array:
		if len(arg.Elements) > 0 {
			return arg.Elements[len(arg.Elements)-1]
		}
		return ConstNil
	default:
		return newError(token.Position{}, "argument to 'last' not supported, got '%s'", args[0].Type())
	}
}

func rest(args ...object.Object) object.Object {
	if len(args) != 1 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '1'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		l := len(arg.Value)
		if l > 0 {
			newStr := make([]byte, l-1, l-1)
			copy(newStr, arg.Value[1:l])
			return &object.String{Value: string(newStr)}
		}
		return ConstNil
	case *object.Array:
		l := len(arg.Elements)
		if l > 0 {
			newElems := make([]object.Object, l-1, l-1)
			copy(newElems, arg.Elements[1:l])
			return &object.Array{Elements: newElems}
		}
		return ConstNil
	default:
		return newError(token.Position{}, "argument to 'rest' not supported, got '%s'", args[0].Type())
	}
}

func push(args ...object.Object) object.Object {
	if len(args) != 2 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '2'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		if p, ok := args[1].(*object.String); ok {
			str := arg.Value + p.Value
			return &object.String{Value: str}
		}
		return newError(token.Position{}, "cannot push '%s' to string", args[1].Type())
	case *object.Array:
		l := len(arg.Elements)
		newElems := make([]object.Object, l+1, l+1)
		copy(newElems, arg.Elements)
		newElems[l] = args[1]
		return &object.Array{Elements: newElems}
	default:
		return newError(token.Position{}, "argument to 'push' not supported, got '%s'", args[0].Type())
	}
}

func alloc(args ...object.Object) object.Object {
	if len(args) != 2 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '2'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.Integer:
		if arg.Value >= 0 {
			newArr := make([]object.Object, arg.Value, arg.Value)
			for i := range newArr {
				newArr[i] = args[1]
			}
			return &object.Array{Elements: newArr}
		}
		return ConstNil
	default:
		return newError(token.Position{}, "argument to 'alloc' not supported, got '%s'", arg.Type())
	}
}

func set(args ...object.Object) object.Object {
	if len(args) != 3 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '3'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.Array:
		if i, ok := args[1].(*object.Integer); ok {
			arg.Elements[i.Value] = args[2]
			return ConstNil
		}
		return newError(token.Position{}, "second argument to 'set' not supported, got '%s'", args[1].Type())
	default:
		return newError(token.Position{}, "argument to 'set' not supported, got '%s'", arg.Type())
	}
}

func join(args ...object.Object) object.Object {
	if len(args) != 2 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '2'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.Array:
		if s, ok := args[1].(*object.String); ok {
			parts := make([]string, len(arg.Elements), len(arg.Elements))
			for i := range arg.Elements {
				parts[i] = arg.Elements[i].String()
			}
			return &object.String{Value: strings.Join(parts, s.Value)}
		}
		return newError(token.Position{}, "second argument to 'join' not supported, got '%s'", args[1].Type())
	default:
		return newError(token.Position{}, "argument to 'join' not supported, got '%s'", arg.Type())
	}
}

func split(args ...object.Object) object.Object {
	if len(args) != 2 {
		return newError(token.Position{}, "wrong number of arguments. got '%d', expected '2'", len(args))
	}

	switch arg := args[0].(type) {
	case *object.String:
		if s, ok := args[1].(*object.String); ok {

			parts := strings.Split(arg.Value, s.Value)

			elems := make([]object.Object, len(parts), len(parts))
			for i := range parts {
				elems[i] = &object.String{Value: parts[i]}
			}
			return &object.Array{Elements: elems}
		}
		return newError(token.Position{}, "second argument to 'split' not supported, got '%s'", args[1].Type())
	default:
		return newError(token.Position{}, "argument to 'split' not supported, got '%s'", arg.Type())
	}
}

func println(args ...object.Object) object.Object {
	for _, arg := range args {
		fmt.Println(arg)
	}
	return ConstNil
}

func print(args ...object.Object) object.Object {
	for _, arg := range args {
		fmt.Print(arg)
	}
	return ConstNil
}

func readln(args ...object.Object) object.Object {
	if len(args) != 0 {
		return newError(token.Position{}, "readln does not take any arguments. given '%d'", len(args))
	}

	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()

	return &object.String{Value: scanner.Text()}
}

func read(args ...object.Object) object.Object {
	if len(args) != 0 {
		return newError(token.Position{}, "readln does not take any arguments. given '%d'", len(args))
	}

	s := ""
	fmt.Scanln(&s)

	return &object.String{Value: s}
}

func readc(args ...object.Object) object.Object {
	if len(args) != 0 {
		return newError(token.Position{}, "readln does not take any arguments. given '%d'", len(args))
	}

	var c byte
	fmt.Scanf("%c", &c)

	return &object.String{Value: string(c)}
}

func readall(args ...object.Object) object.Object {
	if len(args) != 0 {
		return newError(token.Position{}, "readln does not take any arguments. given '%d'", len(args))
	}

	s, _ := ioutil.ReadAll(os.Stdin)

	return &object.String{Value: string(s)}
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package main

import (
	"code.google.com/p/go.net/websocket"
	"github.com/gorilla/mux"
	"mozilla.org/simplepush"
	"mozilla.org/simplepush/router"
	storage "mozilla.org/simplepush/storage/mcstorage"
	"mozilla.org/util"

	"encoding/base64"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"runtime/pprof"
	"strconv"
	"syscall"
)

var (
	configFile *string = flag.String("config", "config.ini", "Configuration File")
	profile    *string = flag.String("profile", "", "Profile file output")
	memProfile *string = flag.String("memProfile", "", "Profile file output")
	logging    *int    = flag.Int("logging", 0,
		"logging level (0=none,1=critical ... 10=verbose")
	logger  *util.MzLogger
	metrics *util.Metrics
	store   *storage.Storage
	route   *router.Router
)

const SIGUSR1 = syscall.SIGUSR1
const VERSION = "1.2"

// -- main
func main() {
	var certFile string
	var keyFile string
	var key []byte
	var wsport string
	var wshost string

	flag.Parse()
	config, err := util.ReadMzConfig(*configFile)
	if err != nil {
		log.Fatal(err.Error())
	}
	// The config file requires some customization and normalization
	config = simplepush.FixConfig(config)
	config.Override("VERSION", VERSION)
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Report what the app believes the current host to be, and what version.
	log.Printf("CurrentHost: %s, Version: %s",
		config.Get("shard.current_host", "localhost"), VERSION)

	// Only create profiles if requested. To view the application profiles,
	// see http://blog.golang.org/profiling-go-programs
	if *profile != "" {
		log.Printf("Creating profile...")
		f, err := os.Create(*profile)
		if err != nil {
			log.Fatal(err)
		}
		defer func() {
			log.Printf("Closing profile...")
			pprof.StopCPUProfile()
		}()
		pprof.StartCPUProfile(f)
	}
	if *memProfile != "" {
		defer func() {
			profFile, err := os.Create(*memProfile)
			if err != nil {
				log.Fatalln(err)
			}
			pprof.WriteHeapProfile(profFile)
			profFile.Close()
		}()
	}

	// Logging can be CPU intensive (note: variable reflection is VERY
	// CPU intensive. Avoid things like log.Printf("%v", someStruct) )
	// If logging is specified as a command line flag, it overrides the
	// value specified in the config file. This allows short term logging
	// for operations.
	if *logging > 0 {
		config.Override("logger.enable", "1")
		config.Override("logger.filter", strconv.FormatInt(int64(*logging), 10))
	}
	logger = util.NewMzLogger(config)

	metrics := util.NewMetrics(config.Get(
		"metrics.prefix",
		"simplepush"),
		logger,
		config)

	// Currently, we're opting for a memcache "storage" mechanism, however
	// and key/value store would suffice. (bonus points if the records are
	// self expiring.)
	store = storage.New(config, logger)

	// Routing allows stand-alone instances to send updates between themselves.
	// Websock does not allow for native redirects in some browsers. Routing
	// allows websocket connections and updates to be handled by any server,
	// with the approriate notification sent.
	//
	// Note: While this is fairly primative, it works. There are more efficient
	// models and systems that could be used for this (e.g. 0mq, rabbit, etc.)
	// however those also add additional complexity to the server system.
	// Since this is mostly point-to-point (we know the host location to send
	// to), there wasn't much justification to add that complexity.
	// Obviously, this can and will change over time.
	route = router.New(config, logger, metrics, store, simplepush.ClientCollision)
	if route == nil {
		log.Fatal("No router")
	}

	token_str := config.Get("token_key", "")
	if len(token_str) > 0 {
		key, err = base64.URLEncoding.DecodeString(token_str)
		if err != nil {
			log.Fatal(err.Error())
		}
	}

	// Initialize the common server.
	simplepush.InitServer(config, logger, metrics, store, key)
	handlers := simplepush.NewHandler(config, logger, store, route, metrics, key)

	// Config the server
	RESTMux := mux.NewRouter()
	WSMux := RESTMux
	RouteMux := mux.NewRouter()
	host := config.Get("host", "localhost")
	port := config.Get("port", "8080")

	// Register the handlers
	// each websocket gets it's own handler.
	if config.Get("wsport", port) != port {
		wsport = config.Get("wsport", port)
		wshost = config.Get("wshost", host)
		WSMux = mux.NewRouter()
	}

	RESTMux.HandleFunc("/update/{key}", handlers.UpdateHandler)
	RESTMux.HandleFunc("/status/", handlers.StatusHandler)
	RESTMux.HandleFunc("/realstatus/", handlers.RealStatusHandler)
	RESTMux.HandleFunc("/metrics/", handlers.MetricsHandler)
	WSMux.Handle("/", websocket.Handler(handlers.PushSocketHandler))

	RouteMux.HandleFunc("/route/{uaid}", handlers.RouteHandler)

	// Hoist the main sail.
	if logger != nil {
		logger.Info("main",
			fmt.Sprintf("listening on %s:%s", host, port), nil)
	}

	// Get the (optional) SSL certs
	certFile = config.Get("ssl.certfile", "")
	keyFile = config.Get("ssl.keyfile", "")
	wscertFile := config.Get("ssl.ws.certfile", certFile)
	wskeyFile := config.Get("ssl.ws.keyfile", keyFile)

	// wait for sigint
	sigChan := make(chan os.Signal)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGHUP, SIGUSR1)
	errChan := make(chan error)

	// Weigh the anchor!
	go func() {
		addr := host + ":" + port
		if len(certFile) > 0 && len(keyFile) > 0 {
			if logger != nil {
				logger.Info("main", "Using TLS", nil)
			}
			errChan <- http.ListenAndServeTLS(addr, certFile, keyFile, RESTMux)
		} else {
			errChan <- http.ListenAndServe(addr, RESTMux)
		}
	}()

	go func() {
		addr := ":" + config.Get("shard.port", "3000")
		if logger != nil {
			logger.Info("main", "Starting Router", util.Fields{"port": addr})
			// TODO: Need TLS?
		}
		errChan <- http.ListenAndServe(addr, RouteMux)
	}()

	// Oh, we have a different context for WebSockets. Weigh that anchor too!
	if WSMux != RESTMux {
		if logger != nil {
			logger.Info("main", "Starting separate context for WS", nil)
			logger.Info("main",
				fmt.Sprintf("ws listen on %s:%s", wshost, wsport), nil)
		}
		go func() {
			wsaddr := wshost + ":" + wsport
			if len(wscertFile) > 0 && len(wskeyFile) > 0 {
				errChan <- http.ListenAndServeTLS(wsaddr, wscertFile, wskeyFile, WSMux)
			} else {
				errChan <- http.ListenAndServe(wsaddr, WSMux)
			}
		}()
	}

	// And we're underway!

	select {
	case err := <-errChan:
		if err != nil {
			panic("ListenAndServe: " + err.Error())
		}
	case <-sigChan:
		if logger != nil {
			logger.Info("main", "Recieved signal, shutting down.", nil)
			route.Unregister()
		}
		route = nil
	}
}

// 04fs
// vim: set tabstab=4 softtabstop=4 shiftwidth=4 noexpandtab
<commit_msg>Remove bits as I copy them into the new app setup.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package main

import (
	"code.google.com/p/go.net/websocket"
	"github.com/gorilla/mux"
	"mozilla.org/simplepush"

	"encoding/base64"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"runtime/pprof"
	"strconv"
	"syscall"
)

var (
	configFile *string = flag.String("config", "config.ini", "Configuration File")
	profile    *string = flag.String("profile", "", "Profile file output")
	memProfile *string = flag.String("memProfile", "", "Profile file output")
	logging    *int    = flag.Int("logging", 0,
		"logging level (0=none,1=critical ... 10=verbose")
	logger  *util.MzLogger
	metrics *util.Metrics
	store   *storage.Storage
	route   *router.Router
)

const SIGUSR1 = syscall.SIGUSR1
const VERSION = "1.2"

// -- main
func main() {
	var certFile string
	var keyFile string
	var key []byte

	flag.Parse()

	runtime.GOMAXPROCS(runtime.NumCPU())

	app, err := simplepush.LoadApplicationFromFileName(*configFile)
	if err != nil {
		log.Fatal(err.Error())
	}

	// Report what the app believes the current host to be, and what version.
	log.Printf("CurrentHost: %s, Version: %s", app.Hostname(), VERSION)

	// Only create profiles if requested. To view the application profiles,
	// see http://blog.golang.org/profiling-go-programs
	if *profile != "" {
		log.Printf("Creating profile...")
		f, err := os.Create(*profile)
		if err != nil {
			log.Fatal(err)
		}
		defer func() {
			log.Printf("Closing profile...")
			pprof.StopCPUProfile()
		}()
		pprof.StartCPUProfile(f)
	}
	if *memProfile != "" {
		defer func() {
			profFile, err := os.Create(*memProfile)
			if err != nil {
				log.Fatalln(err)
			}
			pprof.WriteHeapProfile(profFile)
			profFile.Close()
		}()
	}

	// Currently, we're opting for a memcache "storage" mechanism, however
	// and key/value store would suffice. (bonus points if the records are
	// self expiring.)
	store = storage.New(config, logger)

	// Routing allows stand-alone instances to send updates between themselves.
	// Websock does not allow for native redirects in some browsers. Routing
	// allows websocket connections and updates to be handled by any server,
	// with the approriate notification sent.
	//
	// Note: While this is fairly primative, it works. There are more efficient
	// models and systems that could be used for this (e.g. 0mq, rabbit, etc.)
	// however those also add additional complexity to the server system.
	// Since this is mostly point-to-point (we know the host location to send
	// to), there wasn't much justification to add that complexity.
	// Obviously, this can and will change over time.
	route = router.New(config, logger, metrics, store, simplepush.ClientCollision)
	if route == nil {
		log.Fatal("No router")
	}
	config.SetDefault("shard.prefix", "_h-")

	token_str := config.Get("token_key", "")
	if len(token_str) > 0 {
		key, err = base64.URLEncoding.DecodeString(token_str)
		if err != nil {
			log.Fatal(err.Error())
		}
	}

	// Initialize the common server.
	simplepush.InitServer(config, logger, metrics, store, key)
	handlers := simplepush.NewHandler(config, logger, store, route, metrics, key)

	// Config the server
	RESTMux := mux.NewRouter()
	WSMux := RESTMux
	RouteMux := mux.NewRouter()
	host := config.Get("host", "localhost")
	port := config.Get("port", "8080")

	// Register the handlers
	// each websocket gets it's own handler.
	if config.Get("wsport", port) != port {
		wsport = config.Get("wsport", port)
		wshost = config.Get("wshost", host)
		WSMux = mux.NewRouter()
	}

	RESTMux.HandleFunc("/update/{key}", handlers.UpdateHandler)
	RESTMux.HandleFunc("/status/", handlers.StatusHandler)
	RESTMux.HandleFunc("/realstatus/", handlers.RealStatusHandler)
	RESTMux.HandleFunc("/metrics/", handlers.MetricsHandler)
	WSMux.Handle("/", websocket.Handler(handlers.PushSocketHandler))

	RouteMux.HandleFunc("/route/{uaid}", handlers.RouteHandler)

	// Hoist the main sail.
	if logger != nil {
		logger.Info("main",
			fmt.Sprintf("listening on %s:%s", host, port), nil)
	}

	// wait for sigint
	sigChan := make(chan os.Signal)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGHUP, SIGUSR1)
	errChan := make(chan error)

	// Weigh the anchor!
	go func() {
		addr := host + ":" + port
		if len(certFile) > 0 && len(keyFile) > 0 {
			if logger != nil {
				logger.Info("main", "Using TLS", nil)
			}
			errChan <- http.ListenAndServeTLS(addr, certFile, keyFile, RESTMux)
		} else {
			errChan <- http.ListenAndServe(addr, RESTMux)
		}
	}()

	go func() {
		addr := ":" + config.Get("shard.port", "3000")
		if logger != nil {
			logger.Info("main", "Starting Router", util.Fields{"port": addr})
			// TODO: Need TLS?
		}
		errChan <- http.ListenAndServe(addr, RouteMux)
	}()

	// Oh, we have a different context for WebSockets. Weigh that anchor too!
	if WSMux != RESTMux {
		if logger != nil {
			logger.Info("main", "Starting separate context for WS", nil)
			logger.Info("main",
				fmt.Sprintf("ws listen on %s:%s", wshost, wsport), nil)
		}
		go func() {
			wsaddr := wshost + ":" + wsport
			if len(wscertFile) > 0 && len(wskeyFile) > 0 {
				errChan <- http.ListenAndServeTLS(wsaddr, wscertFile, wskeyFile, WSMux)
			} else {
				errChan <- http.ListenAndServe(wsaddr, WSMux)
			}
		}()
	}

	// And we're underway!

	select {
	case err := <-errChan:
		if err != nil {
			panic("ListenAndServe: " + err.Error())
		}
	case <-sigChan:
		if logger != nil {
			logger.Info("main", "Recieved signal, shutting down.", nil)
			route.Unregister()
		}
		route = nil
	}
}

// 04fs
// vim: set tabstab=4 softtabstop=4 shiftwidth=4 noexpandtab
<|endoftext|>