1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package xmpp implements the XMPP IM protocol, as specified in RFC 6120 and
6// 6121.
7package xmpp
8
9import (
10	"encoding/base64"
11	"fmt"
12	"strings"
13
14	"github.com/coyim/coyim/xmpp/data"
15)
16
17func mediaForPresentation(field data.FormFieldX, datas []data.BobData) [][]data.Media {
18	if len(field.Media) == 0 {
19		return nil
20	}
21
22	ret := make([][]data.Media, 0, len(field.Media))
23
24	for _, media := range field.Media {
25		options := make([]data.Media, 0, len(media.URIs))
26		for _, uri := range media.URIs {
27			media := data.Media{
28				MIMEType: uri.MIMEType,
29				URI:      uri.URI,
30			}
31			if strings.HasPrefix(media.URI, "cid:") {
32				// cid URIs are references to data
33				// blobs that, hopefully, were sent
34				// along with the form.
35				cid := media.URI[4:]
36				media.URI = ""
37
38				for _, data := range datas {
39					if data.CID == cid {
40						var err error
41						if media.Data, err = base64.StdEncoding.DecodeString(data.Base64); err != nil {
42							media.Data = nil
43						}
44					}
45				}
46			}
47			if len(media.URI) > 0 || len(media.Data) > 0 {
48				options = append(options, media)
49			}
50		}
51
52		ret = append(ret, options)
53	}
54
55	return ret
56}
57
58func toFormField(field data.FormFieldX, media [][]data.Media) interface{} {
59	base := data.FormField{
60		Label:    field.Label,
61		Type:     field.Type,
62		Name:     field.Var,
63		Required: field.Required != nil,
64		Media:    media,
65	}
66
67	switch field.Type {
68	case "fixed":
69		if len(field.Values) < 1 {
70			return nil
71		}
72		f := &data.FixedFormField{
73			FormField: base,
74			Text:      field.Values[0],
75		}
76		return f
77	case "boolean":
78		result := false
79		if len(field.Values) > 0 {
80			//See: XEP-0040, Appendix G, item 10.
81			result = field.Values[0] == "true" || field.Values[0] == "1"
82		}
83
84		f := &data.BooleanFormField{
85			FormField: base,
86			Result:    result,
87		}
88		return f
89	case "jid-multi", "text-multi":
90		f := &data.MultiTextFormField{
91			FormField: base,
92			Defaults:  field.Values,
93		}
94		return f
95	case "list-single":
96		f := &data.SelectionFormField{
97			FormField: base,
98		}
99
100		for i, opt := range field.Options {
101			f.Ids = append(f.Ids, opt.Value)
102			f.Values = append(f.Values, opt.Label)
103
104			if field.Values[0] == opt.Value {
105				f.Result = i
106			}
107		}
108		return f
109	case "list-multi":
110		f := &data.MultiSelectionFormField{
111			FormField: base,
112		}
113		for i, opt := range field.Options {
114			f.Ids = append(f.Ids, opt.Value)
115			f.Values = append(f.Values, opt.Label)
116
117			if len(f.Results) < len(field.Values) {
118				for _, v := range field.Values {
119					if v == opt.Value {
120						f.Results = append(f.Results, i)
121						break
122					}
123				}
124			}
125		}
126		return f
127	case "hidden":
128		return nil
129	default:
130		f := &data.TextFormField{
131			FormField: base,
132			Private:   field.Type == "text-private",
133		}
134		if len(field.Values) > 0 {
135			f.Default = field.Values[0]
136		}
137		return f
138	}
139
140	return nil
141}
142
143func toFormFieldX(field interface{}) *data.FormFieldX {
144	switch field := field.(type) {
145	case *data.BooleanFormField:
146		value := "false"
147		if field.Result {
148			value = "true"
149		}
150		return &data.FormFieldX{
151			Var:    field.Name,
152			Values: []string{value},
153		}
154	case *data.TextFormField:
155		return &data.FormFieldX{
156			Var:    field.Name,
157			Values: []string{field.Result},
158		}
159	case *data.MultiTextFormField:
160		return &data.FormFieldX{
161			Var:    field.Name,
162			Values: field.Results,
163		}
164	case *data.SelectionFormField:
165		return &data.FormFieldX{
166			Var:    field.Name,
167			Values: []string{field.Ids[field.Result]},
168		}
169	case *data.MultiSelectionFormField:
170		var values []string
171		for _, selected := range field.Results {
172			values = append(values, field.Ids[selected])
173		}
174
175		return &data.FormFieldX{
176			Var:    field.Name,
177			Values: values,
178		}
179	case *data.FixedFormField:
180		return nil
181	default:
182		panic(fmt.Sprintf("unknown field type in result from callback: %T", field))
183	}
184
185	return nil
186}
187
188// processForm calls the callback with the given XMPP form and returns the
189// result form. The datas argument contains any additional XEP-0231 blobs that
190// might contain media for the questions in the form.
191func processForm(form *data.Form, datas []data.BobData, callback data.FormCallback) (*data.Form, error) {
192	fields := make([]interface{}, 0, len(form.Fields))
193	result := &data.Form{
194		Type: "submit",
195	}
196
197	for _, field := range form.Fields {
198		// Copy the hidden fields across.
199		if field.Type == "hidden" {
200			//skipping hidden fields has a consequence of not processing their media
201			result.Fields = append(result.Fields, data.FormFieldX{
202				Var:    field.Var,
203				Values: field.Values,
204			})
205			continue
206		}
207
208		media := mediaForPresentation(field, datas)
209		formField := toFormField(field, media)
210		if formField != nil {
211			fields = append(fields, formField)
212		}
213	}
214
215	if err := callback(form.Title, form.Instructions, fields); err != nil {
216		return nil, err
217	}
218
219	for _, field := range fields {
220		formFieldX := toFormFieldX(field)
221		if formFieldX != nil {
222			result.Fields = append(result.Fields, *formFieldX)
223		}
224	}
225
226	return result, nil
227}
228