Skip to content

Commit

Permalink
Remove unnecessary FormData struct
Browse files Browse the repository at this point in the history
This was making the code more complex, as it made an unnecessary
fragmentation between the cli composer and the web gui. Many of the
fields exposed via the web api was in fact unused.
  • Loading branch information
martinhpedersen committed Dec 29, 2023
1 parent fadac07 commit 87f28ba
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 95 deletions.
5 changes: 2 additions & 3 deletions cli_composer.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,8 @@ func composeFormReport(ctx context.Context, args []string) {

msg.SetBody(formMsg.Body)

if xml := formMsg.AttachmentXML; xml != "" {
attachmentFile := fbb.NewFile(formMsg.AttachmentName, []byte(xml))
msg.AddFile(attachmentFile)
for _, f := range formMsg.Attachments() {
msg.AddFile(f)
}

postMessage(msg)
Expand Down
21 changes: 15 additions & 6 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/la5nta/wl2k-go/transport/ardop"

"github.com/la5nta/pat/internal/buildinfo"
"github.com/la5nta/pat/internal/debug"
"github.com/la5nta/pat/internal/directories"
"github.com/la5nta/pat/internal/gpsd"

Expand Down Expand Up @@ -277,12 +278,20 @@ func postOutboundMessageHandler(w http.ResponseWriter, r *http.Request) {
}
}

cookie, err := r.Cookie("forminstance")
if err == nil {
formData := formsMgr.GetPostedFormData(cookie.Value)
if xml := formData.MsgXML; xml != "" {
name := formsMgr.GetXMLAttachmentNameForForm(formData.TargetForm, formData.IsReply)
msg.AddFile(fbb.NewFile(name, []byte(formData.MsgXML)))
if cookie, err := r.Cookie("forminstance"); err == nil {
// We must add the attachment files here because it is impossible
// for the frontend to dynamically add form files due to legacy
// security vulnerabilities in older HTML specs.
// The rest of the form data (to, subject, body etc) is added by
// the frontend.
formData, ok := formsMgr.GetPostedFormData(cookie.Value)
if !ok {
debug.Printf("form instance key (%q) not valid", cookie.Value)
http.Error(w, "form instance key not valid", http.StatusBadRequest)
return
}
for _, f := range formData.Attachments() {
msg.AddFile(f)
}
}

Expand Down
130 changes: 68 additions & 62 deletions internal/forms/forms.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/la5nta/pat/internal/debug"
"github.com/la5nta/pat/internal/directories"
"github.com/la5nta/pat/internal/gpsd"
"github.com/la5nta/wl2k-go/fbb"
"github.com/pd0mz/go-maidenhead"
)

Expand All @@ -47,15 +48,21 @@ const (
)

// Manager manages the forms subsystem
// When the web frontend POSTs the form template data, this map holds the POST'ed data.
// Each form composer instance renders into another browser tab, and has a unique instance cookie.
// This instance cookie is the key into the map, so that we can keep the values
// from different form authoring sessions separate from each other.
type Manager struct {
config Config
config Config

// postedFormData serves as an kv-store holding intermediate data for
// communicating form values submitted by the served HTML form files to
// the rest of the app.
//
// When the web frontend POSTs the form template data, this map holds
// the POST'ed data. Each form composer instance renders into another
// browser tab, and has a unique instance cookie. This instance cookie
// is the key into the map, so that we can keep the values from
// different form authoring sessions separate from each other.
postedFormData struct {
sync.RWMutex
internalFormDataMap map[string]FormData
mu sync.RWMutex
m map[string]MessageForm
}
}

Expand Down Expand Up @@ -91,27 +98,27 @@ type FormFolder struct {
Folders []FormFolder `json:"folders"`
}

// FormData holds the instance data that define a filled-in form
type FormData struct {
TargetForm Form `json:"target_form"`
Fields map[string]string `json:"fields"`
MsgTo string `json:"msg_to"`
MsgCc string `json:"msg_cc"`
MsgSubject string `json:"msg_subject"`
MsgBody string `json:"msg_body"`
MsgXML string `json:"msg_xml"`
IsReply bool `json:"is_reply"`
Submitted time.Time `json:"submitted"`
}

// MessageForm represents a concrete form-based message
type MessageForm struct {
To string
Cc string
Subject string
Body string
AttachmentXML string
AttachmentName string
To string `json:"msg_to"`
Cc string `json:"msg_cc"`
Subject string `json:"msg_subject"`
Body string `json:"msg_body"`

attachmentXML string
targetForm Form
isReply bool
submitted time.Time
}

// Attachments returns the attachments generated by the filled-in form
func (m MessageForm) Attachments() []*fbb.File {
var files []*fbb.File
if xml := m.attachmentXML; xml != "" {
name := getXMLAttachmentNameForForm(m.targetForm, m.isReply)
files = append(files, fbb.NewFile(name, []byte(xml)))
}
return files
}

// UpdateResponse is the API response format for the upgrade forms endpoint
Expand All @@ -128,7 +135,7 @@ func NewManager(conf Config) *Manager {
retval := &Manager{
config: conf,
}
retval.postedFormData.internalFormDataMap = make(map[string]FormData)
retval.postedFormData.m = make(map[string]MessageForm)
return retval
}

Expand Down Expand Up @@ -180,18 +187,14 @@ func (m *Manager) PostFormDataHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("missing cookie %s %s", formPath, r.URL)
return
}
formData := FormData{
IsReply: composeReply,
TargetForm: form,
Fields: make(map[string]string),
}
fields := make(map[string]string, len(r.PostForm))
for key, values := range r.PostForm {
formData.Fields[strings.TrimSpace(strings.ToLower(key))] = values[0]
fields[strings.TrimSpace(strings.ToLower(key))] = values[0]
}

formMsg, err := formMessageBuilder{
Template: form,
FormValues: formData.Fields,
FormValues: fields,
Interactive: false,
IsReply: composeReply,
FormsMgr: m,
Expand All @@ -200,16 +203,10 @@ func (m *Manager) PostFormDataHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusBadRequest)
log.Printf("%s %s: %s", r.Method, r.URL.Path, err)
}
formData.MsgTo = formMsg.To
formData.MsgCc = formMsg.Cc
formData.MsgSubject = formMsg.Subject
formData.MsgBody = formMsg.Body
formData.MsgXML = formMsg.AttachmentXML
formData.Submitted = time.Now()

