http NewRequest get unexpected characters - http

I try to parse a page with this code :
client := &http.Client{}
profile := getEpiURL(login)
log.Print("Fetch " + profile)
req, err := http.NewRequest("GET", profile, nil)
if err != nil {
log.Fatal(err)
}
req.AddCookie(cookieSessionIntra)
body, _ := httputil.DumpRequestOut(req, true)
With this as getEpiURL function :
func getEpiURL(login string) (url string) {
url = "https://********/user/" + login + "/?format=json"
return
}
And when i look at the output the profile variable is good, but in the Request it seems obviously wrong...
2016/11/24 12:53:53 Fetch https://********/user/le****in-vi**rd#e*******/?format=json
Then the debug for the request prints me :
GET /user/%00l%*****0o%*00.%00c***0-%00v%00i%0*a%00r%00d%00#%00e%00i%00t%00e%00c%0***0.%0***00/?format=json
HTTP/1.1 Host: ****** User-Agent: Go-http-client/1.1 Cookie:
PHPSESSID=********* Accept-Encoding: gzip

I think your original string somehow contains NUL characters. Try this out in the playground:
func main() {
req, err := http.NewRequest("GET", "https://some/normal/path", nil)
if err != nil {
log.Fatal(err)
}
body, _ := httputil.DumpRequestOut(req, true)
fmt.Printf("Hello, playground, %q", body)
}
You'll get:
"GET /normal/path HTTP/1.1\r\nHost: some...
Now try it with a string in which you inserted NUL characters:
req, err := http.NewRequest("GET", "https://some/\000n\000o\000rmal/path", nil)
...
"GET /%00n%00o%00rmal/path HTTP/1.1\r\nHost: some...
Not quite sure how your string ended up containing those. Read more about percent encoding.

Related

Getting the status code from a get request in golang

I'm trying to get the http status code in goland.
I'm passing the authorization token as well.
This is what I tried so far:
func StatusCode(PAGE string, AUTH string) (r string){
resp, err := http.NewRequest("GET", PAGE, nil)
if err != nil {
log.Fatal(err)
}
resp.Header.Set("Authorization", AUTH)
fmt.Println("HTTP Response Status:", resp.StatusCode, http.StatusText(resp.StatusCode))
r := resp.StatusCode + http.StatusText(resp.StatusCode)
}
Basically I want to get this:
r = "200 OK"
or
r= "400 Bad request"
The previous code it´s complaining from resp.StatusCode and http.StatusText(resp.StatusCode)
There are two problems. The first is that the application uses the request as the response. Execute the request to get the response.
The second problem is that resp.StatusCode + http.StatusText(resp.StatusCode) does not compile because operand types are mismatched. The value resp.StatusCode is an int. The value of http.StatusText(resp.StatusCode) is a string. Go does not have the implicit conversion of numbers to strings that would make this work the way you expect.
Use r := resp.Status if you want the status string as sent from the server.
Use r := fmt.Sprintf("%d %s", resp.StatusCode, http.StatusText(resp.StatusCode)) to construct a status string from the server's status code and the Go's status strings.
Here's the code:
func StatusCode(PAGE string, AUTH string) (r string) {
// Setup the request.
req, err := http.NewRequest("GET", PAGE, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", AUTH)
// Execute the request.
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err.Error()
}
// Close response body as required.
defer resp.Body.Close()
fmt.Println("HTTP Response Status:", resp.StatusCode, http.StatusText(resp.StatusCode))
return resp.Status
// or fmt.Sprintf("%d %s", resp.StatusCode, http.StatusText(resp.StatusCode))
}

How to perform a GET request with application/x-www-form-urlencoded content-type in Go?

