1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.libraries.rcs.simpleclient.protocol.cpim;
18 
19 import android.text.TextUtils;
20 import com.google.auto.value.AutoValue;
21 import com.google.common.base.Ascii;
22 import com.google.common.base.Utf8;
23 import com.google.common.collect.ImmutableMap;
24 import com.google.common.io.CharStreams;
25 import java.io.BufferedReader;
26 import java.io.ByteArrayInputStream;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.util.Map;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 
33 /**
34  * The CPIM implementation as per RFC 3862. This class supports minimal fields that is required to
35  * represent a simple message for test purpose.
36  */
37 @AutoValue
38 public abstract class SimpleCpimMessage {
39     private static final String CRLF = "\r\n";
40     private static final String COLSP = ": ";
41     private static final Pattern NAMESPACE_HEADER_PATTERN =
42             Pattern.compile("NS:\\s+(\\S+)\\s+<(.+)>");
43     private static final Pattern HEADER_PATTERN = Pattern.compile("([^\\s:]+):\\s+(.+)");
44 
namespaces()45     public abstract ImmutableMap<String, String> namespaces();
46 
headers()47     public abstract ImmutableMap<String, String> headers();
48 
contentType()49     public abstract String contentType();
50 
content()51     public abstract String content();
52 
encode()53     public String encode() {
54         StringBuilder builder = new StringBuilder();
55         for (Map.Entry<String, String> entry : namespaces().entrySet()) {
56             builder
57                     .append("NS: ")
58                     .append(entry.getKey())
59                     .append(" <")
60                     .append(entry.getValue())
61                     .append(">")
62                     .append(CRLF);
63         }
64 
65         for (Map.Entry<String, String> entry : headers().entrySet()) {
66             builder.append(entry.getKey()).append(COLSP).append(entry.getValue()).append(CRLF);
67         }
68 
69         builder.append(CRLF);
70         builder.append("Content-Type").append(COLSP).append(contentType());
71         builder.append(CRLF);
72         builder.append("Content-Length").append(COLSP).append(Utf8.encodedLength(content()));
73         builder.append(CRLF).append(CRLF);
74         builder.append(content());
75 
76         return builder.toString();
77     }
78 
parse(byte[] content)79     public static SimpleCpimMessage parse(byte[] content) throws IOException {
80         BufferedReader reader =
81                 new BufferedReader(new InputStreamReader(new ByteArrayInputStream(content)));
82         Builder builder = newBuilder();
83 
84         String line = reader.readLine();
85         while (!TextUtils.isEmpty(line)) {
86             Matcher namespaceMatcher = NAMESPACE_HEADER_PATTERN.matcher(line);
87             Matcher headerMatcher = HEADER_PATTERN.matcher(line);
88             if (namespaceMatcher.matches()) {
89                 builder.addNamespace(namespaceMatcher.group(1), namespaceMatcher.group(2));
90             } else if (headerMatcher.matches()) {
91                 builder.addHeader(headerMatcher.group(1), headerMatcher.group(2));
92             }
93 
94             line = reader.readLine();
95         }
96 
97         line = reader.readLine();
98         while (!TextUtils.isEmpty(line)) {
99             Matcher headerMatcher = HEADER_PATTERN.matcher(line);
100             if (headerMatcher.matches()) {
101                 if (Ascii.equalsIgnoreCase("content-type", headerMatcher.group(1))) {
102                     builder.setContentType(headerMatcher.group(2));
103                 }
104             }
105 
106             line = reader.readLine();
107         }
108 
109         String body = CharStreams.toString(reader);
110         builder.setContent(body);
111 
112         return builder.build();
113     }
114 
115     @AutoValue.Builder
116     public abstract static class Builder {
namespacesBuilder()117         public abstract ImmutableMap.Builder<String, String> namespacesBuilder();
118 
headersBuilder()119         public abstract ImmutableMap.Builder<String, String> headersBuilder();
120 
setContentType(String value)121         public abstract Builder setContentType(String value);
122 
setContent(String value)123         public abstract Builder setContent(String value);
124 
build()125         public abstract SimpleCpimMessage build();
126 
addNamespace(String name, String value)127         public Builder addNamespace(String name, String value) {
128             namespacesBuilder().put(name, value);
129             return this;
130         }
131 
addHeader(String name, String value)132         public Builder addHeader(String name, String value) {
133             headersBuilder().put(name, value);
134             return this;
135         }
136     }
137 
newBuilder()138     public static SimpleCpimMessage.Builder newBuilder() {
139         return new AutoValue_SimpleCpimMessage.Builder();
140     }
141 }
142