msg.go (8270B)
1 // Message bundles manipulations (encode/decode). 2 // Decode message from user (point). 3 // Some validation functions. 4 5 package ii 6 7 import ( 8 "crypto/sha256" 9 "encoding/base64" 10 "errors" 11 "fmt" 12 "strings" 13 "time" 14 ) 15 16 // II-tags, encoded in raw message as key1/value1/key2/value2.. string 17 // When message is decoded into Msg, 18 // key/value properties of tags associated with it. 19 // When encoding Msg, all properties will translated to tags string 20 // List - is the names of properties 21 // Hash - is the map of properties (Name->Value) 22 type Tags struct { 23 Hash map[string]string 24 List []string 25 } 26 27 // Decoded message. 28 // Has all atrributes of message 29 // including Tags. 30 type Msg struct { 31 MsgId string 32 Tags Tags 33 Echo string 34 Date int64 35 From string 36 Addr string 37 To string 38 Subj string 39 Text string 40 } 41 42 // Make MsgId from raw text 43 // MsgId is unique identificator of message 44 // It is supposed that there is no collision of MsgId 45 // It is base64(sha256(text)) transformation 46 func MsgId(msg string) string { 47 h := sha256.Sum256([]byte(msg)) 48 id := base64.StdEncoding.EncodeToString(h[:]) 49 id = strings.Replace(id, "+", "A", -1) 50 id = strings.Replace(id, "/", "Z", -1) 51 return id[0:20] 52 } 53 54 // Check if string is valid MsgId 55 func IsMsgId(id string) bool { 56 return len(id) == 20 && !strings.Contains(id, ".") 57 } 58 59 // Check if Echoarea is private area 60 // This is openidec extension, echoareas 61 // that has "." prefix are for private messaging. 62 // Those areas can be fetched only with /u/point/auth/u/e/ scheme 63 func IsPrivate(e string) bool { 64 return strings.HasPrefix(e, ".") 65 } 66 67 // Check if string is valid echoarea name 68 func IsEcho(e string) bool { 69 l := len(e) 70 return l >= 3 && l <= 120 && strings.Contains(e, ".") && !strings.Contains(e, ":") 71 } 72 73 // Check if string is valid subject 74 // In fact, it is just return true stub :) 75 func IsSubject(s string) bool { 76 return true // len(strings.TrimSpace(s)) > 0 77 } 78 79 // Check if subject is empty string 80 // Used when validate msg from points 81 func IsEmptySubject(s string) bool { 82 return len(strings.TrimSpace(s)) > 0 83 } 84 85 // Decode message from point sent with /u/point scheme. 86 // Try to use URL save and STD base64. 87 // Returns pointrt to decoded Msg or nil (and error) 88 // Note: This function adds "ii/ok" to Tags and 89 // set Date field with UTC Unix time. 90 func DecodeMsgline(msg string, enc bool) (*Msg, error) { 91 var m Msg 92 var data []byte 93 var err error 94 if len(msg) > 65536 { 95 return nil, errors.New("Message too long") 96 } 97 if enc { 98 if data, err = base64.StdEncoding.DecodeString(msg); err != nil { 99 if data, err = base64.URLEncoding.DecodeString(msg); err != nil { 100 return nil, err 101 } 102 } 103 } else { 104 data = []byte(msg) 105 } 106 text := strings.Split(string(data), "\n") 107 if len(text) < 5 { 108 return nil, errors.New("Wrong message format") 109 } 110 if text[3] != "" { 111 return nil, errors.New("No body delimiter in message") 112 } 113 m.Echo = strings.TrimSpace(text[0]) 114 if !IsEcho(m.Echo) { 115 return nil, errors.New("Wrong echoarea format") 116 } 117 m.To = strings.TrimSpace(text[1]) 118 if len(m.To) == 0 { 119 m.To = "All" 120 } 121 m.Subj = strings.TrimSpace(text[2]) 122 if !IsEmptySubject(m.Subj) { 123 return nil, errors.New("Wrong subject") 124 } 125 m.Date = time.Now().Unix() 126 start := 4 127 repto := text[4] 128 m.Tags, _ = MakeTags("ii/ok") 129 if strings.HasPrefix(repto, "@repto:") { 130 start += 1 131 repto = strings.Trim(strings.Split(repto, ":")[1], " ") 132 if err := m.Tags.Add("repto/" + repto); err != nil { 133 return nil, err 134 } 135 Trace.Printf("Add repto tag: %s", repto) 136 } 137 for i := start; i < len(text); i++ { 138 m.Text += text[i] + "\n" 139 } 140 m.Text = strings.TrimSuffix(m.Text, "\n") 141 Trace.Printf("Final message: %s\n", m.String()) 142 return &m, nil 143 } 144 145 // Decode bundle line in msgid:message format or just message 146 // Returns pointer to decoded Msg or nil, error if fail. 147 // Can parse URL safe and STD BASE64. 148 // This function does NOT add ii/ok tag and does NOT change Date 149 func DecodeBundle(msg string) (*Msg, error) { 150 var m Msg 151 if strings.Contains(msg, ":") { 152 spl := strings.Split(msg, ":") 153 if len(spl) != 2 { 154 return nil, errors.New("Wrong bundle format") 155 } 156 msg = spl[1] 157 m.MsgId = spl[0] 158 if !IsMsgId(m.MsgId) { 159 return nil, errors.New("Wrong MsgId format") 160 } 161 } 162 msg = strings.Replace(msg, "-", "+", -1) /* if it is URL base64 */ 163 msg = strings.Replace(msg, "_", "/", -1) /* make it base64 */ 164 data, err := base64.StdEncoding.DecodeString(msg) 165 if err != nil { 166 return nil, err 167 } 168 if m.MsgId == "" { 169 m.MsgId = MsgId(string(data)) 170 } 171 text := strings.Split(string(data), "\n") 172 if len(text) <= 8 { 173 return nil, errors.New("Wrong message format") 174 } 175 m.Tags, err = MakeTags(text[0]) 176 if err != nil { 177 return nil, err 178 } 179 m.Echo = text[1] 180 if !IsEcho(m.Echo) { 181 return nil, errors.New("Wrong echoarea format") 182 } 183 _, err = fmt.Sscanf(text[2], "%d", &m.Date) 184 if err != nil { 185 return nil, err 186 } 187 m.From = text[3] 188 m.Addr = text[4] 189 m.To = text[5] 190 m.Subj = text[6] 191 if !IsSubject(m.Subj) { 192 return nil, errors.New("Wrong subject") 193 } 194 for i := 8; i < len(text); i++ { 195 m.Text += text[i] + "\n" 196 } 197 m.Text = strings.TrimSuffix(m.Text, "\n") 198 return &m, nil 199 } 200 201 // Creates Tags from string in key1/value1/key2/value2/... format 202 // Can return error (with unfilled Tags) if format is wrong. 203 func MakeTags(str string) (Tags, error) { 204 var t Tags 205 str = strings.Trim(str, " ") 206 if str == "" { // empty 207 return t, nil 208 } 209 tags := strings.Split(str, "/") 210 if len(tags)%2 != 0 { 211 return t, errors.New("Wrong tags: " + str) 212 } 213 t.Hash = make(map[string]string) 214 for i := 0; i < len(tags); i += 2 { 215 t.Hash[tags[i]] = tags[i+1] 216 t.List = append(t.List, tags[i]) 217 } 218 return t, nil 219 } 220 221 // Create Tags from string in key1/value1/key2/value2/... format 222 // ignoring errors. This is useful for creating new "ii/ok" tag. 223 func NewTags(str string) Tags { 224 t, _ := MakeTags(str) 225 return t 226 } 227 228 // Returns Tags propertie with name n. 229 // Returns "", false if such propertie does not exists in Tags. 230 func (t *Tags) Get(n string) (string, bool) { 231 if t == nil || t.Hash == nil { 232 return "", false 233 } 234 v, ok := t.Hash[n] 235 if ok { 236 return v, true 237 } 238 return "", false 239 } 240 241 // Add tags in key/value/... format to existing Tags. 242 func (t *Tags) Add(str string) error { 243 tags := strings.Split(str, "/") 244 if len(tags)%2 != 0 { 245 return errors.New("Wrong tags") 246 } 247 if t.Hash == nil { 248 t.Hash = make(map[string]string) 249 } 250 for i := 0; i < len(tags); i += 2 { 251 _, ok := t.Hash[tags[i]] 252 t.Hash[tags[i]] = tags[i+1] 253 if !ok { /* new tag */ 254 t.List = append(t.List, tags[i]) 255 } 256 } 257 return nil 258 } 259 260 // Remove tag with name tag from Tags. 261 func (t *Tags) Del(tag string) bool { 262 if t.Hash == nil { 263 return false 264 } 265 _, ok := t.Hash[tag] 266 if !ok { 267 return false 268 } 269 delete(t.Hash, tag) 270 for k, v := range t.List { 271 if v == tag { 272 copy(t.List[k:], t.List[k+1:]) 273 t.List[len(t.List)-1] = "" 274 t.List = t.List[:len(t.List)-1] 275 return true 276 } 277 } 278 return false 279 } 280 281 // Translate Tags to string in key1/value1/key2/value2/... format. 282 func (t Tags) String() string { 283 var text string 284 if t.Hash == nil { 285 return "" 286 } 287 for _, n := range t.List { 288 if val, ok := t.Hash[n]; ok { 289 text += fmt.Sprintf("%s/%s/", n, val) 290 } 291 } 292 text = strings.TrimSuffix(text, "/") 293 return text 294 } 295 296 // Dump (returns string) decoded message for debug purposes. 297 func (m *Msg) Dump() string { 298 if m == nil { 299 return "" 300 } 301 return fmt.Sprintf("id: %s\ntags: %s\nechoarea: %s\ndate: %s\nmsgfrom: %s\naddr: %s\nmsgto: %s\nsubj: %s\n\n%s", 302 m.MsgId, m.Tags.String(), m.Echo, time.Unix(m.Date, 0), m.From, m.Addr, m.To, m.Subj, m.Text) 303 } 304 305 // Get if tag property with name n is associated with Msg 306 func (m *Msg) Tag(n string) (string, bool) { 307 return m.Tags.Get(n) 308 } 309 310 // Translate decoded Msg to raw text format ready to encoding. 311 func (m *Msg) String() string { 312 tags := m.Tags.String() 313 text := strings.Join([]string{tags, m.Echo, 314 fmt.Sprint(m.Date), 315 m.From, 316 m.Addr, 317 m.To, 318 m.Subj, 319 "", 320 m.Text}, "\n") 321 return text 322 } 323 324 // Encode Msg into bundle format (msgid:base64text). 325 func (m *Msg) Encode() string { 326 var text string 327 if m == nil || m.Echo == "" { 328 return "" 329 } 330 if m.Date == 0 { 331 now := time.Now() 332 m.Date = now.Unix() 333 } 334 text = m.String() 335 if m.MsgId == "" { 336 m.MsgId = MsgId(text) 337 } 338 return m.MsgId + ":" + base64.StdEncoding.EncodeToString([]byte(text)) 339 }