Basically, I need to implement the following method in Go - https://api.slack.com/methods/users.lookupByEmail.
I tried doing it like this:
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
)
type Payload struct {
Email string `json:"email,omitempty"`
}
// assume the following code is inside some function
client := &http.Client{}
payload := Payload{
Email: "octocat#github.com",
}
body, err := json.Marshal(payload)
if err != nil {
return "", err
}
req, err := http.NewRequest("GET", "https://slack.com/api/users.lookupByEmail", bytes.NewReader(body))
if err != nil {
return "", err
}
req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t, _ := ioutil.ReadAll(resp.Body)
return "", errors.New(string(t))
}
responseData, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(responseData), nil
But I get an error that "email" field is missing, which is obvious because this content-type does not support JSON payload:
{"ok":false,"error":"invalid_arguments","response_metadata":{"messages":["[ERROR] missing required field: email"]}} (type: string)
I couldn't find how to include a post form with the GET request - there is no available post form argument neither to http.NewRequest, nor to http.Client.Get; http.Client.PostForm issues a POST request but GET is needed in this case. Also, I think I have to use http.NewRequest here (unless another approach exists) because I need to set the Authorization header.
You misunderstand the application/x-www-form-urlencoded header, you should pass an URL parameters here. Check out an example:
import (
...
"net/url"
...
)
data := url.Values{}
data.Set("email", "foo#bar.com")
data.Set("token", "SOME_TOKEN_GOES_HERE")
r, _ := http.NewRequest("GET", "https://slack.com/api/users.lookupByEmail", strings.NewReader(data.Encode()))
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))

getting 404 in GET request in Go

When I use http://localhost:8080/login?id=ddfd#vcv.com&pwd=dccccf in postman or use it in android app I am getting 404. On curl I get
{"name":"Miss Moneypenny","email":"ddfd#vcv.com","password":"dccccf","mobile":27,"address":"dscsdacc"}
I am not able to understand what can I do to achieve json output in postman and on other platforms like Apps in ios as well as android when I use this api and also on the browser window.
My Main.go code
func getSession() *mgo.Session {
s, err := mgo.Dial("mongodb://localhost")
if err != nil {
panic(err)
}
return s
}
func main() {
r := httprouter.New()
uc := controllers.NewUserController(getSession())
r.GET("/login", uc.LoginUser)
http.ListenAndServe(":8080", r)
}
code in controller/user.go
type UserController struct {
session *mgo.Session
}
func NewUserController(s *mgo.Session) *UserController {
return &UserController{s}
}
func (uc UserController) LoginUser(w http.ResponseWriter, request *http.Request, params httprouter.Params) {
dump,err :=httputil.DumpRequest(request, true)
if err != nil {
http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
return
}
fmt.Println("Request Dump:\n", string(dump))
encodedValue := request.URL.Query().Get("id")
pwd := request.URL.Query().Get("pwd")
emailId, err := url.QueryUnescape(encodedValue)
if err != nil {
log.Fatal(err)
return
}
u := models.User{}
if err := uc.session.DB("go-web-dev-db").C("users").FindId(emailId + pwd).One(&u); err != nil {
w.WriteHeader(404)
return
}
uj, err := json.Marshal(u)
if err != nil {
fmt.Println(err)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) // 200
fmt.Fprintf(w, "%s\n", uj)
}
code in model/user.go
type User struct {
Name string `json:"name" bson:"name"`
Email string `json:"email" bson:"_id"`
Password string `json:"password" bson:"password"`
Mobile int `json:"mobile" bson:"mobile"`
Address string `json:"address" bson:"address"`
}
After using dump when I am using i am using curl 'http://localhost:8080/login?id=ddfd#vcv.com&pwd=dccccf' I get :-
Request Dump:
GET /login?id=ddfd#vcv.com&pwd=dccccf HTTP/1.1
Host: localhost:8080
Accept: */*
User-Agent: curl/7.69.1
After using dump when I am using i am using http://localhost:8080/login?id=ddfd#vcv.com&pwd=dccccf in postman I get :-
Request Dump:
GET /login?id=ddfd#vcv.com&pwd=dccccf HTTP/1.1
Host: localhost:8080
Accept: */*
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Postman-Token: 8e925738-b8db-4656-9f53-813f4cd53a80
User-Agent: PostmanRuntime/7.24.1

How to send JSON inside JSON in POST

