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