m.postedFormData.Lock()
m.postedFormData.internalFormDataMap[formInstanceKey.Value] = formData
m.postedFormData.Unlock()
m.postedFormData.mu.Lock()
m.postedFormData.m[formInstanceKey.Value] = formMsg
m.postedFormData.mu.Unlock()

m.cleanupOldFormData()
_, _ = io.WriteString(w, "<script>window.close()</script>")
Expand All @@ -223,14 +220,20 @@ func (m *Manager) GetFormDataHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("missing cookie %s %s", formInstanceKey, r.URL)
return
}
_ = json.NewEncoder(w).Encode(m.GetPostedFormData(formInstanceKey.Value))
v, ok := m.GetPostedFormData(formInstanceKey.Value)
if !ok {
http.NotFound(w, r)
return
}
_ = json.NewEncoder(w).Encode(v)
}

// GetPostedFormData is similar to GetFormDataHandler, but used when posting the form-based message to the outbox
func (m *Manager) GetPostedFormData(key string) FormData {
m.postedFormData.RLock()
defer m.postedFormData.RUnlock()
return m.postedFormData.internalFormDataMap[key]
func (m *Manager) GetPostedFormData(key string) (MessageForm, bool) {
m.postedFormData.mu.RLock()
defer m.postedFormData.mu.RUnlock()
v, ok := m.postedFormData.m[key]
return v, ok
}

// GetFormTemplateHandler handles the request for viewing a form filled-in with instance values
Expand Down Expand Up @@ -392,8 +395,8 @@ func unzip(srcArchivePath, dstRoot string) error {
return nil
}

// GetXMLAttachmentNameForForm returns the user-visible filename for the message attachment that holds the form instance values
func (m *Manager) GetXMLAttachmentNameForForm(f Form, isReply bool) string {
// getXMLAttachmentNameForForm returns the user-visible filename for the message attachment that holds the form instance values
func getXMLAttachmentNameForForm(f Form, isReply bool) string {
attachmentName := filepath.Base(f.ViewerURI)
if isReply {
attachmentName = filepath.Base(f.ReplyViewerURI)
Expand Down Expand Up @@ -917,23 +920,27 @@ func (b formMessageBuilder) build() (MessageForm, error) {

b.initFormValues()

formVarsAsXML := ""
for varKey, varVal := range b.FormValues {
formVarsAsXML += fmt.Sprintf(" <%s>%s</%s>\n", xmlEscape(varKey), xmlEscape(varVal), xmlEscape(varKey))
}

msgForm, err := b.scanTmplBuildMessage(tmplPath)
if err != nil {
return MessageForm{}, err
}
//TODO: Should any of these be set in scanTmplBuildMessage?
msgForm.targetForm = b.Template
msgForm.isReply = b.IsReply
msgForm.submitted = time.Now()

formVarsAsXML := ""
for varKey, varVal := range b.FormValues {
formVarsAsXML += fmt.Sprintf(" <%s>%s</%s>\n", xmlEscape(varKey), xmlEscape(varVal), xmlEscape(varKey))
}

// Add XML if a viewer is defined for this form
if b.Template.ViewerURI != "" {
viewer := b.Template.ViewerURI
if b.IsReply && b.Template.ReplyViewerURI != "" {
viewer = b.Template.ReplyViewerURI
}
msgForm.AttachmentXML = fmt.Sprintf(`%s<RMS_Express_Form>
msgForm.attachmentXML = fmt.Sprintf(`%s<RMS_Express_Form>
<form_parameters>
<xml_file_version>%s</xml_file_version>
<rms_express_version>%s</rms_express_version>
Expand All @@ -957,7 +964,6 @@ func (b formMessageBuilder) build() (MessageForm, error) {
filepath.Base(viewer),
filepath.Base(b.Template.ReplyTxtFileURI),
formVarsAsXML)
msgForm.AttachmentName = b.FormsMgr.GetXMLAttachmentNameForForm(b.Template, false)
}

msgForm.To = strings.TrimSpace(msgForm.To)
Expand Down Expand Up @@ -1075,13 +1081,13 @@ func fillPlaceholders(s string, re *regexp.Regexp, values map[string]string) str
}

func (m *Manager) cleanupOldFormData() {
m.postedFormData.Lock()
defer m.postedFormData.Unlock()
for key, form := range m.postedFormData.internalFormDataMap {
elapsed := time.Since(form.Submitted).Hours()
m.postedFormData.mu.Lock()
defer m.postedFormData.mu.Unlock()
for key, form := range m.postedFormData.m {
elapsed := time.Since(form.submitted).Hours()
if elapsed > 24 {
log.Println("deleting old FormData after", elapsed, "hrs")
delete(m.postedFormData.internalFormDataMap, key)
delete(m.postedFormData.m, key)
}
}
}
Expand Down
49 changes: 25 additions & 24 deletions web/src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,37 +217,38 @@ function forgetFormData() {
let pollTimer;

function pollFormData() {
$.get(
'api/form',
{},
function (data) {
$.ajax({
method: 'GET',
url: '/api/form',
dataType: 'json',
success: function (data) {
// TODO: Should verify forminstance key in case of multi-user scenario
console.log('done polling');
console.log(data);
if (!$('#composer').hasClass('hidden') && (!data.target_form || !data.target_form.name)) {
if (!$('#composer').hasClass('hidden')) {
writeFormDataToComposer(data);
}
},
error: function () {
if (!$('#composer').hasClass('hidden')) {
// TODO: Consider replacing this polling mechanism with a WS message (push)
pollTimer = window.setTimeout(pollFormData, 1000);
} else {
console.log('done polling');
if (!$('#composer').hasClass('hidden') && data.target_form && data.target_form.name) {
writeFormDataToComposer(data);
}
}
},
'json'
);
});
}

function writeFormDataToComposer(data) {
if (data.target_form) {
$('#msg_body').val(data.msg_body);
if (data.msg_to) {
$('#msg_to').tokenfield('setTokens', data.msg_to.split(/[ ;,]/).filter(Boolean));
}
if (data.msg_cc) {
$('#msg_cc').tokenfield('setTokens', data.msg_cc.split(/[ ;,]/).filter(Boolean));
}
if (data.msg_subject) {
// in case of composing a form-based reply we keep the 'Re: ...' subject line
$('#msg_subject').val(data.msg_subject);
}
$('#msg_body').val(data.msg_body);
if (data.msg_to) {
$('#msg_to').tokenfield('setTokens', data.msg_to.split(/[ ;,]/).filter(Boolean));
}
if (data.msg_cc) {
$('#msg_cc').tokenfield('setTokens', data.msg_cc.split(/[ ;,]/).filter(Boolean));
}
if (data.msg_subject) {
// in case of composing a form-based reply we keep the 'Re: ...' subject line
$('#msg_subject').val(data.msg_subject);
}
}

Expand Down

0 comments on commit 87f28ba

Please sign in to comment.