I am trying to send the following data via a POST HTTP request to an API:
{
"client_interface":{
"source_address":source,
"destination_address":destn,
"message":encrypted_msg,
"business_event_url":settings.Message_CallbackURL
},
"server_interface":{
"message_id":msg_id
}
}
The API is responding with the following error:
{
"Meta":{
"Requestid":12301343169471000
},
"Error":{
"Message":"Request body contains badly-formed JSON (at position 51)",
"Param":""
}
}
CODE:
apiUrl := "http://example.com"
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify : true},
}
jsonStr := []byte(`{
"client_interface": {
"source_address": source,
"destination_address": destn,
"message": encrypted_msg,
"business_event_url": settings.Message_CallbackURL
},
"server_interface": {
"message_id": msg_id
}
}`)
req, err := http.NewRequest("POST", apiUrl, bytes.NewBuffer(jsonStr))
fmt.Println("req..........",req)
if err!=nil{
log.Println("err in http req..............",err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("requestid", msg_id)
req.Header.Set("Authorization", "Bearer "+conn_token)
client := &http.Client{Transport: tr}
resp, err := client.Do(req)
if resp!=nil{
body, _ := ioutil.ReadAll(resp.Body)
}
Using struct :
package main
import (
"fmt"
"net/http"
"io/ioutil"
"bytes"
//"crypto/tls"
"encoding/json"
)
type client_interface struct {
source_address string `json:"string"`
destination_address uint64 `json:"uint64"`
message string `json:"string"`
business_event_url string `json:"string"`
}
type server_interface struct {
message_id uint64 `json:"uint64"`
}
type data struct {
client_interface client_interface `json:"client_interface"`
server_interface server_interface `json:"server_interface"`
}
func main() {
url := "https://example.com"
fmt.Println("URL:>", url)
client_interface := client_interface{}
server_interface := server_interface{}
client_interface.source_address="1"
client_interface.destination_address=1111111111
client_interface.message="khsjhdjks"
client_interface.business_event_url="http://callbackurl-hdfc"
server_interface.message_id=8210993557215399651
fmt.Println("server_interface..........",server_interface)
fmt.Println("client_interface..........",client_interface)
body1 := &data{
client_interface: client_interface,
server_interface: server_interface,
}
fmt.Println("body1..........",body1)
t,e:=json.Marshal(body1)
fmt.Println("t..........",t)
fmt.Println("e..........",e)
req, err := http.NewRequest("POST", url, bytes.NewReader(t))
fmt.Println("req......",req)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("requestid", "8210993557215399651")
req.Header.Set("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiJhcGk6Ly90cC1kZXYtdGFubGEtYXBpIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvY2JhYThhYmItZTcwZi00YmI4LWIwNDQtZmZiZjAwNzk0NzkwLyIsImlhdCI6MTU3NTg5MTI3NCwibmJmIjoxNTc1ODkxMjc0LCJleHAiOjE1NzU4OTUxNzQsImFjciI6IjEiLCJhaW8iOiI0MlZnWU9EY3JjenlhZXIxdkRMRDVlNHVtWUxha1UrRUplOVYrZGVlRFgrOTNUMytNRGNBIiwiYW1yIjpbInB3ZCJdLCJhcHBpZCI6IjFmMjI1N2ZlLWIzYjktNGQ2Ny05M2YyLWRjNjM2N2Q2MGM4MCIsImFwcGlkYWNyIjoiMCIsImlwYWRkciI6IjE0LjE0My4xODcuMjUwIiwibmFtZSI6ImhkZmMuMTEiLCJvaWQiOiIzOGQxMGFlNS01OGYyLTQ0NjUtYTFkOC04YTc0NDAzYjc5MmEiLCJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLCJzdWIiOiIzNDdUd0ZwYUw5MDhmOXlNRWlGOWNHMU84THFQYmJxZk45VzhyQWVEX1prIiwidGlkIjoiY2JhYThhYmItZTcwZi00YmI4LWIwNDQtZmZiZjAwNzk0NzkwIiwidW5pcXVlX25hbWUiOiJoZGZjLjExQFRhbmxhUHJvZHVjdC5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJoZGZjLjExQFRhbmxhUHJvZHVjdC5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJuS05TTXRsT3VFeXMtQjRIOGJ3TEFRIiwidmVyIjoiMS4wIn0.F5H9WCOktau3JaqNyWM91A5jFpJ9eJE99fBWvqDq9kOfCk3OCJnHFKXtIaIA7MoqbxWpNZt1yWpVKuw8gd2Lg_9nfUvvXts2DJHVQN0EqQmFUyWTzhdLW8ZVi6E9RtXK2aEWrI2TVceL5C2wbYOQYfvV4LzjTuNbs6k_20cQ0nD6oO1Id16VVFQWy9yKvpDzsTrvlQdFBZeohIfyL9XWKa8DOk0gxe4bjC7OFmuMsF3FZE5XPaQPHOJ3ejlZJiApml2TlRHnvLpkn1biE3NTAu9aO2lE262lyLg8ZaU0sbPuQaS8P797a-outxLvKEMh07895mA9g6vMxEdRV9X2eA")
client := &http.Client{}
resp, err := client.Do(req)
fmt.Println("err.............",err)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("response Status:", resp.Status)
fmt.Println("response Headers:", resp.Header)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println("response Body:", string(body))
}
First of all: please use gofmt
Your first code can't work because golang doesn't substitute variables inside string. It's better to use structs.
With structs it's not working because you named struct fields from lower case latter, it means "private" fields in go. So encoding/json package can't access them and just skip them. Use Capital first letters.
Another fix is about 'json:"tag"' - here tag means encoded field name, not type. So instead of 'json:"string"' you should use 'json:"message_id"' or so. You can specify type like this 'json:"field_name,type"' or like this 'json:",type"' but encoding/json guess type on his own.
(I used wrong quotes in tags because of markdown)
I used netcat -l 5000 to listen on 5000 port on localhost and print everything to the terminal. Then I changed url to http://localhost:5000 (not https) to send request to myself.
You need to restart netcat each time to work.
And I made logging a bit more readable.
Also it's CamelCase naming convention in go.
Changed your code a little bit
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"net/http"
//"crypto/tls"
"encoding/json"
"github.com/nikandfor/tlog"
)
type ClientInterface struct {
SourceAddress string `json:"source_address"`
DestinationAddress uint64 `json:"destination_address"`
Message string `json:"message"`
BusinessEventURL string `json:"business_event_url"`
}
type ServerInterface struct {
MessageID uint64 `json:"message_id"`
}
type Data struct {
ClientInterface ClientInterface `json:"client_interface"`
ServerInterface ServerInterface `json:"server_interface"`
}
var (
// use command line flag. so run like so:
// go run ./file.go -addr https://example.com
addr = flag.String("addr", "http://localhost:5000", "address to send data to")
)
func main() {
flag.Parse() // DO NOT FORGET TO PARSE FLAGS
fmt.Println("URL:>", *addr)
clientInterface := ClientInterface{
SourceAddress: "1",
DestinationAddress: 8886121111,
Message: "khsjhdjks",
BusinessEventURL: "http://callbackurl-hdfc",
}
serverInterface := ServerInterface{
MessageID: 8210993557215399651,
}
tlog.Printf("server_interface %+v", serverInterface)
tlog.Printf("client_interface %+v", clientInterface)
body1 := &Data{
ClientInterface: clientInterface,
ServerInterface: serverInterface,
}
tlog.Printf("body %+v", body1)
t, err := json.Marshal(body1)
if err != nil {
panic(err)
}
tlog.Printf("marshalled: %s", t)
req, err := http.NewRequest("POST", *addr, bytes.NewReader(t))
tlog.Printf("req %v", req)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("requestid", "8210993557215399651")
req.Header.Set("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiJhcGk6Ly90cC1kZXYtdGFubGEtYXBpIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvY2JhYThhYmItZTcwZi00YmI4LWIwNDQtZmZiZjAwNzk0NzkwLyIsImlhdCI6MTU3NTg5MTI3NCwibmJmIjoxNTc1ODkxMjc0LCJleHAiOjE1NzU4OTUxNzQsImFjciI6IjEiLCJhaW8iOiI0MlZnWU9EY3JjenlhZXIxdkRMRDVlNHVtWUxha1UrRUplOVYrZGVlRFgrOTNUMytNRGNBIiwiYW1yIjpbInB3ZCJdLCJhcHBpZCI6IjFmMjI1N2ZlLWIzYjktNGQ2Ny05M2YyLWRjNjM2N2Q2MGM4MCIsImFwcGlkYWNyIjoiMCIsImlwYWRkciI6IjE0LjE0My4xODcuMjUwIiwibmFtZSI6ImhkZmMuMTEiLCJvaWQiOiIzOGQxMGFlNS01OGYyLTQ0NjUtYTFkOC04YTc0NDAzYjc5MmEiLCJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLCJzdWIiOiIzNDdUd0ZwYUw5MDhmOXlNRWlGOWNHMU84THFQYmJxZk45VzhyQWVEX1prIiwidGlkIjoiY2JhYThhYmItZTcwZi00YmI4LWIwNDQtZmZiZjAwNzk0NzkwIiwidW5pcXVlX25hbWUiOiJoZGZjLjExQFRhbmxhUHJvZHVjdC5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJoZGZjLjExQFRhbmxhUHJvZHVjdC5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJuS05TTXRsT3VFeXMtQjRIOGJ3TEFRIiwidmVyIjoiMS4wIn0.F5H9WCOktau3JaqNyWM91A5jFpJ9eJE99fBWvqDq9kOfCk3OCJnHFKXtIaIA7MoqbxWpNZt1yWpVKuw8gd2Lg_9nfUvvXts2DJHVQN0EqQmFUyWTzhdLW8ZVi6E9RtXK2aEWrI2TVceL5C2wbYOQYfvV4LzjTuNbs6k_20cQ0nD6oO1Id16VVFQWy9yKvpDzsTrvlQdFBZeohIfyL9XWKa8DOk0gxe4bjC7OFmuMsF3FZE5XPaQPHOJ3ejlZJiApml2TlRHnvLpkn1biE3NTAu9aO2lE262lyLg8ZaU0sbPuQaS8P797a-outxLvKEMh07895mA9g6vMxEdRV9X2eA")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
tlog.Printf("response Status: %v", resp.Status)
tlog.Printf("response Headers: %v", resp.Header)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
tlog.Printf("response Body: %s", string(body))
}
$ nc -l 5000
POST / HTTP/1.1
Host: localhost:5000
User-Agent: Go-http-client/1.1
Content-Length: 92
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiJhcGk6Ly90cC1kZXYtdGFubGEtYXBpIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvY2JhYThhYmItZTcwZi00YmI4LWIwNDQtZmZiZjAwNzk0NzkwLyIsImlhdCI6MTU3NTg5MTI3NCwibmJmIjoxNTc1ODkxMjc0LCJleHAiOjE1NzU4OTUxNzQsImFjciI6IjEiLCJhaW8iOiI0MlZnWU9EY3JjenlhZXIxdkRMRDVlNHVtWUxha1UrRUplOVYrZGVlRFgrOTNUMytNRGNBIiwiYW1yIjpbInB3ZCJdLCJhcHBpZCI6IjFmMjI1N2ZlLWIzYjktNGQ2Ny05M2YyLWRjNjM2N2Q2MGM4MCIsImFwcGlkYWNyIjoiMCIsImlwYWRkciI6IjE0LjE0My4xODcuMjUwIiwibmFtZSI6ImhkZmMuMTEiLCJvaWQiOiIzOGQxMGFlNS01OGYyLTQ0NjUtYTFkOC04YTc0NDAzYjc5MmEiLCJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLCJzdWIiOiIzNDdUd0ZwYUw5MDhmOXlNRWlGOWNHMU84THFQYmJxZk45VzhyQWVEX1prIiwidGlkIjoiY2JhYThhYmItZTcwZi00YmI4LWIwNDQtZmZiZjAwNzk0NzkwIiwidW5pcXVlX25hbWUiOiJoZGZjLjExQFRhbmxhUHJvZHVjdC5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJoZGZjLjExQFRhbmxhUHJvZHVjdC5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJuS05TTXRsT3VFeXMtQjRIOGJ3TEFRIiwidmVyIjoiMS4wIn0.F5H9WCOktau3JaqNyWM91A5jFpJ9eJE99fBWvqDq9kOfCk3OCJnHFKXtIaIA7MoqbxWpNZt1yWpVKuw8gd2Lg_9nfUvvXts2DJHVQN0EqQmFUyWTzhdLW8ZVi6E9RtXK2aEWrI2TVceL5C2wbYOQYfvV4LzjTuNbs6k_20cQ0nD6oO1Id16VVFQWy9yKvpDzsTrvlQdFBZeohIfyL9XWKa8DOk0gxe4bjC7OFmuMsF3FZE5XPaQPHOJ3ejlZJiApml2TlRHnvLpkn1biE3NTAu9aO2lE262lyLg8ZaU0sbPuQaS8P797a-outxLvKEMh07895mA9g6vMxEdRV9X2eA
Content-Type: application/json
Requestid: 8210993557215399651
Accept-Encoding: gzip
{"client_interface":{"source_address":"1","destination_address":8886121111,"message":"khsjhdjks","business_event_url":"http://callbackurl-hdfc"},"server_interface":{"message_id":8210993557215399651}}
Is it what you've expected?
And the last. I strongly suggest you to read https://golang.org/doc/effective_go.html

How do you do a HTTP POST with digest authentication in Golang?

I am trying to use the Gerrit API that requires digest authentication. After reading up some I know I am supposed to make a request, get a 401, then use the realm and nonce and maybe other headers to then create the actual request authentication using MD5. I have found some examples on digest but they all seem to be the server side, not the client side.
I mostly followed what Wikipedia said about how to make a request then looked at the details of a verbose curl request to figure out the parts curl -v --digest --user username:password http://url.com/api. Here are the parts. You need to make a request, receive a 401 unauthorized, then compute an authorization header using MD5 sums based on the nonce and realm in the headers of the unauthorized request.
import (
"bytes"
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
)
func digestPost(host string, uri string, postBody []byte) bool {
url := host + uri
method := "POST"
req, err := http.NewRequest(method, url, nil)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusUnauthorized {
log.Printf("Recieved status code '%v' auth skipped", resp.StatusCode)
return true
}
digestParts := digestParts(resp)
digestParts["uri"] = uri
digestParts["method"] = method
digestParts["username"] = "username"
digestParts["password"] = "password"
req, err = http.NewRequest(method, url, bytes.NewBuffer(postBody))
req.Header.Set("Authorization", getDigestAuthrization(digestParts))
req.Header.Set("Content-Type", "application/json")
resp, err = client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
log.Println("response body: ", string(body))
return false
}
return true
}
func digestParts(resp *http.Response) map[string]string {
result := map[string]string{}
if len(resp.Header["Www-Authenticate"]) > 0 {
wantedHeaders := []string{"nonce", "realm", "qop"}
responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",")
for _, r := range responseHeaders {
for _, w := range wantedHeaders {
if strings.Contains(r, w) {
result[w] = strings.Split(r, `"`)[1]
}
}
}
}
return result
}
func getMD5(text string) string {
hasher := md5.New()
hasher.Write([]byte(text))
return hex.EncodeToString(hasher.Sum(nil))
}
func getCnonce() string {
b := make([]byte, 8)
io.ReadFull(rand.Reader, b)
return fmt.Sprintf("%x", b)[:16]
}
func getDigestAuthrization(digestParts map[string]string) string {
d := digestParts
ha1 := getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"])
ha2 := getMD5(d["method"] + ":" + d["uri"])
nonceCount := 00000001
cnonce := getCnonce()
response := getMD5(fmt.Sprintf("%s:%s:%v:%s:%s:%s", ha1, d["nonce"], nonceCount, cnonce, d["qop"], ha2))
authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc="%v", qop="%s", response="%s"`,
d["username"], d["realm"], d["nonce"], d["uri"], cnonce, nonceCount, d["qop"], response)
return authorization
}

Resources