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 += "&amp;";
31         break;
32       case '<':
33         r += "&lt;";
34         break;
35       case '>':
36         r += "&gt;";
37         break;
38       case '\'':
39         r += "&apos;";
40         break;
41       case '"':
42         r += "&quot;";
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