FlightGear next
httpd.cxx
Go to the documentation of this file.
1// httpd.cxx -- a http daemon subsystem based on Mongoose http
2//
3// Written by Torsten Dreyer, started April 2014.
4//
5// Copyright (C) 2014 Torsten Dreyer
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21#include "config.h"
22
23#include "httpd.hxx"
24#include "HTTPRequest.hxx"
29#include "JsonUriHandler.hxx"
31#include "PkgUriHandler.hxx"
32#include "RunUriHandler.hxx"
33#include "NavdbUriHandler.hxx"
35#include <Main/fg_props.hxx>
36
37#include <mongoose.h>
38#include <cJSON.h>
39
40#include <string>
41#include <vector>
42
43using std::string;
44using std::vector;
45
46namespace flightgear {
47namespace http {
48
49const char * PROPERTY_ROOT = "/sim/http";
50
57class URIHandlerMap: public vector<SGSharedPtr<URIHandler> > {
58public:
68 SGSharedPtr<URIHandler> findHandler(const std::string & uri)
69 {
70 for (iterator it = begin(); it != end(); ++it) {
71 SGSharedPtr<URIHandler> handler = *it;
72 // check if the request-uri starts with the registered uri-string
73 if (0 == uri.find(handler->getUri())) return handler;
74 }
75 return SGSharedPtr<URIHandler>();
76 }
77};
78
83private:
94 static inline string NotNull(const char * cp, size_t len = string::npos)
95 {
96 if ( NULL == cp || 0 == len) return string("");
97 if (string::npos == len) return string(cp);
98 return string(cp, len);
99 }
100
101public:
108 MongooseHTTPRequest(struct mg_connection * connection)
109 {
110 Method = NotNull(connection->request_method);
111 Uri = urlDecode(NotNull(connection->uri));
112 HttpVersion = NotNull(connection->http_version);
113 QueryString = NotNull(connection->query_string);
114
115 remoteAddress = NotNull(connection->remote_ip);
116 remotePort = connection->remote_port;
117 localAddress = NotNull(connection->local_ip);
118 localPort = connection->local_port;
119
120 using namespace simgear::strutils;
121 string_list pairs = split(string(QueryString), "&");
122 for (string_list::iterator it = pairs.begin(); it != pairs.end(); ++it) {
123 string_list nvp = split(*it, "=");
124 if (nvp.size() != 2) continue;
125 RequestVariables.insert(make_pair(urlDecode(nvp[0]), urlDecode(nvp[1])));
126 }
127
128 for (int i = 0; i < connection->num_headers; i++)
129 HeaderVariables[connection->http_headers[i].name] = connection->http_headers[i].value;
130
131 Content = NotNull(connection->content, connection->content_len);
132
133 }
134
143 static string urlDecode(const string & s)
144 {
145 string r = "";
146 int max = s.length();
147 int a, b;
148 for (int i = 0; i < max; i++) {
149 if (s[i] == '+') {
150 r += ' ';
151 } else if (s[i] == '%' && i + 2 < max && isxdigit(s[i + 1]) && isxdigit(s[i + 2])) {
152 i++;
153 a = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
154 i++;
155 b = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
156 r += (char) (a * 16 + b);
157 } else {
158 r += s[i];
159 }
160 }
161 return r;
162 }
163
164};
165
171class MongooseHttpd : public FGHttpd
172{
173public:
177 MongooseHttpd(SGPropertyNode_ptr);
178
183
184 // Subsystem API.
185 void bind() override; // Currently a noop
186 void init() override; // Reads the configuration PropertyNode, installs URIHandlers and configures mongoose
187 void unbind() override; // shutdown of mongoose, clear connections, unregister URIHandlers
188 void update(double dt) override; // poll connections, check for changed properties
189
190 // Subsystem identification.
191 static const char* staticSubsystemClassId() { return "mongoose-httpd"; }
192
198 SGSharedPtr<URIHandler> findHandler(const std::string & uri) {
199 return _uriHandler.findHandler(uri);
200 }
201
202 Websocket * newWebsocket(const string & uri);
203
204private:
205 int poll(struct mg_connection * connection);
206 int auth(struct mg_connection * connection);
207 int request(struct mg_connection * connection);
208 int onConnect(struct mg_connection * connection);
209 void close(struct mg_connection * connection);
210
211 static int staticRequestHandler(struct mg_connection *, mg_event event);
212
213 struct mg_server *_server;
214 SGPropertyNode_ptr _configNode;
215
216 typedef int (MongooseHttpd::*handler_t)(struct mg_connection *);
217 URIHandlerMap _uriHandler;
218
219 PropertyChangeObserver _propertyChangeObserver;
220};
221
223public:
225 : _httpd(httpd)
226 {
227 }
228 virtual ~MongooseConnection();
229
230 virtual void close(struct mg_connection * connection) = 0;
231 virtual int poll(struct mg_connection * connection) = 0;
232 virtual int request(struct mg_connection * connection) = 0;
233 virtual int onConnect(struct mg_connection * connection) {return 0;}
234 virtual void write(const char * data, size_t len)
235 {
236 if (_connection) mg_send_data(_connection, data, len);
237 }
238
239 static MongooseConnection * getConnection(MongooseHttpd * httpd, struct mg_connection * connection);
240
241protected:
242 void setConnection(struct mg_connection * connection)
243 {
244 _connection = connection;
245 }
247 struct mg_connection * _connection;
248
249};
250
254
256public:
258 : MongooseConnection(httpd)
259 {
260 }
262 {
263 }
264
265 virtual void close(struct mg_connection * connection);
266 virtual int poll(struct mg_connection * connection);
267 virtual int request(struct mg_connection * connection);
268
269private:
270 SGSharedPtr<URIHandler> _handler;
271};
272
274public:
276 : MongooseConnection(httpd), _websocket(NULL)
277 {
278 }
280 {
281 delete _websocket;
282 }
283 virtual void close(struct mg_connection * connection);
284 virtual int poll(struct mg_connection * connection);
285 virtual int request(struct mg_connection * connection);
286 virtual int onConnect(struct mg_connection * connection);
287
288private:
289 class MongooseWebsocketWriter: public WebsocketWriter {
290 public:
291 MongooseWebsocketWriter(struct mg_connection * connection)
292 : _connection(connection)
293 {
294 }
295
296 virtual int writeToWebsocket(int opcode, const char * data, size_t len)
297 {
298 return mg_websocket_write(_connection, opcode, data, len);
299 }
300 private:
301 struct mg_connection * _connection;
302 };
303 Websocket * _websocket;
304};
305
306MongooseConnection * MongooseConnection::getConnection(MongooseHttpd * httpd, struct mg_connection * connection)
307{
308 if (connection->connection_param) return static_cast<MongooseConnection*>(connection->connection_param);
310 if (connection->is_websocket) c = new WebsocketConnection(httpd);
311 else c = new RegularConnection(httpd);
312
313 connection->connection_param = c;
314 return c;
315}
316
317int RegularConnection::request(struct mg_connection * connection)
318{
319 setConnection(connection);
320 MongooseHTTPRequest request(connection);
321 SG_LOG(SG_NETWORK, SG_INFO, "RegularConnection::request for " << request.Uri);
322
323 // find a handler for the uri and remember it for possible polls on this connection
324 _handler = _httpd->findHandler(request.Uri);
325 if (!_handler.valid()) {
326 // uri not registered - pass false to indicate we have not processed the request
327 return MG_FALSE;
328 }
329
330 // We handle this URI, prepare the response
331 HTTPResponse response;
332 response.Header["Server"] = "FlightGear/" FLIGHTGEAR_VERSION " Mongoose/" MONGOOSE_VERSION;
333 response.Header["Connection"] = "keep-alive";
334 response.Header["Cache-Control"] = "no-cache";
335 {
336 char buf[64];
337 time_t now = time(NULL);
338 strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
339 response.Header["Date"] = buf;
340 }
341
342 // hand the request over to the handler, returns true if request is finished,
343 // false the handler wants to get polled again (calling handlePoll() next time)
344 bool done = _handler->handleRequest(request, response, this);
345 // fill in the response header
346 mg_send_status(connection, response.StatusCode);
347 for (HTTPResponse::Header_t::const_iterator it = response.Header.begin(); it != response.Header.end(); ++it) {
348 const string name = it->first;
349 const string value = it->second;
350 if (name.empty() || value.empty()) continue;
351 mg_send_header(connection, name.c_str(), value.c_str());
352 }
353 if (done || !response.Content.empty()) {
354 SG_LOG(SG_NETWORK, SG_INFO,
355 "RegularConnection::request() responding " << response.Content.length() << " Bytes, done=" << done);
356 mg_send_data(connection, response.Content.c_str(), response.Content.length());
357 }
358 return done ? MG_TRUE : MG_MORE;
359}
360
361int RegularConnection::poll(struct mg_connection * connection)
362{
363 setConnection(connection);
364 if (!_handler.valid()) return MG_FALSE;
365 // only return MG_TRUE if we handle this request
366 return _handler->poll(this) ? MG_TRUE : MG_MORE;
367}
368
369void RegularConnection::close(struct mg_connection * connection)
370{
371 setConnection(connection);
372 // nothing to close
373}
374
375void WebsocketConnection::close(struct mg_connection * connection)
376{
377 setConnection(connection);
378 if ( NULL != _websocket) _websocket->close();
379 delete _websocket;
380 _websocket = NULL;
381}
382
383int WebsocketConnection::poll(struct mg_connection * connection)
384{
385 setConnection(connection);
386 // we get polled before the first request came in but we know
387 // nothing about how to handle that before we know the URI.
388 // so simply ignore that poll
389 if ( NULL != _websocket) {
390 MongooseWebsocketWriter writer(connection);
391 _websocket->poll(writer);
392 }
393 return MG_MORE;
394}
395
396int WebsocketConnection::onConnect(struct mg_connection * connection)
397{
398 setConnection(connection);
399 MongooseHTTPRequest request(connection);
400 SG_LOG(SG_NETWORK, SG_INFO, "WebsocketConnection::connect for " << request.Uri);
401 if ( NULL == _websocket) _websocket = _httpd->newWebsocket(request.Uri);
402 if ( NULL == _websocket) {
403 SG_LOG(SG_NETWORK, SG_WARN, "httpd: unhandled websocket uri: " << request.Uri);
404 return 0;
405 }
406
407 return 0;
408}
409
410int WebsocketConnection::request(struct mg_connection * connection)
411{
412 setConnection(connection);
413 if ((connection->wsbits & 0x0f) >= 0x8) {
414 // control opcode (close/ping/pong)
415 if ((connection->wsbits & 0x0f) == WEBSOCKET_OPCODE_PING) {
416 mg_websocket_write(connection, WEBSOCKET_OPCODE_PONG, NULL, 0);
417 }
418 return MG_MORE;
419 }
420
421 MongooseHTTPRequest request(connection);
422 SG_LOG(SG_NETWORK, SG_DEBUG, "WebsocketConnection::request for " << request.Uri);
423
424 if ( NULL == _websocket) {
425 SG_LOG(SG_NETWORK, SG_ALERT, "httpd: unhandled websocket uri: " << request.Uri);
426 return MG_TRUE; // close connection - good bye
427 }
428
429 MongooseWebsocketWriter writer(connection);
430 _websocket->handleRequest(request, writer);
431 return MG_MORE;
432}
433
434MongooseHttpd::MongooseHttpd(SGPropertyNode_ptr configNode)
435 : _server(NULL), _configNode(configNode)
436{
437}
438
440{
441 mg_destroy_server(&_server);
442}
443
445{
446 SGPropertyNode_ptr n = _configNode->getNode("uri-handler");
447 if (n.valid()) {
448 string uri;
449
450 if (!(uri = n->getStringValue("screenshot")).empty()) {
451 SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding screenshot uri handler at " << uri);
452 _uriHandler.push_back(new flightgear::http::ScreenshotUriHandler(uri));
453 }
454
455 if (!(uri = n->getStringValue("property")).empty()) {
456 SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding property uri handler at " << uri);
457 _uriHandler.push_back(new flightgear::http::PropertyUriHandler(uri));
458 }
459
460 if (!(uri = n->getStringValue("json")).empty()) {
461 SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding json uri handler at " << uri);
462 _uriHandler.push_back(new flightgear::http::JsonUriHandler(uri));
463 }
464
465 if (!(uri = n->getStringValue("pkg")).empty()) {
466 SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding pkg uri handler at " << uri);
467 _uriHandler.push_back(new flightgear::http::PkgUriHandler(uri));
468 }
469
470 if (!(uri = n->getStringValue("flighthistory")).empty()) {
471 SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding flighthistory uri handler at " << uri);
472 _uriHandler.push_back(new flightgear::http::FlightHistoryUriHandler(uri));
473 }
474
475 if (!(uri = n->getStringValue("run")).empty()) {
476 SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding run uri handler at " << uri);
477 _uriHandler.push_back(new flightgear::http::RunUriHandler(uri));
478 }
479
480 if (!(uri = n->getStringValue("navdb")).empty()) {
481 SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding navdb uri handler at " << uri);
482 _uriHandler.push_back(new flightgear::http::NavdbUriHandler(uri));
483 }
484 }
485
486 _server = mg_create_server(this, MongooseHttpd::staticRequestHandler);
487
488 n = _configNode->getNode("options");
489 if (n.valid()) {
490
491 const string fgRoot = fgGetString("/sim/fg-root");
492 string docRoot = n->getStringValue("document-root", fgRoot.c_str());
493 if (docRoot[0] != '/') docRoot.insert(0, "/").insert(0, fgRoot);
494
495 mg_set_option(_server, "document_root", docRoot.c_str());
496
497 mg_set_option(_server, "listening_port", n->getStringValue("listening-port", "8080").c_str());
498 {
499 // build url rewrites relative to fg-root
500 string rewrites = n->getStringValue("url-rewrites", "");
501 string_list rwl = simgear::strutils::split(rewrites, ",");
502 rwl.push_back(string("/aircraft-dir/=") + fgGetString("/sim/aircraft-dir") + "/" );
503 rwl.push_back(string("/fg-home/=") + fgGetString("/sim/fg-home") + "/" );
504 rwl.push_back(string("/fg-root/=") + fgGetString("/sim/fg-root") + "/" );
505 rewrites.clear();
506 for (string_list::iterator it = rwl.begin(); it != rwl.end(); ++it) {
507 string_list rw_entries = simgear::strutils::split(*it, "=");
508 if (rw_entries.size() != 2) {
509 SG_LOG(SG_NETWORK, SG_WARN, "invalid entry '" << *it << "' in url-rewrites ignored.");
510 continue;
511 }
512 string & lhs = rw_entries[0];
513 string & rhs = rw_entries[1];
514 if (!rewrites.empty()) rewrites.append(1, ',');
515 rewrites.append(lhs).append(1, '=');
516 SGPath targetPath(rhs);
517 if (targetPath.isAbsolute() ) {
518 rewrites.append(rhs);
519 } else {
520 // don't use targetPath here because SGPath strips trailing '/'
521 rewrites.append(fgRoot).append(1, '/').append(rhs);
522 }
523 }
524 if (!rewrites.empty()) mg_set_option(_server, "url_rewrites", rewrites.c_str());
525 }
526 mg_set_option(_server, "enable_directory_listing", n->getStringValue("enable-directory-listing", "yes").c_str());
527 mg_set_option(_server, "idle_timeout_ms", n->getStringValue("idle-timeout-ms", "30000").c_str());
528 mg_set_option(_server, "index_files", n->getStringValue("index-files", "index.html").c_str());
529 mg_set_option(_server, "extra_mime_types", n->getStringValue("extra-mime-types", "").c_str());
530 mg_set_option(_server, "access_log_file", n->getStringValue("access-log-file", "").c_str());
531
532 SG_LOG(SG_NETWORK,SG_INFO,"starting mongoose with these options: ");
533 const char ** optionNames = mg_get_valid_option_names();
534 for( int i = 0; optionNames[i] != NULL; i+= 2 ) {
535 SG_LOG(SG_NETWORK,SG_INFO, " > " << optionNames[i] << ": '" << mg_get_option(_server, optionNames[i]) << "'" );
536 }
537 SG_LOG(SG_NETWORK,SG_INFO,"end of mongoose options.");
538 }
539
540 _configNode->setBoolValue("running",true);
541
542}
543
545{
546}
547
549{
550 _configNode->setBoolValue("running",false);
551 mg_destroy_server(&_server);
552 _uriHandler.clear();
553 _propertyChangeObserver.clear();
554}
555
557{
558 _propertyChangeObserver.check();
559 mg_poll_server(_server, 0);
560 _propertyChangeObserver.uncheck();
561}
562
563int MongooseHttpd::poll(struct mg_connection * connection)
564{
565 if ( NULL == connection->connection_param) return MG_FALSE; // connection not yet set up - ignore poll
566
567 return MongooseConnection::getConnection(this, connection)->poll(connection);
568}
569
570int MongooseHttpd::auth(struct mg_connection * connection)
571{
572 // auth preceeds request for websockets and regular connections,
573 // and eventually the websocket has been already set up by mongoose
574 // use this to choose the connection type
575 MongooseConnection::getConnection(this, connection);
576 //return MongooseConnection::getConnection(this,connection)->auth(connection);
577 return MG_TRUE; // unrestricted access for now
578}
579
580int MongooseHttpd::request(struct mg_connection * connection)
581{
582 return MongooseConnection::getConnection(this, connection)->request(connection);
583}
584
585int MongooseHttpd::onConnect(struct mg_connection * connection)
586{
587 return MongooseConnection::getConnection(this, connection)->onConnect(connection);
588}
589
590void MongooseHttpd::close(struct mg_connection * connection)
591{
592 MongooseConnection * c = MongooseConnection::getConnection(this, connection);
593 c->close(connection);
594 delete c;
595}
597{
598 if (uri.find("/PropertyListener") == 0) {
599 SG_LOG(SG_NETWORK, SG_INFO, "new PropertyChangeWebsocket for: " << uri);
600 return new PropertyChangeWebsocket(&_propertyChangeObserver);
601 } else if (uri.find("/PropertyTreeMirror/") == 0) {
602 const auto path = uri.substr(20);
603 SG_LOG(SG_NETWORK, SG_INFO, "new MirrorPropertyTreeWebsocket for: " << path);
604 return new MirrorPropertyTreeWebsocket(path);
605 }
606 return NULL;
607}
608
609int MongooseHttpd::staticRequestHandler(struct mg_connection * connection, mg_event event)
610{
611 switch (event) {
612 case MG_POLL: // MG_TRUE: finished sending data, MG_MORE, call again
613 return static_cast<MongooseHttpd*>(connection->server_param)->poll(connection);
614
615 case MG_AUTH: // If callback returns MG_FALSE, authentication fails
616 return static_cast<MongooseHttpd*>(connection->server_param)->auth(connection);
617
618 case MG_REQUEST: // If callback returns MG_FALSE, Mongoose continues with req
619 return static_cast<MongooseHttpd*>(connection->server_param)->request(connection);
620
621 case MG_CLOSE: // Connection is closed, callback return value is ignored
622 static_cast<MongooseHttpd*>(connection->server_param)->close(connection);
623 return MG_TRUE;
624
625 case MG_HTTP_ERROR: // If callback returns MG_FALSE, Mongoose continues with err
626 return MG_FALSE; // we don't handle errors - let mongoose do the work
627
628 // client services not used/implemented. Signal 'close connection' to be sure
629 case MG_CONNECT: // If callback returns MG_FALSE, connect fails
630 case MG_REPLY: // If callback returns MG_FALSE, Mongoose closes connection
631 return MG_FALSE;
632
633 case MG_WS_CONNECT: // New websocket connection established, return value ignored
634 return static_cast<MongooseHttpd*>(connection->server_param)->onConnect(connection);
635
636 default:
637 return MG_FALSE; // keep compiler happy..
638 }
639}
640
641FGHttpd * FGHttpd::createInstance(SGPropertyNode_ptr configNode)
642{
643// only create a server if a port has been configured
644 if (!configNode.valid()) return NULL;
645 string port = configNode->getStringValue("options/listening-port", "");
646 if (port.empty()) return NULL;
647 return new MongooseHttpd(configNode);
648}
649
650
651// Register the subsystem.
652#if 0
653SGSubsystemMgr::Registrant<MongooseHttpd> registrantMongooseHttpd;
654#endif
655
656} // namespace http
657} // namespace flightgear
#define i(x)
static FGHttpd * createInstance(SGPropertyNode_ptr configNode)
Definition httpd.cxx:641
static MongooseConnection * getConnection(MongooseHttpd *httpd, struct mg_connection *connection)
Definition httpd.cxx:306
virtual void write(const char *data, size_t len)
Definition httpd.cxx:234
virtual int request(struct mg_connection *connection)=0
virtual int poll(struct mg_connection *connection)=0
virtual int onConnect(struct mg_connection *connection)
Definition httpd.cxx:233
struct mg_connection * _connection
Definition httpd.cxx:247
MongooseConnection(MongooseHttpd *httpd)
Definition httpd.cxx:224
virtual void close(struct mg_connection *connection)=0
void setConnection(struct mg_connection *connection)
Definition httpd.cxx:242
A Helper class to create a HTTPRequest from a mongoose connection struct.
Definition httpd.cxx:82
MongooseHTTPRequest(struct mg_connection *connection)
Constructs a HTTPRequest from a mongoose connection struct Copies all fields into STL compatible loca...
Definition httpd.cxx:108
static string urlDecode(const string &s)
Decodes a URL encoded string replaces '+' by ' ' replaces nn hexdigits.
Definition httpd.cxx:143
A FGHttpd implementation based on mongoose httpd.
Definition httpd.cxx:172
static const char * staticSubsystemClassId()
Definition httpd.cxx:191
void update(double dt) override
Definition httpd.cxx:556
MongooseHttpd(SGPropertyNode_ptr)
Construct a MongooseHttpd object from options in a PropertyNode.
Definition httpd.cxx:434
SGSharedPtr< URIHandler > findHandler(const std::string &uri)
Returns a URIHandler for the given uri.
Definition httpd.cxx:198
Websocket * newWebsocket(const string &uri)
Definition httpd.cxx:596
~MongooseHttpd()
Cleanup et.al.
Definition httpd.cxx:439
virtual int request(struct mg_connection *connection)
Definition httpd.cxx:317
RegularConnection(MongooseHttpd *httpd)
Definition httpd.cxx:257
virtual void close(struct mg_connection *connection)
Definition httpd.cxx:369
virtual int poll(struct mg_connection *connection)
Definition httpd.cxx:361
A Helper class for URI Handlers.
Definition httpd.cxx:57
SGSharedPtr< URIHandler > findHandler(const std::string &uri)
Find a URI Handler for a given URI.
Definition httpd.cxx:68
virtual int request(struct mg_connection *connection)
Definition httpd.cxx:410
WebsocketConnection(MongooseHttpd *httpd)
Definition httpd.cxx:275
virtual int poll(struct mg_connection *connection)
Definition httpd.cxx:383
virtual int onConnect(struct mg_connection *connection)
Definition httpd.cxx:396
virtual void close(struct mg_connection *connection)
Definition httpd.cxx:375
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
std::vector< std::string > string_list
Definition globals.hxx:36
const char * PROPERTY_ROOT
Definition httpd.cxx:49
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
const char * name
std::vector< std::string > split(std::string str, char d)