github.com/bluestoneag/bluephish@v0.1.0/controllers/api/import.go (about)

     1  package api
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"net/http"
    10  	"strings"
    11  
    12  	"github.com/PuerkitoBio/goquery"
    13  	"github.com/bluestoneag/bluephish/dialer"
    14  	log "github.com/bluestoneag/bluephish/logger"
    15  	"github.com/bluestoneag/bluephish/models"
    16  	"github.com/bluestoneag/bluephish/util"
    17  	"github.com/jordan-wright/email"
    18  )
    19  
    20  type cloneRequest struct {
    21  	URL              string `json:"url"`
    22  	IncludeResources bool   `json:"include_resources"`
    23  }
    24  
    25  func (cr *cloneRequest) validate() error {
    26  	if cr.URL == "" {
    27  		return errors.New("No URL Specified")
    28  	}
    29  	return nil
    30  }
    31  
    32  type cloneResponse struct {
    33  	HTML string `json:"html"`
    34  }
    35  
    36  type emailResponse struct {
    37  	Text    string `json:"text"`
    38  	HTML    string `json:"html"`
    39  	Subject string `json:"subject"`
    40  }
    41  
    42  // ImportGroup imports a CSV of group members
    43  func (as *Server) ImportGroup(w http.ResponseWriter, r *http.Request) {
    44  	ts, err := util.ParseCSV(r)
    45  	if err != nil {
    46  		JSONResponse(w, models.Response{Success: false, Message: "Error parsing CSV"}, http.StatusInternalServerError)
    47  		return
    48  	}
    49  	JSONResponse(w, ts, http.StatusOK)
    50  }
    51  
    52  // ImportEmail allows for the importing of email.
    53  // Returns a Message object
    54  func (as *Server) ImportEmail(w http.ResponseWriter, r *http.Request) {
    55  	if r.Method != "POST" {
    56  		JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
    57  		return
    58  	}
    59  	ir := struct {
    60  		Content      string `json:"content"`
    61  		ConvertLinks bool   `json:"convert_links"`
    62  	}{}
    63  	err := json.NewDecoder(r.Body).Decode(&ir)
    64  	if err != nil {
    65  		JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
    66  		return
    67  	}
    68  	e, err := email.NewEmailFromReader(strings.NewReader(ir.Content))
    69  	if err != nil {
    70  		log.Error(err)
    71  	}
    72  	// If the user wants to convert links to point to
    73  	// the landing page, let's make it happen by changing up
    74  	// e.HTML
    75  	if ir.ConvertLinks {
    76  		d, err := goquery.NewDocumentFromReader(bytes.NewReader(e.HTML))
    77  		if err != nil {
    78  			JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
    79  			return
    80  		}
    81  		d.Find("a").Each(func(i int, a *goquery.Selection) {
    82  			a.SetAttr("href", "{{.URL}}")
    83  		})
    84  		h, err := d.Html()
    85  		if err != nil {
    86  			JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
    87  			return
    88  		}
    89  		e.HTML = []byte(h)
    90  	}
    91  	er := emailResponse{
    92  		Subject: e.Subject,
    93  		Text:    string(e.Text),
    94  		HTML:    string(e.HTML),
    95  	}
    96  	JSONResponse(w, er, http.StatusOK)
    97  }
    98  
    99  // ImportSite allows for the importing of HTML from a website
   100  // Without "include_resources" set, it will merely place a "base" tag
   101  // so that all resources can be loaded relative to the given URL.
   102  func (as *Server) ImportSite(w http.ResponseWriter, r *http.Request) {
   103  	cr := cloneRequest{}
   104  	if r.Method != "POST" {
   105  		JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
   106  		return
   107  	}
   108  	err := json.NewDecoder(r.Body).Decode(&cr)
   109  	if err != nil {
   110  		JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
   111  		return
   112  	}
   113  	if err = cr.validate(); err != nil {
   114  		JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
   115  		return
   116  	}
   117  	restrictedDialer := dialer.Dialer()
   118  	tr := &http.Transport{
   119  		DialContext: restrictedDialer.DialContext,
   120  		TLSClientConfig: &tls.Config{
   121  			InsecureSkipVerify: true,
   122  		},
   123  	}
   124  	client := &http.Client{Transport: tr}
   125  	resp, err := client.Get(cr.URL)
   126  	if err != nil {
   127  		JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
   128  		return
   129  	}
   130  	// Insert the base href tag to better handle relative resources
   131  	d, err := goquery.NewDocumentFromResponse(resp)
   132  	if err != nil {
   133  		JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
   134  		return
   135  	}
   136  	// Assuming we don't want to include resources, we'll need a base href
   137  	if d.Find("head base").Length() == 0 {
   138  		d.Find("head").PrependHtml(fmt.Sprintf("<base href=\"%s\">", cr.URL))
   139  	}
   140  	forms := d.Find("form")
   141  	forms.Each(func(i int, f *goquery.Selection) {
   142  		// We'll want to store where we got the form from
   143  		// (the current URL)
   144  		url := f.AttrOr("action", cr.URL)
   145  		if !strings.HasPrefix(url, "http") {
   146  			url = fmt.Sprintf("%s%s", cr.URL, url)
   147  		}
   148  		f.PrependHtml(fmt.Sprintf("<input type=\"hidden\" name=\"__original_url\" value=\"%s\"/>", url))
   149  	})
   150  	h, err := d.Html()
   151  	if err != nil {
   152  		JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
   153  		return
   154  	}
   155  	cs := cloneResponse{HTML: h}
   156  	JSONResponse(w, cs, http.StatusOK)
   157  }