Skip to content

feature: add digest #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: gh-pages
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
312 changes: 312 additions & 0 deletions resources/js/curl-to-go.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ function curlToGo(curl) {
clientName = "client";
}

// digest
// --digest
if (req.digest) {
go += addDigestHead();
go += 'client, err := NewTransport("'+ req.basicauth.user +'","'+ req.basicauth.pass +'").Client()\n'+ err;
clientName = "client";
}

// load body data
// KNOWN ISSUE: -d and --data are treated like --data-binary in
// that we don't strip out carriage returns and newlines.
Expand Down Expand Up @@ -306,7 +314,306 @@ function curlToGo(curl) {
go += "\nresp, err := "+clientName+".Do(req)\n";
go += err+deferClose;

if (req.digest) {
go += '}\n'
}

return go;

function addDigestHead(){
return ['import (',
' "bytes"',
' "crypto/md5"',
' "crypto/rand"',
' "crypto/sha256"',
' "errors"',
' "fmt"',
' "hash"',
' "io"',
' "io/ioutil"',
' "net/http"',
' "strings"',
')',
'const (',
' MsgAuth string = "auth"',
' AlgMD5 string = "MD5"',
' AlgSha256 string = "SHA-256"',
')',
'var (',
' ErrNilTransport = errors.New("transport is nil")',
' ErrBadChallenge = errors.New("challenge is bad")',
' ErrAlgNotImplemented = errors.New("alg not implemented")',
')',
'// Transport is an implementation of http.RoundTripper that takes care of http',
'// digest authentication.',
'type Transport struct {',
' Username string',
' Password string',
' Transport http.RoundTripper',
'}',
'// NewTransport creates a new digest transport using the http.DefaultTransport.',
'func NewTransport(username, password string) *Transport {',
' t := &Transport{',
' Username: username,',
' Password: password,',
' }',
' t.Transport = http.DefaultTransport',
' return t',
'}',
'type challenge struct {',
' Realm string',
' Domain string',
' Nonce string',
' Opaque string',
' Stale string',
' Algorithm string',
' Qop string',
'}',
'func parseChallenge(input string) (*challenge, error) {',
' const ws = " \\n\\r\\t"',
' const qs = `"`',
' s := strings.Trim(input, ws)',
' if !strings.HasPrefix(s, "Digest ") {',
' return nil, ErrBadChallenge',
' }',
' s = strings.Trim(s[7:], ws)',
' sl := strings.Split(s, ", ")',
' c := &challenge{',
' Algorithm: AlgMD5,',
' }',
' var r []string',
' for i := range sl {',
' r = strings.SplitN(sl[i], "=", 2)',
' switch r[0] {',
' case "realm":',
' c.Realm = strings.Trim(r[1], qs)',
' case "domain":',
' c.Domain = strings.Trim(r[1], qs)',
' case "nonce":',
' c.Nonce = strings.Trim(r[1], qs)',
' case "opaque":',
' c.Opaque = strings.Trim(r[1], qs)',
' case "stale":',
' c.Stale = strings.Trim(r[1], qs)',
' case "algorithm":',
' c.Algorithm = strings.Trim(r[1], qs)',
' case "qop":',
' c.Qop = strings.Trim(r[1], qs)',
' default:',
' return nil, ErrBadChallenge',
' }',
' }',
' return c, nil',
'}',
'type credentials struct {',
' Username string',
' Realm string',
' Nonce string',
' DigestURI string',
' Algorithm string',
' Cnonce string',
' Opaque string',
' MessageQop string',
' NonceCount int',
' method string',
' password string',
' impl hashingFunc',
'}',
'type hashingFunc func() hash.Hash',
'func h(data string, f hashingFunc) (string, error) {',
' hf := f()',
' if _, err := io.WriteString(hf, data); err != nil {',
' return "", err',
' }',
' return fmt.Sprintf("%x", hf.Sum(nil)), nil',
'}',
'func kd(secret, data string, f hashingFunc) (string, error) {',
' return h(fmt.Sprintf("%s:%s", secret, data), f)',
'}',
'func (c *credentials) ha1() (string, error) {',
' return h(fmt.Sprintf("%s:%s:%s", c.Username, c.Realm, c.password), c.impl)',
'}',
'func (c *credentials) ha2() (string, error) {',
' return h(fmt.Sprintf("%s:%s", c.method, c.DigestURI), c.impl)',
'}',
'func (c *credentials) resp(cnonce string) (resp string, err error) {',
' var ha1 string',
' var ha2 string',
' c.NonceCount++',
' if c.MessageQop == MsgAuth {',
' if cnonce != "" {',
' c.Cnonce = cnonce',
' } else {',
' b := make([]byte, 8)',
' _, err = io.ReadFull(rand.Reader, b)',
' if err != nil {',
' return "", err',
' }',
' c.Cnonce = fmt.Sprintf("%x", b)[:16]',
' }',
' if ha1, err = c.ha1(); err != nil {',
' return "", err',
' }',
' if ha2, err = c.ha2(); err != nil {',
' return "", err',
' }',
' return kd(ha1, fmt.Sprintf("%s:%08x:%s:%s:%s", c.Nonce, c.NonceCount, c.Cnonce, c.MessageQop, ha2), c.impl)',
' } else if c.MessageQop == "" {',
' if ha1, err = c.ha1(); err != nil {',
' return "", err',
' }',
' if ha2, err = c.ha2(); err != nil {',
' return "", err',
' }',
' return kd(ha1, fmt.Sprintf("%s:%s", c.Nonce, ha2), c.impl)',
' }',
' return "", ErrAlgNotImplemented',
'}',
'func (c *credentials) authorize() (string, error) {',
' // Note that this is only implemented for MD5 and NOT MD5-sess.',
' // MD5-sess is rarely supported and those that do are a big mess.',
' if c.Algorithm != AlgMD5 && c.Algorithm != AlgSha256 {',
' return "", ErrAlgNotImplemented',
' }',
' // Note that this is NOT implemented for "qop=auth-int". Similarly the',
' // auth-int server side implementations that do exist are a mess.',
' if c.MessageQop != MsgAuth && c.MessageQop != "" {',
' return "", ErrAlgNotImplemented',
' }',
' resp, err := c.resp("")',
' if err != nil {',
' return "", ErrAlgNotImplemented',
' }',
' sl := []string{fmt.Sprintf(`username="%s"`, c.Username)}',
' sl = append(sl, fmt.Sprintf(`realm="%s"`, c.Realm),',
' fmt.Sprintf(`nonce="%s"`, c.Nonce),',
' fmt.Sprintf(`uri="%s"`, c.DigestURI),',
' fmt.Sprintf(`response="%s"`, resp))',
' if c.Algorithm != "" {',
' sl = append(sl, fmt.Sprintf(`algorithm="%s"`, c.Algorithm))',
' }',
' if c.Opaque != "" {',
' sl = append(sl, fmt.Sprintf(`opaque="%s"`, c.Opaque))',
' }',
' if c.MessageQop != "" {',
' sl = append(sl, fmt.Sprintf("qop=%s", c.MessageQop),',
' fmt.Sprintf("nc=%08x", c.NonceCount),',
' fmt.Sprintf(`cnonce="%s"`, c.Cnonce))',
' }',
' return fmt.Sprintf("Digest %s", strings.Join(sl, ", ")), nil',
'}',
'func (t *Transport) newCredentials(req *http.Request, c *challenge) (*credentials, error) {',
' cred := &credentials{',
' Username: t.Username,',
' Realm: c.Realm,',
' Nonce: c.Nonce,',
' DigestURI: req.URL.RequestURI(),',
' Algorithm: c.Algorithm,',
' Opaque: c.Opaque,',
' MessageQop: c.Qop, // "auth" must be a single value',
' NonceCount: 0,',
' method: req.Method,',
' password: t.Password,',
' }',
' switch c.Algorithm {',
' case AlgMD5:',
' cred.impl = md5.New',
' case AlgSha256:',
' cred.impl = sha256.New',
' default:',
' return nil, ErrAlgNotImplemented',
' }',
' return cred, nil',
'}',
'// RoundTrip makes a request expecting a 401 response that will require digest',
'// authentication. It creates the credentials it needs and makes a follow-up',
'// request.',
'func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {',
' if t.Transport == nil {',
' return nil, ErrNilTransport',
' }',
' // Copy the request so we don\'t modify the input.',
' origReq := new(http.Request)',
' *origReq = *req',
' origReq.Header = make(http.Header, len(req.Header))',
' for k, s := range req.Header {',
' origReq.Header[k] = s',
' }',
' // We\'ll need the request body twice. In some cases we can use GetBody',
' // to obtain a fresh reader for the second request, which we do right',
' // before the RoundTrip(origReq) call. If GetBody is unavailable, read',
' // the body into a memory buffer and use it for both requests.',
' if req.Body != nil && req.GetBody == nil {',
' body, err := ioutil.ReadAll(req.Body)',
' if err != nil {',
' return nil, err',
' }',
' req.Body = ioutil.NopCloser(bytes.NewBuffer(body))',
' origReq.Body = ioutil.NopCloser(bytes.NewBuffer(body))',
' }',
' // Make a request to get the 401 that contains the challenge.',
' challenge, resp, err := t.fetchChallenge(req)',
' if challenge == "" || err != nil {',
' return resp, err',
' }',
' c, err := parseChallenge(challenge)',
' if err != nil {',
' return nil, err',
' }',
' // Form credentials based on the challenge.',
' cr, err := t.newCredentials(origReq, c)',
' if err != nil {',
' return nil, err',
' }',
' auth, err := cr.authorize()',
' if err != nil {',
' return nil, err',
' }',
' // Obtain a fresh body.',
' if req.Body != nil && req.GetBody != nil {',
' origReq.Body, err = req.GetBody()',
' if err != nil {',
' return nil, err',
' }',
' }',
' // Make authenticated request.',
' origReq.Header.Set("Authorization", auth)',
' return t.Transport.RoundTrip(origReq)',
'}',
'func (t *Transport) fetchChallenge(req *http.Request) (string, *http.Response, error) {',
' resp, err := t.Transport.RoundTrip(req)',
' if err != nil {',
' return "", resp, err',
' }',
' if resp.StatusCode != http.StatusUnauthorized {',
' return "", resp, nil',
' }',
' // We\'ll no longer use the initial response, so close it',
' defer func() {',
' // Ensure the response body is fully read and closed',
' // before we reconnect, so that we reuse the same TCP connection.',
' // Close the previous response\'s body. But read at least some of',
' // the body so if it\'s small the underlying TCP connection will be',
' // re-used. No need to check for errors: if it fails, the Transport',
' // won\'t reuse it anyway.',
' const maxBodySlurpSize = 2 << 10',
' if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize {',
' _, _ = io.CopyN(ioutil.Discard, resp.Body, maxBodySlurpSize)',
' }',
' resp.Body.Close()',
' }()',
' return resp.Header.Get("WWW-Authenticate"), resp, nil',
'}',
'// Client returns an HTTP client that uses the digest transport.',
'func (t *Transport) Client() (*http.Client, error) {',
' if t.Transport == nil {',
' return nil, ErrNilTransport',
' }',
' return &http.Client{Transport: t}, nil',
'}',
'func DoRequest() (err error) {',
].join("\n");
}
}

// extractRelevantPieces returns an object with relevant pieces
Expand Down Expand Up @@ -383,6 +690,9 @@ function curlToGo(curl) {
if (dataFiles.length > 0)
relevant.data.files = dataFiles;

// set digest value
relevant.digest = cmd.digest

var basicAuthString = "";
if (cmd.user && cmd.user.length > 0)
basicAuthString = cmd.user[cmd.user.length-1];
Expand Down Expand Up @@ -637,4 +947,6 @@ function parseCommand(input, options) {
function whitespace(ch) {
return ch == " " || ch == "\t" || ch == "\n" || ch == "\r";
}

//
}