mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 12:46:46 +00:00
198 lines
5.2 KiB
Go
198 lines
5.2 KiB
Go
// Copyright (c) 2021 Proton Technologies AG
|
|
//
|
|
// This file is part of ProtonMail Bridge.
|
|
//
|
|
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package pmapi
|
|
|
|
import (
|
|
"archive/zip"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/textproto"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
// ClientType is required by API.
|
|
const (
|
|
EmailClientType = iota + 1
|
|
VPNClientType
|
|
)
|
|
|
|
type reportAtt struct {
|
|
name, filename string
|
|
body io.Reader
|
|
}
|
|
|
|
// ReportReq stores data for report.
|
|
type ReportReq struct {
|
|
OS string `json:",omitempty"`
|
|
OSVersion string `json:",omitempty"`
|
|
Browser string `json:",omitempty"`
|
|
BrowserVersion string `json:",omitempty"`
|
|
BrowserExtensions string `json:",omitempty"`
|
|
Resolution string `json:",omitempty"`
|
|
DisplayMode string `json:",omitempty"`
|
|
Client string `json:",omitempty"`
|
|
ClientVersion string `json:",omitempty"`
|
|
ClientType int `json:",omitempty"`
|
|
Title string `json:",omitempty"`
|
|
Description string `json:",omitempty"`
|
|
Username string `json:",omitempty"`
|
|
Email string `json:",omitempty"`
|
|
Country string `json:",omitempty"`
|
|
ISP string `json:",omitempty"`
|
|
Debug string `json:",omitempty"`
|
|
Attachments []reportAtt `json:",omitempty"`
|
|
}
|
|
|
|
// AddAttachment to report.
|
|
func (rep *ReportReq) AddAttachment(name, filename string, r io.Reader) {
|
|
rep.Attachments = append(rep.Attachments, reportAtt{name: name, filename: filename, body: r})
|
|
}
|
|
|
|
func writeMultipartReport(w *multipart.Writer, rep *ReportReq) error { // nolint[funlen]
|
|
fieldData := map[string]string{
|
|
"OS": rep.OS,
|
|
"OSVersion": rep.OSVersion,
|
|
"Browser": rep.Browser,
|
|
"BrowserVersion": rep.BrowserVersion,
|
|
"BrowserExtensions": rep.BrowserExtensions,
|
|
"Resolution": rep.Resolution,
|
|
"DisplayMode": rep.DisplayMode,
|
|
"Client": rep.Client,
|
|
"ClientVersion": rep.ClientVersion,
|
|
"ClientType": "1",
|
|
"Title": rep.Title,
|
|
"Description": rep.Description,
|
|
"Username": rep.Username,
|
|
"Email": rep.Email,
|
|
"Country": rep.Country,
|
|
"ISP": rep.ISP,
|
|
"Debug": rep.Debug,
|
|
}
|
|
|
|
for field, data := range fieldData {
|
|
if data == "" {
|
|
continue
|
|
}
|
|
if err := w.WriteField(field, data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
quoteEscaper := strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
|
|
|
for _, att := range rep.Attachments {
|
|
h := make(textproto.MIMEHeader)
|
|
h.Set("Content-Disposition",
|
|
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
|
|
quoteEscaper.Replace(att.name), quoteEscaper.Replace(att.filename+".zip")))
|
|
h.Set("Content-Type", "application/octet-stream")
|
|
// h.Set("Content-Transfer-Encoding", "base64")
|
|
attWr, err := w.CreatePart(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
zipArch := zip.NewWriter(attWr)
|
|
zipWr, err := zipArch.Create(att.filename)
|
|
// b64 := base64.NewEncoder(base64.StdEncoding, zipWr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = io.Copy(zipWr, att.body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = zipArch.Close()
|
|
// err = b64.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Report sends request as json or multipart (if has attachment).
|
|
func (c *client) Report(rep ReportReq) (err error) {
|
|
rep.Client = c.cm.config.ClientID
|
|
rep.ClientVersion = c.cm.config.AppVersion
|
|
rep.ClientType = EmailClientType
|
|
|
|
var req *http.Request
|
|
var w *MultipartWriter
|
|
if len(rep.Attachments) > 0 {
|
|
req, w, err = c.NewMultipartRequest("POST", "/reports/bug")
|
|
} else {
|
|
req, err = c.NewJSONRequest("POST", "/reports/bug", rep)
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var res Res
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- c.DoJSON(req, &res)
|
|
}()
|
|
|
|
if w != nil {
|
|
err = writeMultipartReport(w.Writer, &rep)
|
|
if err != nil {
|
|
c.log.Errorln("report write: ", err)
|
|
return
|
|
}
|
|
err = w.Close()
|
|
if err != nil {
|
|
c.log.Errorln("report close: ", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if err = <-done; err != nil {
|
|
return
|
|
}
|
|
|
|
return res.Err()
|
|
}
|
|
|
|
// ReportCrash is old. Use sentry instead.
|
|
func (c *client) ReportCrash(stacktrace string) (err error) {
|
|
crashReq := ReportReq{
|
|
Client: c.cm.config.ClientID,
|
|
ClientVersion: c.cm.config.AppVersion,
|
|
ClientType: EmailClientType,
|
|
OS: runtime.GOOS,
|
|
Debug: stacktrace,
|
|
}
|
|
req, err := c.NewJSONRequest("POST", "/reports/crash", crashReq)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var res Res
|
|
if err = c.DoJSON(req, &res); err != nil {
|
|
return
|
|
}
|
|
|
|
err = res.Err()
|
|
return
|
|
}
|