1 /*
2 AirSane Imaging Daemon
3 Copyright (C) 2018-2021 Simul Piscator
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "webpage.h"
20 #include <locale>
21 #include <sstream>
22
23 std::string
htmlEscape(const std::string & s)24 WebPage::htmlEscape(const std::string& s)
25 {
26 std::string r;
27 for (auto c : s)
28 switch (c) {
29 case '&':
30 r += "&";
31 break;
32 case '<':
33 r += "<";
34 break;
35 case '>':
36 r += ">";
37 break;
38 case '\'':
39 r += "'";
40 break;
41 case '"':
42 r += """;
43 break;
44 case '\n':
45 r += "<br>\n";
46 break;
47 default:
48 r += c;
49 }
50 return r;
51 }
52
53 static std::locale clocale("C");
54
55 std::string
numtostr(double d)56 WebPage::numtostr(double d)
57 {
58 std::ostringstream oss;
59 oss.imbue(clocale);
60 oss << d;
61 return oss.str();
62 }
63
WebPage()64 WebPage::WebPage()
65 : mpResponse(nullptr)
66 , mpRequest(nullptr)
67 , mpOut(nullptr)
68 {
69 addStyle("body { font-family:sans-serif }");
70 }
71
72 WebPage&
setFavicon(const std::string & type,const std::string & url)73 WebPage::setFavicon(const std::string& type, const std::string& url)
74 {
75 mFaviconType = type;
76 mFaviconUrl = url;
77 return *this;
78 }
79
80 WebPage&
clearFavicon()81 WebPage::clearFavicon()
82 {
83 mFaviconType.clear();
84 mFaviconUrl.clear();
85 return *this;
86 }
87
88 WebPage&
clearStyle()89 WebPage::clearStyle()
90 {
91 mStyle.clear();
92 return *this;
93 }
94
95 WebPage&
addStyle(const std::string & s)96 WebPage::addStyle(const std::string& s)
97 {
98 mStyle += s + "\n";
99 return *this;
100 }
101
102 WebPage&
render(const HttpServer::Request & request,HttpServer::Response & response)103 WebPage::render(const HttpServer::Request& request,
104 HttpServer::Response& response)
105 {
106 std::ostringstream oss;
107 mpOut = &oss;
108 mpRequest = &request;
109 mpResponse = &response;
110 onRender();
111 mpResponse = nullptr;
112 mpRequest = nullptr;
113 mpOut = nullptr;
114 if (!response.sent()) {
115 std::string html = "<!DOCTYPE HTML>\n"
116 "<html>\n"
117 "<head>\n"
118 "<meta charset='utf-8'/>\n"
119 "<title>" +
120 htmlEscape(mTitle) +
121 "</title>\n"
122 "<style>" +
123 mStyle +
124 "</style>\n";
125
126 if (!mFaviconType.empty() && !mFaviconUrl.empty())
127 html += "<link rel='icon' type='" + mFaviconType + "' href='" + mFaviconUrl + "'>\n";
128
129 html += "</head>\n"
130 "<body>\n";
131 html += oss.str();
132 html += "</body>\n</html>\n";
133 response.setHeader(HttpServer::HTTP_HEADER_CONTENT_TYPE, "text/html");
134 response.sendWithContent(html);
135 }
136 return *this;
137 }
138
139 WebPage::element&
setAttribute(const std::string & key,const std::string & value)140 WebPage::element::setAttribute(const std::string& key, const std::string& value)
141 {
142 mAttributes[key] = value;
143 return *this;
144 }
145
146 std::string
toString() const147 WebPage::element::toString() const
148 {
149 std::string r = "<" + mTag;
150 for (auto& a : mAttributes)
151 r += " " + a.first + "='" + htmlEscape(a.second) + "'";
152 r += ">";
153 if (!mText.empty())
154 r += mText + "</" + mTag + ">";
155 return r;
156 }
157
158 WebPage::list&
addItem(const std::string & s)159 WebPage::list::addItem(const std::string& s)
160 {
161 addContent("<li>" + s + "</li>");
162 return *this;
163 }
164
165 WebPage::list&
addItem(const WebPage::element & el)166 WebPage::list::addItem(const WebPage::element& el)
167 {
168 return addItem(el.toString());
169 }
170
171 WebPage::formSelect&
addOption(const std::string & value,const std::string & text)172 WebPage::formSelect::addOption(const std::string& value,
173 const std::string& text)
174 {
175 mOptions[value] = text.empty() ? value : text;
176 return *this;
177 }
178
179 WebPage::formSelect&
addOptions(const std::vector<std::string> & options)180 WebPage::formSelect::addOptions(const std::vector<std::string>& options)
181 {
182 for (auto& opt : options)
183 addOption(opt);
184 return *this;
185 }
186
187 std::string
toString() const188 WebPage::formSelect::toString() const
189 {
190 std::string r = labelHtml();
191 r += "<select autocomplete='off'";
192 std::string value;
193 for (auto& a : attributes()) {
194 if (a.first == "value")
195 value = a.second;
196 else
197 r += " " + a.first + "='" + a.second + "'";
198 }
199 r += ">\n";
200 for (auto& opt : mOptions) {
201 r += "<option value='" + opt.first + "'";
202 if (opt.first == value)
203 r += " selected";
204 r += ">" + opt.second + "</option>\n";
205 }
206 r += "</select>\n";
207 return r;
208 }
209
210 std::string
toString() const211 WebPage::formField::toString() const
212 {
213 return labelHtml() + element::toString();
214 }
215
216 std::string
labelHtml() const217 WebPage::formField::labelHtml() const
218 {
219 std::string r;
220 if (!mLabel.empty()) {
221 const std::string& label = mLabel == "*" ? attributes()["name"] : mLabel;
222 r += "<label for='" + attributes()["name"] + "'>" + label + "</label>\n";
223 }
224 return r;
225 }
226